From 8d33d93c77122321a9dd4474666dde0f64fa2109 Mon Sep 17 00:00:00 2001 From: faluco Date: Fri, 12 Nov 2021 11:15:17 +0100 Subject: [PATCH 01/77] Fix a race condition in the enb csv metrics where set_handle was called while the metrics thread is running causing a race in the enb member variable. Fix it by setting it during object construction. --- srsenb/hdr/metrics_csv.h | 3 +-- srsenb/src/main.cc | 3 +-- srsenb/src/metrics_csv.cc | 8 ++------ srsenb/test/enb_metrics_test.cc | 15 +++++++-------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/srsenb/hdr/metrics_csv.h b/srsenb/hdr/metrics_csv.h index 4cda5927d..89adc0879 100644 --- a/srsenb/hdr/metrics_csv.h +++ b/srsenb/hdr/metrics_csv.h @@ -32,11 +32,10 @@ namespace srsenb { class metrics_csv : public srsran::metrics_listener { public: - metrics_csv(std::string filename); + metrics_csv(std::string filename, enb_metrics_interface* enb_); ~metrics_csv(); void set_metrics(const enb_metrics_t& m, const uint32_t period_usec); - void set_handle(enb_metrics_interface* enb_); void stop(); private: diff --git a/srsenb/src/main.cc b/srsenb/src/main.cc index 8fa2bd5a7..9a8e79d24 100644 --- a/srsenb/src/main.cc +++ b/srsenb/src/main.cc @@ -632,10 +632,9 @@ int main(int argc, char* argv[]) metricshub.add_listener(&metrics_screen); metrics_screen.set_handle(enb.get()); - srsenb::metrics_csv metrics_file(args.general.metrics_csv_filename); + srsenb::metrics_csv metrics_file(args.general.metrics_csv_filename, enb.get()); if (args.general.metrics_csv_enable) { metricshub.add_listener(&metrics_file); - metrics_file.set_handle(enb.get()); } srsenb::metrics_json json_metrics(json_channel, enb.get()); diff --git a/srsenb/src/metrics_csv.cc b/srsenb/src/metrics_csv.cc index 779eb8d22..2afbe5892 100644 --- a/srsenb/src/metrics_csv.cc +++ b/srsenb/src/metrics_csv.cc @@ -27,7 +27,8 @@ using namespace std; namespace srsenb { -metrics_csv::metrics_csv(std::string filename) : n_reports(0), metrics_report_period(1.0), enb(NULL) +metrics_csv::metrics_csv(std::string filename, enb_metrics_interface* enb_) : + n_reports(0), metrics_report_period(1.0), enb(enb_) { file.open(filename.c_str(), std::ios_base::out); } @@ -37,11 +38,6 @@ metrics_csv::~metrics_csv() stop(); } -void metrics_csv::set_handle(enb_metrics_interface* enb_) -{ - enb = enb_; -} - void metrics_csv::stop() { if (file.is_open()) { diff --git a/srsenb/test/enb_metrics_test.cc b/srsenb/test/enb_metrics_test.cc index 5e877e67e..5afd87f35 100644 --- a/srsenb/test/enb_metrics_test.cc +++ b/srsenb/test/enb_metrics_test.cc @@ -53,8 +53,8 @@ public: metrics[0].stack.mac.ues[0].dl_pmi = 1.0; metrics[0].stack.mac.ues[0].phr = 12.0; metrics[0].phy.resize(2); - metrics[0].phy[0].dl.mcs = 28.0; - metrics[0].phy[0].ul.mcs = 20.2; + metrics[0].phy[0].dl.mcs = 28.0; + metrics[0].phy[0].ul.mcs = 20.2; metrics[0].phy[0].ul.pucch_sinr = 14.2; metrics[0].phy[0].ul.pusch_sinr = 14.2; @@ -96,8 +96,8 @@ public: metrics[1].stack.mac.ues[0].dl_pmi = 1.0; metrics[1].stack.mac.ues[0].phr = 99.1; metrics[1].phy.resize(1); - metrics[1].phy[0].dl.mcs = 6.2; - metrics[1].phy[0].ul.mcs = 28.0; + metrics[1].phy[0].dl.mcs = 6.2; + metrics[1].phy[0].ul.mcs = 28.0; metrics[1].phy[0].ul.pucch_sinr = 22.2; metrics[1].phy[0].ul.pusch_sinr = 22.2; @@ -119,8 +119,8 @@ public: metrics[2].stack.mac.ues[0].dl_pmi = 1.0; metrics[2].stack.mac.ues[0].phr = 12.0; metrics[2].phy.resize(1); - metrics[2].phy[0].dl.mcs = 28.0; - metrics[2].phy[0].ul.mcs = 20.2; + metrics[2].phy[0].dl.mcs = 28.0; + metrics[2].phy[0].ul.mcs = 20.2; metrics[2].phy[0].ul.pusch_sinr = 14.2; metrics[2].phy[0].ul.pucch_sinr = 14.2; @@ -201,8 +201,7 @@ int main(int argc, char** argv) metrics_screen.set_handle(&enb); // the CSV file writer - metrics_csv metrics_file(csv_file_name); - metrics_file.set_handle(&enb); + metrics_csv metrics_file(csv_file_name, &enb); // create metrics hub and register metrics for stdout srsran::metrics_hub metricshub; From 085d561f65574f1fc1b8533a16622e619d5a958e Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Sun, 7 Nov 2021 22:36:55 +0100 Subject: [PATCH 02/77] skiq: allow running without root permissions do what the comment says, when thread can't be created with privelages, try to create it without them. --- lib/src/phy/rf/rf_skiq_imp_card.c | 8 +++++++- lib/src/phy/rf/rf_skiq_imp_port.c | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/src/phy/rf/rf_skiq_imp_card.c b/lib/src/phy/rf/rf_skiq_imp_card.c index f2a7c2aac..408d616b4 100644 --- a/lib/src/phy/rf/rf_skiq_imp_card.c +++ b/lib/src/phy/rf/rf_skiq_imp_card.c @@ -209,7 +209,13 @@ int rf_skiq_card_init(rf_skiq_card_t* q, uint8_t card, uint8_t nof_ports, const // Launch thread if (pthread_create(&q->thread, &attr, reader_thread, q)) { ERROR("Error creating reader thread with attributes (Did you miss sudo?). Trying without attributes.\n"); - return SRSRAN_ERROR; + + // try to create thread without attributes + pthread_attr_destroy(&attr); + if (pthread_create(&q->thread, NULL, reader_thread, q)) { + ERROR("Error creating reader thread, even without thread attributes. Exiting.\n"); + return SRSRAN_ERROR; + } } // Rename thread diff --git a/lib/src/phy/rf/rf_skiq_imp_port.c b/lib/src/phy/rf/rf_skiq_imp_port.c index e0376bf23..a1719cb29 100644 --- a/lib/src/phy/rf/rf_skiq_imp_port.c +++ b/lib/src/phy/rf/rf_skiq_imp_port.c @@ -222,7 +222,13 @@ int rf_skiq_tx_port_init(rf_skiq_tx_port_t* q, uint8_t card, skiq_tx_hdl_t hdl, // Launch thread if (pthread_create(&q->thread, &attr, writer_thread, q)) { ERROR("Error creating writer thread with attributes (Did you miss sudo?). Trying without attributes.\n"); - return SRSRAN_ERROR; + + // try to create thread without attributes + pthread_attr_destroy(&attr); + if (pthread_create(&q->thread, NULL, writer_thread, q)) { + ERROR("Error creating writer thread, even without thread attributes. Exiting.\n"); + return SRSRAN_ERROR; + } } // Rename thread From 02f261c0a97f4107d5ee5d4fccb8e85a6ae65aff Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Thu, 4 Nov 2021 14:21:10 +0100 Subject: [PATCH 03/77] rf: build srsran_rf as static library For Ubuntu 18.04: Linking the static srsran_rf to libuhd also requires to link Boost's "system" library. Newer systems do not require this special handling. --- CMakeLists.txt | 3 +++ lib/src/phy/rf/CMakeLists.txt | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49870cec0..893667179 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,6 +268,9 @@ endif(BUILD_STATIC) set(BOOST_REQUIRED_COMPONENTS program_options ) +if(UHD_FOUND) # Ubuntu 18.04 requires component 'system' to link UHD + list(APPEND BOOST_REQUIRED_COMPONENTS "system") +endif(UHD_FOUND) if(UNIX AND EXISTS "/usr/lib64") list(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix endif(UNIX AND EXISTS "/usr/lib64") diff --git a/lib/src/phy/rf/CMakeLists.txt b/lib/src/phy/rf/CMakeLists.txt index 5176719e8..ba67085ea 100644 --- a/lib/src/phy/rf/CMakeLists.txt +++ b/lib/src/phy/rf/CMakeLists.txt @@ -59,12 +59,12 @@ if(RF_FOUND) list(APPEND SOURCES_RF rf_zmq_imp.c rf_zmq_imp_tx.c rf_zmq_imp_rx.c) endif (ZEROMQ_FOUND) - add_library(srsran_rf SHARED ${SOURCES_RF}) + add_library(srsran_rf STATIC ${SOURCES_RF}) target_link_libraries(srsran_rf srsran_rf_utils srsran_phy) set_target_properties(srsran_rf PROPERTIES VERSION ${SRSRAN_VERSION_STRING} SOVERSION ${SRSRAN_SOVERSION}) if (UHD_FOUND) - target_link_libraries(srsran_rf ${UHD_LIBRARIES}) + target_link_libraries(srsran_rf ${UHD_LIBRARIES} ${Boost_LIBRARIES}) # Ubuntu 18.04 requires 'system' from Boost_LIBRARIES endif (UHD_FOUND) if (BLADERF_FOUND) From 83c1fb65d767de236907896e036c5604ec03b9de Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 12 Nov 2021 11:38:28 +0000 Subject: [PATCH 04/77] nr,gnb,rrc: add rrc nr message handler and send functions to establish an SA RRC connection --- lib/include/srsran/common/test_common.h | 6 +++ srsgnb/hdr/stack/rrc/rrc_nr_ue.h | 20 +++++++- srsgnb/src/stack/rrc/rrc_nr.cc | 12 +++++ srsgnb/src/stack/rrc/rrc_nr_ue.cc | 62 +++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/lib/include/srsran/common/test_common.h b/lib/include/srsran/common/test_common.h index 9acfcbadc..7626e58e0 100644 --- a/lib/include/srsran/common/test_common.h +++ b/lib/include/srsran/common/test_common.h @@ -154,6 +154,12 @@ inline void copy_msg_to_buffer(unique_byte_buffer_t& pdu, const_byte_span msg) pdu->N_bytes = msg.size(); } +/** + * Delimits beginning/ending of a test with the following console output: + * ============= [Test ] =============== + * + * ======================================================= + */ class test_delimit_logger { const size_t delimiter_length = 128; diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h index cc2c38569..f79c01712 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h @@ -31,8 +31,6 @@ public: ue(rrc_nr* parent_, uint16_t rnti_, const sched_nr_ue_cfg_t& uecfg, bool start_msg3_timer = true); ~ue(); - void send_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_dcch_msg); - int handle_sgnb_addition_request(uint16_t eutra_rnti, const sgnb_addition_req_params_t& params); void crnti_ce_received(); @@ -58,14 +56,32 @@ public: void handle_rrc_setup_request(const asn1::rrc_nr::rrc_setup_request_s& msg); void handle_rrc_setup_complete(const asn1::rrc_nr::rrc_setup_complete_s& msg); + /* TS 38.331 - 5.3.4 Initial AS security activation */ + void handle_security_mode_complete(const asn1::rrc_nr::security_mode_complete_s& msg); + + /* TS 38.331 - 5.3.5 RRC reconfiguration */ + void handle_rrc_reconfiguration_complete(const asn1::rrc_nr::rrc_recfg_complete_s& msg); + + /* TS 38.331 - 5.7.2 UL information transfer */ + void handle_ul_information_transfer(const asn1::rrc_nr::ul_info_transfer_s& msg); + private: rrc_nr* parent = nullptr; uint16_t rnti = SRSRAN_INVALID_RNTI; + void send_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_ccch_msg); + void send_dl_dcch(srsran::nr_srb srb, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg); + /* TS 38.331 - 5.3.3 RRC connection establishment */ void send_rrc_setup(); void send_rrc_reject(uint8_t reject_wait_time_secs); + /* TS 38.331 - 5.3.4 Initial AS security activation */ + void send_security_mode_command(); + + /* TS 38.331 - 5.3.5 RRC reconfiguration */ + void send_rrc_reconfiguration(); + int pack_rrc_reconfiguration(asn1::dyn_octstring& packed_rrc_reconfig); int pack_secondary_cell_group_cfg(asn1::dyn_octstring& packed_secondary_cell_config); diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index 906cb1593..b7430d63c 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -145,6 +145,11 @@ template void rrc_nr::log_rrc_message(const char* sou srsran::const_byte_span pdu, const dl_ccch_msg_s& msg, const char* msg_type); +template void rrc_nr::log_rrc_message(const char* source, + const direction_t dir, + srsran::const_byte_span pdu, + const dl_dcch_msg_s& msg, + const char* msg_type); template void rrc_nr::log_rrc_message(const char* source, const direction_t dir, srsran::const_byte_span pdu, @@ -509,6 +514,12 @@ 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_setup_complete: u.handle_rrc_setup_complete(ul_dcch_msg.msg.c1().rrc_setup_complete()); break; + case ul_dcch_msg_type_c::c1_c_::types_opts::security_mode_complete: + u.handle_security_mode_complete(ul_dcch_msg.msg.c1().security_mode_complete()); + case ul_dcch_msg_type_c::c1_c_::types_opts::rrc_recfg_complete: + u.handle_rrc_reconfiguration_complete(ul_dcch_msg.msg.c1().rrc_recfg_complete()); + case ul_dcch_msg_type_c::c1_c_::types_opts::ul_info_transfer: + u.handle_ul_information_transfer(ul_dcch_msg.msg.c1().ul_info_transfer()); default: log_rx_pdu_fail(rnti, srb_to_lcid(lte_srb::srb0), pdu, "Unsupported UL-CCCH message type", false); // TODO Remove user @@ -659,5 +670,6 @@ srsran::unique_byte_buffer_t rrc_nr::pack_into_pdu(const T& msg) return pdu; } template srsran::unique_byte_buffer_t rrc_nr::pack_into_pdu(const dl_ccch_msg_s& msg); +template srsran::unique_byte_buffer_t rrc_nr::pack_into_pdu(const dl_dcch_msg_s& msg); } // namespace srsenb diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index c7107f0d9..77fc9e430 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -126,6 +126,20 @@ void rrc_nr::ue::send_dl_ccch(const dl_ccch_msg_s& dl_ccch_msg) parent->rlc->write_sdu(rnti, srsran::srb_to_lcid(srsran::nr_srb::srb0), std::move(pdu)); } +void rrc_nr::ue::send_dl_dcch(srsran::nr_srb srb, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg) +{ + // Allocate a new PDU buffer, pack the message and send to PDCP + srsran::unique_byte_buffer_t pdu = parent->pack_into_pdu(dl_dcch_msg); + if (pdu == nullptr) { + parent->logger.error("Failed to send DL-DCCH"); + return; + } + fmt::memory_buffer fmtbuf; + fmt::format_to(fmtbuf, "DL-DCCH.{}", dl_dcch_msg.msg.c1().type().to_string()); + log_rrc_message(srb, Tx, *pdu.get(), dl_dcch_msg, srsran::to_c_str(fmtbuf)); + parent->pdcp->write_sdu(rnti, srsran::srb_to_lcid(srb), std::move(pdu)); +} + 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) @@ -927,6 +941,54 @@ void rrc_nr::ue::send_rrc_setup() void rrc_nr::ue::handle_rrc_setup_complete(const asn1::rrc_nr::rrc_setup_complete_s& msg) { // TODO: handle RRCSetupComplete + + send_security_mode_command(); +} + +/// TS 38.331, SecurityModeCommand +void rrc_nr::ue::send_security_mode_command() +{ + asn1::rrc_nr::dl_dcch_msg_s dl_dcch_msg; + dl_dcch_msg.msg.set_c1().set_security_mode_cmd().rrc_transaction_id = (uint8_t)((transaction_id++) % 4); + security_mode_cmd_ies_s& ies = dl_dcch_msg.msg.c1().security_mode_cmd().crit_exts.set_security_mode_cmd(); + + ies.security_cfg_smc.security_algorithm_cfg.integrity_prot_algorithm_present = true; + ies.security_cfg_smc.security_algorithm_cfg.integrity_prot_algorithm.value = integrity_prot_algorithm_opts::nia0; + ies.security_cfg_smc.security_algorithm_cfg.ciphering_algorithm.value = ciphering_algorithm_opts::nea0; + + send_dl_dcch(srsran::nr_srb::srb1, dl_dcch_msg); +} + +/// TS 38.331, SecurityModeComplete +void rrc_nr::ue::handle_security_mode_complete(const asn1::rrc_nr::security_mode_complete_s& msg) +{ + // TODO: handle SecurityModeComplete + + send_rrc_reconfiguration(); +} + +void rrc_nr::ue::send_rrc_reconfiguration() +{ + asn1::rrc_nr::dl_dcch_msg_s dl_dcch_msg; + dl_dcch_msg.msg.set_c1().set_rrc_recfg().rrc_transaction_id = (uint8_t)((transaction_id++) % 4); + rrc_recfg_ies_s& ies = dl_dcch_msg.msg.c1().rrc_recfg().crit_exts.set_rrc_recfg(); + + ies.non_crit_ext_present = true; + ies.non_crit_ext.master_cell_group_present = false; // TODO + + // Update lower layers + + send_dl_dcch(srsran::nr_srb::srb1, dl_dcch_msg); +} + +void rrc_nr::ue::handle_rrc_reconfiguration_complete(const asn1::rrc_nr::rrc_recfg_complete_s& msg) +{ + // TODO: handle RRCReconfComplete +} + +void rrc_nr::ue::handle_ul_information_transfer(const asn1::rrc_nr::ul_info_transfer_s& msg) +{ + // TODO: handle UL information transfer } /** From 930e3699d0e570acc60745e52660f407ec87fbe7 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Thu, 29 Jul 2021 11:59:49 +0100 Subject: [PATCH 05/77] Added max_s1_retries parameter --- lib/include/srsran/interfaces/enb_s1ap_interfaces.h | 1 + srsenb/enb.conf.example | 4 ++-- srsenb/src/main.cc | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/include/srsran/interfaces/enb_s1ap_interfaces.h b/lib/include/srsran/interfaces/enb_s1ap_interfaces.h index 5e6b11859..8ccba2f5e 100644 --- a/lib/include/srsran/interfaces/enb_s1ap_interfaces.h +++ b/lib/include/srsran/interfaces/enb_s1ap_interfaces.h @@ -32,6 +32,7 @@ struct s1ap_args_t { std::string enb_name; uint32_t ts1_reloc_prep_timeout; uint32_t ts1_reloc_overall_timeout; + uint32_t max_s1_setup_retries; }; // S1AP interface for RRC diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index 61f1d95f0..90f07dda2 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -359,8 +359,7 @@ enable = false # gtpu_tunnel_timeout: Time that GTPU takes to release indirect forwarding tunnel since the last received GTPU PDU (0 for no timer) # ts1_reloc_prep_timeout: S1AP TS 36.413 TS1RelocPrep Expiry Timeout value in milliseconds # ts1_reloc_overall_timeout: S1AP TS 36.413 TS1RelocOverall Expiry Timeout value in milliseconds -# 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 # ##################################################################### [expert] @@ -397,3 +396,4 @@ enable = false #ts1_reloc_overall_timeout = 10000 #rlf_release_timer_ms = 4000 #rlf_min_ul_snr_estim = -2 +#s1_setup_max_retries = 12 diff --git a/srsenb/src/main.cc b/srsenb/src/main.cc index 9a8e79d24..81fc55ea5 100644 --- a/srsenb/src/main.cc +++ b/srsenb/src/main.cc @@ -247,6 +247,7 @@ void parse_args(all_args_t* args, int argc, char* argv[]) ("expert.ts1_reloc_prep_timeout", bpo::value(&args->stack.s1ap.ts1_reloc_prep_timeout)->default_value(10000), "S1AP TS 36.413 TS1RelocPrep Expiry Timeout value in milliseconds.") ("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(12), "Max S1 setup retries") // eMBMS section ("embms.enable", bpo::value(&args->stack.embms.enable)->default_value(false), "Enables MBMS in the eNB") From e5a83474cc6a78a504f4a1916e26cbbcba4d2304 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Thu, 1 Jul 2021 12:34:00 +0100 Subject: [PATCH 06/77] Added max_s1_setup retries option to S1AP --- lib/src/common/network_utils.cc | 2 ++ srsenb/hdr/stack/s1ap/s1ap.h | 4 +++- srsenb/src/stack/s1ap/s1ap.cc | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/src/common/network_utils.cc b/lib/src/common/network_utils.cc index e6dc6a97b..29759d153 100644 --- a/lib/src/common/network_utils.cc +++ b/lib/src/common/network_utils.cc @@ -234,11 +234,13 @@ bool connect_to(int fd, const char* dest_addr_str, int dest_port, sockaddr_in* d if (dest_sockaddr != nullptr) { *dest_sockaddr = sockaddr_tmp; } + if (connect(fd, (const struct sockaddr*)&sockaddr_tmp, sizeof(sockaddr_tmp)) == -1) { srslog::fetch_basic_logger(LOGSERVICE).info("Failed to establish socket connection to %s", dest_addr_str); perror("connect()"); return false; } + return true; } diff --git a/srsenb/hdr/stack/s1ap/s1ap.h b/srsenb/hdr/stack/s1ap/s1ap.h index 79b58a56a..0f37ef5ae 100644 --- a/srsenb/hdr/stack/s1ap/s1ap.h +++ b/srsenb/hdr/stack/s1ap/s1ap.h @@ -122,6 +122,7 @@ private: rrc_interface_s1ap* rrc = nullptr; s1ap_args_t args; srslog::basic_logger& logger; + srslog::log_channel& alarms_channel; srsran::task_sched_handle task_sched; srsran::task_queue_handle mme_task_queue; srsran::socket_manager_itf* rx_socket_handler; @@ -329,8 +330,9 @@ private: srsran::proc_outcome_t init(); srsran::proc_outcome_t step() { return srsran::proc_outcome_t::yield; } srsran::proc_outcome_t react(const s1setupresult& event); - void then(const srsran::proc_state_t& result) const; + void then(const srsran::proc_state_t& result); const char* name() const { return "MME Connection"; } + uint16_t connect_count = 0; private: srsran::proc_outcome_t start_mme_connection(); diff --git a/srsenb/src/stack/s1ap/s1ap.cc b/srsenb/src/stack/s1ap/s1ap.cc index f64b6a2a9..d1af2535d 100644 --- a/srsenb/src/stack/s1ap/s1ap.cc +++ b/srsenb/src/stack/s1ap/s1ap.cc @@ -220,6 +220,7 @@ void s1ap::ue::ho_prep_proc_t::then(const srsran::proc_state_t& result) srsran::proc_outcome_t s1ap::s1_setup_proc_t::init() { procInfo("Starting new MME connection."); + connect_count++; return start_mme_connection(); } @@ -265,7 +266,7 @@ srsran::proc_outcome_t s1ap::s1_setup_proc_t::react(const srsenb::s1ap::s1_setup return srsran::proc_outcome_t::error; } -void s1ap::s1_setup_proc_t::then(const srsran::proc_state_t& result) const +void s1ap::s1_setup_proc_t::then(const srsran::proc_state_t& result) { if (result.is_error()) { procInfo("Failed to initiate S1 connection. Attempting reconnection in %d seconds", @@ -276,7 +277,12 @@ void s1ap::s1_setup_proc_t::then(const srsran::proc_state_t& result) const s1ap_ptr->mme_socket.close(); procInfo("S1AP socket closed."); s1ap_ptr->mme_connect_timer.run(); + if (connect_count > s1ap_ptr->args.max_s1_setup_retries) { + s1ap_ptr->alarms_channel("s1apError"); + } // Try again with in 10 seconds + } else { + connect_count = 0; } } @@ -287,7 +293,11 @@ void s1ap::s1_setup_proc_t::then(const srsran::proc_state_t& result) const s1ap::s1ap(srsran::task_sched_handle task_sched_, srslog::basic_logger& logger, srsran::socket_manager_itf* rx_socket_handler_) : - s1setup_proc(this), logger(logger), task_sched(task_sched_), rx_socket_handler(rx_socket_handler_) + s1setup_proc(this), + logger(logger), + task_sched(task_sched_), + rx_socket_handler(rx_socket_handler_), + alarms_channel(srslog::fetch_log_channel("alarms")) { mme_task_queue = task_sched.make_task_queue(); } @@ -303,7 +313,7 @@ int s1ap::init(const s1ap_args_t& args_, rrc_interface_s1ap* rrc_) mme_connect_timer = task_sched.get_unique_timer(); auto mme_connect_run = [this](uint32_t tid) { if (s1setup_proc.is_busy()) { - logger.error("Failed to initiate S1Setup procedure."); + logger.error("Failed to initiate S1Setup procedure: procedure is busy."); } s1setup_proc.launch(); }; @@ -321,7 +331,7 @@ int s1ap::init(const s1ap_args_t& args_, rrc_interface_s1ap* rrc_) running = true; // starting MME connection if (not s1setup_proc.launch()) { - logger.error("Failed to initiate S1Setup procedure."); + logger.error("Failed to initiate S1Setup procedure: error launching procedure."); } return SRSRAN_SUCCESS; From 99e8ddefeec2e7e848b668b346af8340da8ce2a0 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Mon, 5 Jul 2021 19:21:26 +0100 Subject: [PATCH 07/77] Terminate application if the eNB cannot connect to the MME after max s1 retries is reached --- srsenb/src/stack/s1ap/s1ap.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/srsenb/src/stack/s1ap/s1ap.cc b/srsenb/src/stack/s1ap/s1ap.cc index d1af2535d..d128e93f7 100644 --- a/srsenb/src/stack/s1ap/s1ap.cc +++ b/srsenb/src/stack/s1ap/s1ap.cc @@ -279,6 +279,7 @@ void s1ap::s1_setup_proc_t::then(const srsran::proc_state_t& result) s1ap_ptr->mme_connect_timer.run(); if (connect_count > s1ap_ptr->args.max_s1_setup_retries) { s1ap_ptr->alarms_channel("s1apError"); + srsran_terminate("Error connecting to MME"); } // Try again with in 10 seconds } else { From e65bcd7147df922c25e9e888ee006e1e2ecbb342 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 12 Nov 2021 13:10:10 +0000 Subject: [PATCH 08/77] Changed default max S1 Setup retries to infinity --- lib/include/srsran/interfaces/enb_s1ap_interfaces.h | 2 +- lib/src/common/network_utils.cc | 2 -- srsenb/enb.conf.example | 6 ++++-- srsenb/src/main.cc | 2 +- srsenb/src/stack/s1ap/s1ap.cc | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/include/srsran/interfaces/enb_s1ap_interfaces.h b/lib/include/srsran/interfaces/enb_s1ap_interfaces.h index 8ccba2f5e..41013bc37 100644 --- a/lib/include/srsran/interfaces/enb_s1ap_interfaces.h +++ b/lib/include/srsran/interfaces/enb_s1ap_interfaces.h @@ -32,7 +32,7 @@ struct s1ap_args_t { std::string enb_name; uint32_t ts1_reloc_prep_timeout; uint32_t ts1_reloc_overall_timeout; - uint32_t max_s1_setup_retries; + int32_t max_s1_setup_retries; }; // S1AP interface for RRC diff --git a/lib/src/common/network_utils.cc b/lib/src/common/network_utils.cc index 29759d153..e6dc6a97b 100644 --- a/lib/src/common/network_utils.cc +++ b/lib/src/common/network_utils.cc @@ -234,13 +234,11 @@ bool connect_to(int fd, const char* dest_addr_str, int dest_port, sockaddr_in* d if (dest_sockaddr != nullptr) { *dest_sockaddr = sockaddr_tmp; } - if (connect(fd, (const struct sockaddr*)&sockaddr_tmp, sizeof(sockaddr_tmp)) == -1) { srslog::fetch_basic_logger(LOGSERVICE).info("Failed to establish socket connection to %s", dest_addr_str); perror("connect()"); return false; } - return true; } diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index 90f07dda2..ee7ec0fe8 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -359,7 +359,9 @@ enable = false # gtpu_tunnel_timeout: Time that GTPU takes to release indirect forwarding tunnel since the last received GTPU PDU (0 for no timer) # ts1_reloc_prep_timeout: S1AP TS 36.413 TS1RelocPrep Expiry Timeout value in milliseconds # ts1_reloc_overall_timeout: S1AP TS 36.413 TS1RelocOverall Expiry Timeout value in milliseconds -# 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 +# 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. # ##################################################################### [expert] @@ -396,4 +398,4 @@ enable = false #ts1_reloc_overall_timeout = 10000 #rlf_release_timer_ms = 4000 #rlf_min_ul_snr_estim = -2 -#s1_setup_max_retries = 12 +#s1_setup_max_retries = -1 diff --git a/srsenb/src/main.cc b/srsenb/src/main.cc index 81fc55ea5..d31d959f9 100644 --- a/srsenb/src/main.cc +++ b/srsenb/src/main.cc @@ -247,7 +247,7 @@ void parse_args(all_args_t* args, int argc, char* argv[]) ("expert.ts1_reloc_prep_timeout", bpo::value(&args->stack.s1ap.ts1_reloc_prep_timeout)->default_value(10000), "S1AP TS 36.413 TS1RelocPrep Expiry Timeout value in milliseconds.") ("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(12), "Max S1 setup retries") + ("expert.max_s1_setup_retries", bpo::value(&args->stack.s1ap.max_s1_setup_retries)->default_value(-1), "Max S1 setup retries") // eMBMS section ("embms.enable", bpo::value(&args->stack.embms.enable)->default_value(false), "Enables MBMS in the eNB") diff --git a/srsenb/src/stack/s1ap/s1ap.cc b/srsenb/src/stack/s1ap/s1ap.cc index d128e93f7..12d598e30 100644 --- a/srsenb/src/stack/s1ap/s1ap.cc +++ b/srsenb/src/stack/s1ap/s1ap.cc @@ -277,7 +277,7 @@ void s1ap::s1_setup_proc_t::then(const srsran::proc_state_t& result) s1ap_ptr->mme_socket.close(); procInfo("S1AP socket closed."); s1ap_ptr->mme_connect_timer.run(); - if (connect_count > s1ap_ptr->args.max_s1_setup_retries) { + if (s1ap_ptr->args.max_s1_setup_retries > 0 && connect_count > s1ap_ptr->args.max_s1_setup_retries) { s1ap_ptr->alarms_channel("s1apError"); srsran_terminate("Error connecting to MME"); } From b15f63f32ff333fccb32f4d594a204879b0df349 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Tue, 6 Jul 2021 17:45:03 +0100 Subject: [PATCH 09/77] Added an RLC AM base class to avoid code duplication in the RLC AM NR entity. This class is based on a template that receives as argument the rlc_am_*_tx/rx entities, so that those are different for LTE and NR. Moved code from rlc_am_lte/nr entities so that they use the new base class. --- lib/include/srsran/rlc/rlc_am_base.h | 183 +++++++++++++- lib/include/srsran/rlc/rlc_am_lte.h | 351 ++++++++++++--------------- lib/include/srsran/rlc/rlc_am_nr.h | 162 +++++-------- lib/src/rlc/rlc_am_lte.cc | 269 ++++---------------- lib/src/rlc/rlc_am_nr.cc | 185 ++++---------- 5 files changed, 480 insertions(+), 670 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index 6743ae4db..bf9f21a74 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -15,6 +15,8 @@ #include "srsran/common/buffer_pool.h" #include "srsran/common/common.h" +#include "srsran/common/timers.h" +#include "srsran/interfaces/ue_rrc_interfaces.h" #include "srsran/rlc/rlc_common.h" #include "srsran/upper/byte_buffer_queue.h" #include @@ -22,20 +24,187 @@ #include #include -namespace srsran { +namespace srsue { + +class pdcp_interface_rlc; +class rrc_interface_rlc; -///< Add rlc_am_base here +} // namespace srsue + +namespace srsran { bool rlc_am_is_control_pdu(uint8_t* payload); bool rlc_am_is_control_pdu(byte_buffer_t* pdu); -} // namespace srsran +/******************************* + * rlc_am_base class + ******************************/ +template +class rlc_am_base : public rlc_common +{ +public: + rlc_am_base(srslog::basic_logger& logger, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srsran::timer_handler* timers_) : + logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx(this), rx(this) + {} -namespace srsue { + bool configure(const rlc_config_t& cfg_) final + { + // determine bearer name and configure Rx/Tx objects + rb_name = rrc->get_rb_name(lcid); -class pdcp_interface_rlc; -class rrc_interface_rlc; + // store config + cfg = cfg_; -} // namespace srsue + if (not rx.configure(cfg)) { + logger.error("Error configuring bearer (RX)"); + return false; + } + + if (not tx.configure(cfg)) { + logger.error("Error configuring bearer (TX)"); + return false; + } + + logger.info("%s configured: t_poll_retx=%d, poll_pdu=%d, poll_byte=%d, max_retx_thresh=%d, " + "t_reordering=%d, t_status_prohibit=%d", + rb_name.c_str(), + 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); + return true; + } + + void reestablish() final + { + logger.debug("Reestablished bearer %s", rb_name.c_str()); + tx.reestablish(); // calls stop and enables tx again + rx.reestablish(); // calls only stop + } + + void stop() final + { + logger.debug("Stopped bearer %s", rb_name.c_str()); + tx.stop(); + rx.stop(); + } + + void empty_queue() final + { + // Drop all messages in TX SDU queue + tx.empty_queue(); + } + + rlc_mode_t get_mode() final { return rlc_mode_t::am; } + + uint32_t get_bearer() final { return lcid; } + + /**************************************************************************** + * PDCP interface + ***************************************************************************/ + void write_sdu(unique_byte_buffer_t sdu) final + { + if (tx.write_sdu(std::move(sdu)) == SRSRAN_SUCCESS) { + std::lock_guard lock(metrics_mutex); + metrics.num_tx_sdus++; + } + } + + void discard_sdu(uint32_t discard_sn) final + { + tx.discard_sdu(discard_sn); + + std::lock_guard lock(metrics_mutex); + metrics.num_lost_sdus++; + } + + bool sdu_queue_is_full() final { return tx.sdu_queue_is_full(); } + + /**************************************************************************** + * MAC interface + ***************************************************************************/ + bool has_data() final { return tx.has_data(); } + uint32_t get_buffer_state() final { return tx.get_buffer_state(); } + void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) final + { + tx.get_buffer_state(tx_queue, prio_tx_queue); + } + + uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) final + { + uint32_t read_bytes = tx.read_pdu(payload, nof_bytes); + + std::lock_guard lock(metrics_mutex); + metrics.num_tx_pdus++; + metrics.num_tx_pdu_bytes += read_bytes; + + return read_bytes; + } + + void write_pdu(uint8_t* payload, uint32_t nof_bytes) final + { + rx.write_pdu(payload, nof_bytes); + + std::lock_guard lock(metrics_mutex); + metrics.num_rx_pdus++; + metrics.num_rx_pdu_bytes += nof_bytes; + } + + /**************************************************************************** + * Metrics + ***************************************************************************/ + rlc_bearer_metrics_t get_metrics() + { + // update values that aren't calculated on the fly + uint32_t latency = rx.get_sdu_rx_latency_ms(); + uint32_t buffered_bytes = rx.get_rx_buffered_bytes(); + + std::lock_guard lock(metrics_mutex); + metrics.rx_latency_ms = latency; + metrics.rx_buffered_bytes = buffered_bytes; + + return metrics; + } + + void reset_metrics() + { + std::lock_guard lock(metrics_mutex); + metrics = {}; + } + + // BSR callback + void set_bsr_callback(bsr_callback_t callback) final { tx.set_bsr_callback(callback); } + +private: + // Common variables needed/provided by parent class + srsue::rrc_interface_rlc* rrc = nullptr; + srslog::basic_logger& logger; + srsue::pdcp_interface_rlc* pdcp = nullptr; + srsran::timer_handler* timers = nullptr; + uint32_t lcid = 0; + rlc_config_t cfg = {}; + std::string rb_name; + + static const int poll_periodicity = 8; // After how many data PDUs a status PDU shall be requested + + // Rx and Tx objects + friend class rlc_am_lte_tx; + friend class rlc_am_lte_rx; + friend class rlc_am_nr_tx; + friend class rlc_am_nr_rx; + T_rlc_tx tx; + T_rlc_rx rx; + + std::mutex metrics_mutex; + rlc_bearer_metrics_t metrics = {}; +}; + +} // namespace srsran #endif // SRSRAN_RLC_AM_BASE_H diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index 30263a1a7..c1d62ac5a 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -80,251 +80,198 @@ private: size_t rpos = 0; }; -class rlc_am_lte : public rlc_common +// Transmitter sub-class +class rlc_am_lte_tx; +class rlc_am_lte_rx; +using rlc_am_lte = rlc_am_base; +class rlc_am_lte_tx : public timer_callback { public: - rlc_am_lte(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_); + explicit rlc_am_lte_tx(rlc_am_lte* parent_); + ~rlc_am_lte_tx() = default; bool configure(const rlc_config_t& cfg_); - void reestablish(); - void stop(); void empty_queue(); + void reestablish(); + void stop(); - rlc_mode_t get_mode(); - uint32_t get_bearer(); - - // PDCP interface - void write_sdu(unique_byte_buffer_t sdu); - void discard_sdu(uint32_t pdcp_sn); - bool sdu_queue_is_full(); + int write_sdu(unique_byte_buffer_t sdu); + uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); + void discard_sdu(uint32_t discard_sn); + bool sdu_queue_is_full(); - // MAC interface bool has_data(); uint32_t get_buffer_state(); void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); - uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); - void write_pdu(uint8_t* payload, uint32_t nof_bytes); - rlc_bearer_metrics_t get_metrics(); - void reset_metrics(); + // Timeout callback interface + void timer_expired(uint32_t timeout_id) final; + + // Interface for Rx subclass + void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes); void set_bsr_callback(bsr_callback_t callback); private: - // Transmitter sub-class - class rlc_am_lte_tx : public timer_callback - { - public: - rlc_am_lte_tx(rlc_am_lte* parent_); - ~rlc_am_lte_tx(); - - bool configure(const rlc_config_t& cfg_); + void stop_nolock(); - void empty_queue(); - void reestablish(); - void stop(); + 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_data_pdu(uint8_t* payload, uint32_t nof_bytes); + void update_notification_ack_info(uint32_t rlc_sn); - int write_sdu(unique_byte_buffer_t sdu); - uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); - void discard_sdu(uint32_t discard_sn); - bool sdu_queue_is_full(); + void empty_queue_nolock(); + void debug_state(); - bool has_data(); - uint32_t get_buffer_state(); - void get_buffer_state(uint32_t& new_tx, uint32_t& prio_tx); + int required_buffer_size(const rlc_amd_retx_t& retx); + void retransmit_pdu(uint32_t sn); - // Timeout callback interface - void timer_expired(uint32_t timeout_id); + // Helpers + void get_buffer_state_nolock(uint32_t& new_tx, uint32_t& prio_tx); + bool poll_required(); + bool do_status(); + void check_sn_reached_max_retx(uint32_t sn); - // Interface for Rx subclass - void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes); + rlc_am_lte* parent = nullptr; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger& logger; + rlc_am_pdu_segment_pool segment_pool; - void set_bsr_callback(bsr_callback_t callback); + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ - private: - void stop_nolock(); + rlc_am_config_t cfg = {}; - 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_data_pdu(uint8_t* payload, uint32_t nof_bytes); - void update_notification_ack_info(uint32_t rlc_sn); + // TX SDU buffers + byte_buffer_queue tx_sdu_queue; + unique_byte_buffer_t tx_sdu; - void debug_state(); - void empty_queue_nolock(); + bool tx_enabled = false; - int required_buffer_size(const rlc_amd_retx_t& retx); - void retransmit_pdu(uint32_t sn); + /**************************************************************************** + * State variables and counters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ - void get_buffer_state_nolock(uint32_t& new_tx, uint32_t& prio_tx); + // Tx state variables + uint32_t vt_a = 0; // ACK state. SN of next PDU in sequence to be ACKed. Low edge of tx window. + uint32_t vt_ms = RLC_AM_WINDOW_SIZE; // Max send state. High edge of tx window. vt_a + window_size. + uint32_t vt_s = 0; // Send state. SN to be assigned for next PDU. + uint32_t poll_sn = 0; // Poll send state. SN of most recent PDU txed with poll bit set. - // Helpers - bool poll_required(); - bool do_status(); - void check_sn_reached_max_retx(uint32_t sn); + // Tx counters + uint32_t pdu_without_poll = 0; + uint32_t byte_without_poll = 0; - rlc_am_lte* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; - rlc_am_pdu_segment_pool segment_pool; + rlc_status_pdu_t tx_status; - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + /**************************************************************************** + * Timers + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ - rlc_am_config_t cfg = {}; + srsran::timer_handler::unique_timer poll_retx_timer; + srsran::timer_handler::unique_timer status_prohibit_timer; - // TX SDU buffers - byte_buffer_queue tx_sdu_queue; - unique_byte_buffer_t tx_sdu; + // SDU info for PDCP notifications + buffered_pdcp_pdu_list undelivered_sdu_info_queue; - bool tx_enabled = false; + // Callback function for buffer status report + bsr_callback_t bsr_callback; - /**************************************************************************** - * State variables and counters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + // Tx windows + rlc_ringbuffer_t tx_window; + pdu_retx_queue retx_queue; + pdcp_sn_vector_t notify_info_vec; - // Tx state variables - uint32_t vt_a = 0; // ACK state. SN of next PDU in sequence to be ACKed. Low edge of tx window. - uint32_t vt_ms = RLC_AM_WINDOW_SIZE; // Max send state. High edge of tx window. vt_a + window_size. - uint32_t vt_s = 0; // Send state. SN to be assigned for next PDU. - uint32_t poll_sn = 0; // Poll send state. SN of most recent PDU txed with poll bit set. + // Mutexes + std::mutex mutex; - // Tx counters - uint32_t pdu_without_poll = 0; - uint32_t byte_without_poll = 0; - - rlc_status_pdu_t tx_status; - - /**************************************************************************** - * Timers - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + // default to RLC SDU queue length + const uint32_t MAX_SDUS_PER_RLC_PDU = RLC_TX_QUEUE_LEN; +}; - srsran::timer_handler::unique_timer poll_retx_timer; - srsran::timer_handler::unique_timer status_prohibit_timer; +// Receiver sub-class +class rlc_am_lte_rx : public timer_callback +{ +public: + rlc_am_lte_rx(rlc_am_lte* parent_); + ~rlc_am_lte_rx(); - // SDU info for PDCP notifications - buffered_pdcp_pdu_list undelivered_sdu_info_queue; + bool configure(rlc_config_t cfg_); + void reestablish(); + void stop(); - // Callback function for buffer status report - bsr_callback_t bsr_callback; + void write_pdu(uint8_t* payload, uint32_t nof_bytes); - // Tx windows - rlc_ringbuffer_t tx_window; - pdu_retx_queue retx_queue; - pdcp_sn_vector_t notify_info_vec; + uint32_t get_rx_buffered_bytes(); // returns sum of PDUs in rx_window + uint32_t get_sdu_rx_latency_ms(); - // Mutexes - std::mutex mutex; + // Timeout callback interface + void timer_expired(uint32_t timeout_id); - // default to RLC SDU queue length - const uint32_t MAX_SDUS_PER_RLC_PDU = RLC_TX_QUEUE_LEN; - }; + // Functions needed by Tx subclass to query rx state + int get_status_pdu_length(); + int get_status_pdu(rlc_status_pdu_t* status, const uint32_t nof_bytes); + bool get_do_status(); - // Receiver sub-class - class rlc_am_lte_rx : public timer_callback - { - public: - rlc_am_lte_rx(rlc_am_lte* parent_); - ~rlc_am_lte_rx(); - - bool configure(rlc_am_config_t cfg_); - void reestablish(); - void stop(); - - void write_pdu(uint8_t* payload, uint32_t nof_bytes); - - uint32_t get_rx_buffered_bytes(); // returns sum of PDUs in rx_window - uint32_t get_sdu_rx_latency_ms(); - - // Timeout callback interface - void timer_expired(uint32_t timeout_id); - - // Functions needed by Tx subclass to query rx state - int get_status_pdu_length(); - int get_status_pdu(rlc_status_pdu_t* status, const uint32_t nof_bytes); - bool get_do_status(); - - private: - void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); - void handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); - void reassemble_rx_sdus(); - bool inside_rx_window(const int16_t sn); - void debug_state(); - void print_rx_segments(); - bool add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment); - void reset_status(); - - rlc_am_lte* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - rlc_am_config_t cfg = {}; - - // RX SDU buffers - unique_byte_buffer_t rx_sdu; - - /**************************************************************************** - * State variables and counters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - - // Rx state variables - uint32_t vr_r = 0; // Receive state. SN following last in-sequence received PDU. Low edge of rx window - uint32_t vr_mr = RLC_AM_WINDOW_SIZE; // Max acceptable receive state. High edge of rx window. vr_r + window size. - uint32_t vr_x = 0; // t_reordering state. SN following PDU which triggered t_reordering. - uint32_t vr_ms = 0; // Max status tx state. Highest possible value of SN for ACK_SN in status PDU. - uint32_t vr_h = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs. - - // Mutex to protect members - std::mutex mutex; - - // Rx windows - rlc_ringbuffer_t rx_window; - std::map rx_segments; - - bool poll_received = false; - std::atomic do_status = {false}; // light-weight access from Tx entity - - /**************************************************************************** - * Timers - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - - srsran::timer_handler::unique_timer reordering_timer; - - srsran::rolling_average sdu_rx_latency_ms; - }; - - // Common variables needed/provided by parent class - srsue::rrc_interface_rlc* rrc = nullptr; - srslog::basic_logger& logger; - srsue::pdcp_interface_rlc* pdcp = nullptr; - srsran::timer_handler* timers = nullptr; - uint32_t lcid = 0; - rlc_config_t cfg = {}; - std::string rb_name; - - static const int poll_periodicity = 8; // After how many data PDUs a status PDU shall be requested - - // Rx and Tx objects - rlc_am_lte_tx tx; - rlc_am_lte_rx rx; - - std::mutex metrics_mutex; - rlc_bearer_metrics_t metrics = {}; +private: + void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); + void handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); + void reassemble_rx_sdus(); + bool inside_rx_window(const int16_t sn); + void debug_state(); + void print_rx_segments(); + bool add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment); + void reset_status(); + + rlc_am_lte* parent = nullptr; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger& logger; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + rlc_am_config_t cfg = {}; + + // RX SDU buffers + unique_byte_buffer_t rx_sdu; + + /**************************************************************************** + * State variables and counters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + + // Rx state variables + uint32_t vr_r = 0; // Receive state. SN following last in-sequence received PDU. Low edge of rx window + uint32_t vr_mr = RLC_AM_WINDOW_SIZE; // Max acceptable receive state. High edge of rx window. vr_r + window size. + uint32_t vr_x = 0; // t_reordering state. SN following PDU which triggered t_reordering. + uint32_t vr_ms = 0; // Max status tx state. Highest possible value of SN for ACK_SN in status PDU. + uint32_t vr_h = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs. + + // Mutex to protect members + std::mutex mutex; + + // Rx windows + rlc_ringbuffer_t rx_window; + std::map rx_segments; + + bool poll_received = false; + std::atomic do_status = {false}; // light-weight access from Tx entity + + /**************************************************************************** + * Timers + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + + srsran::timer_handler::unique_timer reordering_timer; + + srsran::rolling_average sdu_rx_latency_ms; }; } // namespace srsran diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 49d0dddc3..f52795ac0 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -25,112 +25,70 @@ namespace srsran { -class rlc_am_nr : public rlc_common +// Transmitter sub-class +class rlc_am_nr_tx; +class rlc_am_nr_rx; +using rlc_am_nr = rlc_am_base; + +// Transmitter sub-class +class rlc_am_nr_tx +{ +public: + explicit rlc_am_nr_tx(rlc_am_nr* parent_); + ~rlc_am_nr_tx() = default; + + bool configure(const rlc_config_t& cfg_); + void stop(); + + int write_sdu(unique_byte_buffer_t sdu); + uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); + void discard_sdu(uint32_t discard_sn); + bool sdu_queue_is_full(); + void reestablish(); + + void empty_queue(); + bool has_data(); + uint32_t get_buffer_state(); + void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); + void set_bsr_callback(const bsr_callback_t& callback); + +private: + rlc_am_nr* parent = nullptr; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger& logger; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 + ***************************************************************************/ + rlc_am_config_t cfg = {}; +}; + +// Receiver sub-class +class rlc_am_nr_rx { public: - rlc_am_nr(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_); - bool configure(const rlc_config_t& cfg_) final; - void stop() final; - - rlc_mode_t get_mode() final; - uint32_t get_bearer() final; - - void reestablish() final; - void empty_queue() final; - void set_bsr_callback(bsr_callback_t callback) final; - - // PDCP interface - void write_sdu(unique_byte_buffer_t sdu) final; - void discard_sdu(uint32_t pdcp_sn) final; - bool sdu_queue_is_full() final; - - // MAC interface - bool has_data() final; - uint32_t get_buffer_state() final; - void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) final; - uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) final; - void write_pdu(uint8_t* payload, uint32_t nof_bytes) final; - - rlc_bearer_metrics_t get_metrics() final; - void reset_metrics() final; + explicit rlc_am_nr_rx(rlc_am_nr* parent_); + ~rlc_am_nr_rx() = default; + + bool configure(const rlc_config_t& cfg_); + void stop(); + void reestablish(); + + void write_pdu(uint8_t* payload, uint32_t nof_bytes); + uint32_t get_sdu_rx_latency_ms(); + uint32_t get_rx_buffered_bytes(); private: - // Transmitter sub-class - class rlc_am_nr_tx - { - public: - explicit rlc_am_nr_tx(rlc_am_nr* parent_); - ~rlc_am_nr_tx() = default; - - bool configure(const rlc_am_config_t& cfg_); - void stop(); - - int write_sdu(unique_byte_buffer_t sdu); - 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(); - uint32_t get_buffer_state(); - - rlc_am_nr* parent = nullptr; - srslog::basic_logger& logger; - - private: - byte_buffer_pool* pool = nullptr; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 - ***************************************************************************/ - rlc_am_config_t cfg = {}; - }; - - // Receiver sub-class - class rlc_am_nr_rx - { - public: - explicit rlc_am_nr_rx(rlc_am_nr* parent_); - ~rlc_am_nr_rx() = default; - - bool configure(const rlc_am_config_t& cfg_); - void stop(); - - void write_pdu(uint8_t* payload, uint32_t nof_bytes); - - rlc_am_nr* parent = nullptr; - srslog::basic_logger& logger; - - private: - byte_buffer_pool* pool = nullptr; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 - ***************************************************************************/ - rlc_am_config_t cfg = {}; - }; - - // Common variables needed/provided by parent class - srsue::rrc_interface_rlc* rrc = nullptr; - srslog::basic_logger& logger; - srsue::pdcp_interface_rlc* pdcp = nullptr; - srsran::timer_handler* timers = nullptr; - uint32_t lcid = 0; - rlc_config_t cfg = {}; - std::string rb_name; - - static const int poll_periodicity = 8; // After how many data PDUs a status PDU shall be requested - - // Rx and Tx objects - rlc_am_nr_tx tx; - rlc_am_nr_rx rx; - - rlc_bearer_metrics_t metrics = {}; + rlc_am_nr* parent = nullptr; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger& logger; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 + ***************************************************************************/ + rlc_am_config_t cfg = {}; }; } // namespace srsran diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index add79c851..73398a936 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -89,172 +89,11 @@ rlc_amd_tx_pdu::~rlc_amd_tx_pdu() } } -/******************************* - * rlc_am_lte class - ******************************/ - -rlc_am_lte::rlc_am_lte(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_) : - logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx(this), rx(this) -{} - -// Applies new configuration. Must be just reestablished or initiated -bool rlc_am_lte::configure(const rlc_config_t& cfg_) -{ - // determine bearer name and configure Rx/Tx objects - rb_name = rrc->get_rb_name(lcid); - - // store config - cfg = cfg_; - - if (not rx.configure(cfg.am)) { - logger.error("Error configuring bearer (RX)"); - return false; - } - - if (not tx.configure(cfg)) { - logger.error("Error configuring bearer (TX)"); - return false; - } - - logger.info("%s configured: t_poll_retx=%d, poll_pdu=%d, poll_byte=%d, max_retx_thresh=%d, " - "t_reordering=%d, t_status_prohibit=%d", - rb_name.c_str(), - 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); - return true; -} - -void rlc_am_lte::set_bsr_callback(bsr_callback_t callback) -{ - tx.set_bsr_callback(callback); -} - -void rlc_am_lte::empty_queue() -{ - // Drop all messages in TX SDU queue - tx.empty_queue(); -} - -void rlc_am_lte::reestablish() -{ - logger.debug("Reestablished bearer %s", rb_name.c_str()); - tx.reestablish(); // calls stop and enables tx again - rx.reestablish(); // calls only stop -} - -void rlc_am_lte::stop() -{ - logger.debug("Stopped bearer %s", rb_name.c_str()); - tx.stop(); - rx.stop(); -} - -rlc_mode_t rlc_am_lte::get_mode() -{ - return rlc_mode_t::am; -} - -uint32_t rlc_am_lte::get_bearer() -{ - return lcid; -} - -rlc_bearer_metrics_t rlc_am_lte::get_metrics() -{ - // update values that aren't calculated on the fly - uint32_t latency = rx.get_sdu_rx_latency_ms(); - uint32_t buffered_bytes = rx.get_rx_buffered_bytes(); - - std::lock_guard lock(metrics_mutex); - metrics.rx_latency_ms = latency; - metrics.rx_buffered_bytes = buffered_bytes; - - return metrics; -} - -void rlc_am_lte::reset_metrics() -{ - std::lock_guard lock(metrics_mutex); - metrics = {}; -} - -/**************************************************************************** - * PDCP interface - ***************************************************************************/ - -void rlc_am_lte::write_sdu(unique_byte_buffer_t sdu) -{ - if (tx.write_sdu(std::move(sdu)) == SRSRAN_SUCCESS) { - std::lock_guard lock(metrics_mutex); - metrics.num_tx_sdus++; - } -} - -void rlc_am_lte::discard_sdu(uint32_t discard_sn) -{ - tx.discard_sdu(discard_sn); - - std::lock_guard lock(metrics_mutex); - metrics.num_lost_sdus++; -} - -bool rlc_am_lte::sdu_queue_is_full() -{ - return tx.sdu_queue_is_full(); -} - -/**************************************************************************** - * MAC interface - ***************************************************************************/ - -bool rlc_am_lte::has_data() -{ - return tx.has_data(); -} - -uint32_t rlc_am_lte::get_buffer_state() -{ - return tx.get_buffer_state(); -} - -void rlc_am_lte::get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) -{ - tx.get_buffer_state(tx_queue, prio_tx_queue); -} - -uint32_t rlc_am_lte::read_pdu(uint8_t* payload, uint32_t nof_bytes) -{ - uint32_t read_bytes = tx.read_pdu(payload, nof_bytes); - - std::lock_guard lock(metrics_mutex); - metrics.num_tx_pdus++; - metrics.num_tx_pdu_bytes += read_bytes; - - return read_bytes; -} - -void rlc_am_lte::write_pdu(uint8_t* payload, uint32_t nof_bytes) -{ - rx.write_pdu(payload, nof_bytes); - - std::lock_guard lock(metrics_mutex); - metrics.num_rx_pdus++; - metrics.num_rx_pdu_bytes += nof_bytes; -} - /**************************************************************************** * Tx subclass implementation ***************************************************************************/ -rlc_am_lte::rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : +rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : parent(parent_), logger(parent_->logger), pool(byte_buffer_pool::get_instance()), @@ -262,14 +101,12 @@ rlc_am_lte::rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : status_prohibit_timer(parent_->timers->get_unique_timer()) {} -rlc_am_lte::rlc_am_lte_tx::~rlc_am_lte_tx() {} - -void rlc_am_lte::rlc_am_lte_tx::set_bsr_callback(bsr_callback_t callback) +void rlc_am_lte_tx::set_bsr_callback(bsr_callback_t callback) { bsr_callback = callback; } -bool rlc_am_lte::rlc_am_lte_tx::configure(const rlc_config_t& cfg_) +bool rlc_am_lte_tx::configure(const rlc_config_t& cfg_) { std::lock_guard lock(mutex); if (cfg_.tx_queue_length > MAX_SDUS_PER_RLC_PDU) { @@ -307,13 +144,13 @@ bool rlc_am_lte::rlc_am_lte_tx::configure(const rlc_config_t& cfg_) return true; } -void rlc_am_lte::rlc_am_lte_tx::stop() +void rlc_am_lte_tx::stop() { std::lock_guard lock(mutex); stop_nolock(); } -void rlc_am_lte::rlc_am_lte_tx::stop_nolock() +void rlc_am_lte_tx::stop_nolock() { empty_queue_nolock(); @@ -345,13 +182,13 @@ void rlc_am_lte::rlc_am_lte_tx::stop_nolock() undelivered_sdu_info_queue.clear(); } -void rlc_am_lte::rlc_am_lte_tx::empty_queue() +void rlc_am_lte_tx::empty_queue() { std::lock_guard lock(mutex); empty_queue_nolock(); } -void rlc_am_lte::rlc_am_lte_tx::empty_queue_nolock() +void rlc_am_lte_tx::empty_queue_nolock() { // deallocate all SDUs in transmit queue while (tx_sdu_queue.size() > 0) { @@ -365,20 +202,20 @@ void rlc_am_lte::rlc_am_lte_tx::empty_queue_nolock() tx_sdu.reset(); } -void rlc_am_lte::rlc_am_lte_tx::reestablish() +void rlc_am_lte_tx::reestablish() { std::lock_guard lock(mutex); stop_nolock(); tx_enabled = true; } -bool rlc_am_lte::rlc_am_lte_tx::do_status() +bool rlc_am_lte_tx::do_status() { return parent->rx.get_do_status(); } // Function is supposed to return as fast as possible -bool rlc_am_lte::rlc_am_lte_tx::has_data() +bool rlc_am_lte_tx::has_data() { return (((do_status() && not status_prohibit_timer.is_running())) || // if we have a status PDU to transmit (not retx_queue.empty()) || // if we have a retransmission @@ -395,7 +232,7 @@ bool rlc_am_lte::rlc_am_lte_tx::has_data() * * @param sn The SN of the PDU to check */ -void rlc_am_lte::rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) +void rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) { if (tx_window[sn].retx_count == cfg.max_retx_thresh) { logger.warning("%s Signaling max number of reTx=%d for SN=%d", RB_NAME, tx_window[sn].retx_count, sn); @@ -411,27 +248,27 @@ void rlc_am_lte::rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) } } -uint32_t rlc_am_lte::rlc_am_lte_tx::get_buffer_state() +uint32_t rlc_am_lte_tx::get_buffer_state() { uint32_t new_tx_queue = 0, prio_tx_queue = 0; get_buffer_state(new_tx_queue, prio_tx_queue); return new_tx_queue + prio_tx_queue; } -void rlc_am_lte::rlc_am_lte_tx::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) +void rlc_am_lte_tx::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) { std::lock_guard lock(mutex); get_buffer_state_nolock(n_bytes_newtx, n_bytes_prio); } -void rlc_am_lte::rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) +void rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) { n_bytes_newtx = 0; n_bytes_prio = 0; uint32_t n_sdus = 0; logger.debug("%s Buffer state - do_status=%s, status_prohibit_running=%s (%d/%d)", - RB_NAME, + parent->rb_name, do_status() ? "yes" : "no", status_prohibit_timer.is_running() ? "yes" : "no", status_prohibit_timer.time_elapsed(), @@ -490,7 +327,7 @@ void rlc_am_lte::rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, } } -int rlc_am_lte::rlc_am_lte_tx::write_sdu(unique_byte_buffer_t sdu) +int rlc_am_lte_tx::write_sdu(unique_byte_buffer_t sdu) { std::lock_guard lock(mutex); @@ -526,7 +363,7 @@ int rlc_am_lte::rlc_am_lte_tx::write_sdu(unique_byte_buffer_t sdu) return SRSRAN_SUCCESS; } -void rlc_am_lte::rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) +void rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) { if (!tx_enabled) { return; @@ -544,12 +381,12 @@ void rlc_am_lte::rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) logger.info("%s PDU with PDCP_SN=%d", discarded ? "Discarding" : "Couldn't discard", discard_sn); } -bool rlc_am_lte::rlc_am_lte_tx::sdu_queue_is_full() +bool rlc_am_lte_tx::sdu_queue_is_full() { return tx_sdu_queue.is_full(); } -uint32_t rlc_am_lte::rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) +uint32_t rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) { std::lock_guard lock(mutex); @@ -587,7 +424,7 @@ uint32_t rlc_am_lte::rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_byte return build_data_pdu(payload, nof_bytes); } -void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) +void rlc_am_lte_tx::timer_expired(uint32_t timeout_id) { std::unique_lock lock(mutex); if (poll_retx_timer.is_valid() && poll_retx_timer.id() == timeout_id) { @@ -608,7 +445,7 @@ void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) } } -void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) +void rlc_am_lte_tx::retransmit_pdu(uint32_t sn) { if (tx_window.empty()) { logger.warning("%s No PDU to retransmit", RB_NAME); @@ -647,7 +484,7 @@ void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) * * @return True if a status PDU needs to be requested, false otherwise. */ -bool rlc_am_lte::rlc_am_lte_tx::poll_required() +bool rlc_am_lte_tx::poll_required() { if (cfg.poll_pdu > 0 && pdu_without_poll > static_cast(cfg.poll_pdu)) { return true; @@ -675,14 +512,14 @@ bool rlc_am_lte::rlc_am_lte_tx::poll_required() * However, it seems more appropiate to request more often if polling * is disabled otherwise, e.g. every N PDUs. */ - if (cfg.poll_pdu == 0 && cfg.poll_byte == 0 && vt_s % poll_periodicity == 0) { + if (cfg.poll_pdu == 0 && cfg.poll_byte == 0 && vt_s % rlc_am_lte::poll_periodicity == 0) { return true; } return false; } -int rlc_am_lte::rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_bytes) { int pdu_len = parent->rx.get_status_pdu(&tx_status, nof_bytes); if (pdu_len == SRSRAN_ERROR) { @@ -704,7 +541,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_b return pdu_len; } -int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte_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()) { @@ -785,7 +622,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_byt return (ptr - payload) + tx_window[retx.sn].buf->N_bytes; } -int rlc_am_lte::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_t retx) { if (tx_window[retx.sn].buf == NULL) { logger.error("In build_segment: retx.sn=%d has null buffer", retx.sn); @@ -960,7 +797,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_byte return pdu_len; } -int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) { if (tx_sdu == NULL && tx_sdu_queue.is_empty()) { logger.info("No data available to be sent"); @@ -1176,7 +1013,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt return total_len; } -void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) +void rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) { if (not tx_enabled) { return; @@ -1324,7 +1161,7 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no * @tx_pdu: RLC PDU that was ack'ed. * @notify_info_vec: Vector which will keep track of the PDCP PDU SNs that have been fully ack'ed. */ -void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) +void rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) { logger.debug("Updating ACK info: RLC SN=%d, number of notified SDU=%ld, number of undelivered SDUs=%ld", rlc_sn, @@ -1361,12 +1198,12 @@ void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) } } -void rlc_am_lte::rlc_am_lte_tx::debug_state() +void rlc_am_lte_tx::debug_state() { logger.debug("%s vt_a = %d, vt_ms = %d, vt_s = %d, poll_sn = %d", RB_NAME, vt_a, vt_ms, vt_s, poll_sn); } -int rlc_am_lte::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_t& retx) { if (!retx.is_segment) { if (tx_window.has_sn(retx.sn)) { @@ -1443,19 +1280,19 @@ int rlc_am_lte::rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_t& retx) * Rx subclass implementation ***************************************************************************/ -rlc_am_lte::rlc_am_lte_rx::rlc_am_lte_rx(rlc_am_lte* parent_) : +rlc_am_lte_rx::rlc_am_lte_rx(rlc_am_lte* parent_) : parent(parent_), pool(byte_buffer_pool::get_instance()), logger(parent_->logger), reordering_timer(parent_->timers->get_unique_timer()) {} -rlc_am_lte::rlc_am_lte_rx::~rlc_am_lte_rx() {} +rlc_am_lte_rx::~rlc_am_lte_rx() {} -bool rlc_am_lte::rlc_am_lte_rx::configure(rlc_am_config_t cfg_) +bool rlc_am_lte_rx::configure(rlc_config_t cfg_) { // TODO: add config checks - cfg = cfg_; + cfg = cfg_.am; // check timers if (not reordering_timer.is_valid()) { @@ -1471,12 +1308,12 @@ bool rlc_am_lte::rlc_am_lte_rx::configure(rlc_am_config_t cfg_) return true; } -void rlc_am_lte::rlc_am_lte_rx::reestablish() +void rlc_am_lte_rx::reestablish() { stop(); } -void rlc_am_lte::rlc_am_lte_rx::stop() +void rlc_am_lte_rx::stop() { std::lock_guard lock(mutex); @@ -1508,7 +1345,7 @@ void rlc_am_lte::rlc_am_lte_rx::stop() * @param nof_bytes Payload length * @param header Reference to PDU header (unpacked by caller) */ -void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) +void rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) { std::map::iterator it; @@ -1625,9 +1462,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b debug_state(); } -void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* payload, - uint32_t nof_bytes, - rlc_amd_pdu_header_t& header) +void rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) { std::map::iterator it; @@ -1715,7 +1550,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* pa debug_state(); } -void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() +void rlc_am_lte_rx::reassemble_rx_sdus() { uint32_t len = 0; if (rx_sdu == NULL) { @@ -1866,18 +1701,18 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() } } -void rlc_am_lte::rlc_am_lte_rx::reset_status() +void rlc_am_lte_rx::reset_status() { do_status = false; poll_received = false; } -bool rlc_am_lte::rlc_am_lte_rx::get_do_status() +bool rlc_am_lte_rx::get_do_status() { return do_status.load(std::memory_order_relaxed); } -void rlc_am_lte::rlc_am_lte_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) +void rlc_am_lte_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) { if (nof_bytes < 1) { return; @@ -1902,13 +1737,13 @@ void rlc_am_lte::rlc_am_lte_rx::write_pdu(uint8_t* payload, const uint32_t nof_b } } -uint32_t rlc_am_lte::rlc_am_lte_rx::get_rx_buffered_bytes() +uint32_t rlc_am_lte_rx::get_rx_buffered_bytes() { std::lock_guard lock(mutex); return rx_window.get_buffered_bytes(); } -uint32_t rlc_am_lte::rlc_am_lte_rx::get_sdu_rx_latency_ms() +uint32_t rlc_am_lte_rx::get_sdu_rx_latency_ms() { std::lock_guard lock(mutex); return sdu_rx_latency_ms.value(); @@ -1919,7 +1754,7 @@ uint32_t rlc_am_lte::rlc_am_lte_rx::get_sdu_rx_latency_ms() * * @param timeout_id */ -void rlc_am_lte::rlc_am_lte_rx::timer_expired(uint32_t timeout_id) +void rlc_am_lte_rx::timer_expired(uint32_t timeout_id) { std::lock_guard lock(mutex); if (reordering_timer.is_valid() and reordering_timer.id() == timeout_id) { @@ -1946,7 +1781,7 @@ void rlc_am_lte::rlc_am_lte_rx::timer_expired(uint32_t timeout_id) // Called from Tx object to pack status PDU that doesn't exceed a given size // If lock-acquisition fails, return -1. Otherwise it returns the length of the generated PDU. -int rlc_am_lte::rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const uint32_t max_pdu_size) +int rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const uint32_t max_pdu_size) { std::unique_lock lock(mutex, std::try_to_lock); if (not lock.owns_lock()) { @@ -1999,7 +1834,7 @@ int rlc_am_lte::rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const ui } // Called from Tx object to obtain length of the full status PDU -int rlc_am_lte::rlc_am_lte_rx::get_status_pdu_length() +int rlc_am_lte_rx::get_status_pdu_length() { std::unique_lock lock(mutex, std::try_to_lock); if (not lock.owns_lock()) { @@ -2017,7 +1852,7 @@ int rlc_am_lte::rlc_am_lte_rx::get_status_pdu_length() return rlc_am_packed_length(&status); } -void rlc_am_lte::rlc_am_lte_rx::print_rx_segments() +void rlc_am_lte_rx::print_rx_segments() { std::map::iterator it; std::stringstream ss; @@ -2033,7 +1868,7 @@ void rlc_am_lte::rlc_am_lte_rx::print_rx_segments() } // NOTE: Preference would be to capture by value, and then move; but header is stack allocated -bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment) +bool rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment) { // Find segment insertion point in the list of segments auto it1 = pdu->segments.begin(); @@ -2213,7 +2048,7 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* return true; } -bool rlc_am_lte::rlc_am_lte_rx::inside_rx_window(const int16_t sn) +bool rlc_am_lte_rx::inside_rx_window(const int16_t sn) { if (RX_MOD_BASE(sn) >= RX_MOD_BASE(static_cast(vr_r)) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_mr)) { return true; @@ -2222,7 +2057,7 @@ bool rlc_am_lte::rlc_am_lte_rx::inside_rx_window(const int16_t sn) } } -void rlc_am_lte::rlc_am_lte_rx::debug_state() +void rlc_am_lte_rx::debug_state() { logger.debug("%s vr_r = %d, vr_mr = %d, vr_x = %d, vr_ms = %d, vr_h = %d", RB_NAME, vr_r, vr_mr, vr_x, vr_ms, vr_h); } diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 4b96e5724..a6845bfde 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -20,197 +20,98 @@ namespace srsran { /******************************* - * RLC AM NR class - ******************************/ -rlc_am_nr::rlc_am_nr(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_) : - logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx(this), rx(this) + * RLC AM NR + * Tx subclass implementation + ***************************************************************************/ +rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* parent_) : + parent(parent_), logger(parent_->logger), pool(byte_buffer_pool::get_instance()) {} -// Applies new configuration. Must be just reestablished or initiated -bool rlc_am_nr::configure(const rlc_config_t& cfg_) +bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_) { - // determine bearer name and configure Rx/Tx objects - rb_name = rrc->get_rb_name(lcid); - - // store config - cfg = cfg_; - - if (not rx.configure(cfg.am)) { - logger.error("Error configuring bearer (RX)"); - return false; - } - - if (not tx.configure(cfg.am)) { - logger.error("Error configuring bearer (TX)"); - return false; - } - - logger.info("%s configured: t_poll_retx=%d, poll_pdu=%d, poll_byte=%d, max_retx_thresh=%d, " - "t_reordering=%d, t_status_prohibit=%d", - rb_name.c_str(), - 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_.tx_queue_length > MAX_SDUS_PER_RLC_PDU) { + logger.error("Configuring Tx queue length of %d PDUs too big. Maximum value is %d.", + cfg_.tx_queue_length, + MAX_SDUS_PER_RLC_PDU); + return false; + } + */ + cfg = cfg_.am; + return true; } -void rlc_am_nr::stop() {} - -rlc_mode_t rlc_am_nr::get_mode() +bool rlc_am_nr_tx::has_data() { - return rlc_mode_t::am; + return true; } -uint32_t rlc_am_nr::get_bearer() +uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) { return 0; } -void rlc_am_nr::reestablish() {} - -void rlc_am_nr::empty_queue() {} - -void rlc_am_nr::set_bsr_callback(bsr_callback_t callback) {} - -rlc_bearer_metrics_t rlc_am_nr::get_metrics() +void rlc_am_nr_tx::reestablish() { - return {}; + stop(); } -void rlc_am_nr::reset_metrics() {} - -/**************************************************************************** - * PDCP interface - ***************************************************************************/ -void rlc_am_nr::write_sdu(unique_byte_buffer_t sdu) +uint32_t rlc_am_nr_tx::get_buffer_state() { - if (tx.write_sdu(std::move(sdu)) == SRSRAN_SUCCESS) { - metrics.num_tx_sdus++; - } + return 0; } -void rlc_am_nr::discard_sdu(uint32_t pdcp_sn) -{ - tx.discard_sdu(pdcp_sn); - metrics.num_lost_sdus++; -} +void rlc_am_nr_tx::get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) {} -bool rlc_am_nr::sdu_queue_is_full() +int rlc_am_nr_tx::write_sdu(unique_byte_buffer_t sdu) { - return tx.sdu_queue_is_full(); + return 0; } -/**************************************************************************** - * MAC interface - ***************************************************************************/ +void rlc_am_nr_tx::discard_sdu(uint32_t discard_sn) {} -bool rlc_am_nr::has_data() +bool rlc_am_nr_tx::sdu_queue_is_full() { - return tx.has_data(); -} - -uint32_t rlc_am_nr::get_buffer_state() -{ - return tx.get_buffer_state(); + return false; } -void rlc_am_nr::get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) -{ - // TODO - tx_queue = tx.get_buffer_state(); - prio_tx_queue = 0; -} +void rlc_am_nr_tx::empty_queue() {} -uint32_t rlc_am_nr::read_pdu(uint8_t* payload, uint32_t nof_bytes) -{ - uint32_t read_bytes = tx.read_pdu(payload, nof_bytes); - metrics.num_tx_pdus++; - metrics.num_tx_pdu_bytes += read_bytes; - return read_bytes; -} +void rlc_am_nr_tx::set_bsr_callback(const bsr_callback_t& callback) {} -void rlc_am_nr::write_pdu(uint8_t* payload, uint32_t nof_bytes) -{ - rx.write_pdu(payload, nof_bytes); - metrics.num_rx_pdus++; - metrics.num_rx_pdu_bytes += nof_bytes; -} +void rlc_am_nr_tx::stop() {} /**************************************************************************** - * Tx subclass implementation + * Rx subclass implementation ***************************************************************************/ -rlc_am_nr::rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* parent_) : - parent(parent_), logger(parent_->logger), pool(byte_buffer_pool::get_instance()) +rlc_am_nr_rx::rlc_am_nr_rx(rlc_am_nr* parent_) : + parent(parent_), pool(byte_buffer_pool::get_instance()), logger(parent_->logger) {} -bool rlc_am_nr::rlc_am_nr_tx::configure(const rlc_am_config_t& cfg_) +bool rlc_am_nr_rx::configure(const rlc_config_t& cfg_) { - /* - if (cfg_.tx_queue_length > MAX_SDUS_PER_RLC_PDU) { - logger.error("Configuring Tx queue length of %d PDUs too big. Maximum value is %d.", - cfg_.tx_queue_length, - MAX_SDUS_PER_RLC_PDU); - return false; - } - */ - cfg = cfg_; + cfg = cfg_.am; return true; } -int rlc_am_nr::rlc_am_nr_tx::write_sdu(unique_byte_buffer_t sdu) -{ - return 0; -} +void rlc_am_nr_rx::stop() {} -void rlc_am_nr::rlc_am_nr_tx::discard_sdu(uint32_t sn) -{ - return; -} +void rlc_am_nr_rx::write_pdu(uint8_t* payload, uint32_t nof_bytes) {} -bool rlc_am_nr::rlc_am_nr_tx::sdu_queue_is_full() +void rlc_am_nr_rx::reestablish() { - return false; + stop(); } -bool rlc_am_nr::rlc_am_nr_tx::has_data() -{ - return true; -} - -uint32_t rlc_am_nr::rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) +uint32_t rlc_am_nr_rx::get_sdu_rx_latency_ms() { return 0; } -uint32_t rlc_am_nr::rlc_am_nr_tx::get_buffer_state() +uint32_t rlc_am_nr_rx::get_rx_buffered_bytes() { return 0; } - -/**************************************************************************** - * Rx subclass implementation - ***************************************************************************/ -rlc_am_nr::rlc_am_nr_rx::rlc_am_nr_rx(rlc_am_nr* parent_) : - parent(parent_), pool(byte_buffer_pool::get_instance()), logger(parent_->logger) -{} - -bool rlc_am_nr::rlc_am_nr_rx::configure(const rlc_am_config_t& cfg_) -{ - cfg = cfg_; - - return true; -} - -void rlc_am_nr::rlc_am_nr_rx::stop() {} - -void rlc_am_nr::rlc_am_nr_rx::write_pdu(uint8_t* payload, uint32_t nof_bytes) {} - } // namespace srsran From 022c51493bf1edf1db6a97832e2e93c2dbd7c9e7 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 23 Jul 2021 13:13:10 +0100 Subject: [PATCH 10/77] Refactored RLC AM NR/LTE Rx and Tx entities to use a rlc_am_base_rx/tx class. This was done to make it easier to share entity specific code between LTE and NR. This removes the previously used templates. --- lib/include/srsran/rlc/rlc_am_base.h | 224 ++++++++---------- lib/include/srsran/rlc/rlc_am_lte.h | 336 ++++++++++++++------------- lib/include/srsran/rlc/rlc_am_nr.h | 146 +++++++----- lib/src/rlc/rlc_am_base.cc | 187 +++++++++++++++ lib/src/rlc/rlc_am_lte.cc | 163 ++++++------- lib/src/rlc/rlc_am_nr.cc | 56 +++-- 6 files changed, 645 insertions(+), 467 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index bf9f21a74..fcde9a069 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -36,70 +36,35 @@ namespace srsran { bool rlc_am_is_control_pdu(uint8_t* payload); bool rlc_am_is_control_pdu(byte_buffer_t* pdu); -/******************************* - * rlc_am_base class - ******************************/ -template +/******************************************************* + * RLC AM entity + * This entity is common between LTE and NR + * and only the TX/RX entities change between them + *******************************************************/ class rlc_am_base : public rlc_common { +protected: + class rlc_am_base_tx; + class rlc_am_base_rx; + public: rlc_am_base(srslog::basic_logger& logger, uint32_t lcid_, srsue::pdcp_interface_rlc* pdcp_, srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_) : - logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx(this), rx(this) + srsran::timer_handler* timers_, + rlc_am_base_tx* tx_, + rlc_am_base_rx* rx_) : + logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx_base(tx_), rx_base(rx_) {} - bool configure(const rlc_config_t& cfg_) final - { - // determine bearer name and configure Rx/Tx objects - rb_name = rrc->get_rb_name(lcid); - - // store config - cfg = cfg_; - - if (not rx.configure(cfg)) { - logger.error("Error configuring bearer (RX)"); - return false; - } - - if (not tx.configure(cfg)) { - logger.error("Error configuring bearer (TX)"); - return false; - } - - logger.info("%s configured: t_poll_retx=%d, poll_pdu=%d, poll_byte=%d, max_retx_thresh=%d, " - "t_reordering=%d, t_status_prohibit=%d", - rb_name.c_str(), - 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); - return true; - } - - void reestablish() final - { - logger.debug("Reestablished bearer %s", rb_name.c_str()); - tx.reestablish(); // calls stop and enables tx again - rx.reestablish(); // calls only stop - } + bool configure(const rlc_config_t& cfg_) final; - void stop() final - { - logger.debug("Stopped bearer %s", rb_name.c_str()); - tx.stop(); - rx.stop(); - } + void reestablish() final; - void empty_queue() final - { - // Drop all messages in TX SDU queue - tx.empty_queue(); - } + void stop() final; + + void empty_queue() final { tx_base->empty_queue(); } rlc_mode_t get_mode() final { return rlc_mode_t::am; } @@ -108,101 +73,104 @@ public: /**************************************************************************** * PDCP interface ***************************************************************************/ - void write_sdu(unique_byte_buffer_t sdu) final - { - if (tx.write_sdu(std::move(sdu)) == SRSRAN_SUCCESS) { - std::lock_guard lock(metrics_mutex); - metrics.num_tx_sdus++; - } - } + void write_sdu(unique_byte_buffer_t sdu) final; - void discard_sdu(uint32_t discard_sn) final - { - tx.discard_sdu(discard_sn); - - std::lock_guard lock(metrics_mutex); - metrics.num_lost_sdus++; - } + void discard_sdu(uint32_t discard_sn) final; - bool sdu_queue_is_full() final { return tx.sdu_queue_is_full(); } + bool sdu_queue_is_full() final; /**************************************************************************** * MAC interface ***************************************************************************/ - bool has_data() final { return tx.has_data(); } - uint32_t get_buffer_state() final { return tx.get_buffer_state(); } - void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) final - { - tx.get_buffer_state(tx_queue, prio_tx_queue); - } + bool has_data() final; + uint32_t get_buffer_state() final; + void get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) final; - uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) final - { - uint32_t read_bytes = tx.read_pdu(payload, nof_bytes); + uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) final; - std::lock_guard lock(metrics_mutex); - metrics.num_tx_pdus++; - metrics.num_tx_pdu_bytes += read_bytes; - - return read_bytes; - } - - void write_pdu(uint8_t* payload, uint32_t nof_bytes) final - { - rx.write_pdu(payload, nof_bytes); - - std::lock_guard lock(metrics_mutex); - metrics.num_rx_pdus++; - metrics.num_rx_pdu_bytes += nof_bytes; - } + void write_pdu(uint8_t* payload, uint32_t nof_bytes) final; /**************************************************************************** * Metrics ***************************************************************************/ - rlc_bearer_metrics_t get_metrics() - { - // update values that aren't calculated on the fly - uint32_t latency = rx.get_sdu_rx_latency_ms(); - uint32_t buffered_bytes = rx.get_rx_buffered_bytes(); - - std::lock_guard lock(metrics_mutex); - metrics.rx_latency_ms = latency; - metrics.rx_buffered_bytes = buffered_bytes; - - return metrics; - } + rlc_bearer_metrics_t get_metrics() final; + void reset_metrics() final; - void reset_metrics() - { - std::lock_guard lock(metrics_mutex); - metrics = {}; - } - - // BSR callback - void set_bsr_callback(bsr_callback_t callback) final { tx.set_bsr_callback(callback); } + /**************************************************************************** + * BSR Callback + ***************************************************************************/ + void set_bsr_callback(bsr_callback_t callback) final; -private: +protected: // Common variables needed/provided by parent class - srsue::rrc_interface_rlc* rrc = nullptr; - srslog::basic_logger& logger; - srsue::pdcp_interface_rlc* pdcp = nullptr; - srsran::timer_handler* timers = nullptr; - uint32_t lcid = 0; - rlc_config_t cfg = {}; - std::string rb_name; + srslog::basic_logger& logger; + srsran::timer_handler* timers = nullptr; + uint32_t lcid = 0; + rlc_config_t cfg = {}; + std::string rb_name; static const int poll_periodicity = 8; // After how many data PDUs a status PDU shall be requested - // Rx and Tx objects - friend class rlc_am_lte_tx; - friend class rlc_am_lte_rx; - friend class rlc_am_nr_tx; - friend class rlc_am_nr_rx; - T_rlc_tx tx; - T_rlc_rx rx; - std::mutex metrics_mutex; rlc_bearer_metrics_t metrics = {}; + + srsue::rrc_interface_rlc* rrc = nullptr; + srsue::pdcp_interface_rlc* pdcp = nullptr; + + /******************************************************* + * RLC AM TX entity + * This class is used for common code between the + * LTE and NR TX entitites + *******************************************************/ + class rlc_am_base_tx + { + public: + virtual bool configure(const rlc_config_t& cfg_) = 0; + virtual void reestablish() = 0; + virtual void stop() = 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 uint32_t get_buffer_state() = 0; + virtual void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) = 0; + virtual uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; + + void set_bsr_callback(bsr_callback_t callback); + + int write_sdu(unique_byte_buffer_t sdu); + + byte_buffer_pool* pool = nullptr; + bool tx_enabled = false; + std::string rb_name; + + bsr_callback_t bsr_callback; + + // Tx SDU buffers + byte_buffer_queue tx_sdu_queue; + + // Mutexes + std::mutex mutex; + }; + + /******************************************************* + * RLC AM RX entity + * This class is used for common code between the + * LTE and NR RX entitites + *******************************************************/ + class rlc_am_base_rx + { + public: + virtual bool configure(const rlc_config_t& cfg_) = 0; + virtual void reestablish() = 0; + virtual void stop() = 0; + virtual void write_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; + virtual uint32_t get_sdu_rx_latency_ms() = 0; + virtual uint32_t get_rx_buffered_bytes() = 0; + }; + + rlc_am_base_tx* tx_base = nullptr; + rlc_am_base_rx* rx_base = nullptr; }; } // namespace srsran diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index c1d62ac5a..0c5c79e34 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -80,198 +80,208 @@ private: size_t rpos = 0; }; -// Transmitter sub-class -class rlc_am_lte_tx; -class rlc_am_lte_rx; -using rlc_am_lte = rlc_am_base; -class rlc_am_lte_tx : public timer_callback +/****************************** + * + * RLC AM LTE entity + * + *****************************/ +class rlc_am_lte : public rlc_am_base { public: - explicit rlc_am_lte_tx(rlc_am_lte* parent_); - ~rlc_am_lte_tx() = default; - - bool configure(const rlc_config_t& cfg_); - - void empty_queue(); - void reestablish(); - void stop(); - - int write_sdu(unique_byte_buffer_t sdu); - 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(); - uint32_t get_buffer_state(); - void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); - - // Timeout callback interface - void timer_expired(uint32_t timeout_id) final; - - // Interface for Rx subclass - void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes); - - void set_bsr_callback(bsr_callback_t callback); - -private: - void stop_nolock(); - - 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_data_pdu(uint8_t* payload, uint32_t nof_bytes); - void update_notification_ack_info(uint32_t rlc_sn); + rlc_am_lte(srslog::basic_logger& logger, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srsran::timer_handler* timers_); + + /****************************** + * RLC AM LTE TX entity + *****************************/ + class rlc_am_lte_tx : public rlc_am_base_tx, timer_callback + { + public: + explicit rlc_am_lte_tx(rlc_am_lte* parent_); + ~rlc_am_lte_tx() = default; - void empty_queue_nolock(); - void debug_state(); + bool configure(const rlc_config_t& cfg_); + void empty_queue(); + void reestablish(); + void stop(); - int required_buffer_size(const rlc_amd_retx_t& retx); - void retransmit_pdu(uint32_t sn); + uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); + void discard_sdu(uint32_t discard_sn); + bool sdu_queue_is_full(); - // Helpers - void get_buffer_state_nolock(uint32_t& new_tx, uint32_t& prio_tx); - bool poll_required(); - bool do_status(); - void check_sn_reached_max_retx(uint32_t sn); + bool has_data(); + uint32_t get_buffer_state(); + void get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio); - rlc_am_lte* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; - rlc_am_pdu_segment_pool segment_pool; + void empty_queue_nolock(); + void debug_state(); - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + // Timeout callback interface + void timer_expired(uint32_t timeout_id) final; - rlc_am_config_t cfg = {}; + // Interface for Rx subclass + void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes); - // TX SDU buffers - byte_buffer_queue tx_sdu_queue; - unique_byte_buffer_t tx_sdu; + private: + void stop_nolock(); - bool tx_enabled = false; + 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_data_pdu(uint8_t* payload, uint32_t nof_bytes); + void update_notification_ack_info(uint32_t rlc_sn); - /**************************************************************************** - * State variables and counters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + int required_buffer_size(const rlc_amd_retx_t& retx); + void retransmit_pdu(uint32_t sn); - // Tx state variables - uint32_t vt_a = 0; // ACK state. SN of next PDU in sequence to be ACKed. Low edge of tx window. - uint32_t vt_ms = RLC_AM_WINDOW_SIZE; // Max send state. High edge of tx window. vt_a + window_size. - uint32_t vt_s = 0; // Send state. SN to be assigned for next PDU. - uint32_t poll_sn = 0; // Poll send state. SN of most recent PDU txed with poll bit set. + // Helpers + bool poll_required(); + bool do_status(); + void check_sn_reached_max_retx(uint32_t sn); + void get_buffer_state_nolock(uint32_t& new_tx, uint32_t& prio_tx); - // Tx counters - uint32_t pdu_without_poll = 0; - uint32_t byte_without_poll = 0; + rlc_am_lte* parent = nullptr; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger& logger; + rlc_am_pdu_segment_pool segment_pool; - rlc_status_pdu_t tx_status; + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ - /**************************************************************************** - * Timers - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + rlc_am_config_t cfg = {}; - srsran::timer_handler::unique_timer poll_retx_timer; - srsran::timer_handler::unique_timer status_prohibit_timer; + // TX SDU buffers + unique_byte_buffer_t tx_sdu; - // SDU info for PDCP notifications - buffered_pdcp_pdu_list undelivered_sdu_info_queue; + /**************************************************************************** + * State variables and counters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ - // Callback function for buffer status report - bsr_callback_t bsr_callback; + // Tx state variables + uint32_t vt_a = 0; // ACK state. SN of next PDU in sequence to be ACKed. Low edge of tx window. + uint32_t vt_ms = RLC_AM_WINDOW_SIZE; // Max send state. High edge of tx window. vt_a + window_size. + uint32_t vt_s = 0; // Send state. SN to be assigned for next PDU. + uint32_t poll_sn = 0; // Poll send state. SN of most recent PDU txed with poll bit set. - // Tx windows - rlc_ringbuffer_t tx_window; - pdu_retx_queue retx_queue; - pdcp_sn_vector_t notify_info_vec; + // Tx counters + uint32_t pdu_without_poll = 0; + uint32_t byte_without_poll = 0; - // Mutexes - std::mutex mutex; + rlc_status_pdu_t tx_status; - // default to RLC SDU queue length - const uint32_t MAX_SDUS_PER_RLC_PDU = RLC_TX_QUEUE_LEN; -}; + /**************************************************************************** + * Timers + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ -// Receiver sub-class -class rlc_am_lte_rx : public timer_callback -{ -public: - rlc_am_lte_rx(rlc_am_lte* parent_); - ~rlc_am_lte_rx(); + srsran::timer_handler::unique_timer poll_retx_timer; + srsran::timer_handler::unique_timer status_prohibit_timer; - bool configure(rlc_config_t cfg_); - void reestablish(); - void stop(); + // SDU info for PDCP notifications + buffered_pdcp_pdu_list undelivered_sdu_info_queue; - void write_pdu(uint8_t* payload, uint32_t nof_bytes); + // Tx windows + rlc_ringbuffer_t tx_window; + pdu_retx_queue retx_queue; + pdcp_sn_vector_t notify_info_vec; - uint32_t get_rx_buffered_bytes(); // returns sum of PDUs in rx_window - uint32_t get_sdu_rx_latency_ms(); + // Mutexes + std::mutex mutex; - // Timeout callback interface - void timer_expired(uint32_t timeout_id); + // default to RLC SDU queue length + const uint32_t MAX_SDUS_PER_RLC_PDU = RLC_TX_QUEUE_LEN; + }; - // Functions needed by Tx subclass to query rx state - int get_status_pdu_length(); - int get_status_pdu(rlc_status_pdu_t* status, const uint32_t nof_bytes); - bool get_do_status(); + /****************************** + * RLC AM LTE RX entity + *****************************/ + class rlc_am_lte_rx : public rlc_am_base_rx, public timer_callback + { + public: + rlc_am_lte_rx(rlc_am_lte* parent_); + ~rlc_am_lte_rx(); + + bool configure(const rlc_config_t& cfg_) final; + void reestablish() final; + void stop() final; + + void write_pdu(uint8_t* payload, uint32_t nof_bytes) final; + + uint32_t get_rx_buffered_bytes() final; // returns sum of PDUs in rx_window + uint32_t get_sdu_rx_latency_ms() final; + + // Timeout callback interface + void timer_expired(uint32_t timeout_id) final; + + // Functions needed by Tx subclass to query rx state + int get_status_pdu_length(); + int get_status_pdu(rlc_status_pdu_t* status, uint32_t nof_bytes); + bool get_do_status(); + + private: + void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); + void handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); + void reassemble_rx_sdus(); + bool inside_rx_window(const int16_t sn); + void debug_state(); + void print_rx_segments(); + bool add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment); + void reset_status(); + + rlc_am_lte* parent = nullptr; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger& logger; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + rlc_am_config_t cfg = {}; + + // RX SDU buffers + unique_byte_buffer_t rx_sdu; + + /**************************************************************************** + * State variables and counters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + + // Rx state variables + uint32_t vr_r = 0; // Receive state. SN following last in-sequence received PDU. Low edge of rx window + uint32_t vr_mr = RLC_AM_WINDOW_SIZE; // Max acceptable receive state. High edge of rx window. vr_r + window size. + uint32_t vr_x = 0; // t_reordering state. SN following PDU which triggered t_reordering. + uint32_t vr_ms = 0; // Max status tx state. Highest possible value of SN for ACK_SN in status PDU. + uint32_t vr_h = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs. + + // Mutex to protect members + std::mutex mutex; + + // Rx windows + rlc_ringbuffer_t rx_window; + std::map rx_segments; + + bool poll_received = false; + std::atomic do_status = {false}; // light-weight access from Tx entity + + /**************************************************************************** + * Timers + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + + srsran::timer_handler::unique_timer reordering_timer; + + srsran::rolling_average sdu_rx_latency_ms; + }; private: - void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); - void handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); - void reassemble_rx_sdus(); - bool inside_rx_window(const int16_t sn); - void debug_state(); - void print_rx_segments(); - bool add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment); - void reset_status(); - - rlc_am_lte* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - rlc_am_config_t cfg = {}; - - // RX SDU buffers - unique_byte_buffer_t rx_sdu; - - /**************************************************************************** - * State variables and counters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - - // Rx state variables - uint32_t vr_r = 0; // Receive state. SN following last in-sequence received PDU. Low edge of rx window - uint32_t vr_mr = RLC_AM_WINDOW_SIZE; // Max acceptable receive state. High edge of rx window. vr_r + window size. - uint32_t vr_x = 0; // t_reordering state. SN following PDU which triggered t_reordering. - uint32_t vr_ms = 0; // Max status tx state. Highest possible value of SN for ACK_SN in status PDU. - uint32_t vr_h = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs. - - // Mutex to protect members - std::mutex mutex; - - // Rx windows - rlc_ringbuffer_t rx_window; - std::map rx_segments; - - bool poll_received = false; - std::atomic do_status = {false}; // light-weight access from Tx entity - - /**************************************************************************** - * Timers - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - - srsran::timer_handler::unique_timer reordering_timer; - - srsran::rolling_average sdu_rx_latency_ms; + rlc_am_lte_tx* tx = nullptr; + rlc_am_lte_rx* rx = nullptr; }; } // namespace srsran diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index f52795ac0..0b7a20c94 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -25,72 +25,96 @@ namespace srsran { -// Transmitter sub-class -class rlc_am_nr_tx; -class rlc_am_nr_rx; -using rlc_am_nr = rlc_am_base; - -// Transmitter sub-class -class rlc_am_nr_tx -{ -public: - explicit rlc_am_nr_tx(rlc_am_nr* parent_); - ~rlc_am_nr_tx() = default; - - bool configure(const rlc_config_t& cfg_); - void stop(); - - int write_sdu(unique_byte_buffer_t sdu); - uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); - void discard_sdu(uint32_t discard_sn); - bool sdu_queue_is_full(); - void reestablish(); - - void empty_queue(); - bool has_data(); - uint32_t get_buffer_state(); - void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); - void set_bsr_callback(const bsr_callback_t& callback); - -private: - rlc_am_nr* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 - ***************************************************************************/ - rlc_am_config_t cfg = {}; -}; - -// Receiver sub-class -class rlc_am_nr_rx +/****************************** + * + * RLC AM NR entity + * + *****************************/ +class rlc_am_nr : public rlc_am_base { public: - explicit rlc_am_nr_rx(rlc_am_nr* parent_); - ~rlc_am_nr_rx() = default; - - bool configure(const rlc_config_t& cfg_); - void stop(); - void reestablish(); - - void write_pdu(uint8_t* payload, uint32_t nof_bytes); - uint32_t get_sdu_rx_latency_ms(); - uint32_t get_rx_buffered_bytes(); + rlc_am_nr(srslog::basic_logger& logger, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srsran::timer_handler* timers_); + + // Transmitter sub-class + class rlc_am_nr_tx : public rlc_am_base_tx + { + public: + explicit rlc_am_nr_tx(rlc_am_nr* parent_); + ~rlc_am_nr_tx() = default; + + bool configure(const rlc_config_t& cfg_); + void stop(); + + int write_sdu(unique_byte_buffer_t sdu); + uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); + void discard_sdu(uint32_t discard_sn); + bool sdu_queue_is_full(); + void reestablish(); + + void empty_queue(); + bool has_data(); + uint32_t get_buffer_state(); + void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); + + private: + rlc_am_nr* parent = nullptr; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger& logger; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 + ***************************************************************************/ + rlc_am_config_t cfg = {}; + + /**************************************************************************** + * Tx state variables + * Ref: 3GPP TS 38.322 v10.0.0 Section 7.1 + ***************************************************************************/ + struct rlc_nr_tx_state_t { + uint32_t tx_next_ack; + uint32_t tx_next; + uint32_t poll_sn; + uint32_t pdu_without_poll; + uint32_t byte_without_poll; + } st = {}; + }; + + // Receiver sub-class + class rlc_am_nr_rx : public rlc_am_base_rx + { + public: + explicit rlc_am_nr_rx(rlc_am_nr* parent_); + ~rlc_am_nr_rx() = default; + + bool configure(const rlc_config_t& cfg_); + void stop(); + void reestablish(); + + void write_pdu(uint8_t* payload, uint32_t nof_bytes); + uint32_t get_sdu_rx_latency_ms(); + uint32_t get_rx_buffered_bytes(); + + private: + rlc_am_nr* parent = nullptr; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger& logger; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 + ***************************************************************************/ + rlc_am_config_t cfg = {}; + }; private: - rlc_am_nr* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 - ***************************************************************************/ - rlc_am_config_t cfg = {}; + rlc_am_nr_tx* tx = nullptr; + rlc_am_nr_rx* rx = nullptr; }; } // namespace srsran - #endif // SRSRAN_RLC_AM_NR_H diff --git a/lib/src/rlc/rlc_am_base.cc b/lib/src/rlc/rlc_am_base.cc index 9de01ac07..dc629a4c9 100644 --- a/lib/src/rlc/rlc_am_base.cc +++ b/lib/src/rlc/rlc_am_base.cc @@ -25,4 +25,191 @@ bool rlc_am_is_control_pdu(byte_buffer_t* pdu) return rlc_am_is_control_pdu(pdu->msg); } +/******************************************************* + * RLC AM entity + * This entity is common between LTE and NR + * and only the TX/RX entities change between them + *******************************************************/ +bool rlc_am_base::configure(const rlc_config_t& cfg_) +{ + // determine bearer name and configure Rx/Tx objects + rb_name = rrc->get_rb_name(lcid); + + // store config + cfg = cfg_; + + if (not rx_base->configure(cfg)) { + logger.error("Error configuring bearer (RX)"); + return false; + } + + if (not tx_base->configure(cfg)) { + logger.error("Error configuring bearer (TX)"); + return false; + } + + logger.info("%s configured: t_poll_retx=%d, poll_pdu=%d, poll_byte=%d, max_retx_thresh=%d, " + "t_reordering=%d, t_status_prohibit=%d", + rb_name.c_str(), + 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); + return true; +} + +void rlc_am_base::stop() +{ + logger.debug("Stopped bearer %s", rb_name.c_str()); + tx_base->stop(); + rx_base->stop(); +} + +void rlc_am_base::reestablish() +{ + logger.debug("Reestablished bearer %s", rb_name.c_str()); + tx_base->reestablish(); // calls stop and enables tx again + rx_base->reestablish(); // calls only stop +} + +/**************************************************************************** + * PDCP interface + ***************************************************************************/ +void rlc_am_base::write_sdu(unique_byte_buffer_t sdu) +{ + if (tx_base->write_sdu(std::move(sdu)) == SRSRAN_SUCCESS) { + std::lock_guard lock(metrics_mutex); + metrics.num_tx_sdus++; + } +} + +void rlc_am_base::discard_sdu(uint32_t discard_sn) +{ + tx_base->discard_sdu(discard_sn); + + std::lock_guard lock(metrics_mutex); + metrics.num_lost_sdus++; +} + +bool rlc_am_base::sdu_queue_is_full() +{ + return tx_base->sdu_queue_is_full(); +} + +/**************************************************************************** + * MAC interface + ***************************************************************************/ +bool rlc_am_base::has_data() +{ + return tx_base->has_data(); +} + +uint32_t rlc_am_base::get_buffer_state() +{ + return tx_base->get_buffer_state(); +} + +void rlc_am_base::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) +{ + tx_base->get_buffer_state(n_bytes_newtx, n_bytes_prio); + return; +} + +uint32_t rlc_am_base::read_pdu(uint8_t* payload, uint32_t nof_bytes) +{ + uint32_t read_bytes = tx_base->read_pdu(payload, nof_bytes); + + std::lock_guard lock(metrics_mutex); + metrics.num_tx_pdus++; + metrics.num_tx_pdu_bytes += read_bytes; + return read_bytes; +} + +void rlc_am_base::write_pdu(uint8_t* payload, uint32_t nof_bytes) +{ + rx_base->write_pdu(payload, nof_bytes); + + std::lock_guard lock(metrics_mutex); + metrics.num_rx_pdus++; + metrics.num_rx_pdu_bytes += nof_bytes; +} + +/**************************************************************************** + * Metrics + ***************************************************************************/ +rlc_bearer_metrics_t rlc_am_base::get_metrics() +{ + // update values that aren't calculated on the fly + uint32_t latency = rx_base->get_sdu_rx_latency_ms(); + uint32_t buffered_bytes = rx_base->get_rx_buffered_bytes(); + + std::lock_guard lock(metrics_mutex); + metrics.rx_latency_ms = latency; + metrics.rx_buffered_bytes = buffered_bytes; + return metrics; +} + +void rlc_am_base::reset_metrics() +{ + std::lock_guard lock(metrics_mutex); + metrics = {}; +} + +/**************************************************************************** + * BSR callback + ***************************************************************************/ +void rlc_am_base::set_bsr_callback(bsr_callback_t callback) +{ + tx_base->set_bsr_callback(callback); +} + +/******************************************************* + * RLC AM TX entity + * This class is used for common code between the + * LTE and NR TX entitites + *******************************************************/ +int rlc_am_base::rlc_am_base_tx::write_sdu(unique_byte_buffer_t sdu) +{ + std::lock_guard lock(mutex); + + if (!tx_enabled) { + return SRSRAN_ERROR; + } + + if (sdu.get() == nullptr) { + srslog::fetch_basic_logger("RLC").warning("NULL SDU pointer in write_sdu()"); + return SRSRAN_ERROR; + } + + // Get SDU info + uint32_t sdu_pdcp_sn = sdu->md.pdcp_sn; + + // Store SDU + uint8_t* msg_ptr = sdu->msg; + uint32_t nof_bytes = sdu->N_bytes; + srsran::error_type ret = tx_sdu_queue.try_write(std::move(sdu)); + if (ret) { + srslog::fetch_basic_logger("RLC").info( + msg_ptr, nof_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", rb_name, nof_bytes, tx_sdu_queue.size()); + } else { + // in case of fail, the try_write returns back the sdu + srslog::fetch_basic_logger("RLC").warning(ret.error()->msg, + ret.error()->N_bytes, + "[Dropped SDU] %s Tx SDU (%d B, tx_sdu_queue_len=%d)", + rb_name, + ret.error()->N_bytes, + tx_sdu_queue.size()); + return SRSRAN_ERROR; + } + + return SRSRAN_SUCCESS; +} + +void rlc_am_base::rlc_am_base_tx::set_bsr_callback(bsr_callback_t callback) +{ + bsr_callback = callback; +} + } // namespace srsran diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index 73398a936..5a479e1cf 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -89,11 +89,25 @@ rlc_amd_tx_pdu::~rlc_amd_tx_pdu() } } +/**************************************************************************** + * RLC AM LTE entity + ***************************************************************************/ +rlc_am_lte::rlc_am_lte(srslog::basic_logger& logger, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srsran::timer_handler* timers_) : + rlc_am_base(logger, lcid_, pdcp_, rrc_, timers_, nullptr, nullptr) +{ + tx = new rlc_am_lte::rlc_am_lte_tx(this); + rx = new rlc_am_lte::rlc_am_lte_rx(this); + tx_base = tx; + rx_base = rx; +} /**************************************************************************** * Tx subclass implementation ***************************************************************************/ - -rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : +rlc_am_lte::rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : parent(parent_), logger(parent_->logger), pool(byte_buffer_pool::get_instance()), @@ -101,12 +115,7 @@ rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : status_prohibit_timer(parent_->timers->get_unique_timer()) {} -void rlc_am_lte_tx::set_bsr_callback(bsr_callback_t callback) -{ - bsr_callback = callback; -} - -bool rlc_am_lte_tx::configure(const rlc_config_t& cfg_) +bool rlc_am_lte::rlc_am_lte_tx::configure(const rlc_config_t& cfg_) { std::lock_guard lock(mutex); if (cfg_.tx_queue_length > MAX_SDUS_PER_RLC_PDU) { @@ -144,13 +153,13 @@ bool rlc_am_lte_tx::configure(const rlc_config_t& cfg_) return true; } -void rlc_am_lte_tx::stop() +void rlc_am_lte::rlc_am_lte_tx::stop() { std::lock_guard lock(mutex); stop_nolock(); } -void rlc_am_lte_tx::stop_nolock() +void rlc_am_lte::rlc_am_lte_tx::stop_nolock() { empty_queue_nolock(); @@ -182,13 +191,13 @@ void rlc_am_lte_tx::stop_nolock() undelivered_sdu_info_queue.clear(); } -void rlc_am_lte_tx::empty_queue() +void rlc_am_lte::rlc_am_lte_tx::empty_queue() { std::lock_guard lock(mutex); empty_queue_nolock(); } -void rlc_am_lte_tx::empty_queue_nolock() +void rlc_am_lte::rlc_am_lte_tx::empty_queue_nolock() { // deallocate all SDUs in transmit queue while (tx_sdu_queue.size() > 0) { @@ -202,20 +211,20 @@ void rlc_am_lte_tx::empty_queue_nolock() tx_sdu.reset(); } -void rlc_am_lte_tx::reestablish() +void rlc_am_lte::rlc_am_lte_tx::reestablish() { std::lock_guard lock(mutex); stop_nolock(); tx_enabled = true; } -bool rlc_am_lte_tx::do_status() +bool rlc_am_lte::rlc_am_lte_tx::do_status() { - return parent->rx.get_do_status(); + return parent->rx->get_do_status(); } // Function is supposed to return as fast as possible -bool rlc_am_lte_tx::has_data() +bool rlc_am_lte::rlc_am_lte_tx::has_data() { return (((do_status() && not status_prohibit_timer.is_running())) || // if we have a status PDU to transmit (not retx_queue.empty()) || // if we have a retransmission @@ -232,7 +241,7 @@ bool rlc_am_lte_tx::has_data() * * @param sn The SN of the PDU to check */ -void rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) +void rlc_am_lte::rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) { if (tx_window[sn].retx_count == cfg.max_retx_thresh) { logger.warning("%s Signaling max number of reTx=%d for SN=%d", RB_NAME, tx_window[sn].retx_count, sn); @@ -248,20 +257,20 @@ void rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) } } -uint32_t rlc_am_lte_tx::get_buffer_state() +uint32_t rlc_am_lte::rlc_am_lte_tx::get_buffer_state() { uint32_t new_tx_queue = 0, prio_tx_queue = 0; get_buffer_state(new_tx_queue, prio_tx_queue); return new_tx_queue + prio_tx_queue; } -void rlc_am_lte_tx::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) +void rlc_am_lte::rlc_am_lte_tx::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) { std::lock_guard lock(mutex); get_buffer_state_nolock(n_bytes_newtx, n_bytes_prio); } -void rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) +void rlc_am_lte::rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) { n_bytes_newtx = 0; n_bytes_prio = 0; @@ -276,7 +285,7 @@ void rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n // Bytes needed for status report if (do_status() && not status_prohibit_timer.is_running()) { - n_bytes_prio += parent->rx.get_status_pdu_length(); + n_bytes_prio += parent->rx->get_status_pdu_length(); logger.debug("%s Buffer state - total status report: %d bytes", RB_NAME, n_bytes_prio); } @@ -323,47 +332,12 @@ void rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n } if (bsr_callback) { + logger.debug("%s Calling BSR callback - %d new_tx, %d prio bytes", RB_NAME, n_bytes_newtx, n_bytes_prio); bsr_callback(parent->lcid, n_bytes_newtx, n_bytes_prio); } } -int rlc_am_lte_tx::write_sdu(unique_byte_buffer_t sdu) -{ - std::lock_guard lock(mutex); - - if (!tx_enabled) { - return SRSRAN_ERROR; - } - - if (sdu.get() == nullptr) { - logger.warning("NULL SDU pointer in write_sdu()"); - return SRSRAN_ERROR; - } - - // Get SDU info - uint32_t sdu_pdcp_sn = sdu->md.pdcp_sn; - - // Store SDU - uint8_t* msg_ptr = sdu->msg; - uint32_t nof_bytes = sdu->N_bytes; - srsran::error_type ret = tx_sdu_queue.try_write(std::move(sdu)); - if (ret) { - logger.info(msg_ptr, nof_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", RB_NAME, nof_bytes, tx_sdu_queue.size()); - } else { - // in case of fail, the try_write returns back the sdu - logger.warning(ret.error()->msg, - ret.error()->N_bytes, - "[Dropped SDU] %s Tx SDU (%d B, tx_sdu_queue_len=%d)", - RB_NAME, - ret.error()->N_bytes, - tx_sdu_queue.size()); - return SRSRAN_ERROR; - } - - return SRSRAN_SUCCESS; -} - -void rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) +void rlc_am_lte::rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) { if (!tx_enabled) { return; @@ -381,12 +355,12 @@ void rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) logger.info("%s PDU with PDCP_SN=%d", discarded ? "Discarding" : "Couldn't discard", discard_sn); } -bool rlc_am_lte_tx::sdu_queue_is_full() +bool rlc_am_lte::rlc_am_lte_tx::sdu_queue_is_full() { return tx_sdu_queue.is_full(); } -uint32_t rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) +uint32_t rlc_am_lte::rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) { std::lock_guard lock(mutex); @@ -424,7 +398,7 @@ uint32_t rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) return build_data_pdu(payload, nof_bytes); } -void rlc_am_lte_tx::timer_expired(uint32_t timeout_id) +void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) { std::unique_lock lock(mutex); if (poll_retx_timer.is_valid() && poll_retx_timer.id() == timeout_id) { @@ -445,7 +419,7 @@ void rlc_am_lte_tx::timer_expired(uint32_t timeout_id) } } -void rlc_am_lte_tx::retransmit_pdu(uint32_t sn) +void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) { if (tx_window.empty()) { logger.warning("%s No PDU to retransmit", RB_NAME); @@ -484,7 +458,7 @@ void rlc_am_lte_tx::retransmit_pdu(uint32_t sn) * * @return True if a status PDU needs to be requested, false otherwise. */ -bool rlc_am_lte_tx::poll_required() +bool rlc_am_lte::rlc_am_lte_tx::poll_required() { if (cfg.poll_pdu > 0 && pdu_without_poll > static_cast(cfg.poll_pdu)) { return true; @@ -519,9 +493,10 @@ bool rlc_am_lte_tx::poll_required() return false; } -int rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte::rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_bytes) { - int pdu_len = parent->rx.get_status_pdu(&tx_status, nof_bytes); + logger->debug("%s Generating status PDU. Nof bytes %d", RB_NAME, nof_bytes); + int pdu_len = parent->rx->get_status_pdu(&tx_status, nof_bytes); if (pdu_len == SRSRAN_ERROR) { logger.debug("%s Deferred Status PDU. Cause: Failed to acquire Rx lock", RB_NAME); pdu_len = 0; @@ -541,7 +516,7 @@ int rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_bytes) return pdu_len; } -int rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte::rlc_am_lte_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()) { @@ -622,7 +597,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::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_retx_t retx) { if (tx_window[retx.sn].buf == NULL) { logger.error("In build_segment: retx.sn=%d has null buffer", retx.sn); @@ -797,7 +772,7 @@ int rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_r return pdu_len; } -int rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) { if (tx_sdu == NULL && tx_sdu_queue.is_empty()) { logger.info("No data available to be sent"); @@ -1013,7 +988,7 @@ int rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) return total_len; } -void rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) +void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) { if (not tx_enabled) { return; @@ -1161,7 +1136,7 @@ void rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) * @tx_pdu: RLC PDU that was ack'ed. * @notify_info_vec: Vector which will keep track of the PDCP PDU SNs that have been fully ack'ed. */ -void rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) +void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) { logger.debug("Updating ACK info: RLC SN=%d, number of notified SDU=%ld, number of undelivered SDUs=%ld", rlc_sn, @@ -1198,12 +1173,12 @@ void rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) } } -void rlc_am_lte_tx::debug_state() +void rlc_am_lte::rlc_am_lte_tx::debug_state() { logger.debug("%s vt_a = %d, vt_ms = %d, vt_s = %d, poll_sn = %d", RB_NAME, 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::rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_t& retx) { if (!retx.is_segment) { if (tx_window.has_sn(retx.sn)) { @@ -1280,16 +1255,16 @@ int rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_t& retx) * Rx subclass implementation ***************************************************************************/ -rlc_am_lte_rx::rlc_am_lte_rx(rlc_am_lte* parent_) : +rlc_am_lte::rlc_am_lte_rx::rlc_am_lte_rx(rlc_am_lte* parent_) : parent(parent_), pool(byte_buffer_pool::get_instance()), logger(parent_->logger), reordering_timer(parent_->timers->get_unique_timer()) {} -rlc_am_lte_rx::~rlc_am_lte_rx() {} +rlc_am_lte::rlc_am_lte_rx::~rlc_am_lte_rx() {} -bool rlc_am_lte_rx::configure(rlc_config_t cfg_) +bool rlc_am_lte::rlc_am_lte_rx::configure(const rlc_config_t& cfg_) { // TODO: add config checks cfg = cfg_.am; @@ -1308,12 +1283,12 @@ bool rlc_am_lte_rx::configure(rlc_config_t cfg_) return true; } -void rlc_am_lte_rx::reestablish() +void rlc_am_lte::rlc_am_lte_rx::reestablish() { stop(); } -void rlc_am_lte_rx::stop() +void rlc_am_lte::rlc_am_lte_rx::stop() { std::lock_guard lock(mutex); @@ -1345,7 +1320,7 @@ void rlc_am_lte_rx::stop() * @param nof_bytes Payload length * @param header Reference to PDU header (unpacked by caller) */ -void rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) +void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) { std::map::iterator it; @@ -1462,7 +1437,9 @@ void rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_am debug_state(); } -void rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) +void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* payload, + uint32_t nof_bytes, + rlc_amd_pdu_header_t& header) { std::map::iterator it; @@ -1550,7 +1527,7 @@ void rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes debug_state(); } -void rlc_am_lte_rx::reassemble_rx_sdus() +void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() { uint32_t len = 0; if (rx_sdu == NULL) { @@ -1701,25 +1678,25 @@ void rlc_am_lte_rx::reassemble_rx_sdus() } } -void rlc_am_lte_rx::reset_status() +void rlc_am_lte::rlc_am_lte_rx::reset_status() { do_status = false; poll_received = false; } -bool rlc_am_lte_rx::get_do_status() +bool rlc_am_lte::rlc_am_lte_rx::get_do_status() { return do_status.load(std::memory_order_relaxed); } -void rlc_am_lte_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) +void rlc_am_lte::rlc_am_lte_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) { if (nof_bytes < 1) { return; } if (rlc_am_is_control_pdu(payload)) { - parent->tx.handle_control_pdu(payload, nof_bytes); + parent->tx->handle_control_pdu(payload, nof_bytes); } else { std::lock_guard lock(mutex); rlc_amd_pdu_header_t header = {}; @@ -1737,13 +1714,13 @@ void rlc_am_lte_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) } } -uint32_t rlc_am_lte_rx::get_rx_buffered_bytes() +uint32_t rlc_am_lte::rlc_am_lte_rx::get_rx_buffered_bytes() { std::lock_guard lock(mutex); return rx_window.get_buffered_bytes(); } -uint32_t rlc_am_lte_rx::get_sdu_rx_latency_ms() +uint32_t rlc_am_lte::rlc_am_lte_rx::get_sdu_rx_latency_ms() { std::lock_guard lock(mutex); return sdu_rx_latency_ms.value(); @@ -1754,7 +1731,7 @@ uint32_t rlc_am_lte_rx::get_sdu_rx_latency_ms() * * @param timeout_id */ -void rlc_am_lte_rx::timer_expired(uint32_t timeout_id) +void rlc_am_lte::rlc_am_lte_rx::timer_expired(uint32_t timeout_id) { std::lock_guard lock(mutex); if (reordering_timer.is_valid() and reordering_timer.id() == timeout_id) { @@ -1781,7 +1758,7 @@ void rlc_am_lte_rx::timer_expired(uint32_t timeout_id) // Called from Tx object to pack status PDU that doesn't exceed a given size // If lock-acquisition fails, return -1. Otherwise it returns the length of the generated PDU. -int rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const uint32_t max_pdu_size) +int rlc_am_lte::rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const uint32_t max_pdu_size) { std::unique_lock lock(mutex, std::try_to_lock); if (not lock.owns_lock()) { @@ -1834,7 +1811,7 @@ int rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const uint32_t max_p } // Called from Tx object to obtain length of the full status PDU -int rlc_am_lte_rx::get_status_pdu_length() +int rlc_am_lte::rlc_am_lte_rx::get_status_pdu_length() { std::unique_lock lock(mutex, std::try_to_lock); if (not lock.owns_lock()) { @@ -1852,7 +1829,7 @@ int rlc_am_lte_rx::get_status_pdu_length() return rlc_am_packed_length(&status); } -void rlc_am_lte_rx::print_rx_segments() +void rlc_am_lte::rlc_am_lte_rx::print_rx_segments() { std::map::iterator it; std::stringstream ss; @@ -1868,7 +1845,7 @@ void rlc_am_lte_rx::print_rx_segments() } // NOTE: Preference would be to capture by value, and then move; but header is stack allocated -bool rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment) +bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment) { // Find segment insertion point in the list of segments auto it1 = pdu->segments.begin(); @@ -2048,7 +2025,7 @@ bool rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_am return true; } -bool rlc_am_lte_rx::inside_rx_window(const int16_t sn) +bool rlc_am_lte::rlc_am_lte_rx::inside_rx_window(const int16_t sn) { if (RX_MOD_BASE(sn) >= RX_MOD_BASE(static_cast(vr_r)) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_mr)) { return true; @@ -2057,7 +2034,7 @@ bool rlc_am_lte_rx::inside_rx_window(const int16_t sn) } } -void rlc_am_lte_rx::debug_state() +void rlc_am_lte::rlc_am_lte_rx::debug_state() { logger.debug("%s vr_r = %d, vr_mr = %d, vr_x = %d, vr_ms = %d, vr_h = %d", RB_NAME, vr_r, vr_mr, vr_x, vr_ms, vr_h); } diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index a6845bfde..d927681da 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -19,15 +19,29 @@ namespace srsran { +/**************************************************************************** + * RLC AM NR entity + ***************************************************************************/ +rlc_am_nr::rlc_am_nr(srslog::basic_logger& logger, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srsran::timer_handler* timers_) : + rlc_am_base(logger, lcid_, pdcp_, rrc_, timers_, new rlc_am_nr::rlc_am_nr_tx(this), new rlc_am_nr::rlc_am_nr_rx(this)) +{ + tx = dynamic_cast(tx_base); + rx = dynamic_cast(rx_base); +} + /******************************* * RLC AM NR * Tx subclass implementation ***************************************************************************/ -rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* parent_) : +rlc_am_nr::rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* parent_) : parent(parent_), logger(parent_->logger), pool(byte_buffer_pool::get_instance()) {} -bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_) +bool rlc_am_nr::rlc_am_nr_tx::configure(const rlc_config_t& cfg_) { /* if (cfg_.tx_queue_length > MAX_SDUS_PER_RLC_PDU) { @@ -42,75 +56,73 @@ bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_) return true; } -bool rlc_am_nr_tx::has_data() +bool rlc_am_nr::rlc_am_nr_tx::has_data() { return true; } -uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) +uint32_t rlc_am_nr::rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) { return 0; } -void rlc_am_nr_tx::reestablish() +void rlc_am_nr::rlc_am_nr_tx::reestablish() { stop(); } -uint32_t rlc_am_nr_tx::get_buffer_state() +uint32_t rlc_am_nr::rlc_am_nr_tx::get_buffer_state() { return 0; } -void rlc_am_nr_tx::get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) {} +void rlc_am_nr::rlc_am_nr_tx::get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) {} -int rlc_am_nr_tx::write_sdu(unique_byte_buffer_t sdu) +int rlc_am_nr::rlc_am_nr_tx::write_sdu(unique_byte_buffer_t sdu) { return 0; } -void rlc_am_nr_tx::discard_sdu(uint32_t discard_sn) {} +void rlc_am_nr::rlc_am_nr_tx::discard_sdu(uint32_t discard_sn) {} -bool rlc_am_nr_tx::sdu_queue_is_full() +bool rlc_am_nr::rlc_am_nr_tx::sdu_queue_is_full() { return false; } -void rlc_am_nr_tx::empty_queue() {} - -void rlc_am_nr_tx::set_bsr_callback(const bsr_callback_t& callback) {} +void rlc_am_nr::rlc_am_nr_tx::empty_queue() {} -void rlc_am_nr_tx::stop() {} +void rlc_am_nr::rlc_am_nr_tx::stop() {} /**************************************************************************** * Rx subclass implementation ***************************************************************************/ -rlc_am_nr_rx::rlc_am_nr_rx(rlc_am_nr* parent_) : - parent(parent_), pool(byte_buffer_pool::get_instance()), logger(parent_->logger) +rlc_am_nr::rlc_am_nr_rx::rlc_am_nr_rx(rlc_am_nr* parent_) : + parent(parent_), logger(parent_->logger), pool(byte_buffer_pool::get_instance()) {} -bool rlc_am_nr_rx::configure(const rlc_config_t& cfg_) +bool rlc_am_nr::rlc_am_nr_rx::configure(const rlc_config_t& cfg_) { cfg = cfg_.am; return true; } -void rlc_am_nr_rx::stop() {} +void rlc_am_nr::rlc_am_nr_rx::stop() {} -void rlc_am_nr_rx::write_pdu(uint8_t* payload, uint32_t nof_bytes) {} +void rlc_am_nr::rlc_am_nr_rx::write_pdu(uint8_t* payload, uint32_t nof_bytes) {} -void rlc_am_nr_rx::reestablish() +void rlc_am_nr::rlc_am_nr_rx::reestablish() { stop(); } -uint32_t rlc_am_nr_rx::get_sdu_rx_latency_ms() +uint32_t rlc_am_nr::rlc_am_nr_rx::get_sdu_rx_latency_ms() { return 0; } -uint32_t rlc_am_nr_rx::get_rx_buffered_bytes() +uint32_t rlc_am_nr::rlc_am_nr_rx::get_rx_buffered_bytes() { return 0; } From 476f9e115686b6cef2cb37220fd0f8c06054b1fc Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 28 Jul 2021 12:26:11 +0100 Subject: [PATCH 11/77] Changed logger initialization in RLC AM entities --- lib/include/srsran/rlc/rlc_am_base.h | 14 +- lib/include/srsran/rlc/rlc_am_lte.h | 6 +- lib/include/srsran/rlc/rlc_am_nr.h | 9 +- lib/src/rlc/rlc_am_lte.cc | 466 ++++++++++++++------------- lib/src/rlc/rlc_am_nr.cc | 14 +- 5 files changed, 257 insertions(+), 252 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index fcde9a069..471e07910 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -125,6 +125,8 @@ protected: class rlc_am_base_tx { public: + explicit rlc_am_base_tx(srslog::basic_logger* logger_) : logger(logger_) {} + virtual bool configure(const rlc_config_t& cfg_) = 0; virtual void reestablish() = 0; virtual void stop() = 0; @@ -140,9 +142,10 @@ protected: int write_sdu(unique_byte_buffer_t sdu); - byte_buffer_pool* pool = nullptr; - bool tx_enabled = false; - std::string rb_name; + bool tx_enabled = false; + byte_buffer_pool* pool = nullptr; + srslog::basic_logger* logger; + std::string rb_name; bsr_callback_t bsr_callback; @@ -161,12 +164,17 @@ protected: class rlc_am_base_rx { public: + explicit rlc_am_base_rx(srslog::basic_logger* logger_) : logger(logger_) {} + virtual bool configure(const rlc_config_t& cfg_) = 0; virtual void reestablish() = 0; virtual void stop() = 0; virtual void write_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; virtual uint32_t get_sdu_rx_latency_ms() = 0; virtual uint32_t get_rx_buffered_bytes() = 0; + + srslog::basic_logger* logger; + byte_buffer_pool* pool = nullptr; }; rlc_am_base_tx* tx_base = nullptr; diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index 0c5c79e34..e3d201619 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -145,7 +145,6 @@ public: rlc_am_lte* parent = nullptr; byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; rlc_am_pdu_segment_pool segment_pool; /**************************************************************************** @@ -234,9 +233,8 @@ public: bool add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment); void reset_status(); - rlc_am_lte* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; + rlc_am_lte* parent = nullptr; + byte_buffer_pool* pool = nullptr; /**************************************************************************** * Configurable parameters diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 0b7a20c94..1321440b8 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -61,9 +61,7 @@ public: void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); private: - rlc_am_nr* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; + rlc_am_nr* parent = nullptr; /**************************************************************************** * Configurable parameters @@ -100,9 +98,8 @@ public: uint32_t get_rx_buffered_bytes(); private: - rlc_am_nr* parent = nullptr; - byte_buffer_pool* pool = nullptr; - srslog::basic_logger& logger; + rlc_am_nr* parent = nullptr; + byte_buffer_pool* pool = nullptr; /**************************************************************************** * Configurable parameters diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index 5a479e1cf..9c36a8666 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -109,19 +109,19 @@ rlc_am_lte::rlc_am_lte(srslog::basic_logger& logger, ***************************************************************************/ rlc_am_lte::rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : parent(parent_), - logger(parent_->logger), pool(byte_buffer_pool::get_instance()), poll_retx_timer(parent_->timers->get_unique_timer()), - status_prohibit_timer(parent_->timers->get_unique_timer()) + status_prohibit_timer(parent_->timers->get_unique_timer()), + rlc_am_base_tx(&parent_->logger) {} bool rlc_am_lte::rlc_am_lte_tx::configure(const rlc_config_t& cfg_) { std::lock_guard lock(mutex); if (cfg_.tx_queue_length > MAX_SDUS_PER_RLC_PDU) { - logger.error("Configuring Tx queue length of %d PDUs too big. Maximum value is %d.", - cfg_.tx_queue_length, - MAX_SDUS_PER_RLC_PDU); + logger->error("Configuring Tx queue length of %d PDUs too big. Maximum value is %d.", + cfg_.tx_queue_length, + MAX_SDUS_PER_RLC_PDU); return false; } @@ -130,7 +130,7 @@ bool rlc_am_lte::rlc_am_lte_tx::configure(const rlc_config_t& cfg_) // check timers if (not poll_retx_timer.is_valid() or not status_prohibit_timer.is_valid()) { - logger.error("Configuring RLC AM TX: timers not configured"); + logger->error("Configuring RLC AM TX: timers not configured"); return false; } @@ -244,7 +244,7 @@ bool rlc_am_lte::rlc_am_lte_tx::has_data() void rlc_am_lte::rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) { if (tx_window[sn].retx_count == cfg.max_retx_thresh) { - logger.warning("%s Signaling max number of reTx=%d for SN=%d", RB_NAME, tx_window[sn].retx_count, sn); + logger->warning("%s Signaling max number of reTx=%d for SN=%d", RB_NAME, tx_window[sn].retx_count, sn); parent->rrc->max_retx_attempted(); srsran::pdcp_sn_vector_t pdcp_sns; for (const rlc_am_pdu_segment& segment : tx_window[sn]) { @@ -276,36 +276,36 @@ void rlc_am_lte::rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, n_bytes_prio = 0; uint32_t n_sdus = 0; - logger.debug("%s Buffer state - do_status=%s, status_prohibit_running=%s (%d/%d)", - parent->rb_name, - do_status() ? "yes" : "no", - status_prohibit_timer.is_running() ? "yes" : "no", - status_prohibit_timer.time_elapsed(), - status_prohibit_timer.duration()); + logger->debug("%s Buffer state - do_status=%s, status_prohibit_running=%s (%d/%d)", + parent->rb_name, + do_status() ? "yes" : "no", + status_prohibit_timer.is_running() ? "yes" : "no", + status_prohibit_timer.time_elapsed(), + status_prohibit_timer.duration()); // Bytes needed for status report if (do_status() && not status_prohibit_timer.is_running()) { n_bytes_prio += parent->rx->get_status_pdu_length(); - logger.debug("%s Buffer state - total status report: %d bytes", RB_NAME, n_bytes_prio); + logger->debug("%s Buffer state - total status report: %d bytes", RB_NAME, n_bytes_prio); } // Bytes needed for retx if (not retx_queue.empty()) { rlc_amd_retx_t& retx = retx_queue.front(); - logger.debug("%s Buffer state - retx - SN=%d, Segment: %s, %d:%d", - RB_NAME, - retx.sn, - retx.is_segment ? "true" : "false", - retx.so_start, - retx.so_end); + logger->debug("%s Buffer state - retx - SN=%d, Segment: %s, %d:%d", + RB_NAME, + retx.sn, + retx.is_segment ? "true" : "false", + retx.so_start, + retx.so_end); if (tx_window.has_sn(retx.sn)) { int req_bytes = required_buffer_size(retx); if (req_bytes < 0) { - logger.error("In get_buffer_state(): Removing retx.sn=%d from queue", retx.sn); + logger->error("In get_buffer_state(): Removing retx.sn=%d from queue", retx.sn); retx_queue.pop(); } else { n_bytes_prio += req_bytes; - logger.debug("Buffer state - retx: %d bytes", n_bytes_prio); + logger->debug("Buffer state - retx: %d bytes", n_bytes_prio); } } } @@ -328,11 +328,11 @@ void rlc_am_lte::rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, // Room needed for fixed header of data PDUs if (n_bytes_newtx > 0 && n_sdus > 0) { n_bytes_newtx += 2; // Two bytes for fixed header with SN length = 10 - logger.debug("%s Total buffer state - %d SDUs (%d B)", RB_NAME, n_sdus, n_bytes_newtx); + logger->debug("%s Total buffer state - %d SDUs (%d B)", RB_NAME, n_sdus, n_bytes_newtx); } if (bsr_callback) { - logger.debug("%s Calling BSR callback - %d new_tx, %d prio bytes", RB_NAME, n_bytes_newtx, n_bytes_prio); + logger->debug("%s Calling BSR callback - %d new_tx, %d prio bytes", RB_NAME, n_bytes_newtx, n_bytes_prio); bsr_callback(parent->lcid, n_bytes_newtx, n_bytes_prio); } } @@ -352,7 +352,7 @@ void rlc_am_lte::rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) }); // Discard fails when the PDCP PDU is already in Tx window. - logger.info("%s PDU with PDCP_SN=%d", discarded ? "Discarding" : "Couldn't discard", discard_sn); + logger->info("%s PDU with PDCP_SN=%d", discarded ? "Discarding" : "Couldn't discard", discard_sn); } bool rlc_am_lte::rlc_am_lte_tx::sdu_queue_is_full() @@ -368,11 +368,11 @@ uint32_t rlc_am_lte::rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_byte return 0; } - logger.debug("MAC opportunity - %d bytes", nof_bytes); - logger.debug("tx_window size - %zu PDUs", tx_window.size()); + logger->debug("MAC opportunity - %d bytes", nof_bytes); + logger->debug("tx_window size - %zu PDUs", tx_window.size()); if (not tx_enabled) { - logger.debug("RLC entity not active. Not generating PDU."); + logger->debug("RLC entity not active. Not generating PDU."); return 0; } @@ -402,7 +402,7 @@ void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) { std::unique_lock lock(mutex); if (poll_retx_timer.is_valid() && poll_retx_timer.id() == timeout_id) { - logger.debug("%s Poll reTx timer expired after %dms", RB_NAME, poll_retx_timer.duration()); + logger->debug("%s Poll reTx timer expired after %dms", RB_NAME, poll_retx_timer.duration()); // Section 5.2.2.3 in TS 36.322, schedule PDU for retransmission if // (a) both tx and retx buffer are empty (excluding tx'ed PDU waiting for ack), or // (b) no new data PDU can be transmitted (tx window is full) @@ -410,7 +410,7 @@ void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) retransmit_pdu(vt_a); // TODO: TS says to send vt_s - 1 here } } else if (status_prohibit_timer.is_valid() && status_prohibit_timer.id() == timeout_id) { - logger.debug("%s Status prohibit timer expired after %dms", RB_NAME, status_prohibit_timer.duration()); + logger->debug("%s Status prohibit timer expired after %dms", RB_NAME, status_prohibit_timer.duration()); } if (bsr_callback) { @@ -422,12 +422,12 @@ void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) { if (tx_window.empty()) { - logger.warning("%s No PDU to retransmit", RB_NAME); + logger->warning("%s No PDU to retransmit", RB_NAME); return; } if (not tx_window.has_sn(sn)) { - logger.warning("%s Can't retransmit unexisting SN=%d", RB_NAME, sn); + logger->warning("%s Can't retransmit unexisting SN=%d", RB_NAME, sn); return; } @@ -438,7 +438,8 @@ void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) pdu.retx_count++; check_sn_reached_max_retx(sn); - logger.info("%s Schedule SN=%d for reTx", RB_NAME, pdu.rlc_sn); + logger->info("%s Schedule SN=%d for reTx", RB_NAME, pdu.rlc_sn); + rlc_amd_retx_t& retx = retx_queue.push(); retx.is_segment = false; retx.so_start = 0; @@ -498,10 +499,10 @@ int rlc_am_lte::rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_b logger->debug("%s Generating status PDU. Nof bytes %d", RB_NAME, nof_bytes); int pdu_len = parent->rx->get_status_pdu(&tx_status, nof_bytes); if (pdu_len == SRSRAN_ERROR) { - logger.debug("%s Deferred Status PDU. Cause: Failed to acquire Rx lock", RB_NAME); + logger->debug("%s Deferred Status PDU. Cause: Failed to acquire Rx lock", RB_NAME); pdu_len = 0; } else if (pdu_len > 0 && nof_bytes >= static_cast(pdu_len)) { - log_rlc_am_status_pdu_to_string(logger.info, "%s Tx status PDU - %s", &tx_status, RB_NAME); + log_rlc_am_status_pdu_to_string(logger->info, "%s Tx status PDU - %s", &tx_status, RB_NAME); if (cfg.t_status_prohibit > 0 && status_prohibit_timer.is_valid()) { // re-arm timer status_prohibit_timer.run(); @@ -509,7 +510,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_b debug_state(); pdu_len = rlc_am_write_status_pdu(&tx_status, payload); } else { - logger.info("%s Cannot tx status PDU - %d bytes available, %d bytes required", RB_NAME, nof_bytes, pdu_len); + logger->info("%s Cannot tx status PDU - %d bytes available, %d bytes required", RB_NAME, nof_bytes, pdu_len); pdu_len = 0; } @@ -520,7 +521,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_byt { // Check there is at least 1 element before calling front() if (retx_queue.empty()) { - logger.error("In build_retx_pdu(): retx_queue is empty"); + logger->error("In build_retx_pdu(): retx_queue is empty"); return -1; } @@ -532,7 +533,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_byt if (!retx_queue.empty()) { retx = retx_queue.front(); } else { - logger.info("%s SN=%d not in Tx window. Ignoring retx.", RB_NAME, retx.sn); + logger->info("%s SN=%d not in Tx window. Ignoring retx.", RB_NAME, retx.sn); if (tx_window.has_sn(vt_a)) { // schedule next SN for retx retransmit_pdu(vt_a); @@ -547,13 +548,13 @@ int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_byt // Is resegmentation needed? int req_size = required_buffer_size(retx); if (req_size < 0) { - logger.error("In build_retx_pdu(): Removing retx.sn=%d from queue", retx.sn); + logger->error("In build_retx_pdu(): Removing retx.sn=%d from queue", retx.sn); retx_queue.pop(); return -1; } if (retx.is_segment || req_size > static_cast(nof_bytes)) { - logger.debug("%s build_retx_pdu - resegmentation required", RB_NAME); + logger->debug("%s build_retx_pdu - resegmentation required", RB_NAME); return build_segment(payload, nof_bytes, retx); } @@ -564,8 +565,8 @@ int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_byt // Set poll bit pdu_without_poll++; byte_without_poll += (tx_window[retx.sn].buf->N_bytes + rlc_am_packed_length(&new_header)); - logger.info("%s pdu_without_poll: %d", RB_NAME, pdu_without_poll); - logger.info("%s byte_without_poll: %d", RB_NAME, byte_without_poll); + logger->info("%s pdu_without_poll: %d", RB_NAME, pdu_without_poll); + logger->info("%s byte_without_poll: %d", RB_NAME, byte_without_poll); if (poll_required()) { new_header.p = 1; // vt_s won't change for reTx, so don't update poll_sn @@ -583,15 +584,15 @@ int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_byt retx_queue.pop(); - logger.info(payload, - tx_window[retx.sn].buf->N_bytes, - "%s Tx PDU SN=%d (%d B) (attempt %d/%d)", - RB_NAME, - retx.sn, - tx_window[retx.sn].buf->N_bytes, - tx_window[retx.sn].retx_count + 1, - cfg.max_retx_thresh); - log_rlc_amd_pdu_header_to_string(logger.debug, new_header); + logger->info(payload, + tx_window[retx.sn].buf->N_bytes, + "%s Tx PDU SN=%d (%d B) (attempt %d/%d)", + RB_NAME, + retx.sn, + tx_window[retx.sn].buf->N_bytes, + tx_window[retx.sn].retx_count + 1, + cfg.max_retx_thresh); + log_rlc_amd_pdu_header_to_string(logger->debug, new_header); debug_state(); return (ptr - payload) + tx_window[retx.sn].buf->N_bytes; @@ -600,7 +601,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_byt int rlc_am_lte::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_retx_t retx) { if (tx_window[retx.sn].buf == NULL) { - logger.error("In build_segment: retx.sn=%d has null buffer", retx.sn); + logger->error("In build_segment: retx.sn=%d has null buffer", retx.sn); return 0; } if (!retx.is_segment) { @@ -614,8 +615,8 @@ int rlc_am_lte::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_byte pdu_without_poll++; byte_without_poll += (tx_window[retx.sn].buf->N_bytes + rlc_am_packed_length(&new_header)); - logger.info("%s pdu_without_poll: %d", RB_NAME, pdu_without_poll); - logger.info("%s byte_without_poll: %d", RB_NAME, byte_without_poll); + logger->info("%s pdu_without_poll: %d", RB_NAME, pdu_without_poll); + logger->info("%s byte_without_poll: %d", RB_NAME, byte_without_poll); new_header.dc = RLC_DC_FIELD_DATA_PDU; new_header.rf = 1; @@ -626,7 +627,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_byte new_header.N_li = 0; new_header.p = 0; if (poll_required()) { - logger.debug("%s setting poll bit to request status", RB_NAME); + logger->debug("%s setting poll bit to request status", RB_NAME); new_header.p = 1; // vt_s won't change for reTx, so don't update poll_sn pdu_without_poll = 0; @@ -646,10 +647,10 @@ int rlc_am_lte::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_byte } if (nof_bytes <= head_len) { - logger.info("%s Cannot build a PDU segment - %d bytes available, %d bytes required for header", - RB_NAME, - nof_bytes, - head_len); + logger->info("%s Cannot build a PDU segment - %d bytes available, %d bytes required for header", + RB_NAME, + nof_bytes, + head_len); return 0; } @@ -750,24 +751,24 @@ int rlc_am_lte::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_byte debug_state(); int pdu_len = (ptr - payload) + len; if (pdu_len > static_cast(nof_bytes)) { - logger.error("%s Retx PDU segment length error. Available: %d, Used: %d", RB_NAME, nof_bytes, pdu_len); + logger->error("%s Retx PDU segment length error. Available: %d, Used: %d", RB_NAME, nof_bytes, pdu_len); int header_len = (ptr - payload); - logger.debug("%s Retx PDU segment length error. Actual header len: %d, Payload len: %d, N_li: %d", - RB_NAME, - header_len, - len, - new_header.N_li); - } - - logger.info(payload, - pdu_len, - "%s Retx PDU segment SN=%d [so=%d] (%d B) (attempt %d/%d)", - RB_NAME, - retx.sn, - retx.so_start, - pdu_len, - tx_window[retx.sn].retx_count + 1, - cfg.max_retx_thresh); + logger->debug("%s Retx PDU segment length error. Actual header len: %d, Payload len: %d, N_li: %d", + RB_NAME, + header_len, + len, + new_header.N_li); + } + + logger->info(payload, + pdu_len, + "%s Retx PDU segment SN=%d [so=%d] (%d B) (attempt %d/%d)", + RB_NAME, + retx.sn, + retx.so_start, + pdu_len, + tx_window[retx.sn].retx_count + 1, + cfg.max_retx_thresh); return pdu_len; } @@ -775,21 +776,21 @@ int rlc_am_lte::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_byte int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) { if (tx_sdu == NULL && tx_sdu_queue.is_empty()) { - logger.info("No data available to be sent"); + logger->info("No data available to be sent"); return 0; } // do not build any more PDU if window is already full if (tx_sdu == NULL && tx_window.size() >= RLC_AM_WINDOW_SIZE) { - logger.info("Tx window full."); + logger->info("Tx window full."); return 0; } if (nof_bytes < RLC_AM_MIN_DATA_PDU_SIZE) { - logger.info("%s Cannot build data PDU - %d bytes available but at least %d bytes are required ", - RB_NAME, - nof_bytes, - RLC_AM_MIN_DATA_PDU_SIZE); + logger->info("%s Cannot build data PDU - %d bytes available but at least %d bytes are required ", + RB_NAME, + nof_bytes, + RLC_AM_MIN_DATA_PDU_SIZE); return 0; } @@ -806,7 +807,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt } exit(-1); #else - logger.error("Fatal Error: Couldn't allocate PDU in build_data_pdu()."); + logger->error("Fatal Error: Couldn't allocate PDU in build_data_pdu()."); return 0; #endif } @@ -816,7 +817,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt header.sn = vt_s; if (not segment_pool.has_segments()) { - logger.info("Can't build a PDU - No segments available"); + logger->info("Can't build a PDU - No segments available"); return 0; } @@ -830,7 +831,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt uint32_t pdu_space = SRSRAN_MIN(nof_bytes, pdu->get_tailroom()); uint8_t* pdu_ptr = pdu->msg; - logger.debug("%s Building PDU - pdu_space: %d, head_len: %d ", RB_NAME, pdu_space, head_len); + logger->debug("%s Building PDU - pdu_space: %d, head_len: %d ", RB_NAME, pdu_space, head_len); // Check for SDU segment if (tx_sdu != nullptr) { @@ -849,11 +850,11 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt } } else { // PDCP SNs for the RLC SDU has been removed from the queue - logger.warning("Couldn't find PDCP_SN=%d in SDU info queue (segment)", tx_sdu->md.pdcp_sn); + logger->warning("Couldn't find PDCP_SN=%d in SDU info queue (segment)", tx_sdu->md.pdcp_sn); } if (tx_sdu->N_bytes == 0) { - logger.debug("%s Complete SDU scheduled for tx.", RB_NAME); + logger->debug("%s Complete SDU scheduled for tx.", RB_NAME); tx_sdu.reset(); } if (pdu_space > to_move) { @@ -863,7 +864,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt } header.fi |= RLC_FI_FIELD_NOT_START_ALIGNED; // First byte does not correspond to first byte of SDU - logger.debug( + logger->debug( "%s Building PDU - added SDU segment from previous PDU (len:%d) - pdu_space: %d, head_len: %d header_sn=%d", RB_NAME, to_move, @@ -875,7 +876,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt // Pull SDUs from queue while (pdu_space > head_len && tx_sdu_queue.get_n_sdus() > 0 && header.N_li < MAX_SDUS_PER_PDU) { if (not segment_pool.has_segments()) { - logger.info("Can't build a PDU segment - No segment resources available"); + logger->info("Can't build a PDU segment - No segment resources available"); if (pdu_ptr != pdu->msg) { break; // continue with the segments created up to this point } @@ -906,11 +907,11 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt // store sdu info if (undelivered_sdu_info_queue.has_pdcp_sn(tx_sdu->md.pdcp_sn)) { - logger.warning("PDCP_SN=%d already marked as undelivered", tx_sdu->md.pdcp_sn); + logger->warning("PDCP_SN=%d already marked as undelivered", tx_sdu->md.pdcp_sn); } else { - logger.debug("marking pdcp_sn=%d as undelivered (queue_len=%ld)", - tx_sdu->md.pdcp_sn, - undelivered_sdu_info_queue.nof_sdus()); + logger->debug("marking pdcp_sn=%d as undelivered (queue_len=%ld)", + tx_sdu->md.pdcp_sn, + undelivered_sdu_info_queue.nof_sdus()); undelivered_sdu_info_queue.add_pdcp_sdu(tx_sdu->md.pdcp_sn); } pdcp_pdu_info& pdcp_pdu = undelivered_sdu_info_queue[tx_sdu->md.pdcp_sn]; @@ -928,7 +929,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt } if (tx_sdu->N_bytes == 0) { - logger.debug("%s Complete SDU scheduled for tx. PDCP SN=%d", RB_NAME, tx_sdu->md.pdcp_sn); + logger->debug("%s Complete SDU scheduled for tx. PDCP SN=%d", RB_NAME, tx_sdu->md.pdcp_sn); tx_sdu.reset(); } if (pdu_space > to_move) { @@ -937,16 +938,16 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt pdu_space = 0; } - logger.debug("%s Building PDU - added SDU segment (len:%d) - pdu_space: %d, head_len: %d ", - RB_NAME, - to_move, - pdu_space, - head_len); + logger->debug("%s Building PDU - added SDU segment (len:%d) - pdu_space: %d, head_len: %d ", + RB_NAME, + to_move, + pdu_space, + head_len); } // Make sure, at least one SDU (segment) has been added until this point if (pdu->N_bytes == 0) { - logger.error("Generated empty RLC PDU."); + logger->error("Generated empty RLC PDU."); } if (tx_sdu != NULL) { @@ -956,10 +957,10 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt // Set Poll bit pdu_without_poll++; byte_without_poll += (pdu->N_bytes + head_len); - logger.debug("%s pdu_without_poll: %d", RB_NAME, pdu_without_poll); - logger.debug("%s byte_without_poll: %d", RB_NAME, byte_without_poll); + logger->debug("%s pdu_without_poll: %d", RB_NAME, pdu_without_poll); + logger->debug("%s byte_without_poll: %d", RB_NAME, byte_without_poll); if (poll_required()) { - logger.debug("%s setting poll bit to request status", RB_NAME); + logger->debug("%s setting poll bit to request status", RB_NAME); header.p = 1; poll_sn = vt_s; pdu_without_poll = 0; @@ -981,8 +982,8 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt rlc_am_write_data_pdu_header(&header, &ptr); memcpy(ptr, buffer_ptr->msg, buffer_ptr->N_bytes); int total_len = (ptr - payload) + buffer_ptr->N_bytes; - logger.info(payload, total_len, "%s Tx PDU SN=%d (%d B)", RB_NAME, header.sn, total_len); - log_rlc_amd_pdu_header_to_string(logger.debug, header); + logger->info(payload, total_len, "%s Tx PDU SN=%d (%d B)", RB_NAME, header.sn, total_len); + log_rlc_amd_pdu_header_to_string(logger->debug, header); debug_state(); return total_len; @@ -1002,27 +1003,27 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no { std::lock_guard lock(mutex); - logger.debug(payload, nof_bytes, "%s Rx control PDU", RB_NAME); + logger->debug(payload, nof_bytes, "%s Rx control PDU", RB_NAME); rlc_am_read_status_pdu(payload, nof_bytes, &status); - log_rlc_am_status_pdu_to_string(logger.info, "%s Rx Status PDU: %s", &status, RB_NAME); + log_rlc_am_status_pdu_to_string(logger->info, "%s Rx Status PDU: %s", &status, RB_NAME); // make sure ACK_SN is within our Tx window if (((MOD + status.ack_sn - vt_a) % MOD > RLC_AM_WINDOW_SIZE) || ((MOD + vt_s - status.ack_sn) % MOD > RLC_AM_WINDOW_SIZE)) { - logger.warning("%s Received invalid status PDU (ack_sn=%d, vt_a=%d, vt_s=%d). Dropping PDU.", - RB_NAME, - status.ack_sn, - vt_a, - vt_s); + logger->warning("%s Received invalid status PDU (ack_sn=%d, vt_a=%d, vt_s=%d). Dropping PDU.", + RB_NAME, + status.ack_sn, + vt_a, + vt_s); return; } // Sec 5.2.2.2, stop poll reTx timer if status PDU comprises a positive _or_ negative acknowledgement // for the RLC data PDU with sequence number poll_sn if (poll_retx_timer.is_valid() && (TX_MOD_BASE(poll_sn) < TX_MOD_BASE(status.ack_sn))) { - logger.debug("%s Stopping pollRetx timer", RB_NAME); + logger->debug("%s Stopping pollRetx timer", RB_NAME); poll_retx_timer.stop(); } @@ -1063,7 +1064,7 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no // sanity check if (status.nacks[j].so_start >= pdu.buf->N_bytes) { // print error but try to send original PDU again - logger.info( + logger->info( "SO_start is larger than original PDU (%d >= %d)", status.nacks[j].so_start, pdu.buf->N_bytes); status.nacks[j].so_start = 0; } @@ -1079,19 +1080,19 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no retx.is_segment = true; retx.so_start = status.nacks[j].so_start; } else { - logger.warning("%s invalid segment NACK received for SN %d. so_start: %d, so_end: %d, N_bytes: %d", - RB_NAME, - i, - status.nacks[j].so_start, - status.nacks[j].so_end, - pdu.buf->N_bytes); + logger->warning("%s invalid segment NACK received for SN %d. so_start: %d, so_end: %d, N_bytes: %d", + RB_NAME, + i, + status.nacks[j].so_start, + status.nacks[j].so_end, + pdu.buf->N_bytes); } } } else { - logger.info("%s NACKed SN=%d already considered for retransmission", RB_NAME, i); + logger->info("%s NACKed SN=%d already considered for retransmission", RB_NAME, i); } } else { - logger.error("%s NACKed SN=%d already removed from Tx window", RB_NAME, i); + logger->error("%s NACKed SN=%d already removed from Tx window", RB_NAME, i); } } } @@ -1101,7 +1102,7 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no std::lock_guard lock(mutex); if (tx_window.has_sn(i)) { update_notification_ack_info(i); - logger.debug("Tx PDU SN=%zd being removed from tx window", i); + logger->debug("Tx PDU SN=%zd being removed from tx window", i); tx_window.remove_pdu(i); } // Advance window if possible @@ -1117,7 +1118,7 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no // Make sure vt_a points to valid SN std::lock_guard lock(mutex); if (not tx_window.empty() && not tx_window.has_sn(vt_a)) { - logger.error("%s vt_a=%d points to invalid position in Tx window.", RB_NAME, vt_a); + logger->error("%s vt_a=%d points to invalid position in Tx window.", RB_NAME, vt_a); parent->rrc->protocol_failure(); } } @@ -1138,10 +1139,10 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no */ void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) { - logger.debug("Updating ACK info: RLC SN=%d, number of notified SDU=%ld, number of undelivered SDUs=%ld", - rlc_sn, - notify_info_vec.size(), - undelivered_sdu_info_queue.nof_sdus()); + logger->debug("Updating ACK info: RLC SN=%d, number of notified SDU=%ld, number of undelivered SDUs=%ld", + rlc_sn, + notify_info_vec.size(), + undelivered_sdu_info_queue.nof_sdus()); // Iterate over all undelivered SDUs if (not tx_window.has_sn(rlc_sn)) { return; @@ -1151,7 +1152,7 @@ void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) for (rlc_am_pdu_segment& acked_segment : acked_pdu) { uint32_t pdcp_sn = acked_segment.pdcp_sn(); if (pdcp_sn == rlc_am_pdu_segment::invalid_pdcp_sn) { - logger.debug("ACKed segment in RLC_SN=%d already discarded in PDCP. No need to notify the PDCP.", rlc_sn); + logger->debug("ACKed segment in RLC_SN=%d already discarded in PDCP. No need to notify the PDCP.", rlc_sn); continue; } pdcp_pdu_info& info = undelivered_sdu_info_queue[pdcp_sn]; @@ -1165,9 +1166,9 @@ void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) if (not notify_info_vec.full()) { notify_info_vec.push_back(pdcp_sn); } else { - logger.warning("Can't notify delivery of PDCP_SN=%d.", pdcp_sn); + logger->warning("Can't notify delivery of PDCP_SN=%d.", pdcp_sn); } - logger.debug("Erasing SDU info: PDCP_SN=%d", pdcp_sn); + logger->debug("Erasing SDU info: PDCP_SN=%d", pdcp_sn); undelivered_sdu_info_queue.clear_pdcp_sdu(pdcp_sn); } } @@ -1175,7 +1176,7 @@ void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) void rlc_am_lte::rlc_am_lte_tx::debug_state() { - logger.debug("%s vt_a = %d, vt_ms = %d, vt_s = %d, poll_sn = %d", RB_NAME, vt_a, vt_ms, vt_s, poll_sn); + logger->debug("%s vt_a = %d, vt_ms = %d, vt_s = %d, poll_sn = %d", RB_NAME, vt_a, vt_ms, vt_s, poll_sn); } int rlc_am_lte::rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_t& retx) @@ -1185,11 +1186,11 @@ int rlc_am_lte::rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_t& retx) if (tx_window[retx.sn].buf) { return rlc_am_packed_length(&tx_window[retx.sn].header) + tx_window[retx.sn].buf->N_bytes; } else { - logger.warning("retx.sn=%d has null ptr in required_buffer_size()", retx.sn); + logger->warning("retx.sn=%d has null ptr in required_buffer_size()", retx.sn); return -1; } } else { - logger.warning("retx.sn=%d does not exist in required_buffer_size()", retx.sn); + logger->warning("retx.sn=%d does not exist in required_buffer_size()", retx.sn); return -1; } } @@ -1258,8 +1259,8 @@ int rlc_am_lte::rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_t& retx) rlc_am_lte::rlc_am_lte_rx::rlc_am_lte_rx(rlc_am_lte* parent_) : parent(parent_), pool(byte_buffer_pool::get_instance()), - logger(parent_->logger), - reordering_timer(parent_->timers->get_unique_timer()) + reordering_timer(parent_->timers->get_unique_timer()), + rlc_am_base_rx(&parent_->logger) {} rlc_am_lte::rlc_am_lte_rx::~rlc_am_lte_rx() {} @@ -1271,7 +1272,7 @@ bool rlc_am_lte::rlc_am_lte_rx::configure(const rlc_config_t& cfg_) // check timers if (not reordering_timer.is_valid()) { - logger.error("Configuring RLC AM TX: timers not configured"); + logger->error("Configuring RLC AM TX: timers not configured"); return false; } @@ -1324,8 +1325,8 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b { std::map::iterator it; - logger.info(payload, nof_bytes, "%s Rx data PDU SN=%d (%d B)", RB_NAME, header.sn, nof_bytes); - log_rlc_amd_pdu_header_to_string(logger.debug, header); + logger->info(payload, nof_bytes, "%s Rx data PDU SN=%d (%d B)", RB_NAME, header.sn, nof_bytes); + log_rlc_amd_pdu_header_to_string(logger->debug, header); // sanity check for segments not exceeding PDU length if (header.N_li > 0) { @@ -1333,7 +1334,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b for (uint32_t i = 0; i < header.N_li; i++) { segments_len += header.li[i]; if (segments_len > nof_bytes) { - logger.info("Dropping corrupted PDU (segments_len=%d > pdu_len=%d)", segments_len, nof_bytes); + logger->info("Dropping corrupted PDU (segments_len=%d > pdu_len=%d)", segments_len, nof_bytes); return; } } @@ -1341,19 +1342,19 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b if (!inside_rx_window(header.sn)) { if (header.p) { - logger.info("%s Status packet requested through polling bit", RB_NAME); + logger->info("%s Status packet requested through polling bit", RB_NAME); do_status = true; } - logger.info("%s SN=%d outside rx window [%d:%d] - discarding", RB_NAME, header.sn, vr_r, vr_mr); + logger->info("%s SN=%d outside rx window [%d:%d] - discarding", RB_NAME, header.sn, vr_r, vr_mr); return; } if (rx_window.has_sn(header.sn)) { if (header.p) { - logger.info("%s Status packet requested through polling bit", RB_NAME); + logger->info("%s Status packet requested through polling bit", RB_NAME); do_status = true; } - logger.info("%s Discarding duplicate SN=%d", RB_NAME, header.sn); + logger->info("%s Discarding duplicate SN=%d", RB_NAME, header.sn); return; } @@ -1365,7 +1366,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b srsran::console("Fatal Error: Couldn't allocate PDU in handle_data_pdu().\n"); exit(-1); #else - logger.error("Fatal Error: Couldn't allocate PDU in handle_data_pdu()."); + logger->error("Fatal Error: Couldn't allocate PDU in handle_data_pdu()."); rx_window.remove_pdu(header.sn); return; #endif @@ -1374,11 +1375,11 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b // check available space for payload if (nof_bytes > pdu.buf->get_tailroom()) { - logger.error("%s Discarding SN=%d of size %d B (available space %d B)", - RB_NAME, - header.sn, - nof_bytes, - pdu.buf->get_tailroom()); + logger->error("%s Discarding SN=%d of size %d B (available space %d B)", + RB_NAME, + header.sn, + nof_bytes, + pdu.buf->get_tailroom()); return; } memcpy(pdu.buf->msg, payload, nof_bytes); @@ -1397,7 +1398,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b // Check poll bit if (header.p) { - logger.info("%s Status packet requested through polling bit", RB_NAME); + logger->info("%s Status packet requested through polling bit", RB_NAME); poll_received = true; // 36.322 v10 Section 5.2.3 @@ -1414,21 +1415,21 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b if (reordering_timer.is_valid()) { if (reordering_timer.is_running()) { if (vr_x == vr_r || (!inside_rx_window(vr_x) && vr_x != vr_mr)) { - logger.debug("Stopping reordering timer."); + logger->debug("Stopping reordering timer."); reordering_timer.stop(); } else { - logger.debug("Leave reordering timer running."); + logger->debug("Leave reordering timer running."); } debug_state(); } if (not reordering_timer.is_running()) { if (RX_MOD_BASE(vr_h) > RX_MOD_BASE(vr_r)) { - logger.debug("Starting reordering timer."); + logger->debug("Starting reordering timer."); reordering_timer.run(); vr_x = vr_h; } else { - logger.debug("Leave reordering timer stopped."); + logger->debug("Leave reordering timer stopped."); } debug_state(); } @@ -1443,23 +1444,23 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* pa { std::map::iterator it; - logger.info(payload, - nof_bytes, - "%s Rx data PDU segment of SN=%d (%d B), SO=%d, N_li=%d", - RB_NAME, - header.sn, - nof_bytes, - header.so, - header.N_li); - log_rlc_amd_pdu_header_to_string(logger.debug, header); + logger->info(payload, + nof_bytes, + "%s Rx data PDU segment of SN=%d (%d B), SO=%d, N_li=%d", + RB_NAME, + header.sn, + nof_bytes, + header.so, + header.N_li); + log_rlc_amd_pdu_header_to_string(logger->debug, header); // Check inside rx window if (!inside_rx_window(header.sn)) { if (header.p) { - logger.info("%s Status packet requested through polling bit", RB_NAME); + logger->info("%s Status packet requested through polling bit", RB_NAME); do_status = true; } - logger.info("%s SN=%d outside rx window [%d:%d] - discarding", RB_NAME, header.sn, vr_r, vr_mr); + logger->info("%s SN=%d outside rx window [%d:%d] - discarding", RB_NAME, header.sn, vr_r, vr_mr); return; } @@ -1470,13 +1471,13 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* pa srsran::console("Fatal Error: Couldn't allocate PDU in handle_data_pdu_segment().\n"); exit(-1); #else - logger.error("Fatal Error: Couldn't allocate PDU in handle_data_pdu_segment()."); + logger->error("Fatal Error: Couldn't allocate PDU in handle_data_pdu_segment()."); return; #endif } if (segment.buf->get_tailroom() < nof_bytes) { - logger.info("Dropping corrupted segment SN=%d, not enough space to fit %d B", header.sn, nof_bytes); + logger->info("Dropping corrupted segment SN=%d, not enough space to fit %d B", header.sn, nof_bytes); return; } @@ -1488,7 +1489,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* pa it = rx_segments.find(header.sn); if (rx_segments.end() != it) { if (header.p) { - logger.info("%s Status packet requested through polling bit", RB_NAME); + logger->info("%s Status packet requested through polling bit", RB_NAME); do_status = true; } @@ -1511,7 +1512,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* pa // Check poll bit if (header.p) { - logger.info("%s Status packet requested through polling bit", RB_NAME); + logger->info("%s Status packet requested through polling bit", RB_NAME); poll_received = true; // 36.322 v10 Section 5.2.3 @@ -1537,7 +1538,7 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() srsran::console("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (1)\n"); exit(-1); #else - logger.error("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (1)"); + logger->error("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (1)"); return; #endif } @@ -1549,13 +1550,13 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() for (uint32_t i = 0; i < rx_window[vr_r].header.N_li; i++) { len = rx_window[vr_r].header.li[i]; - logger.debug(rx_window[vr_r].buf->msg, - len, - "Handling segment %d/%d of length %d B of SN=%d", - i + 1, - rx_window[vr_r].header.N_li, - len, - vr_r); + logger->debug(rx_window[vr_r].buf->msg, + len, + "Handling segment %d/%d of length %d B of SN=%d", + i + 1, + rx_window[vr_r].header.N_li, + len, + vr_r); // sanity check to avoid zero-size SDUs if (len == 0) { @@ -1565,7 +1566,7 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() if (rx_sdu->get_tailroom() >= len) { if ((rx_window[vr_r].buf->msg - rx_window[vr_r].buf->buffer) + len < SRSRAN_MAX_BUFFER_SIZE_BYTES) { if (rx_window[vr_r].buf->N_bytes < len) { - logger.error("Dropping corrupted SN=%d", vr_r); + logger->error("Dropping corrupted SN=%d", vr_r); rx_sdu.reset(); goto exit; } @@ -1579,7 +1580,7 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() rx_window[vr_r].buf->msg += len; rx_window[vr_r].buf->N_bytes -= len; - logger.info(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU (%d B)", RB_NAME, rx_sdu->N_bytes); + logger->info(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU (%d B)", RB_NAME, rx_sdu->N_bytes); sdu_rx_latency_ms.push(std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - rx_sdu->get_timestamp()) .count()); @@ -1595,18 +1596,18 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() srsran::console("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (2)\n"); exit(-1); #else - logger.error("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (2)"); + logger->error("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (2)"); return; #endif } } else { int buf_len = rx_window[vr_r].buf->msg - rx_window[vr_r].buf->buffer; - logger.error("Cannot read %d bytes from rx_window. vr_r=%d, msg-buffer=%d B", len, vr_r, buf_len); + logger->error("Cannot read %d bytes from rx_window. vr_r=%d, msg-buffer=%d B", len, vr_r, buf_len); rx_sdu.reset(); goto exit; } } else { - logger.error("Cannot fit RLC PDU in SDU buffer, dropping both."); + logger->error("Cannot fit RLC PDU in SDU buffer, dropping both."); rx_sdu.reset(); goto exit; } @@ -1614,7 +1615,7 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() // Handle last segment len = rx_window[vr_r].buf->N_bytes; - logger.debug(rx_window[vr_r].buf->msg, len, "Handling last segment of length %d B of SN=%d", len, vr_r); + logger->debug(rx_window[vr_r].buf->msg, len, "Handling last segment of length %d B of SN=%d", len, vr_r); if (rx_sdu->get_tailroom() >= len) { // store timestamp of the first segment when starting to assemble SDUs if (rx_sdu->N_bytes == 0) { @@ -1632,7 +1633,7 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() } if (rlc_am_end_aligned(rx_window[vr_r].header.fi)) { - logger.info(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU (%d B)", RB_NAME, rx_sdu->N_bytes); + logger->info(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU (%d B)", RB_NAME, rx_sdu->N_bytes); sdu_rx_latency_ms.push(std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - rx_sdu->get_timestamp()) .count()); @@ -1648,7 +1649,7 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() srsran::console("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (3)\n"); exit(-1); #else - logger.error("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (3)"); + logger->error("Fatal Error: Could not allocate PDU in reassemble_rx_sdus() (3)"); return; #endif } @@ -1656,19 +1657,19 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() exit: // Move the rx_window - logger.debug("Erasing SN=%d.", vr_r); + logger->debug("Erasing SN=%d.", vr_r); // also erase any segments of this SN std::map::iterator it; it = rx_segments.find(vr_r); if (rx_segments.end() != it) { - logger.debug("Erasing segments of SN=%d", vr_r); + logger->debug("Erasing segments of SN=%d", vr_r); std::list::iterator segit; for (segit = it->second.segments.begin(); segit != it->second.segments.end(); ++segit) { - logger.debug(" Erasing segment of SN=%d SO=%d Len=%d N_li=%d", - segit->header.sn, - segit->header.so, - segit->buf->N_bytes, - segit->header.N_li); + logger->debug(" Erasing segment of SN=%d SO=%d Len=%d N_li=%d", + segit->header.sn, + segit->header.so, + segit->buf->N_bytes, + segit->header.N_li); } it->second.segments.clear(); } @@ -1703,7 +1704,7 @@ void rlc_am_lte::rlc_am_lte_rx::write_pdu(uint8_t* payload, const uint32_t nof_b uint32_t payload_len = nof_bytes; rlc_am_read_data_pdu_header(&payload, &payload_len, &header); if (payload_len > nof_bytes) { - logger.info("Dropping corrupted PDU (%d B). Remaining length after header %d B.", nof_bytes, payload_len); + logger->info("Dropping corrupted PDU (%d B). Remaining length after header %d B.", nof_bytes, payload_len); return; } if (header.rf) { @@ -1735,7 +1736,7 @@ void rlc_am_lte::rlc_am_lte_rx::timer_expired(uint32_t timeout_id) { std::lock_guard lock(mutex); if (reordering_timer.is_valid() and reordering_timer.id() == timeout_id) { - logger.debug("%s reordering timeout expiry - updating vr_ms (was %d)", RB_NAME, vr_ms); + logger->debug("%s reordering timeout expiry - updating vr_ms (was %d)", RB_NAME, vr_ms); // 36.322 v10 Section 5.1.3.2.4 vr_ms = vr_x; @@ -1781,22 +1782,23 @@ int rlc_am_lte::rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const ui // make sure we don't exceed grant size if (rlc_am_packed_length(status) > max_pdu_size) { - logger.debug("Status PDU too big (%d > %d)", rlc_am_packed_length(status), max_pdu_size); + logger->debug("Status PDU too big (%d > %d)", rlc_am_packed_length(status), max_pdu_size); if (status->N_nack >= 1 && status->N_nack < RLC_AM_WINDOW_SIZE) { - logger.debug("Removing last NACK SN=%d", status->nacks[status->N_nack].nack_sn); + logger->debug("Removing last NACK SN=%d", status->nacks[status->N_nack].nack_sn); status->N_nack--; // make sure we don't have the current ACK_SN in the NACK list if (rlc_am_is_valid_status_pdu(*status, vr_r) == false) { // No space to send any NACKs, play safe and just ack lower edge - logger.warning("Resetting ACK_SN and N_nack to initial state"); + logger->warning("Resetting ACK_SN and N_nack to initial state"); status->ack_sn = vr_r; status->N_nack = 0; } } else { - logger.warning("Failed to generate small enough status PDU (packed_len=%d, max_pdu_size=%d, status->N_nack=%d)", - rlc_am_packed_length(status), - max_pdu_size, - status->N_nack); + logger->warning( + "Failed to generate small enough status PDU (packed_len=%d, max_pdu_size=%d, status->N_nack=%d)", + rlc_am_packed_length(status), + max_pdu_size, + status->N_nack); return 0; } break; @@ -1841,7 +1843,7 @@ void rlc_am_lte::rlc_am_lte_rx::print_rx_segments() << " N_li: " << segit->header.N_li << std::endl; } } - logger.debug("%s", ss.str().c_str()); + logger->debug("%s", ss.str().c_str()); } // NOTE: Preference would be to capture by value, and then move; but header is stack allocated @@ -1915,7 +1917,7 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* header.fi |= (pdu->segments.front().header.fi & RLC_FI_FIELD_NOT_START_ALIGNED); header.fi |= (pdu->segments.back().header.fi & RLC_FI_FIELD_NOT_END_ALIGNED); - logger.debug("Starting header reconstruction of %zd segments", pdu->segments.size()); + logger->debug("Starting header reconstruction of %zd segments", pdu->segments.size()); // Reconstruct li fields uint16_t count = 0; @@ -1923,7 +1925,7 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* uint16_t consumed_bytes = 0; // rolling sum of all allocated LIs during segment reconstruction for (it = pdu->segments.begin(); it != pdu->segments.end(); ++it) { - logger.debug(" Handling %d PDU segments", it->header.N_li); + logger->debug(" Handling %d PDU segments", it->header.N_li); for (uint32_t i = 0; i < it->header.N_li; i++) { // variable marks total offset of each _processed_ LI of this segment uint32_t total_pdu_offset = it->header.so; @@ -1931,28 +1933,28 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* total_pdu_offset += it->header.li[k]; } - logger.debug(" - (total_pdu_offset=%d, consumed_bytes=%d, header.li[i]=%d)", - total_pdu_offset, - consumed_bytes, - header.li[i]); + logger->debug(" - (total_pdu_offset=%d, consumed_bytes=%d, header.li[i]=%d)", + total_pdu_offset, + consumed_bytes, + header.li[i]); if (total_pdu_offset > header.li[i] && total_pdu_offset > consumed_bytes) { header.li[header.N_li] = total_pdu_offset - consumed_bytes; consumed_bytes = total_pdu_offset; - logger.debug(" - adding segment %d/%d (%d B, SO=%d, carryover=%d, count=%d)", - i + 1, - it->header.N_li, - header.li[header.N_li], - header.so, - carryover, - count); + logger->debug(" - adding segment %d/%d (%d B, SO=%d, carryover=%d, count=%d)", + i + 1, + it->header.N_li, + header.li[header.N_li], + header.so, + carryover, + count); header.N_li++; count += it->header.li[i]; carryover = 0; } else { - logger.debug(" - Skipping segment in reTx PDU segment which is already included (%d B, SO=%d)", - it->header.li[i], - header.so); + logger->debug(" - Skipping segment in reTx PDU segment which is already included (%d B, SO=%d)", + it->header.li[i], + header.so); } } @@ -1962,24 +1964,24 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* for (uint32_t k = 0; k < header.N_li; ++k) { carryover -= header.li[k]; } - logger.debug("Incremented carryover (it->buf->N_bytes=%d, count=%d). New carryover=%d", - it->buf->N_bytes, - count, - carryover); + logger->debug("Incremented carryover (it->buf->N_bytes=%d, count=%d). New carryover=%d", + it->buf->N_bytes, + count, + carryover); } else { // Next segment would be too long, recalculate carryover header.N_li--; carryover = it->buf->N_bytes - (count - header.li[header.N_li]); - logger.debug("Recalculated carryover=%d (it->buf->N_bytes=%d, count=%d, header.li[header.N_li]=%d)", - carryover, - it->buf->N_bytes, - count, - header.li[header.N_li]); + logger->debug("Recalculated carryover=%d (it->buf->N_bytes=%d, count=%d, header.li[header.N_li]=%d)", + carryover, + it->buf->N_bytes, + count, + header.li[header.N_li]); } tmpit = it; if (rlc_am_end_aligned(it->header.fi) && ++tmpit != pdu->segments.end()) { - logger.debug("Header is end-aligned, overwrite header.li[%d]=%d", header.N_li, carryover); + logger->debug("Header is end-aligned, overwrite header.li[%d]=%d", header.N_li, carryover); header.li[header.N_li] = carryover; header.N_li++; consumed_bytes += carryover; @@ -1991,7 +1993,7 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* header.p |= it->header.p; } - logger.debug("Finished header reconstruction of %zd segments", pdu->segments.size()); + logger->debug("Finished header reconstruction of %zd segments", pdu->segments.size()); // Copy data unique_byte_buffer_t full_pdu = srsran::make_byte_buffer(); @@ -2000,7 +2002,7 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* srsran::console("Fatal Error: Could not allocate PDU in add_segment_and_check()\n"); exit(-1); #else - logger.error("Fatal Error: Could not allocate PDU in add_segment_and_check()"); + logger->error("Fatal Error: Could not allocate PDU in add_segment_and_check()"); return false; #endif } @@ -2036,7 +2038,7 @@ bool rlc_am_lte::rlc_am_lte_rx::inside_rx_window(const int16_t sn) void rlc_am_lte::rlc_am_lte_rx::debug_state() { - logger.debug("%s vr_r = %d, vr_mr = %d, vr_x = %d, vr_ms = %d, vr_h = %d", RB_NAME, vr_r, vr_mr, vr_x, vr_ms, vr_h); + logger->debug("%s vr_r = %d, vr_mr = %d, vr_x = %d, vr_ms = %d, vr_h = %d", RB_NAME, vr_r, vr_mr, vr_x, vr_ms, vr_h); } } // namespace srsran diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index d927681da..8fb2950c3 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -27,19 +27,19 @@ rlc_am_nr::rlc_am_nr(srslog::basic_logger& logger, srsue::pdcp_interface_rlc* pdcp_, srsue::rrc_interface_rlc* rrc_, srsran::timer_handler* timers_) : - rlc_am_base(logger, lcid_, pdcp_, rrc_, timers_, new rlc_am_nr::rlc_am_nr_tx(this), new rlc_am_nr::rlc_am_nr_rx(this)) + rlc_am_base(logger, lcid_, pdcp_, rrc_, timers_, nullptr, nullptr) { - tx = dynamic_cast(tx_base); - rx = dynamic_cast(rx_base); + tx = new rlc_am_nr::rlc_am_nr_tx(this); + rx = new rlc_am_nr::rlc_am_nr_rx(this); + tx_base = tx; + rx_base = rx; } /******************************* * RLC AM NR * Tx subclass implementation ***************************************************************************/ -rlc_am_nr::rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* parent_) : - parent(parent_), logger(parent_->logger), pool(byte_buffer_pool::get_instance()) -{} +rlc_am_nr::rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* parent_) : parent(parent_), rlc_am_base_tx(&parent_->logger) {} bool rlc_am_nr::rlc_am_nr_tx::configure(const rlc_config_t& cfg_) { @@ -98,7 +98,7 @@ void rlc_am_nr::rlc_am_nr_tx::stop() {} * Rx subclass implementation ***************************************************************************/ rlc_am_nr::rlc_am_nr_rx::rlc_am_nr_rx(rlc_am_nr* parent_) : - parent(parent_), logger(parent_->logger), pool(byte_buffer_pool::get_instance()) + parent(parent_), pool(byte_buffer_pool::get_instance()), rlc_am_base_rx(&parent_->logger) {} bool rlc_am_nr::rlc_am_nr_rx::configure(const rlc_config_t& cfg_) From 54be15e7a6537a620aa1d0b33c473adaeffa1807 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Thu, 29 Jul 2021 17:00:10 +0100 Subject: [PATCH 12/77] Moved write pdu to rlc_am_base::rlc_am_base_rx --- lib/include/srsran/rlc/rlc_am_base.h | 32 +++++++++-------- lib/include/srsran/rlc/rlc_am_lte.h | 5 ++- lib/include/srsran/rlc/rlc_am_nr.h | 28 ++++++++------- lib/src/rlc/rlc_am_base.cc | 17 +++++++++ lib/src/rlc/rlc_am_lte.cc | 54 ++++++++++++++-------------- lib/src/rlc/rlc_am_nr.cc | 16 +++++---- 6 files changed, 88 insertions(+), 64 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index 471e07910..720a654af 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -128,19 +128,20 @@ protected: explicit rlc_am_base_tx(srslog::basic_logger* logger_) : logger(logger_) {} virtual bool configure(const rlc_config_t& cfg_) = 0; + virtual void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; + virtual uint32_t get_buffer_state() = 0; + virtual void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) = 0; virtual void reestablish() = 0; - virtual void stop() = 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 uint32_t get_buffer_state() = 0; - virtual void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) = 0; - virtual uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; + virtual void stop() = 0; void set_bsr_callback(bsr_callback_t callback); - int write_sdu(unique_byte_buffer_t sdu); + int write_sdu(unique_byte_buffer_t sdu); + virtual uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; bool tx_enabled = false; byte_buffer_pool* pool = nullptr; @@ -164,17 +165,20 @@ protected: class rlc_am_base_rx { public: - explicit rlc_am_base_rx(srslog::basic_logger* logger_) : logger(logger_) {} + explicit rlc_am_base_rx(rlc_am_base* parent_, srslog::basic_logger* logger_) : parent(parent_), logger(logger_) {} - virtual bool configure(const rlc_config_t& cfg_) = 0; - virtual void reestablish() = 0; - virtual void stop() = 0; - virtual void write_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; - virtual uint32_t get_sdu_rx_latency_ms() = 0; - virtual uint32_t get_rx_buffered_bytes() = 0; + virtual bool configure(const rlc_config_t& cfg_) = 0; + virtual void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; + virtual void reestablish() = 0; + virtual void stop() = 0; + virtual uint32_t get_sdu_rx_latency_ms() = 0; + virtual uint32_t get_rx_buffered_bytes() = 0; - srslog::basic_logger* logger; - byte_buffer_pool* pool = nullptr; + void write_pdu(uint8_t* payload, uint32_t nof_bytes); + + srslog::basic_logger* logger = nullptr; + byte_buffer_pool* pool = nullptr; + rlc_am_base* parent = nullptr; }; rlc_am_base_tx* tx_base = nullptr; diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index e3d201619..0fb69324d 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -210,8 +210,6 @@ public: void reestablish() final; void stop() final; - void write_pdu(uint8_t* payload, uint32_t nof_bytes) final; - uint32_t get_rx_buffered_bytes() final; // returns sum of PDUs in rx_window uint32_t get_sdu_rx_latency_ms() final; @@ -224,7 +222,8 @@ public: bool get_do_status(); private: - void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); + void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) final; + void handle_data_pdu_full(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); void handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); void reassemble_rx_sdus(); bool inside_rx_window(const int16_t sn); diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 1321440b8..92f9d531a 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -46,20 +46,22 @@ public: explicit rlc_am_nr_tx(rlc_am_nr* parent_); ~rlc_am_nr_tx() = default; - bool configure(const rlc_config_t& cfg_); - void stop(); + bool configure(const rlc_config_t& cfg_) final; + 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; int write_sdu(unique_byte_buffer_t sdu); - uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes); - void discard_sdu(uint32_t discard_sn); - bool sdu_queue_is_full(); - void reestablish(); - - void empty_queue(); - bool has_data(); - uint32_t get_buffer_state(); + void empty_queue() final; + bool has_data() final; + uint32_t get_buffer_state() final; void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); + void stop() final; + private: rlc_am_nr* parent = nullptr; @@ -89,11 +91,13 @@ public: explicit rlc_am_nr_rx(rlc_am_nr* parent_); ~rlc_am_nr_rx() = default; - bool configure(const rlc_config_t& cfg_); + bool configure(const rlc_config_t& cfg_) final; + + void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) final; + void stop(); void reestablish(); - void write_pdu(uint8_t* payload, uint32_t nof_bytes); uint32_t get_sdu_rx_latency_ms(); uint32_t get_rx_buffered_bytes(); diff --git a/lib/src/rlc/rlc_am_base.cc b/lib/src/rlc/rlc_am_base.cc index dc629a4c9..4d6b00bd0 100644 --- a/lib/src/rlc/rlc_am_base.cc +++ b/lib/src/rlc/rlc_am_base.cc @@ -212,4 +212,21 @@ void rlc_am_base::rlc_am_base_tx::set_bsr_callback(bsr_callback_t callback) bsr_callback = callback; } +/******************************************************* + * RLC AM RX entity + * This class is used for common code between the + * LTE and NR TX entitites + *******************************************************/ +void rlc_am_base::rlc_am_base_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) +{ + if (nof_bytes < 1) { + return; + } + + if (rlc_am_is_control_pdu(payload)) { + parent->tx_base->handle_control_pdu(payload, nof_bytes); + } else { + handle_data_pdu(payload, nof_bytes); + } +} } // namespace srsran diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index 9c36a8666..209fb30d0 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -1260,7 +1260,7 @@ rlc_am_lte::rlc_am_lte_rx::rlc_am_lte_rx(rlc_am_lte* parent_) : parent(parent_), pool(byte_buffer_pool::get_instance()), reordering_timer(parent_->timers->get_unique_timer()), - rlc_am_base_rx(&parent_->logger) + rlc_am_base_rx(parent_, &parent_->logger) {} rlc_am_lte::rlc_am_lte_rx::~rlc_am_lte_rx() {} @@ -1315,13 +1315,36 @@ void rlc_am_lte::rlc_am_lte_rx::stop() rx_window.clear(); } +/** Called from stack thread when MAC has received a new RLC PDU + * + * @param payload Pointer to payload + * @param nof_bytes Payload length + */ +void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) +{ + std::lock_guard lock(mutex); + + rlc_amd_pdu_header_t header = {}; + uint32_t payload_len = nof_bytes; + rlc_am_read_data_pdu_header(&payload, &payload_len, &header); + if (payload_len > nof_bytes) { + logger->info("Dropping corrupted PDU (%d B). Remaining length after header %d B.", nof_bytes, payload_len); + return; + } + if (header.rf != 0) { + handle_data_pdu_segment(payload, payload_len, header); + } else { + handle_data_pdu_full(payload, payload_len, header); + } +} + /** Called from stack thread when MAC has received a new RLC PDU * * @param payload Pointer to payload * @param nof_bytes Payload length * @param header Reference to PDU header (unpacked by caller) */ -void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) +void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_full(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) { std::map::iterator it; @@ -1690,31 +1713,6 @@ bool rlc_am_lte::rlc_am_lte_rx::get_do_status() return do_status.load(std::memory_order_relaxed); } -void rlc_am_lte::rlc_am_lte_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) -{ - if (nof_bytes < 1) { - return; - } - - if (rlc_am_is_control_pdu(payload)) { - parent->tx->handle_control_pdu(payload, nof_bytes); - } else { - std::lock_guard lock(mutex); - rlc_amd_pdu_header_t header = {}; - uint32_t payload_len = nof_bytes; - rlc_am_read_data_pdu_header(&payload, &payload_len, &header); - if (payload_len > nof_bytes) { - logger->info("Dropping corrupted PDU (%d B). Remaining length after header %d B.", nof_bytes, payload_len); - return; - } - if (header.rf) { - handle_data_pdu_segment(payload, payload_len, header); - } else { - handle_data_pdu(payload, payload_len, header); - } - } -} - uint32_t rlc_am_lte::rlc_am_lte_rx::get_rx_buffered_bytes() { std::lock_guard lock(mutex); @@ -2023,7 +2021,7 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* full_pdu->N_bytes += n; } - handle_data_pdu(full_pdu->msg, full_pdu->N_bytes, header); + handle_data_pdu_full(full_pdu->msg, full_pdu->N_bytes, header); return true; } diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 8fb2950c3..39b05ad80 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -66,10 +66,7 @@ uint32_t rlc_am_nr::rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) return 0; } -void rlc_am_nr::rlc_am_nr_tx::reestablish() -{ - stop(); -} +void rlc_am_nr::rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) {} uint32_t rlc_am_nr::rlc_am_nr_tx::get_buffer_state() { @@ -83,6 +80,11 @@ int rlc_am_nr::rlc_am_nr_tx::write_sdu(unique_byte_buffer_t sdu) return 0; } +void rlc_am_nr::rlc_am_nr_tx::reestablish() +{ + stop(); +} + void rlc_am_nr::rlc_am_nr_tx::discard_sdu(uint32_t discard_sn) {} bool rlc_am_nr::rlc_am_nr_tx::sdu_queue_is_full() @@ -98,7 +100,7 @@ void rlc_am_nr::rlc_am_nr_tx::stop() {} * Rx subclass implementation ***************************************************************************/ rlc_am_nr::rlc_am_nr_rx::rlc_am_nr_rx(rlc_am_nr* parent_) : - parent(parent_), pool(byte_buffer_pool::get_instance()), rlc_am_base_rx(&parent_->logger) + parent(parent_), pool(byte_buffer_pool::get_instance()), rlc_am_base_rx(parent_, &parent_->logger) {} bool rlc_am_nr::rlc_am_nr_rx::configure(const rlc_config_t& cfg_) @@ -108,9 +110,9 @@ bool rlc_am_nr::rlc_am_nr_rx::configure(const rlc_config_t& cfg_) return true; } -void rlc_am_nr::rlc_am_nr_rx::stop() {} +void rlc_am_nr::rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) {} -void rlc_am_nr::rlc_am_nr_rx::write_pdu(uint8_t* payload, uint32_t nof_bytes) {} +void rlc_am_nr::rlc_am_nr_rx::stop() {} void rlc_am_nr::rlc_am_nr_rx::reestablish() { From debb4a0c6b1fde7712a95d5c40a991f9984dd7b1 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 4 Aug 2021 12:43:41 +0100 Subject: [PATCH 13/77] Refactored RLC AM segment pool for re-use in both LTE and NR Moved RLC AMD PDU definitions from rlc_am_lte.h to rlc_common.h to make them re-usable in both RLC LTE and RLC NR --- lib/include/srsran/rlc/rlc_am_data_structs.h | 152 +++++++++++++++---- lib/include/srsran/rlc/rlc_am_lte.h | 14 +- lib/include/srsran/rlc/rlc_am_lte_packing.h | 55 ++----- lib/include/srsran/rlc/rlc_am_nr.h | 5 + lib/include/srsran/rlc/rlc_common.h | 5 +- lib/src/rlc/rlc_am_lte.cc | 76 +--------- lib/src/rlc/rlc_am_lte_packing.cc | 3 +- 7 files changed, 159 insertions(+), 151 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_data_structs.h b/lib/include/srsran/rlc/rlc_am_data_structs.h index 2a5800125..465d466d0 100644 --- a/lib/include/srsran/rlc/rlc_am_data_structs.h +++ b/lib/include/srsran/rlc/rlc_am_data_structs.h @@ -16,61 +16,139 @@ #include "srsran/adt/circular_buffer.h" #include "srsran/adt/circular_map.h" #include "srsran/adt/intrusive_list.h" +#include "srsran/common/buffer_pool.h" #include #include namespace srsran { +template class rlc_amd_tx_pdu; +template class pdcp_pdu_info; /// Pool that manages the allocation of RLC AM PDU Segments to RLC PDUs and tracking of segments ACK state +template struct rlc_am_pdu_segment_pool { const static size_t MAX_POOL_SIZE = 16384; - using rlc_list_tag = default_intrusive_tag; - struct free_list_tag {}; /// RLC AM PDU Segment, containing the PDCP SN and RLC SN it has been assigned to, and its current ACK state + using rlc_list_tag = default_intrusive_tag; + struct free_list_tag {}; struct segment_resource : public intrusive_forward_list_element, public intrusive_forward_list_element, public intrusive_double_linked_list_element<> { const static uint32_t invalid_rlc_sn = std::numeric_limits::max(); const static uint32_t invalid_pdcp_sn = std::numeric_limits::max() - 1; // -1 for Status Report - int id() const; - void release_pdcp_sn(); - void release_rlc_sn(); + int id() const { return std::distance(parent_pool->segments.cbegin(), this); } + + void release_pdcp_sn() + { + pdcp_sn_ = invalid_pdcp_sn; + if (empty()) { + parent_pool->free_list.push_front(this); + } + } + + void release_rlc_sn() + { + rlc_sn_ = invalid_rlc_sn; + if (empty()) { + parent_pool->free_list.push_front(this); + } + } + uint32_t rlc_sn() const { return rlc_sn_; } uint32_t pdcp_sn() const { return pdcp_sn_; } bool empty() const { return rlc_sn_ == invalid_rlc_sn and pdcp_sn_ == invalid_pdcp_sn; } private: - friend struct rlc_am_pdu_segment_pool; - uint32_t rlc_sn_ = invalid_rlc_sn; - uint32_t pdcp_sn_ = invalid_pdcp_sn; - rlc_am_pdu_segment_pool* parent_pool = nullptr; + friend struct rlc_am_pdu_segment_pool; + uint32_t rlc_sn_ = invalid_rlc_sn; + uint32_t pdcp_sn_ = invalid_pdcp_sn; + rlc_am_pdu_segment_pool* parent_pool = nullptr; }; - rlc_am_pdu_segment_pool(); + rlc_am_pdu_segment_pool() + { + for (segment_resource& s : segments) { + s.parent_pool = this; + free_list.push_front(&s); + } + } rlc_am_pdu_segment_pool(const rlc_am_pdu_segment_pool&) = delete; rlc_am_pdu_segment_pool(rlc_am_pdu_segment_pool&&) = delete; rlc_am_pdu_segment_pool& operator=(const rlc_am_pdu_segment_pool&) = delete; rlc_am_pdu_segment_pool& operator=(rlc_am_pdu_segment_pool&&) = delete; - bool has_segments() const { return not free_list.empty(); } - bool make_segment(rlc_amd_tx_pdu& rlc_list, pdcp_pdu_info& pdcp_info); + + bool has_segments() const { return not free_list.empty(); } + bool make_segment(rlc_amd_tx_pdu& rlc_list, pdcp_pdu_info& pdcp_list) + { + if (not has_segments()) { + return false; + } + segment_resource* segment = free_list.pop_front(); + segment->rlc_sn_ = rlc_list.rlc_sn; + segment->pdcp_sn_ = pdcp_list.sn; + rlc_list.add_segment(*segment); + pdcp_list.add_segment(*segment); + return true; + } private: - intrusive_forward_list free_list; - std::array segments; + intrusive_forward_list::segment_resource, free_list_tag> free_list; + std::array::segment_resource, MAX_POOL_SIZE> segments; }; -/// RLC AM PDU Segment, containing the PDCP SN and RLC SN it has been assigned to, and its current ACK state -using rlc_am_pdu_segment = rlc_am_pdu_segment_pool::segment_resource; +/// Class that contains the parameters and state (e.g. segments) of a RLC PDU +template +class rlc_amd_tx_pdu +{ + using rlc_am_pdu_segment = typename rlc_am_pdu_segment_pool::segment_resource; + using list_type = intrusive_forward_list; + const static uint32_t invalid_rlc_sn = std::numeric_limits::max(); + + list_type list; + +public: + using iterator = typename list_type::iterator; + using const_iterator = typename list_type::const_iterator; + + const uint32_t rlc_sn = invalid_rlc_sn; + uint32_t retx_count = 0; + header_t header; + unique_byte_buffer_t buf; + + explicit rlc_amd_tx_pdu(uint32_t rlc_sn_) : rlc_sn(rlc_sn_) {} + rlc_amd_tx_pdu(const rlc_amd_tx_pdu&) = delete; + rlc_amd_tx_pdu(rlc_amd_tx_pdu&& other) noexcept = default; + rlc_amd_tx_pdu& operator=(const rlc_amd_tx_pdu& other) = delete; + rlc_amd_tx_pdu& operator=(rlc_amd_tx_pdu&& other) = delete; + ~rlc_amd_tx_pdu() + { + while (not list.empty()) { + // remove from list + rlc_am_pdu_segment* segment = list.pop_front(); + // deallocate if also removed from PDCP + segment->release_rlc_sn(); + } + } + + // Segment List Interface + void add_segment(rlc_am_pdu_segment& segment) { list.push_front(&segment); } + const_iterator begin() const { return list.begin(); } + const_iterator end() const { return list.end(); } + iterator begin() { return list.begin(); } + iterator end() { return list.end(); } +}; /// Class that contains the parameters and state (e.g. unACKed segments) of a PDCP PDU +template class pdcp_pdu_info { - using list_type = intrusive_double_linked_list; + using rlc_am_pdu_segment = typename rlc_am_pdu_segment_pool::segment_resource; + using list_type = intrusive_double_linked_list; list_type list; // List of unACKed RLC PDUs that contain segments that belong to the PDCP PDU. @@ -97,7 +175,13 @@ public: // Interface for list of unACKed RLC segments of the PDCP PDU void add_segment(rlc_am_pdu_segment& segment) { list.push_front(&segment); } - void ack_segment(rlc_am_pdu_segment& segment); + void ack_segment(rlc_am_pdu_segment& segment) + { + // remove from list + list.pop(&segment); + // signal pool that the pdcp handle is released + segment.release_pdcp_sn(); + } void clear() { sn = invalid_pdcp_sn; @@ -106,6 +190,7 @@ public: ack_segment(list.front()); } } + const_iterator begin() const { return list.begin(); } const_iterator end() const { return list.end(); } }; @@ -146,6 +231,7 @@ private: srsran::static_circular_map window; }; +template struct buffered_pdcp_pdu_list { public: explicit buffered_pdcp_pdu_list() : buffered_pdus(buffered_pdcp_pdu_list::buffer_size) { clear(); } @@ -153,7 +239,7 @@ public: void clear() { count = 0; - for (pdcp_pdu_info& b : buffered_pdus) { + for (pdcp_pdu_info& b : buffered_pdus) { b.clear(); } } @@ -162,7 +248,7 @@ public: { srsran_expect(sn <= max_pdcp_sn or sn == status_report_sn, "Invalid PDCP SN=%d", sn); srsran_assert(not has_pdcp_sn(sn), "Cannot re-add same PDCP SN twice"); - pdcp_pdu_info& pdu = get_pdu_(sn); + pdcp_pdu_info& pdu = get_pdu_(sn); if (pdu.valid()) { pdu.clear(); count--; @@ -170,21 +256,25 @@ public: pdu.sn = sn; count++; } + void clear_pdcp_sdu(uint32_t sn) { - pdcp_pdu_info& pdu = get_pdu_(sn); + pdcp_pdu_info& pdu = get_pdu_(sn); if (not pdu.valid()) { - return; + { + return; + } + pdu.clear(); + count--; } - pdu.clear(); - count--; } - pdcp_pdu_info& operator[](uint32_t sn) + pdcp_pdu_info& operator[](uint32_t sn) { srsran_expect(has_pdcp_sn(sn), "Invalid access to non-existent PDCP SN=%d", sn); return get_pdu_(sn); } + bool has_pdcp_sn(uint32_t pdcp_sn) const { srsran_expect(pdcp_sn <= max_pdcp_sn or pdcp_sn == status_report_sn, "Invalid PDCP SN=%d", pdcp_sn); @@ -195,21 +285,21 @@ public: private: const static size_t max_pdcp_sn = 262143u; const static size_t buffer_size = 4096u; - const static uint32_t status_report_sn = pdcp_pdu_info::status_report_sn; + const static uint32_t status_report_sn = pdcp_pdu_info::status_report_sn; - pdcp_pdu_info& get_pdu_(uint32_t sn) + pdcp_pdu_info& get_pdu_(uint32_t sn) { return (sn == status_report_sn) ? status_report_pdu : buffered_pdus[static_cast(sn % buffer_size)]; } - const pdcp_pdu_info& get_pdu_(uint32_t sn) const + const pdcp_pdu_info& get_pdu_(uint32_t sn) const { return (sn == status_report_sn) ? status_report_pdu : buffered_pdus[static_cast(sn % buffer_size)]; } // size equal to buffer_size - std::vector buffered_pdus; - pdcp_pdu_info status_report_pdu; - uint32_t count = 0; + std::vector > buffered_pdus; + pdcp_pdu_info status_report_pdu; + uint32_t count = 0; }; } // namespace srsran diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index 0fb69324d..6c6aeb4b7 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -143,9 +143,9 @@ public: void check_sn_reached_max_retx(uint32_t sn); void get_buffer_state_nolock(uint32_t& new_tx, uint32_t& prio_tx); - rlc_am_lte* parent = nullptr; - byte_buffer_pool* pool = nullptr; - rlc_am_pdu_segment_pool segment_pool; + rlc_am_lte* parent = nullptr; + byte_buffer_pool* pool = nullptr; + rlc_am_pdu_segment_pool segment_pool; /**************************************************************************** * Configurable parameters @@ -183,12 +183,12 @@ public: srsran::timer_handler::unique_timer status_prohibit_timer; // SDU info for PDCP notifications - buffered_pdcp_pdu_list undelivered_sdu_info_queue; + buffered_pdcp_pdu_list undelivered_sdu_info_queue; // Tx windows - rlc_ringbuffer_t tx_window; - pdu_retx_queue retx_queue; - pdcp_sn_vector_t notify_info_vec; + rlc_ringbuffer_t, RLC_AM_WINDOW_SIZE> tx_window; + pdu_retx_queue retx_queue; + pdcp_sn_vector_t notify_info_vec; // Mutexes std::mutex mutex; diff --git a/lib/include/srsran/rlc/rlc_am_lte_packing.h b/lib/include/srsran/rlc/rlc_am_lte_packing.h index 19215d4e4..e9ae3e8d4 100644 --- a/lib/include/srsran/rlc/rlc_am_lte_packing.h +++ b/lib/include/srsran/rlc/rlc_am_lte_packing.h @@ -20,38 +20,6 @@ namespace srsran { -/// Class that contains the parameters and state (e.g. segments) of a RLC PDU -class rlc_amd_tx_pdu -{ - using list_type = intrusive_forward_list; - const static uint32_t invalid_rlc_sn = std::numeric_limits::max(); - - list_type list; - -public: - using iterator = typename list_type::iterator; - using const_iterator = typename list_type::const_iterator; - - const uint32_t rlc_sn = invalid_rlc_sn; - uint32_t retx_count = 0; - rlc_amd_pdu_header_t header; - unique_byte_buffer_t buf; - - explicit rlc_amd_tx_pdu(uint32_t rlc_sn_) : rlc_sn(rlc_sn_) {} - rlc_amd_tx_pdu(const rlc_amd_tx_pdu&) = delete; - rlc_amd_tx_pdu(rlc_amd_tx_pdu&& other) noexcept = default; - rlc_amd_tx_pdu& operator=(const rlc_amd_tx_pdu& other) = delete; - rlc_amd_tx_pdu& operator=(rlc_amd_tx_pdu&& other) = delete; - ~rlc_amd_tx_pdu(); - - // Segment List Interface - void add_segment(rlc_am_pdu_segment& segment) { list.push_front(&segment); } - const_iterator begin() const { return list.begin(); } - const_iterator end() const { return list.end(); } - iterator begin() { return list.begin(); } - iterator end() { return list.end(); } -}; - struct rlc_amd_retx_t { uint32_t sn; bool is_segment; @@ -90,17 +58,18 @@ void rlc_am_read_status_pdu(uint8_t* payload, uint32_t nof_bytes, rlc_status_pdu void rlc_am_write_status_pdu(rlc_status_pdu_t* status, byte_buffer_t* pdu); 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); -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); -std::string rlc_am_undelivered_sdu_info_to_string(const std::map& info_queue); -void log_rlc_amd_pdu_header_to_string(srslog::log_channel& log_ch, const rlc_amd_pdu_header_t& header); -bool rlc_am_start_aligned(const uint8_t fi); -bool rlc_am_end_aligned(const uint8_t fi); -bool rlc_am_is_unaligned(const uint8_t fi); -bool rlc_am_not_start_aligned(const uint8_t fi); +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); +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); +std::string + rlc_am_undelivered_sdu_info_to_string(const std::map >& info_queue); +void log_rlc_amd_pdu_header_to_string(srslog::log_channel& log_ch, const rlc_amd_pdu_header_t& header); +bool rlc_am_start_aligned(const uint8_t fi); +bool rlc_am_end_aligned(const uint8_t fi); +bool rlc_am_is_unaligned(const uint8_t fi); +bool rlc_am_not_start_aligned(const uint8_t fi); /** * Logs Status PDU into provided log channel, using fmt_str as format string diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 92f9d531a..3c8724742 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -17,6 +17,8 @@ #include "srsran/common/common.h" #include "srsran/common/timers.h" #include "srsran/rlc/rlc_am_base.h" +#include "srsran/rlc/rlc_am_data_structs.h" +#include "srsran/rlc/rlc_am_nr_packing.h" #include "srsran/upper/byte_buffer_queue.h" #include #include @@ -82,6 +84,9 @@ public: uint32_t pdu_without_poll; uint32_t byte_without_poll; } st = {}; + + using rlc_amd_tx_pdu_nr = rlc_amd_tx_pdu; + rlc_ringbuffer_t tx_window; }; // Receiver sub-class diff --git a/lib/include/srsran/rlc/rlc_common.h b/lib/include/srsran/rlc/rlc_common.h index bb5848e2c..9d6b0f29f 100644 --- a/lib/include/srsran/rlc/rlc_common.h +++ b/lib/include/srsran/rlc/rlc_common.h @@ -14,10 +14,13 @@ #define SRSRAN_RLC_COMMON_H #include "srsran/adt/circular_buffer.h" +#include "srsran/adt/circular_map.h" +#include "srsran/adt/intrusive_list.h" #include "srsran/interfaces/rlc_interface_types.h" #include "srsran/rlc/bearer_mem_pool.h" #include "srsran/rlc/rlc_metrics.h" -#include +#include +#include namespace srsran { diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index 209fb30d0..1cc5ed7e2 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -25,69 +25,9 @@ namespace srsran { -/******************************* - * RLC AM Segments - ******************************/ - -int rlc_am_pdu_segment_pool::segment_resource::id() const -{ - return std::distance(parent_pool->segments.cbegin(), this); -} - -void rlc_am_pdu_segment_pool::segment_resource::release_pdcp_sn() -{ - pdcp_sn_ = invalid_pdcp_sn; - if (empty()) { - parent_pool->free_list.push_front(this); - } -} - -void rlc_am_pdu_segment_pool::segment_resource::release_rlc_sn() -{ - rlc_sn_ = invalid_rlc_sn; - if (empty()) { - parent_pool->free_list.push_front(this); - } -} - -rlc_am_pdu_segment_pool::rlc_am_pdu_segment_pool() -{ - for (segment_resource& s : segments) { - s.parent_pool = this; - free_list.push_front(&s); - } -} - -bool rlc_am_pdu_segment_pool::make_segment(rlc_amd_tx_pdu& rlc_list, pdcp_pdu_info& pdcp_list) -{ - if (not has_segments()) { - return false; - } - segment_resource* segment = free_list.pop_front(); - segment->rlc_sn_ = rlc_list.rlc_sn; - segment->pdcp_sn_ = pdcp_list.sn; - rlc_list.add_segment(*segment); - pdcp_list.add_segment(*segment); - return true; -} - -void pdcp_pdu_info::ack_segment(rlc_am_pdu_segment& segment) -{ - // remove from list - list.pop(&segment); - // signal pool that the pdcp handle is released - segment.release_pdcp_sn(); -} - -rlc_amd_tx_pdu::~rlc_amd_tx_pdu() -{ - while (not list.empty()) { - // remove from list - rlc_am_pdu_segment* segment = list.pop_front(); - // deallocate if also removed from PDCP - segment->release_rlc_sn(); - } -} +using pdcp_pdu_info_lte = pdcp_pdu_info; +using rlc_amd_tx_pdu_lte = rlc_amd_tx_pdu; +using rlc_am_pdu_segment = rlc_am_pdu_segment_pool::segment_resource; /**************************************************************************** * RLC AM LTE entity @@ -432,7 +372,7 @@ void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) } // select first PDU in tx window for retransmission - rlc_amd_tx_pdu& pdu = tx_window[sn]; + rlc_amd_tx_pdu_lte& pdu = tx_window[sn]; // increment retx counter and inform upper layers pdu.retx_count++; @@ -823,7 +763,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt // 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 vt_s - rlc_amd_tx_pdu& tx_pdu = tx_window.add_pdu(header.sn); + rlc_amd_tx_pdu_lte& tx_pdu = tx_window.add_pdu(header.sn); uint32_t head_len = rlc_am_packed_length(&header); uint32_t to_move = 0; @@ -843,7 +783,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt tx_sdu->N_bytes -= to_move; tx_sdu->msg += to_move; if (undelivered_sdu_info_queue.has_pdcp_sn(tx_sdu->md.pdcp_sn)) { - pdcp_pdu_info& pdcp_pdu = undelivered_sdu_info_queue[tx_sdu->md.pdcp_sn]; + pdcp_pdu_info_lte& pdcp_pdu = undelivered_sdu_info_queue[tx_sdu->md.pdcp_sn]; segment_pool.make_segment(tx_pdu, pdcp_pdu); if (tx_sdu->N_bytes == 0) { pdcp_pdu.fully_txed = true; @@ -914,7 +854,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt undelivered_sdu_info_queue.nof_sdus()); undelivered_sdu_info_queue.add_pdcp_sdu(tx_sdu->md.pdcp_sn); } - pdcp_pdu_info& pdcp_pdu = undelivered_sdu_info_queue[tx_sdu->md.pdcp_sn]; + pdcp_pdu_info_lte& pdcp_pdu = undelivered_sdu_info_queue[tx_sdu->md.pdcp_sn]; to_move = ((pdu_space - head_len) >= tx_sdu->N_bytes) ? tx_sdu->N_bytes : pdu_space - head_len; memcpy(pdu_ptr, tx_sdu->msg, to_move); @@ -1155,7 +1095,7 @@ void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) logger->debug("ACKed segment in RLC_SN=%d already discarded in PDCP. No need to notify the PDCP.", rlc_sn); continue; } - pdcp_pdu_info& info = undelivered_sdu_info_queue[pdcp_sn]; + pdcp_pdu_info_lte& info = undelivered_sdu_info_queue[pdcp_sn]; // Remove RLC SN from PDCP PDU undelivered list info.ack_segment(acked_segment); diff --git a/lib/src/rlc/rlc_am_lte_packing.cc b/lib/src/rlc/rlc_am_lte_packing.cc index 02643f3fc..3cff0dd13 100644 --- a/lib/src/rlc/rlc_am_lte_packing.cc +++ b/lib/src/rlc/rlc_am_lte_packing.cc @@ -276,7 +276,8 @@ bool rlc_am_is_valid_status_pdu(const rlc_status_pdu_t& status, uint32_t rx_win_ return true; } -void rlc_am_undelivered_sdu_info_to_string(fmt::memory_buffer& buffer, const std::vector& info_queue) +void rlc_am_undelivered_sdu_info_to_string(fmt::memory_buffer& buffer, + const std::vector >& info_queue) { fmt::format_to(buffer, "\n"); for (const auto& pdcp_pdu : info_queue) { From e780eb5ab041bc2befb2d0fe3f35626a761371cd Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 6 Oct 2021 17:17:34 +0100 Subject: [PATCH 14/77] Fixed missing TM and RLC AM NR in mem_pool --- lib/src/rlc/bearer_mem_pool.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/rlc/bearer_mem_pool.cc b/lib/src/rlc/bearer_mem_pool.cc index c89fcb945..1c6fc9aeb 100644 --- a/lib/src/rlc/bearer_mem_pool.cc +++ b/lib/src/rlc/bearer_mem_pool.cc @@ -13,6 +13,8 @@ #include "srsran/rlc/bearer_mem_pool.h" #include "srsran/adt/pool/batch_mem_pool.h" #include "srsran/rlc/rlc_am_lte.h" +#include "srsran/rlc/rlc_am_nr.h" +#include "srsran/rlc/rlc_tm.h" #include "srsran/rlc/rlc_um_lte.h" #include "srsran/rlc/rlc_um_nr.h" @@ -21,7 +23,12 @@ namespace srsran { srsran::background_mem_pool* get_bearer_pool() { static background_mem_pool pool( - 4, std::max(std::max(sizeof(rlc_am_lte), sizeof(rlc_um_lte)), sizeof(rlc_um_nr)), 8, 8); + 4, + std::max( + std::max(std::max(std::max(sizeof(rlc_am_lte), sizeof(rlc_am_nr)), sizeof(rlc_um_lte)), sizeof(rlc_um_nr)), + sizeof(rlc_tm)), + 8, + 8); return &pool; } From 779eda98bd009385b770d9aec4681634ec087d78 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Tue, 9 Nov 2021 12:00:46 +0000 Subject: [PATCH 15/77] Temporarly silence unused variable warnings. --- lib/src/rlc/rlc_am_nr.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 39b05ad80..db41fe23b 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -35,11 +35,14 @@ rlc_am_nr::rlc_am_nr(srslog::basic_logger& logger, rx_base = rx; } -/******************************* - * RLC AM NR - * Tx subclass implementation +/*************************************************************************** + * Tx subclass implementation ***************************************************************************/ -rlc_am_nr::rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* parent_) : parent(parent_), rlc_am_base_tx(&parent_->logger) {} +rlc_am_nr::rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* parent_) : parent(parent_), rlc_am_base_tx(&parent_->logger) +{ + parent->logger.debug("Initializing RLC AM NR TX: Tx_Next: %d", + st.tx_next); // Temporarly silence unused variable warning +} bool rlc_am_nr::rlc_am_nr_tx::configure(const rlc_config_t& cfg_) { @@ -101,7 +104,9 @@ void rlc_am_nr::rlc_am_nr_tx::stop() {} ***************************************************************************/ rlc_am_nr::rlc_am_nr_rx::rlc_am_nr_rx(rlc_am_nr* parent_) : parent(parent_), pool(byte_buffer_pool::get_instance()), rlc_am_base_rx(parent_, &parent_->logger) -{} +{ + parent->logger.debug("Initializing RLC AM NR RX"); // Temporarly silence unused variable warning +} bool rlc_am_nr::rlc_am_nr_rx::configure(const rlc_config_t& cfg_) { From aef87d5366506848f83ba8cf762773232002c703 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 10 Nov 2021 15:25:10 +0000 Subject: [PATCH 16/77] rlc_am_nr: Change rlc_am_base to use unique_ptr to hold rx/tx entities --- lib/include/srsran/rlc/rlc_am_base.h | 10 +++++----- lib/include/srsran/rlc/rlc_am_lte.h | 9 +++++---- lib/src/rlc/rlc_am_lte.cc | 22 ++++++++++++---------- lib/src/rlc/rlc_am_nr.cc | 9 ++------- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index 720a654af..67a191322 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -53,9 +53,9 @@ public: srsue::pdcp_interface_rlc* pdcp_, srsue::rrc_interface_rlc* rrc_, srsran::timer_handler* timers_, - rlc_am_base_tx* tx_, - rlc_am_base_rx* rx_) : - logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx_base(tx_), rx_base(rx_) + rlc_am_base_tx* tx_base_, + rlc_am_base_rx* rx_base_) : + logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx_base(tx_base_), rx_base(rx_base_) {} bool configure(const rlc_config_t& cfg_) final; @@ -181,8 +181,8 @@ protected: rlc_am_base* parent = nullptr; }; - rlc_am_base_tx* tx_base = nullptr; - rlc_am_base_rx* rx_base = nullptr; + std::unique_ptr tx_base = {}; + std::unique_ptr rx_base = {}; }; } // namespace srsran diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index 6c6aeb4b7..6533d865f 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -94,6 +94,9 @@ public: srsue::rrc_interface_rlc* rrc_, srsran::timer_handler* timers_); + class rlc_am_lte_tx; + class rlc_am_lte_rx; + /****************************** * RLC AM LTE TX entity *****************************/ @@ -144,6 +147,7 @@ public: void get_buffer_state_nolock(uint32_t& new_tx, uint32_t& prio_tx); rlc_am_lte* parent = nullptr; + rlc_am_lte_rx* rx = nullptr; byte_buffer_pool* pool = nullptr; rlc_am_pdu_segment_pool segment_pool; @@ -233,6 +237,7 @@ public: void reset_status(); rlc_am_lte* parent = nullptr; + rlc_am_lte_tx* tx = nullptr; byte_buffer_pool* pool = nullptr; /**************************************************************************** @@ -275,10 +280,6 @@ public: srsran::rolling_average sdu_rx_latency_ms; }; - -private: - rlc_am_lte_tx* tx = nullptr; - rlc_am_lte_rx* rx = nullptr; }; } // namespace srsran diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index 1cc5ed7e2..5e751d5c9 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -37,18 +37,20 @@ rlc_am_lte::rlc_am_lte(srslog::basic_logger& logger, srsue::pdcp_interface_rlc* pdcp_, srsue::rrc_interface_rlc* rrc_, srsran::timer_handler* timers_) : - rlc_am_base(logger, lcid_, pdcp_, rrc_, timers_, nullptr, nullptr) -{ - tx = new rlc_am_lte::rlc_am_lte_tx(this); - rx = new rlc_am_lte::rlc_am_lte_rx(this); - tx_base = tx; - rx_base = rx; -} + rlc_am_base(logger, + lcid_, + pdcp_, + rrc_, + timers_, + new rlc_am_lte::rlc_am_lte_tx(this), + new rlc_am_lte::rlc_am_lte_rx(this)) +{} /**************************************************************************** * Tx subclass implementation ***************************************************************************/ rlc_am_lte::rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : parent(parent_), + rx(dynamic_cast(parent->rx_base.get())), pool(byte_buffer_pool::get_instance()), poll_retx_timer(parent_->timers->get_unique_timer()), status_prohibit_timer(parent_->timers->get_unique_timer()), @@ -160,7 +162,7 @@ void rlc_am_lte::rlc_am_lte_tx::reestablish() bool rlc_am_lte::rlc_am_lte_tx::do_status() { - return parent->rx->get_do_status(); + return rx->get_do_status(); } // Function is supposed to return as fast as possible @@ -225,7 +227,7 @@ void rlc_am_lte::rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, // Bytes needed for status report if (do_status() && not status_prohibit_timer.is_running()) { - n_bytes_prio += parent->rx->get_status_pdu_length(); + n_bytes_prio += rx->get_status_pdu_length(); logger->debug("%s Buffer state - total status report: %d bytes", RB_NAME, n_bytes_prio); } @@ -437,7 +439,7 @@ bool rlc_am_lte::rlc_am_lte_tx::poll_required() int rlc_am_lte::rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_bytes) { logger->debug("%s Generating status PDU. Nof bytes %d", RB_NAME, nof_bytes); - int pdu_len = parent->rx->get_status_pdu(&tx_status, nof_bytes); + int pdu_len = rx->get_status_pdu(&tx_status, nof_bytes); if (pdu_len == SRSRAN_ERROR) { logger->debug("%s Deferred Status PDU. Cause: Failed to acquire Rx lock", RB_NAME); pdu_len = 0; diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index db41fe23b..c2f3dbde1 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -27,13 +27,8 @@ rlc_am_nr::rlc_am_nr(srslog::basic_logger& logger, srsue::pdcp_interface_rlc* pdcp_, srsue::rrc_interface_rlc* rrc_, srsran::timer_handler* timers_) : - rlc_am_base(logger, lcid_, pdcp_, rrc_, timers_, nullptr, nullptr) -{ - tx = new rlc_am_nr::rlc_am_nr_tx(this); - rx = new rlc_am_nr::rlc_am_nr_rx(this); - tx_base = tx; - rx_base = rx; -} + rlc_am_base(logger, lcid_, pdcp_, rrc_, timers_, new rlc_am_nr::rlc_am_nr_tx(this), new rlc_am_nr::rlc_am_nr_rx(this)) +{} /*************************************************************************** * Tx subclass implementation From eefedcfccde2a900b9c32f0735d518359990de47 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 10 Nov 2021 16:03:38 +0000 Subject: [PATCH 17/77] rlc_am_nr: renamed rlc_am_base to just rlc_am --- lib/include/srsran/rlc/rlc_am_base.h | 20 ++++++++-------- lib/include/srsran/rlc/rlc_am_lte.h | 2 +- lib/include/srsran/rlc/rlc_am_nr.h | 2 +- lib/src/rlc/rlc_am_base.cc | 34 ++++++++++++++-------------- lib/src/rlc/rlc_am_lte.cc | 8 +------ lib/src/rlc/rlc_am_nr.cc | 2 +- 6 files changed, 31 insertions(+), 37 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index 67a191322..e76cd7523 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -41,20 +41,20 @@ bool rlc_am_is_control_pdu(byte_buffer_t* pdu); * This entity is common between LTE and NR * and only the TX/RX entities change between them *******************************************************/ -class rlc_am_base : public rlc_common +class rlc_am : public rlc_common { protected: class rlc_am_base_tx; class rlc_am_base_rx; public: - rlc_am_base(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_, - rlc_am_base_tx* tx_base_, - rlc_am_base_rx* rx_base_) : + rlc_am(srslog::basic_logger& logger, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srsran::timer_handler* timers_, + rlc_am_base_tx* tx_base_, + rlc_am_base_rx* rx_base_) : logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx_base(tx_base_), rx_base(rx_base_) {} @@ -165,7 +165,7 @@ protected: class rlc_am_base_rx { public: - explicit rlc_am_base_rx(rlc_am_base* parent_, srslog::basic_logger* logger_) : parent(parent_), logger(logger_) {} + explicit rlc_am_base_rx(rlc_am* parent_, srslog::basic_logger* logger_) : parent(parent_), logger(logger_) {} virtual bool configure(const rlc_config_t& cfg_) = 0; virtual void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; @@ -178,7 +178,7 @@ protected: srslog::basic_logger* logger = nullptr; byte_buffer_pool* pool = nullptr; - rlc_am_base* parent = nullptr; + rlc_am* parent = nullptr; }; std::unique_ptr tx_base = {}; diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index 6533d865f..99fd4ca04 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -85,7 +85,7 @@ private: * RLC AM LTE entity * *****************************/ -class rlc_am_lte : public rlc_am_base +class rlc_am_lte : public rlc_am { public: rlc_am_lte(srslog::basic_logger& logger, diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 3c8724742..31316bf8e 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -32,7 +32,7 @@ namespace srsran { * RLC AM NR entity * *****************************/ -class rlc_am_nr : public rlc_am_base +class rlc_am_nr : public rlc_am { public: rlc_am_nr(srslog::basic_logger& logger, diff --git a/lib/src/rlc/rlc_am_base.cc b/lib/src/rlc/rlc_am_base.cc index 4d6b00bd0..a8f0ba8ad 100644 --- a/lib/src/rlc/rlc_am_base.cc +++ b/lib/src/rlc/rlc_am_base.cc @@ -30,7 +30,7 @@ bool rlc_am_is_control_pdu(byte_buffer_t* pdu) * This entity is common between LTE and NR * and only the TX/RX entities change between them *******************************************************/ -bool rlc_am_base::configure(const rlc_config_t& cfg_) +bool rlc_am::configure(const rlc_config_t& cfg_) { // determine bearer name and configure Rx/Tx objects rb_name = rrc->get_rb_name(lcid); @@ -60,14 +60,14 @@ bool rlc_am_base::configure(const rlc_config_t& cfg_) return true; } -void rlc_am_base::stop() +void rlc_am::stop() { logger.debug("Stopped bearer %s", rb_name.c_str()); tx_base->stop(); rx_base->stop(); } -void rlc_am_base::reestablish() +void rlc_am::reestablish() { logger.debug("Reestablished bearer %s", rb_name.c_str()); tx_base->reestablish(); // calls stop and enables tx again @@ -77,7 +77,7 @@ void rlc_am_base::reestablish() /**************************************************************************** * PDCP interface ***************************************************************************/ -void rlc_am_base::write_sdu(unique_byte_buffer_t sdu) +void rlc_am::write_sdu(unique_byte_buffer_t sdu) { if (tx_base->write_sdu(std::move(sdu)) == SRSRAN_SUCCESS) { std::lock_guard lock(metrics_mutex); @@ -85,7 +85,7 @@ void rlc_am_base::write_sdu(unique_byte_buffer_t sdu) } } -void rlc_am_base::discard_sdu(uint32_t discard_sn) +void rlc_am::discard_sdu(uint32_t discard_sn) { tx_base->discard_sdu(discard_sn); @@ -93,7 +93,7 @@ void rlc_am_base::discard_sdu(uint32_t discard_sn) metrics.num_lost_sdus++; } -bool rlc_am_base::sdu_queue_is_full() +bool rlc_am::sdu_queue_is_full() { return tx_base->sdu_queue_is_full(); } @@ -101,23 +101,23 @@ bool rlc_am_base::sdu_queue_is_full() /**************************************************************************** * MAC interface ***************************************************************************/ -bool rlc_am_base::has_data() +bool rlc_am::has_data() { return tx_base->has_data(); } -uint32_t rlc_am_base::get_buffer_state() +uint32_t rlc_am::get_buffer_state() { return tx_base->get_buffer_state(); } -void rlc_am_base::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) +void rlc_am::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) { tx_base->get_buffer_state(n_bytes_newtx, n_bytes_prio); return; } -uint32_t rlc_am_base::read_pdu(uint8_t* payload, uint32_t nof_bytes) +uint32_t rlc_am::read_pdu(uint8_t* payload, uint32_t nof_bytes) { uint32_t read_bytes = tx_base->read_pdu(payload, nof_bytes); @@ -127,7 +127,7 @@ uint32_t rlc_am_base::read_pdu(uint8_t* payload, uint32_t nof_bytes) return read_bytes; } -void rlc_am_base::write_pdu(uint8_t* payload, uint32_t nof_bytes) +void rlc_am::write_pdu(uint8_t* payload, uint32_t nof_bytes) { rx_base->write_pdu(payload, nof_bytes); @@ -139,7 +139,7 @@ void rlc_am_base::write_pdu(uint8_t* payload, uint32_t nof_bytes) /**************************************************************************** * Metrics ***************************************************************************/ -rlc_bearer_metrics_t rlc_am_base::get_metrics() +rlc_bearer_metrics_t rlc_am::get_metrics() { // update values that aren't calculated on the fly uint32_t latency = rx_base->get_sdu_rx_latency_ms(); @@ -151,7 +151,7 @@ rlc_bearer_metrics_t rlc_am_base::get_metrics() return metrics; } -void rlc_am_base::reset_metrics() +void rlc_am::reset_metrics() { std::lock_guard lock(metrics_mutex); metrics = {}; @@ -160,7 +160,7 @@ void rlc_am_base::reset_metrics() /**************************************************************************** * BSR callback ***************************************************************************/ -void rlc_am_base::set_bsr_callback(bsr_callback_t callback) +void rlc_am::set_bsr_callback(bsr_callback_t callback) { tx_base->set_bsr_callback(callback); } @@ -170,7 +170,7 @@ void rlc_am_base::set_bsr_callback(bsr_callback_t callback) * This class is used for common code between the * LTE and NR TX entitites *******************************************************/ -int rlc_am_base::rlc_am_base_tx::write_sdu(unique_byte_buffer_t sdu) +int rlc_am::rlc_am_base_tx::write_sdu(unique_byte_buffer_t sdu) { std::lock_guard lock(mutex); @@ -207,7 +207,7 @@ int rlc_am_base::rlc_am_base_tx::write_sdu(unique_byte_buffer_t sdu) return SRSRAN_SUCCESS; } -void rlc_am_base::rlc_am_base_tx::set_bsr_callback(bsr_callback_t callback) +void rlc_am::rlc_am_base_tx::set_bsr_callback(bsr_callback_t callback) { bsr_callback = callback; } @@ -217,7 +217,7 @@ void rlc_am_base::rlc_am_base_tx::set_bsr_callback(bsr_callback_t callback) * This class is used for common code between the * LTE and NR TX entitites *******************************************************/ -void rlc_am_base::rlc_am_base_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) +void rlc_am::rlc_am_base_rx::write_pdu(uint8_t* payload, const uint32_t nof_bytes) { if (nof_bytes < 1) { return; diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index 5e751d5c9..bdca5d751 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -37,13 +37,7 @@ rlc_am_lte::rlc_am_lte(srslog::basic_logger& logger, srsue::pdcp_interface_rlc* pdcp_, srsue::rrc_interface_rlc* rrc_, srsran::timer_handler* timers_) : - rlc_am_base(logger, - lcid_, - pdcp_, - rrc_, - timers_, - new rlc_am_lte::rlc_am_lte_tx(this), - new rlc_am_lte::rlc_am_lte_rx(this)) + rlc_am(logger, lcid_, pdcp_, rrc_, timers_, new rlc_am_lte::rlc_am_lte_tx(this), new rlc_am_lte::rlc_am_lte_rx(this)) {} /**************************************************************************** * Tx subclass implementation diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index c2f3dbde1..2d9592028 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -27,7 +27,7 @@ rlc_am_nr::rlc_am_nr(srslog::basic_logger& logger, srsue::pdcp_interface_rlc* pdcp_, srsue::rrc_interface_rlc* rrc_, srsran::timer_handler* timers_) : - rlc_am_base(logger, lcid_, pdcp_, rrc_, timers_, new rlc_am_nr::rlc_am_nr_tx(this), new rlc_am_nr::rlc_am_nr_rx(this)) + rlc_am(logger, lcid_, pdcp_, rrc_, timers_, new rlc_am_nr::rlc_am_nr_tx(this), new rlc_am_nr::rlc_am_nr_rx(this)) {} /*************************************************************************** From f99e841421b07d21eb397b59081e6670cd7285fb Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 10 Nov 2021 17:31:56 +0000 Subject: [PATCH 18/77] Changed using a rlc_am_lte and rlc_am_nr entity, to a single rlc_am entity. --- lib/include/srsran/rlc/rlc_am_base.h | 19 +- lib/include/srsran/rlc/rlc_am_lte.h | 326 +++++++++++++-------------- lib/include/srsran/rlc/rlc_am_nr.h | 154 ++++++------- lib/src/rlc/bearer_mem_pool.cc | 5 +- lib/src/rlc/rlc.cc | 2 +- lib/src/rlc/rlc_am_base.cc | 20 ++ lib/src/rlc/rlc_am_lte.cc | 122 +++++----- lib/src/rlc/rlc_am_nr.cc | 47 ++-- lib/test/rlc/rlc_am_lte_test.cc | 118 +++++----- lib/test/rlc/rlc_am_nr_test.cc | 6 +- 10 files changed, 402 insertions(+), 417 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index e76cd7523..9d11c64b0 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -43,20 +43,21 @@ bool rlc_am_is_control_pdu(byte_buffer_t* pdu); *******************************************************/ class rlc_am : public rlc_common { -protected: +public: class rlc_am_base_tx; class rlc_am_base_rx; -public: - rlc_am(srslog::basic_logger& logger, + friend class rlc_am_lte_tx; + friend class rlc_am_lte_rx; + friend class rlc_am_nr_tx; + friend class rlc_am_nr_rx; + + rlc_am(srsran_rat_t rat, + srslog::basic_logger& logger, uint32_t lcid_, srsue::pdcp_interface_rlc* pdcp_, srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_, - rlc_am_base_tx* tx_base_, - rlc_am_base_rx* rx_base_) : - logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_), tx_base(tx_base_), rx_base(rx_base_) - {} + srsran::timer_handler* timers_); bool configure(const rlc_config_t& cfg_) final; @@ -122,6 +123,7 @@ protected: * This class is used for common code between the * LTE and NR TX entitites *******************************************************/ +public: class rlc_am_base_tx { public: @@ -181,6 +183,7 @@ protected: rlc_am* parent = nullptr; }; +protected: std::unique_ptr tx_base = {}; std::unique_ptr rx_base = {}; }; diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index 99fd4ca04..82c42afd7 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -85,201 +85,191 @@ private: * RLC AM LTE entity * *****************************/ -class rlc_am_lte : public rlc_am + +/****************************** + * RLC AM LTE TX entity + *****************************/ +class rlc_am_lte_tx; +class rlc_am_lte_rx; +class rlc_am_lte_tx : public rlc_am::rlc_am_base_tx, timer_callback { public: - rlc_am_lte(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_); - - class rlc_am_lte_tx; - class rlc_am_lte_rx; - - /****************************** - * RLC AM LTE TX entity - *****************************/ - class rlc_am_lte_tx : public rlc_am_base_tx, timer_callback - { - public: - explicit rlc_am_lte_tx(rlc_am_lte* parent_); - ~rlc_am_lte_tx() = default; + explicit rlc_am_lte_tx(rlc_am* parent_); + ~rlc_am_lte_tx() = default; - bool configure(const rlc_config_t& cfg_); - void empty_queue(); - void reestablish(); - void stop(); + bool configure(const rlc_config_t& cfg_); + void empty_queue(); + void reestablish(); + 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(); + 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(); - uint32_t get_buffer_state(); - void get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio); + bool has_data(); + uint32_t get_buffer_state(); + void get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio); - void empty_queue_nolock(); - void debug_state(); + void empty_queue_nolock(); + void debug_state(); - // Timeout callback interface - void timer_expired(uint32_t timeout_id) final; + // Timeout callback interface + void timer_expired(uint32_t timeout_id) final; - // Interface for Rx subclass - void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes); + // Interface for Rx subclass + void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes); - private: - void stop_nolock(); +private: + void stop_nolock(); - 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_data_pdu(uint8_t* payload, uint32_t nof_bytes); - void update_notification_ack_info(uint32_t rlc_sn); + 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_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); - void retransmit_pdu(uint32_t sn); + int required_buffer_size(const rlc_amd_retx_t& retx); + void retransmit_pdu(uint32_t sn); - // Helpers - bool poll_required(); - bool do_status(); - void check_sn_reached_max_retx(uint32_t sn); - void get_buffer_state_nolock(uint32_t& new_tx, uint32_t& prio_tx); + // Helpers + bool poll_required(); + bool do_status(); + void check_sn_reached_max_retx(uint32_t sn); + void get_buffer_state_nolock(uint32_t& new_tx, uint32_t& prio_tx); - rlc_am_lte* parent = nullptr; - rlc_am_lte_rx* rx = nullptr; - byte_buffer_pool* pool = nullptr; - rlc_am_pdu_segment_pool segment_pool; + rlc_am* parent = nullptr; + rlc_am_lte_rx* rx = nullptr; + byte_buffer_pool* pool = nullptr; + rlc_am_pdu_segment_pool segment_pool; - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ - rlc_am_config_t cfg = {}; + rlc_am_config_t cfg = {}; - // TX SDU buffers - unique_byte_buffer_t tx_sdu; + // TX SDU buffers + unique_byte_buffer_t tx_sdu; - /**************************************************************************** - * State variables and counters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + /**************************************************************************** + * State variables and counters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ - // Tx state variables - uint32_t vt_a = 0; // ACK state. SN of next PDU in sequence to be ACKed. Low edge of tx window. - uint32_t vt_ms = RLC_AM_WINDOW_SIZE; // Max send state. High edge of tx window. vt_a + window_size. - uint32_t vt_s = 0; // Send state. SN to be assigned for next PDU. - uint32_t poll_sn = 0; // Poll send state. SN of most recent PDU txed with poll bit set. + // Tx state variables + uint32_t vt_a = 0; // ACK state. SN of next PDU in sequence to be ACKed. Low edge of tx window. + uint32_t vt_ms = RLC_AM_WINDOW_SIZE; // Max send state. High edge of tx window. vt_a + window_size. + uint32_t vt_s = 0; // Send state. SN to be assigned for next PDU. + uint32_t poll_sn = 0; // Poll send state. SN of most recent PDU txed with poll bit set. - // Tx counters - uint32_t pdu_without_poll = 0; - uint32_t byte_without_poll = 0; + // Tx counters + uint32_t pdu_without_poll = 0; + uint32_t byte_without_poll = 0; - rlc_status_pdu_t tx_status; + rlc_status_pdu_t tx_status; - /**************************************************************************** - * Timers - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ + /**************************************************************************** + * Timers + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ - srsran::timer_handler::unique_timer poll_retx_timer; - srsran::timer_handler::unique_timer status_prohibit_timer; + srsran::timer_handler::unique_timer poll_retx_timer; + srsran::timer_handler::unique_timer status_prohibit_timer; - // SDU info for PDCP notifications - buffered_pdcp_pdu_list undelivered_sdu_info_queue; + // SDU info for PDCP notifications + buffered_pdcp_pdu_list undelivered_sdu_info_queue; - // Tx windows - rlc_ringbuffer_t, RLC_AM_WINDOW_SIZE> tx_window; - pdu_retx_queue retx_queue; - pdcp_sn_vector_t notify_info_vec; + // Tx windows + rlc_ringbuffer_t, RLC_AM_WINDOW_SIZE> tx_window; + pdu_retx_queue retx_queue; + pdcp_sn_vector_t notify_info_vec; - // Mutexes - std::mutex mutex; + // Mutexes + std::mutex mutex; - // default to RLC SDU queue length - const uint32_t MAX_SDUS_PER_RLC_PDU = RLC_TX_QUEUE_LEN; - }; + // default to RLC SDU queue length + const uint32_t MAX_SDUS_PER_RLC_PDU = RLC_TX_QUEUE_LEN; +}; - /****************************** - * RLC AM LTE RX entity - *****************************/ - class rlc_am_lte_rx : public rlc_am_base_rx, public timer_callback - { - public: - rlc_am_lte_rx(rlc_am_lte* parent_); - ~rlc_am_lte_rx(); - - bool configure(const rlc_config_t& cfg_) final; - void reestablish() final; - void stop() final; - - uint32_t get_rx_buffered_bytes() final; // returns sum of PDUs in rx_window - uint32_t get_sdu_rx_latency_ms() final; - - // Timeout callback interface - void timer_expired(uint32_t timeout_id) final; - - // Functions needed by Tx subclass to query rx state - int get_status_pdu_length(); - int get_status_pdu(rlc_status_pdu_t* status, uint32_t nof_bytes); - bool get_do_status(); - - private: - void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) final; - void handle_data_pdu_full(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); - void handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); - void reassemble_rx_sdus(); - bool inside_rx_window(const int16_t sn); - void debug_state(); - void print_rx_segments(); - bool add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment); - void reset_status(); - - rlc_am_lte* parent = nullptr; - rlc_am_lte_tx* tx = nullptr; - byte_buffer_pool* pool = nullptr; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - rlc_am_config_t cfg = {}; - - // RX SDU buffers - unique_byte_buffer_t rx_sdu; - - /**************************************************************************** - * State variables and counters - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - - // Rx state variables - uint32_t vr_r = 0; // Receive state. SN following last in-sequence received PDU. Low edge of rx window - uint32_t vr_mr = RLC_AM_WINDOW_SIZE; // Max acceptable receive state. High edge of rx window. vr_r + window size. - uint32_t vr_x = 0; // t_reordering state. SN following PDU which triggered t_reordering. - uint32_t vr_ms = 0; // Max status tx state. Highest possible value of SN for ACK_SN in status PDU. - uint32_t vr_h = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs. - - // Mutex to protect members - std::mutex mutex; - - // Rx windows - rlc_ringbuffer_t rx_window; - std::map rx_segments; - - bool poll_received = false; - std::atomic do_status = {false}; // light-weight access from Tx entity - - /**************************************************************************** - * Timers - * Ref: 3GPP TS 36.322 v10.0.0 Section 7 - ***************************************************************************/ - - srsran::timer_handler::unique_timer reordering_timer; - - srsran::rolling_average sdu_rx_latency_ms; - }; +/****************************** + * RLC AM LTE RX entity + *****************************/ +class rlc_am_lte_rx : public rlc_am::rlc_am_base_rx, public timer_callback +{ +public: + rlc_am_lte_rx(rlc_am* parent_); + ~rlc_am_lte_rx(); + + bool configure(const rlc_config_t& cfg_) final; + void reestablish() final; + void stop() final; + + uint32_t get_rx_buffered_bytes() final; // returns sum of PDUs in rx_window + uint32_t get_sdu_rx_latency_ms() final; + + // Timeout callback interface + void timer_expired(uint32_t timeout_id) final; + + // Functions needed by Tx subclass to query rx state + int get_status_pdu_length(); + int get_status_pdu(rlc_status_pdu_t* status, uint32_t nof_bytes); + bool get_do_status(); + +private: + void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) final; + void handle_data_pdu_full(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); + void handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header); + void reassemble_rx_sdus(); + bool inside_rx_window(const int16_t sn); + void debug_state(); + void print_rx_segments(); + bool add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment); + void reset_status(); + + rlc_am* parent = nullptr; + rlc_am_lte_tx* tx = nullptr; + byte_buffer_pool* pool = nullptr; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + rlc_am_config_t cfg = {}; + + // RX SDU buffers + unique_byte_buffer_t rx_sdu; + + /**************************************************************************** + * State variables and counters + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + + // Rx state variables + uint32_t vr_r = 0; // Receive state. SN following last in-sequence received PDU. Low edge of rx window + uint32_t vr_mr = RLC_AM_WINDOW_SIZE; // Max acceptable receive state. High edge of rx window. vr_r + window size. + uint32_t vr_x = 0; // t_reordering state. SN following PDU which triggered t_reordering. + uint32_t vr_ms = 0; // Max status tx state. Highest possible value of SN for ACK_SN in status PDU. + uint32_t vr_h = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs. + + // Mutex to protect members + std::mutex mutex; + + // Rx windows + rlc_ringbuffer_t rx_window; + std::map rx_segments; + + bool poll_received = false; + std::atomic do_status = {false}; // light-weight access from Tx entity + + /**************************************************************************** + * Timers + * Ref: 3GPP TS 36.322 v10.0.0 Section 7 + ***************************************************************************/ + + srsran::timer_handler::unique_timer reordering_timer; + + srsran::rolling_average sdu_rx_latency_ms; }; } // namespace srsran diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index 31316bf8e..ef1dae4fa 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -32,94 +32,80 @@ namespace srsran { * RLC AM NR entity * *****************************/ -class rlc_am_nr : public rlc_am +// Transmitter sub-class +class rlc_am_nr_tx : public rlc_am::rlc_am_base_tx { public: - rlc_am_nr(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_); - - // Transmitter sub-class - class rlc_am_nr_tx : public rlc_am_base_tx - { - public: - explicit rlc_am_nr_tx(rlc_am_nr* parent_); - ~rlc_am_nr_tx() = default; - - bool configure(const rlc_config_t& cfg_) final; - 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; - - int write_sdu(unique_byte_buffer_t sdu); - void empty_queue() final; - bool has_data() final; - uint32_t get_buffer_state() final; - void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); - - void stop() final; - - private: - rlc_am_nr* parent = nullptr; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 - ***************************************************************************/ - rlc_am_config_t cfg = {}; - - /**************************************************************************** - * Tx state variables - * Ref: 3GPP TS 38.322 v10.0.0 Section 7.1 - ***************************************************************************/ - struct rlc_nr_tx_state_t { - uint32_t tx_next_ack; - uint32_t tx_next; - uint32_t poll_sn; - uint32_t pdu_without_poll; - uint32_t byte_without_poll; - } st = {}; - - using rlc_amd_tx_pdu_nr = rlc_amd_tx_pdu; - rlc_ringbuffer_t tx_window; - }; - - // Receiver sub-class - class rlc_am_nr_rx : public rlc_am_base_rx - { - public: - explicit rlc_am_nr_rx(rlc_am_nr* parent_); - ~rlc_am_nr_rx() = default; - - bool configure(const rlc_config_t& cfg_) final; - - void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) final; - - void stop(); - void reestablish(); - - uint32_t get_sdu_rx_latency_ms(); - uint32_t get_rx_buffered_bytes(); - - private: - rlc_am_nr* parent = nullptr; - byte_buffer_pool* pool = nullptr; - - /**************************************************************************** - * Configurable parameters - * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 - ***************************************************************************/ - rlc_am_config_t cfg = {}; - }; + explicit rlc_am_nr_tx(rlc_am* parent_); + ~rlc_am_nr_tx() = default; + + bool configure(const rlc_config_t& cfg_) final; + 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; + + int write_sdu(unique_byte_buffer_t sdu); + void empty_queue() final; + bool has_data() final; + uint32_t get_buffer_state() final; + void get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue); + + void stop() final; + +private: + rlc_am* parent = nullptr; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 + ***************************************************************************/ + rlc_am_config_t cfg = {}; + + /**************************************************************************** + * Tx state variables + * Ref: 3GPP TS 38.322 v10.0.0 Section 7.1 + ***************************************************************************/ + struct rlc_nr_tx_state_t { + uint32_t tx_next_ack; + uint32_t tx_next; + uint32_t poll_sn; + uint32_t pdu_without_poll; + uint32_t byte_without_poll; + } st = {}; + + using rlc_amd_tx_pdu_nr = rlc_amd_tx_pdu; + rlc_ringbuffer_t tx_window; +}; + +// Receiver sub-class +class rlc_am_nr_rx : public rlc_am::rlc_am_base_rx +{ +public: + explicit rlc_am_nr_rx(rlc_am* parent_); + ~rlc_am_nr_rx() = default; + + bool configure(const rlc_config_t& cfg_) final; + + void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) final; + + void stop(); + void reestablish(); + + uint32_t get_sdu_rx_latency_ms(); + uint32_t get_rx_buffered_bytes(); private: - rlc_am_nr_tx* tx = nullptr; - rlc_am_nr_rx* rx = nullptr; + rlc_am* parent = nullptr; + byte_buffer_pool* pool = nullptr; + + /**************************************************************************** + * Configurable parameters + * Ref: 3GPP TS 38.322 v10.0.0 Section 7.4 + ***************************************************************************/ + rlc_am_config_t cfg = {}; }; } // namespace srsran diff --git a/lib/src/rlc/bearer_mem_pool.cc b/lib/src/rlc/bearer_mem_pool.cc index 1c6fc9aeb..e23feda6f 100644 --- a/lib/src/rlc/bearer_mem_pool.cc +++ b/lib/src/rlc/bearer_mem_pool.cc @@ -24,9 +24,8 @@ srsran::background_mem_pool* get_bearer_pool() { static background_mem_pool pool( 4, - std::max( - std::max(std::max(std::max(sizeof(rlc_am_lte), sizeof(rlc_am_nr)), sizeof(rlc_um_lte)), sizeof(rlc_um_nr)), - sizeof(rlc_tm)), + std::max(std::max(std::max(std::max(sizeof(rlc_am), sizeof(rlc_am)), sizeof(rlc_um_lte)), sizeof(rlc_um_nr)), + sizeof(rlc_tm)), 8, 8); return &pool; diff --git a/lib/src/rlc/rlc.cc b/lib/src/rlc/rlc.cc index f0243882e..46ce05ee8 100644 --- a/lib/src/rlc/rlc.cc +++ b/lib/src/rlc/rlc.cc @@ -396,7 +396,7 @@ int rlc::add_bearer(uint32_t lcid, const rlc_config_t& cnfg) case rlc_mode_t::am: switch (cnfg.rat) { case srsran_rat_t::lte: - rlc_entity = std::unique_ptr(new rlc_am_lte(logger, lcid, pdcp, rrc, timers)); + rlc_entity = std::unique_ptr(new rlc_am(cnfg.rat, logger, lcid, pdcp, rrc, timers)); break; default: logger.error("AM not supported for this RAT"); diff --git a/lib/src/rlc/rlc_am_base.cc b/lib/src/rlc/rlc_am_base.cc index a8f0ba8ad..da339d39e 100644 --- a/lib/src/rlc/rlc_am_base.cc +++ b/lib/src/rlc/rlc_am_base.cc @@ -11,6 +11,8 @@ */ #include "srsran/rlc/rlc_am_base.h" +#include "srsran/rlc/rlc_am_lte.h" +#include "srsran/rlc/rlc_am_nr.h" #include namespace srsran { @@ -30,6 +32,24 @@ bool rlc_am_is_control_pdu(byte_buffer_t* pdu) * This entity is common between LTE and NR * and only the TX/RX entities change between them *******************************************************/ +rlc_am::rlc_am(srsran_rat_t rat, + srslog::basic_logger& logger, + uint32_t lcid_, + srsue::pdcp_interface_rlc* pdcp_, + srsue::rrc_interface_rlc* rrc_, + srsran::timer_handler* timers_) : + logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_) +{ + if (rat == srsran_rat_t::lte) { + tx_base = std::unique_ptr(new rlc_am_lte_tx(this)); + rx_base = std::unique_ptr(new rlc_am_lte_rx(this)); + } else if (rat == srsran_rat_t::nr) { + tx_base = std::unique_ptr(new rlc_am_nr_tx(this)); + rx_base = std::unique_ptr(new rlc_am_nr_rx(this)); + } else { + logger.error("Invalid RAT at entity initialization"); + } +} bool rlc_am::configure(const rlc_config_t& cfg_) { // determine bearer name and configure Rx/Tx objects diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index bdca5d751..8565f0603 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -29,29 +29,20 @@ using pdcp_pdu_info_lte = pdcp_pdu_info; using rlc_amd_tx_pdu_lte = rlc_amd_tx_pdu; using rlc_am_pdu_segment = rlc_am_pdu_segment_pool::segment_resource; -/**************************************************************************** - * RLC AM LTE entity - ***************************************************************************/ -rlc_am_lte::rlc_am_lte(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_) : - rlc_am(logger, lcid_, pdcp_, rrc_, timers_, new rlc_am_lte::rlc_am_lte_tx(this), new rlc_am_lte::rlc_am_lte_rx(this)) -{} /**************************************************************************** * Tx subclass implementation ***************************************************************************/ -rlc_am_lte::rlc_am_lte_tx::rlc_am_lte_tx(rlc_am_lte* parent_) : +rlc_am_lte_tx::rlc_am_lte_tx(rlc_am* parent_) : parent(parent_), - rx(dynamic_cast(parent->rx_base.get())), pool(byte_buffer_pool::get_instance()), poll_retx_timer(parent_->timers->get_unique_timer()), status_prohibit_timer(parent_->timers->get_unique_timer()), rlc_am_base_tx(&parent_->logger) -{} +{ + rx = dynamic_cast(parent->rx_base.get()); +} -bool rlc_am_lte::rlc_am_lte_tx::configure(const rlc_config_t& cfg_) +bool rlc_am_lte_tx::configure(const rlc_config_t& cfg_) { std::lock_guard lock(mutex); if (cfg_.tx_queue_length > MAX_SDUS_PER_RLC_PDU) { @@ -89,13 +80,13 @@ bool rlc_am_lte::rlc_am_lte_tx::configure(const rlc_config_t& cfg_) return true; } -void rlc_am_lte::rlc_am_lte_tx::stop() +void rlc_am_lte_tx::stop() { std::lock_guard lock(mutex); stop_nolock(); } -void rlc_am_lte::rlc_am_lte_tx::stop_nolock() +void rlc_am_lte_tx::stop_nolock() { empty_queue_nolock(); @@ -127,13 +118,13 @@ void rlc_am_lte::rlc_am_lte_tx::stop_nolock() undelivered_sdu_info_queue.clear(); } -void rlc_am_lte::rlc_am_lte_tx::empty_queue() +void rlc_am_lte_tx::empty_queue() { std::lock_guard lock(mutex); empty_queue_nolock(); } -void rlc_am_lte::rlc_am_lte_tx::empty_queue_nolock() +void rlc_am_lte_tx::empty_queue_nolock() { // deallocate all SDUs in transmit queue while (tx_sdu_queue.size() > 0) { @@ -147,20 +138,20 @@ void rlc_am_lte::rlc_am_lte_tx::empty_queue_nolock() tx_sdu.reset(); } -void rlc_am_lte::rlc_am_lte_tx::reestablish() +void rlc_am_lte_tx::reestablish() { std::lock_guard lock(mutex); stop_nolock(); tx_enabled = true; } -bool rlc_am_lte::rlc_am_lte_tx::do_status() +bool rlc_am_lte_tx::do_status() { return rx->get_do_status(); } // Function is supposed to return as fast as possible -bool rlc_am_lte::rlc_am_lte_tx::has_data() +bool rlc_am_lte_tx::has_data() { return (((do_status() && not status_prohibit_timer.is_running())) || // if we have a status PDU to transmit (not retx_queue.empty()) || // if we have a retransmission @@ -177,7 +168,7 @@ bool rlc_am_lte::rlc_am_lte_tx::has_data() * * @param sn The SN of the PDU to check */ -void rlc_am_lte::rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) +void rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) { if (tx_window[sn].retx_count == cfg.max_retx_thresh) { logger->warning("%s Signaling max number of reTx=%d for SN=%d", RB_NAME, tx_window[sn].retx_count, sn); @@ -193,25 +184,29 @@ void rlc_am_lte::rlc_am_lte_tx::check_sn_reached_max_retx(uint32_t sn) } } -uint32_t rlc_am_lte::rlc_am_lte_tx::get_buffer_state() +uint32_t rlc_am_lte_tx::get_buffer_state() { uint32_t new_tx_queue = 0, prio_tx_queue = 0; get_buffer_state(new_tx_queue, prio_tx_queue); return new_tx_queue + prio_tx_queue; } -void rlc_am_lte::rlc_am_lte_tx::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) +void rlc_am_lte_tx::get_buffer_state(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) { std::lock_guard lock(mutex); get_buffer_state_nolock(n_bytes_newtx, n_bytes_prio); } -void rlc_am_lte::rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) +void rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, uint32_t& n_bytes_prio) { n_bytes_newtx = 0; n_bytes_prio = 0; uint32_t n_sdus = 0; + if (not tx_enabled) { + return; + } + logger->debug("%s Buffer state - do_status=%s, status_prohibit_running=%s (%d/%d)", parent->rb_name, do_status() ? "yes" : "no", @@ -273,7 +268,7 @@ void rlc_am_lte::rlc_am_lte_tx::get_buffer_state_nolock(uint32_t& n_bytes_newtx, } } -void rlc_am_lte::rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) +void rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) { if (!tx_enabled) { return; @@ -291,12 +286,12 @@ void rlc_am_lte::rlc_am_lte_tx::discard_sdu(uint32_t discard_sn) logger->info("%s PDU with PDCP_SN=%d", discarded ? "Discarding" : "Couldn't discard", discard_sn); } -bool rlc_am_lte::rlc_am_lte_tx::sdu_queue_is_full() +bool rlc_am_lte_tx::sdu_queue_is_full() { return tx_sdu_queue.is_full(); } -uint32_t rlc_am_lte::rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) +uint32_t rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) { std::lock_guard lock(mutex); @@ -334,7 +329,7 @@ uint32_t rlc_am_lte::rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_byte return build_data_pdu(payload, nof_bytes); } -void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) +void rlc_am_lte_tx::timer_expired(uint32_t timeout_id) { std::unique_lock lock(mutex); if (poll_retx_timer.is_valid() && poll_retx_timer.id() == timeout_id) { @@ -355,7 +350,7 @@ void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) } } -void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) +void rlc_am_lte_tx::retransmit_pdu(uint32_t sn) { if (tx_window.empty()) { logger->warning("%s No PDU to retransmit", RB_NAME); @@ -395,7 +390,7 @@ void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) * * @return True if a status PDU needs to be requested, false otherwise. */ -bool rlc_am_lte::rlc_am_lte_tx::poll_required() +bool rlc_am_lte_tx::poll_required() { if (cfg.poll_pdu > 0 && pdu_without_poll > static_cast(cfg.poll_pdu)) { return true; @@ -423,14 +418,14 @@ bool rlc_am_lte::rlc_am_lte_tx::poll_required() * However, it seems more appropiate to request more often if polling * is disabled otherwise, e.g. every N PDUs. */ - if (cfg.poll_pdu == 0 && cfg.poll_byte == 0 && vt_s % rlc_am_lte::poll_periodicity == 0) { + if (cfg.poll_pdu == 0 && cfg.poll_byte == 0 && vt_s % rlc_am::poll_periodicity == 0) { return true; } return false; } -int rlc_am_lte::rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_bytes) { logger->debug("%s Generating status PDU. Nof bytes %d", RB_NAME, nof_bytes); int pdu_len = rx->get_status_pdu(&tx_status, nof_bytes); @@ -453,7 +448,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_status_pdu(uint8_t* payload, uint32_t nof_b return pdu_len; } -int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte_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()) { @@ -534,7 +529,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_byt return (ptr - payload) + tx_window[retx.sn].buf->N_bytes; } -int rlc_am_lte::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_t retx) { if (tx_window[retx.sn].buf == NULL) { logger->error("In build_segment: retx.sn=%d has null buffer", retx.sn); @@ -709,7 +704,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_byte return pdu_len; } -int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) +int rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes) { if (tx_sdu == NULL && tx_sdu_queue.is_empty()) { logger->info("No data available to be sent"); @@ -925,7 +920,7 @@ int rlc_am_lte::rlc_am_lte_tx::build_data_pdu(uint8_t* payload, uint32_t nof_byt return total_len; } -void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) +void rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) { if (not tx_enabled) { return; @@ -1073,7 +1068,7 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no * @tx_pdu: RLC PDU that was ack'ed. * @notify_info_vec: Vector which will keep track of the PDCP PDU SNs that have been fully ack'ed. */ -void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) +void rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) { logger->debug("Updating ACK info: RLC SN=%d, number of notified SDU=%ld, number of undelivered SDUs=%ld", rlc_sn, @@ -1110,12 +1105,12 @@ void rlc_am_lte::rlc_am_lte_tx::update_notification_ack_info(uint32_t rlc_sn) } } -void rlc_am_lte::rlc_am_lte_tx::debug_state() +void rlc_am_lte_tx::debug_state() { logger->debug("%s vt_a = %d, vt_ms = %d, vt_s = %d, poll_sn = %d", RB_NAME, vt_a, vt_ms, vt_s, poll_sn); } -int rlc_am_lte::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_t& retx) { if (!retx.is_segment) { if (tx_window.has_sn(retx.sn)) { @@ -1191,17 +1186,18 @@ int rlc_am_lte::rlc_am_lte_tx::required_buffer_size(const rlc_amd_retx_t& retx) /**************************************************************************** * Rx subclass implementation ***************************************************************************/ - -rlc_am_lte::rlc_am_lte_rx::rlc_am_lte_rx(rlc_am_lte* parent_) : +rlc_am_lte_rx::rlc_am_lte_rx(rlc_am* parent_) : parent(parent_), pool(byte_buffer_pool::get_instance()), reordering_timer(parent_->timers->get_unique_timer()), rlc_am_base_rx(parent_, &parent_->logger) -{} +{ + tx = dynamic_cast(parent->tx_base.get()); +} -rlc_am_lte::rlc_am_lte_rx::~rlc_am_lte_rx() {} +rlc_am_lte_rx::~rlc_am_lte_rx() {} -bool rlc_am_lte::rlc_am_lte_rx::configure(const rlc_config_t& cfg_) +bool rlc_am_lte_rx::configure(const rlc_config_t& cfg_) { // TODO: add config checks cfg = cfg_.am; @@ -1220,12 +1216,12 @@ bool rlc_am_lte::rlc_am_lte_rx::configure(const rlc_config_t& cfg_) return true; } -void rlc_am_lte::rlc_am_lte_rx::reestablish() +void rlc_am_lte_rx::reestablish() { stop(); } -void rlc_am_lte::rlc_am_lte_rx::stop() +void rlc_am_lte_rx::stop() { std::lock_guard lock(mutex); @@ -1256,7 +1252,7 @@ void rlc_am_lte::rlc_am_lte_rx::stop() * @param payload Pointer to payload * @param nof_bytes Payload length */ -void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) +void rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) { std::lock_guard lock(mutex); @@ -1280,7 +1276,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_b * @param nof_bytes Payload length * @param header Reference to PDU header (unpacked by caller) */ -void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_full(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) +void rlc_am_lte_rx::handle_data_pdu_full(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) { std::map::iterator it; @@ -1397,9 +1393,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_full(uint8_t* payload, uint32_t debug_state(); } -void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* payload, - uint32_t nof_bytes, - rlc_amd_pdu_header_t& header) +void rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_pdu_header_t& header) { std::map::iterator it; @@ -1487,7 +1481,7 @@ void rlc_am_lte::rlc_am_lte_rx::handle_data_pdu_segment(uint8_t* pa debug_state(); } -void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() +void rlc_am_lte_rx::reassemble_rx_sdus() { uint32_t len = 0; if (rx_sdu == NULL) { @@ -1638,24 +1632,24 @@ void rlc_am_lte::rlc_am_lte_rx::reassemble_rx_sdus() } } -void rlc_am_lte::rlc_am_lte_rx::reset_status() +void rlc_am_lte_rx::reset_status() { do_status = false; poll_received = false; } -bool rlc_am_lte::rlc_am_lte_rx::get_do_status() +bool rlc_am_lte_rx::get_do_status() { return do_status.load(std::memory_order_relaxed); } -uint32_t rlc_am_lte::rlc_am_lte_rx::get_rx_buffered_bytes() +uint32_t rlc_am_lte_rx::get_rx_buffered_bytes() { std::lock_guard lock(mutex); return rx_window.get_buffered_bytes(); } -uint32_t rlc_am_lte::rlc_am_lte_rx::get_sdu_rx_latency_ms() +uint32_t rlc_am_lte_rx::get_sdu_rx_latency_ms() { std::lock_guard lock(mutex); return sdu_rx_latency_ms.value(); @@ -1666,7 +1660,7 @@ uint32_t rlc_am_lte::rlc_am_lte_rx::get_sdu_rx_latency_ms() * * @param timeout_id */ -void rlc_am_lte::rlc_am_lte_rx::timer_expired(uint32_t timeout_id) +void rlc_am_lte_rx::timer_expired(uint32_t timeout_id) { std::lock_guard lock(mutex); if (reordering_timer.is_valid() and reordering_timer.id() == timeout_id) { @@ -1693,7 +1687,7 @@ void rlc_am_lte::rlc_am_lte_rx::timer_expired(uint32_t timeout_id) // Called from Tx object to pack status PDU that doesn't exceed a given size // If lock-acquisition fails, return -1. Otherwise it returns the length of the generated PDU. -int rlc_am_lte::rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const uint32_t max_pdu_size) +int rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const uint32_t max_pdu_size) { std::unique_lock lock(mutex, std::try_to_lock); if (not lock.owns_lock()) { @@ -1747,7 +1741,7 @@ int rlc_am_lte::rlc_am_lte_rx::get_status_pdu(rlc_status_pdu_t* status, const ui } // Called from Tx object to obtain length of the full status PDU -int rlc_am_lte::rlc_am_lte_rx::get_status_pdu_length() +int rlc_am_lte_rx::get_status_pdu_length() { std::unique_lock lock(mutex, std::try_to_lock); if (not lock.owns_lock()) { @@ -1765,7 +1759,7 @@ int rlc_am_lte::rlc_am_lte_rx::get_status_pdu_length() return rlc_am_packed_length(&status); } -void rlc_am_lte::rlc_am_lte_rx::print_rx_segments() +void rlc_am_lte_rx::print_rx_segments() { std::map::iterator it; std::stringstream ss; @@ -1781,7 +1775,7 @@ void rlc_am_lte::rlc_am_lte_rx::print_rx_segments() } // NOTE: Preference would be to capture by value, and then move; but header is stack allocated -bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment) +bool rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* pdu, rlc_amd_rx_pdu* segment) { // Find segment insertion point in the list of segments auto it1 = pdu->segments.begin(); @@ -1961,7 +1955,7 @@ bool rlc_am_lte::rlc_am_lte_rx::add_segment_and_check(rlc_amd_rx_pdu_segments_t* return true; } -bool rlc_am_lte::rlc_am_lte_rx::inside_rx_window(const int16_t sn) +bool rlc_am_lte_rx::inside_rx_window(const int16_t sn) { if (RX_MOD_BASE(sn) >= RX_MOD_BASE(static_cast(vr_r)) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_mr)) { return true; @@ -1970,7 +1964,7 @@ bool rlc_am_lte::rlc_am_lte_rx::inside_rx_window(const int16_t sn) } } -void rlc_am_lte::rlc_am_lte_rx::debug_state() +void rlc_am_lte_rx::debug_state() { logger->debug("%s vr_r = %d, vr_mr = %d, vr_x = %d, vr_ms = %d, vr_h = %d", RB_NAME, vr_r, vr_mr, vr_x, vr_ms, vr_h); } diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 2d9592028..0e0481fa2 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -22,24 +22,17 @@ namespace srsran { /**************************************************************************** * RLC AM NR entity ***************************************************************************/ -rlc_am_nr::rlc_am_nr(srslog::basic_logger& logger, - uint32_t lcid_, - srsue::pdcp_interface_rlc* pdcp_, - srsue::rrc_interface_rlc* rrc_, - srsran::timer_handler* timers_) : - rlc_am(logger, lcid_, pdcp_, rrc_, timers_, new rlc_am_nr::rlc_am_nr_tx(this), new rlc_am_nr::rlc_am_nr_rx(this)) -{} /*************************************************************************** * Tx subclass implementation ***************************************************************************/ -rlc_am_nr::rlc_am_nr_tx::rlc_am_nr_tx(rlc_am_nr* 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) { parent->logger.debug("Initializing RLC AM NR TX: Tx_Next: %d", st.tx_next); // Temporarly silence unused variable warning } -bool rlc_am_nr::rlc_am_nr_tx::configure(const rlc_config_t& cfg_) +bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_) { /* if (cfg_.tx_queue_length > MAX_SDUS_PER_RLC_PDU) { @@ -54,77 +47,77 @@ bool rlc_am_nr::rlc_am_nr_tx::configure(const rlc_config_t& cfg_) return true; } -bool rlc_am_nr::rlc_am_nr_tx::has_data() +bool rlc_am_nr_tx::has_data() { return true; } -uint32_t rlc_am_nr::rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) +uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) { return 0; } -void rlc_am_nr::rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) {} +void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) {} -uint32_t rlc_am_nr::rlc_am_nr_tx::get_buffer_state() +uint32_t rlc_am_nr_tx::get_buffer_state() { return 0; } -void rlc_am_nr::rlc_am_nr_tx::get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) {} +void rlc_am_nr_tx::get_buffer_state(uint32_t& tx_queue, uint32_t& prio_tx_queue) {} -int rlc_am_nr::rlc_am_nr_tx::write_sdu(unique_byte_buffer_t sdu) +int rlc_am_nr_tx::write_sdu(unique_byte_buffer_t sdu) { return 0; } -void rlc_am_nr::rlc_am_nr_tx::reestablish() +void rlc_am_nr_tx::reestablish() { stop(); } -void rlc_am_nr::rlc_am_nr_tx::discard_sdu(uint32_t discard_sn) {} +void rlc_am_nr_tx::discard_sdu(uint32_t discard_sn) {} -bool rlc_am_nr::rlc_am_nr_tx::sdu_queue_is_full() +bool rlc_am_nr_tx::sdu_queue_is_full() { return false; } -void rlc_am_nr::rlc_am_nr_tx::empty_queue() {} +void rlc_am_nr_tx::empty_queue() {} -void rlc_am_nr::rlc_am_nr_tx::stop() {} +void rlc_am_nr_tx::stop() {} /**************************************************************************** * Rx subclass implementation ***************************************************************************/ -rlc_am_nr::rlc_am_nr_rx::rlc_am_nr_rx(rlc_am_nr* parent_) : +rlc_am_nr_rx::rlc_am_nr_rx(rlc_am* parent_) : parent(parent_), pool(byte_buffer_pool::get_instance()), rlc_am_base_rx(parent_, &parent_->logger) { parent->logger.debug("Initializing RLC AM NR RX"); // Temporarly silence unused variable warning } -bool rlc_am_nr::rlc_am_nr_rx::configure(const rlc_config_t& cfg_) +bool rlc_am_nr_rx::configure(const rlc_config_t& cfg_) { cfg = cfg_.am; return true; } -void rlc_am_nr::rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) {} +void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) {} -void rlc_am_nr::rlc_am_nr_rx::stop() {} +void rlc_am_nr_rx::stop() {} -void rlc_am_nr::rlc_am_nr_rx::reestablish() +void rlc_am_nr_rx::reestablish() { stop(); } -uint32_t rlc_am_nr::rlc_am_nr_rx::get_sdu_rx_latency_ms() +uint32_t rlc_am_nr_rx::get_sdu_rx_latency_ms() { return 0; } -uint32_t rlc_am_nr::rlc_am_nr_rx::get_rx_buffered_bytes() +uint32_t rlc_am_nr_rx::get_rx_buffered_bytes() { return 0; } diff --git a/lib/test/rlc/rlc_am_lte_test.cc b/lib/test/rlc/rlc_am_lte_test.cc index d7dbc71af..c2f14d3a8 100644 --- a/lib/test/rlc/rlc_am_lte_test.cc +++ b/lib/test/rlc/rlc_am_lte_test.cc @@ -29,7 +29,7 @@ using namespace srsran; class ul_writer : public thread { public: - ul_writer(rlc_am_lte* rlc_) : rlc(rlc_), thread("UL_WRITER") {} + ul_writer(rlc_am* rlc_) : rlc(rlc_), thread("UL_WRITER") {} ~ul_writer() { stop(); } void stop() { @@ -65,11 +65,11 @@ private: running = false; } - rlc_am_lte* rlc = nullptr; + rlc_am* rlc = nullptr; std::atomic running = {false}; }; -int basic_test_tx(rlc_am_lte* rlc, byte_buffer_t pdu_bufs[NBUFS]) +int basic_test_tx(rlc_am* rlc, byte_buffer_t pdu_bufs[NBUFS]) { // Push 5 SDUs into RLC1 unique_byte_buffer_t sdu_bufs[NBUFS]; @@ -100,8 +100,8 @@ int basic_test() timer_handler timers(8); byte_buffer_t pdu_bufs[NBUFS]; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); // before configuring entity TESTASSERT(0 == rlc1.get_buffer_state()); @@ -158,8 +158,8 @@ int concat_test() rlc_am_tester tester; srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -233,8 +233,8 @@ int segment_test(bool in_seq_rx) srsran::timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -328,8 +328,8 @@ int retx_test() timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -464,7 +464,7 @@ int max_retx_test() timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); const rlc_config_t rlc_cfg = rlc_config_t::default_rlc_am_config(); if (not rlc1.configure(rlc_cfg)) { @@ -527,8 +527,8 @@ int segment_retx_test() timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -652,8 +652,8 @@ int resegment_test_1() timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -809,8 +809,8 @@ int resegment_test_2() rlc_am_tester tester; timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -941,8 +941,8 @@ int resegment_test_3() rlc_am_tester tester; srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -1071,8 +1071,8 @@ int resegment_test_4() rlc_am_tester tester; srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -1203,8 +1203,8 @@ int resegment_test_5() rlc_am_tester tester; srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -1330,8 +1330,8 @@ int resegment_test_6() srsran::timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -1499,8 +1499,8 @@ int resegment_test_7() #endif srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -1684,8 +1684,8 @@ int resegment_test_8() #endif srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -1836,8 +1836,8 @@ int resegment_test_9() #endif srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(config)) { return SRSRAN_ERROR; @@ -1978,8 +1978,8 @@ int resegment_test_10() #endif srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(config)) { return SRSRAN_ERROR; @@ -2127,8 +2127,8 @@ int resegment_test_11() #endif srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(config)) { return SRSRAN_ERROR; @@ -2285,8 +2285,8 @@ int resegment_test_12() #endif srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(config)) { return SRSRAN_ERROR; @@ -2429,7 +2429,7 @@ int header_reconstruction_test(srsran::log_sink_message_spy& spy) #endif srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -2492,7 +2492,7 @@ int header_reconstruction_test2(srsran::log_sink_message_spy& spy) #endif srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; } @@ -2556,7 +2556,7 @@ int header_reconstruction_test3(srsran::log_sink_message_spy& spy) srsran::timer_handler timers(8); // configure RLC - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; } @@ -2642,7 +2642,7 @@ int header_reconstruction_test4(srsran::log_sink_message_spy& spy) srsran::timer_handler timers(8); // configure RLC - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; } @@ -2719,7 +2719,7 @@ int header_reconstruction_test5(srsran::log_sink_message_spy& spy) srsran::timer_handler timers(8); // configure RLC - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; } @@ -2798,7 +2798,7 @@ int header_reconstruction_test6(srsran::log_sink_message_spy& spy) srsran::timer_handler timers(8); // configure RLC - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; } @@ -2897,7 +2897,7 @@ int header_reconstruction_test7(srsran::log_sink_message_spy& spy) srsran::timer_handler timers(8); // configure RLC - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; } @@ -2999,7 +2999,7 @@ int header_reconstruction_test8(srsran::log_sink_message_spy& spy) srsran::timer_handler timers(8); // configure RLC - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; } @@ -3030,7 +3030,7 @@ bool reset_test() srsran::timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -3072,7 +3072,7 @@ bool resume_test() srsran::timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -3113,7 +3113,7 @@ bool stop_test() rlc_am_tester tester; srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -3140,8 +3140,8 @@ bool status_pdu_test() srsran::timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -3249,8 +3249,8 @@ bool incorrect_status_pdu_test() srsran::timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -3314,8 +3314,8 @@ bool incorrect_status_pdu_test2() srsran::timer_handler timers(8); int len = 0; - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); if (not rlc1.configure(rlc_config_t::default_rlc_am_config())) { return -1; @@ -3428,8 +3428,8 @@ bool reestablish_test() srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); 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); @@ -3548,8 +3548,8 @@ bool discard_test() srsran::timer_handler timers(8); - rlc_am_lte rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_lte rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); 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); diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index 039901cee..c9cfc6784 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -26,7 +26,7 @@ using namespace srsue; using namespace srsran; -int basic_test_tx(rlc_am_nr* rlc, byte_buffer_t pdu_bufs[NBUFS]) +int basic_test_tx(rlc_am* rlc, byte_buffer_t pdu_bufs[NBUFS]) { // Push 5 SDUs into RLC1 unique_byte_buffer_t sdu_bufs[NBUFS]; @@ -57,8 +57,8 @@ int basic_test() timer_handler timers(8); byte_buffer_t pdu_bufs[NBUFS]; - rlc_am_nr rlc1(srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); - rlc_am_nr rlc2(srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + 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); // before configuring entity TESTASSERT(0 == rlc1.get_buffer_state()); From 123ac1665396b656d656a61b7f669af9737b8378 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Wed, 10 Nov 2021 18:12:47 +0000 Subject: [PATCH 19/77] rlc_am_nr: fixed issue with pointers from rx entity to tx entity --- lib/include/srsran/rlc/rlc_am_base.h | 2 ++ lib/include/srsran/rlc/rlc_am_lte.h | 6 ++++-- lib/include/srsran/rlc/rlc_am_nr.h | 9 ++++++++- lib/src/common/crash_handler.cc | 2 +- lib/src/rlc/rlc_am_base.cc | 16 ++++++++++++---- lib/src/rlc/rlc_am_lte.cc | 6 +----- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index 9d11c64b0..0764b4ae1 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -128,6 +128,7 @@ public: { public: explicit rlc_am_base_tx(srslog::basic_logger* logger_) : logger(logger_) {} + virtual ~rlc_am_base_tx() = default; virtual bool configure(const rlc_config_t& cfg_) = 0; virtual void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; @@ -168,6 +169,7 @@ public: { public: explicit rlc_am_base_rx(rlc_am* parent_, srslog::basic_logger* logger_) : parent(parent_), logger(logger_) {} + virtual ~rlc_am_base_rx() = default; virtual bool configure(const rlc_config_t& cfg_) = 0; virtual void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) = 0; diff --git a/lib/include/srsran/rlc/rlc_am_lte.h b/lib/include/srsran/rlc/rlc_am_lte.h index 82c42afd7..ce487c1f9 100644 --- a/lib/include/srsran/rlc/rlc_am_lte.h +++ b/lib/include/srsran/rlc/rlc_am_lte.h @@ -97,6 +97,7 @@ public: explicit rlc_am_lte_tx(rlc_am* parent_); ~rlc_am_lte_tx() = default; + void set_rx(rlc_am_lte_rx* rx_) { rx = rx_; }; bool configure(const rlc_config_t& cfg_); void empty_queue(); void reestablish(); @@ -198,9 +199,10 @@ private: class rlc_am_lte_rx : public rlc_am::rlc_am_base_rx, public timer_callback { public: - rlc_am_lte_rx(rlc_am* parent_); - ~rlc_am_lte_rx(); + explicit rlc_am_lte_rx(rlc_am* parent_); + ~rlc_am_lte_rx() = default; + void set_tx(rlc_am_lte_tx* tx_) { tx = tx_; }; bool configure(const rlc_config_t& cfg_) final; void reestablish() final; void stop() final; diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index ef1dae4fa..4289e3487 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -32,6 +32,9 @@ namespace srsran { * RLC AM NR entity * *****************************/ +class rlc_am_nr_tx; +class rlc_am_nr_rx; + // Transmitter sub-class class rlc_am_nr_tx : public rlc_am::rlc_am_base_tx { @@ -39,6 +42,7 @@ public: explicit rlc_am_nr_tx(rlc_am* parent_); ~rlc_am_nr_tx() = default; + void set_rx(rlc_am_nr_rx* rx_) { rx = rx_; } bool configure(const rlc_config_t& cfg_) final; uint32_t read_pdu(uint8_t* payload, uint32_t nof_bytes) final; void handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) final; @@ -56,7 +60,8 @@ public: void stop() final; private: - rlc_am* parent = nullptr; + rlc_am* parent = nullptr; + rlc_am_nr_rx* rx = nullptr; /**************************************************************************** * Configurable parameters @@ -87,6 +92,7 @@ public: explicit rlc_am_nr_rx(rlc_am* parent_); ~rlc_am_nr_rx() = default; + void set_tx(rlc_am_nr_tx* tx_) { tx = tx_; } bool configure(const rlc_config_t& cfg_) final; void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) final; @@ -99,6 +105,7 @@ public: private: rlc_am* parent = nullptr; + rlc_am_nr_tx* tx = nullptr; byte_buffer_pool* pool = nullptr; /**************************************************************************** diff --git a/lib/src/common/crash_handler.cc b/lib/src/common/crash_handler.cc index 0941daba8..da41bd0c5 100644 --- a/lib/src/common/crash_handler.cc +++ b/lib/src/common/crash_handler.cc @@ -69,4 +69,4 @@ void srsran_debug_handle_crash(int argc, char** argv) signal(SIGPIPE, crash_handler); } -#endif // HAVE_BACKWARD \ No newline at end of file +#endif // HAVE_BACKWARD diff --git a/lib/src/rlc/rlc_am_base.cc b/lib/src/rlc/rlc_am_base.cc index da339d39e..bd2143f17 100644 --- a/lib/src/rlc/rlc_am_base.cc +++ b/lib/src/rlc/rlc_am_base.cc @@ -41,11 +41,19 @@ rlc_am::rlc_am(srsran_rat_t rat, logger(logger), rrc(rrc_), pdcp(pdcp_), timers(timers_), lcid(lcid_) { if (rat == srsran_rat_t::lte) { - tx_base = std::unique_ptr(new rlc_am_lte_tx(this)); - rx_base = std::unique_ptr(new rlc_am_lte_rx(this)); + rlc_am_lte_tx* tx = new rlc_am_lte_tx(this); + rlc_am_lte_rx* rx = new rlc_am_lte_rx(this); + tx_base = std::unique_ptr(tx); + rx_base = std::unique_ptr(rx); + tx->set_rx(rx); + rx->set_tx(tx); } else if (rat == srsran_rat_t::nr) { - tx_base = std::unique_ptr(new rlc_am_nr_tx(this)); - rx_base = std::unique_ptr(new rlc_am_nr_rx(this)); + rlc_am_nr_tx* tx = new rlc_am_nr_tx(this); + rlc_am_nr_rx* rx = new rlc_am_nr_rx(this); + tx_base = std::unique_ptr(tx); + rx_base = std::unique_ptr(rx); + tx->set_rx(rx); + rx->set_tx(tx); } else { logger.error("Invalid RAT at entity initialization"); } diff --git a/lib/src/rlc/rlc_am_lte.cc b/lib/src/rlc/rlc_am_lte.cc index 8565f0603..fc18a2f92 100644 --- a/lib/src/rlc/rlc_am_lte.cc +++ b/lib/src/rlc/rlc_am_lte.cc @@ -1191,11 +1191,7 @@ rlc_am_lte_rx::rlc_am_lte_rx(rlc_am* parent_) : pool(byte_buffer_pool::get_instance()), reordering_timer(parent_->timers->get_unique_timer()), rlc_am_base_rx(parent_, &parent_->logger) -{ - tx = dynamic_cast(parent->tx_base.get()); -} - -rlc_am_lte_rx::~rlc_am_lte_rx() {} +{} bool rlc_am_lte_rx::configure(const rlc_config_t& cfg_) { From 3b150e26cbd8e7565545b8a2336ccad09d5d0a6d Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 12 Nov 2021 10:47:31 +0000 Subject: [PATCH 20/77] rlc_am_nr: Changed header_t to HeaderType typename in RLC data structs. Fix issue in clear_pdcp_sdu() --- lib/include/srsran/rlc/rlc_am_data_structs.h | 60 ++++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_data_structs.h b/lib/include/srsran/rlc/rlc_am_data_structs.h index 465d466d0..3c416cf33 100644 --- a/lib/include/srsran/rlc/rlc_am_data_structs.h +++ b/lib/include/srsran/rlc/rlc_am_data_structs.h @@ -22,13 +22,13 @@ namespace srsran { -template +template class rlc_amd_tx_pdu; -template +template class pdcp_pdu_info; /// Pool that manages the allocation of RLC AM PDU Segments to RLC PDUs and tracking of segments ACK state -template +template struct rlc_am_pdu_segment_pool { const static size_t MAX_POOL_SIZE = 16384; @@ -64,10 +64,10 @@ struct rlc_am_pdu_segment_pool { bool empty() const { return rlc_sn_ == invalid_rlc_sn and pdcp_sn_ == invalid_pdcp_sn; } private: - friend struct rlc_am_pdu_segment_pool; - uint32_t rlc_sn_ = invalid_rlc_sn; - uint32_t pdcp_sn_ = invalid_pdcp_sn; - rlc_am_pdu_segment_pool* parent_pool = nullptr; + friend struct rlc_am_pdu_segment_pool; + uint32_t rlc_sn_ = invalid_rlc_sn; + uint32_t pdcp_sn_ = invalid_pdcp_sn; + rlc_am_pdu_segment_pool* parent_pool = nullptr; }; rlc_am_pdu_segment_pool() @@ -83,7 +83,7 @@ struct rlc_am_pdu_segment_pool { rlc_am_pdu_segment_pool& operator=(rlc_am_pdu_segment_pool&&) = delete; bool has_segments() const { return not free_list.empty(); } - bool make_segment(rlc_amd_tx_pdu& rlc_list, pdcp_pdu_info& pdcp_list) + bool make_segment(rlc_amd_tx_pdu& rlc_list, pdcp_pdu_info& pdcp_list) { if (not has_segments()) { return false; @@ -97,15 +97,15 @@ struct rlc_am_pdu_segment_pool { } private: - intrusive_forward_list::segment_resource, free_list_tag> free_list; - std::array::segment_resource, MAX_POOL_SIZE> segments; + intrusive_forward_list::segment_resource, free_list_tag> free_list; + std::array::segment_resource, MAX_POOL_SIZE> segments; }; /// Class that contains the parameters and state (e.g. segments) of a RLC PDU -template +template class rlc_amd_tx_pdu { - using rlc_am_pdu_segment = typename rlc_am_pdu_segment_pool::segment_resource; + using rlc_am_pdu_segment = typename rlc_am_pdu_segment_pool::segment_resource; using list_type = intrusive_forward_list; const static uint32_t invalid_rlc_sn = std::numeric_limits::max(); @@ -117,7 +117,7 @@ public: const uint32_t rlc_sn = invalid_rlc_sn; uint32_t retx_count = 0; - header_t header; + HeaderType header; unique_byte_buffer_t buf; explicit rlc_amd_tx_pdu(uint32_t rlc_sn_) : rlc_sn(rlc_sn_) {} @@ -144,10 +144,10 @@ public: }; /// Class that contains the parameters and state (e.g. unACKed segments) of a PDCP PDU -template +template class pdcp_pdu_info { - using rlc_am_pdu_segment = typename rlc_am_pdu_segment_pool::segment_resource; + using rlc_am_pdu_segment = typename rlc_am_pdu_segment_pool::segment_resource; using list_type = intrusive_double_linked_list; list_type list; // List of unACKed RLC PDUs that contain segments that belong to the PDCP PDU. @@ -231,7 +231,7 @@ private: srsran::static_circular_map window; }; -template +template struct buffered_pdcp_pdu_list { public: explicit buffered_pdcp_pdu_list() : buffered_pdus(buffered_pdcp_pdu_list::buffer_size) { clear(); } @@ -239,7 +239,7 @@ public: void clear() { count = 0; - for (pdcp_pdu_info& b : buffered_pdus) { + for (pdcp_pdu_info& b : buffered_pdus) { b.clear(); } } @@ -248,7 +248,7 @@ public: { srsran_expect(sn <= max_pdcp_sn or sn == status_report_sn, "Invalid PDCP SN=%d", sn); srsran_assert(not has_pdcp_sn(sn), "Cannot re-add same PDCP SN twice"); - pdcp_pdu_info& pdu = get_pdu_(sn); + pdcp_pdu_info& pdu = get_pdu_(sn); if (pdu.valid()) { pdu.clear(); count--; @@ -259,17 +259,15 @@ public: void clear_pdcp_sdu(uint32_t sn) { - pdcp_pdu_info& pdu = get_pdu_(sn); + pdcp_pdu_info& pdu = get_pdu_(sn); if (not pdu.valid()) { - { - return; - } - pdu.clear(); - count--; + return; } + pdu.clear(); + count--; } - pdcp_pdu_info& operator[](uint32_t sn) + pdcp_pdu_info& operator[](uint32_t sn) { srsran_expect(has_pdcp_sn(sn), "Invalid access to non-existent PDCP SN=%d", sn); return get_pdu_(sn); @@ -285,21 +283,21 @@ public: private: const static size_t max_pdcp_sn = 262143u; const static size_t buffer_size = 4096u; - const static uint32_t status_report_sn = pdcp_pdu_info::status_report_sn; + const static uint32_t status_report_sn = pdcp_pdu_info::status_report_sn; - pdcp_pdu_info& get_pdu_(uint32_t sn) + pdcp_pdu_info& get_pdu_(uint32_t sn) { return (sn == status_report_sn) ? status_report_pdu : buffered_pdus[static_cast(sn % buffer_size)]; } - const pdcp_pdu_info& get_pdu_(uint32_t sn) const + const pdcp_pdu_info& get_pdu_(uint32_t sn) const { return (sn == status_report_sn) ? status_report_pdu : buffered_pdus[static_cast(sn % buffer_size)]; } // size equal to buffer_size - std::vector > buffered_pdus; - pdcp_pdu_info status_report_pdu; - uint32_t count = 0; + std::vector > buffered_pdus; + pdcp_pdu_info status_report_pdu; + uint32_t count = 0; }; } // namespace srsran From f99d6bc224f6aa3a2ab621a5ce0ddd1e8a3afd77 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Fri, 12 Nov 2021 16:19:55 +0000 Subject: [PATCH 21/77] lib,rlc: changed get_bearer() to get_lcid(). --- lib/include/srsran/rlc/rlc_am_base.h | 2 +- lib/include/srsran/rlc/rlc_common.h | 4 ++-- lib/include/srsran/rlc/rlc_tm.h | 2 +- lib/include/srsran/rlc/rlc_um_base.h | 2 +- lib/src/rlc/rlc_tm.cc | 2 +- lib/src/rlc/rlc_um_base.cc | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_base.h b/lib/include/srsran/rlc/rlc_am_base.h index 0764b4ae1..d7d4f5b60 100644 --- a/lib/include/srsran/rlc/rlc_am_base.h +++ b/lib/include/srsran/rlc/rlc_am_base.h @@ -69,7 +69,7 @@ public: rlc_mode_t get_mode() final { return rlc_mode_t::am; } - uint32_t get_bearer() final { return lcid; } + uint32_t get_lcid() final { return lcid; } /**************************************************************************** * PDCP interface diff --git a/lib/include/srsran/rlc/rlc_common.h b/lib/include/srsran/rlc/rlc_common.h index 9d6b0f29f..8e62796c5 100644 --- a/lib/include/srsran/rlc/rlc_common.h +++ b/lib/include/srsran/rlc/rlc_common.h @@ -241,8 +241,8 @@ public: } } - virtual rlc_mode_t get_mode() = 0; - virtual uint32_t get_bearer() = 0; + virtual rlc_mode_t get_mode() = 0; + virtual uint32_t get_lcid() = 0; virtual rlc_bearer_metrics_t get_metrics() = 0; virtual void reset_metrics() = 0; diff --git a/lib/include/srsran/rlc/rlc_tm.h b/lib/include/srsran/rlc/rlc_tm.h index c24883d88..849ba9737 100644 --- a/lib/include/srsran/rlc/rlc_tm.h +++ b/lib/include/srsran/rlc/rlc_tm.h @@ -41,7 +41,7 @@ public: void empty_queue() override; rlc_mode_t get_mode() override; - uint32_t get_bearer() override; + uint32_t get_lcid() override; rlc_bearer_metrics_t get_metrics() override; void reset_metrics() override; diff --git a/lib/include/srsran/rlc/rlc_um_base.h b/lib/include/srsran/rlc/rlc_um_base.h index 4f2f4609d..40cfd2768 100644 --- a/lib/include/srsran/rlc/rlc_um_base.h +++ b/lib/include/srsran/rlc/rlc_um_base.h @@ -48,7 +48,7 @@ public: bool is_mrb(); rlc_mode_t get_mode(); - uint32_t get_bearer(); + uint32_t get_lcid() final; // PDCP interface void write_sdu(unique_byte_buffer_t sdu); diff --git a/lib/src/rlc/rlc_tm.cc b/lib/src/rlc/rlc_tm.cc index 34e260743..6acd35e8b 100644 --- a/lib/src/rlc/rlc_tm.cc +++ b/lib/src/rlc/rlc_tm.cc @@ -64,7 +64,7 @@ rlc_mode_t rlc_tm::get_mode() return rlc_mode_t::tm; } -uint32_t rlc_tm::get_bearer() +uint32_t rlc_tm::get_lcid() { return lcid; } diff --git a/lib/src/rlc/rlc_um_base.cc b/lib/src/rlc/rlc_um_base.cc index c8bc6ae9d..b24ae585c 100644 --- a/lib/src/rlc/rlc_um_base.cc +++ b/lib/src/rlc/rlc_um_base.cc @@ -42,7 +42,7 @@ rlc_mode_t rlc_um_base::get_mode() return rlc_mode_t::um; } -uint32_t rlc_um_base::get_bearer() +uint32_t rlc_um_base::get_lcid() { return lcid; } From 7141fda69ce48160bf2f62189deab4e4a3f88124 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 8 Nov 2021 14:55:24 +0100 Subject: [PATCH 22/77] Revert "rlc_um_nr_test: disable test9 until low TCP UL rates are understood/fixed" This reverts commit e491aef74ed351610956f7f23162cded2440b34d. --- lib/test/rlc/rlc_um_nr_test.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/test/rlc/rlc_um_nr_test.cc b/lib/test/rlc/rlc_um_nr_test.cc index 76fff9ad5..9f1052a1a 100644 --- a/lib/test/rlc/rlc_um_nr_test.cc +++ b/lib/test/rlc/rlc_um_nr_test.cc @@ -667,13 +667,10 @@ int main(int argc, char** argv) return SRSRAN_ERROR; } -// temporarily disabling -#if 0 if (rlc_um_nr_test9()) { fprintf(stderr, "rlc_um_nr_test9() failed.\n"); return SRSRAN_ERROR; } -#endif #if PCAP pcap_handle->close(); From 4187be3ff9147420b795d1138a83d0c98c01aae9 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 8 Nov 2021 14:55:55 +0100 Subject: [PATCH 23/77] Revert "Revert "rlc_um_nr: reimplement update of RX_Next_Reassembly"" This reverts commit 296758e4abe66336b3dcbf743fa90d7c69d59463. --- lib/src/rlc/rlc_um_nr.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/rlc/rlc_um_nr.cc b/lib/src/rlc/rlc_um_nr.cc index 4d0553881..c87ac2eb4 100644 --- a/lib/src/rlc/rlc_um_nr.cc +++ b/lib/src/rlc/rlc_um_nr.cc @@ -433,9 +433,12 @@ void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) // find next SN in rx buffer if (sn == RX_Next_Reassembly) { - RX_Next_Reassembly = ((RX_Next_Reassembly + 1) % mod); - while (RX_MOD_NR_BASE(RX_Next_Reassembly) < RX_MOD_NR_BASE(RX_Next_Highest)) { - RX_Next_Reassembly = (RX_Next_Reassembly + 1) % mod; + for (auto it = rx_window.begin(); it != rx_window.end(); ++it) { + logger.debug("SN=%d has %zd segments", it->first, it->second.segments.size()); + if (RX_MOD_NR_BASE(it->first) > RX_MOD_NR_BASE(RX_Next_Reassembly)) { + RX_Next_Reassembly = it->first; + break; + } } logger.debug("Updating RX_Next_Reassembly=%d", RX_Next_Reassembly); } From b3c7eeedd3ff3f53efb665aed22071fb8b4db663 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 8 Nov 2021 20:57:41 +0100 Subject: [PATCH 24/77] rlc_um_nr_test: add extra check to verify reassembly timer isn't running --- lib/test/rlc/rlc_um_nr_test.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/test/rlc/rlc_um_nr_test.cc b/lib/test/rlc/rlc_um_nr_test.cc index 9f1052a1a..9a44dbeeb 100644 --- a/lib/test/rlc/rlc_um_nr_test.cc +++ b/lib/test/rlc/rlc_um_nr_test.cc @@ -552,7 +552,7 @@ int rlc_um_nr_test8() return SRSRAN_SUCCESS; } -// Similar to rlc_um_nr_test9() but out-of-order PDUs have SNs (from multiple SDUs) +// Similar to rlc_um_nr_test8() but out-of-order PDUs have SNs (from multiple SDUs) int rlc_um_nr_test9() { rlc_um_nr_test_context1 ctxt; @@ -612,6 +612,8 @@ int rlc_um_nr_test9() TESTASSERT(*(ctxt.tester.sdus[i]->msg) == i); } + TESTASSERT(ctxt.timers.nof_running_timers() == 0); + return SRSRAN_SUCCESS; } From 1bec07a64ada25059b78b21a7a8cfac2c76e6f90 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 8 Nov 2021 20:58:38 +0100 Subject: [PATCH 25/77] rlc_um_nr: fix starting/stopping of reassemble timer --- lib/include/srsran/rlc/rlc_um_nr.h | 4 +- lib/src/rlc/rlc_um_nr.cc | 91 +++++++++++++++++------------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_um_nr.h b/lib/include/srsran/rlc/rlc_um_nr.h index b3f3bece3..7e5b860ba 100644 --- a/lib/include/srsran/rlc/rlc_um_nr.h +++ b/lib/include/srsran/rlc/rlc_um_nr.h @@ -102,8 +102,8 @@ private: uint32_t RX_Next_Highest = 0; // the SN following the SN of the UMD PDU with the highest SN among // received UMD PDUs. It serves as the higher edge of the reassembly window. - uint32_t UM_Window_Size; - uint32_t mod; // Rx counter modulus + uint32_t UM_Window_Size = 0; + uint32_t mod = 0; // Rx counter modulus // Rx window typedef struct { diff --git a/lib/src/rlc/rlc_um_nr.cc b/lib/src/rlc/rlc_um_nr.cc index c87ac2eb4..45a395f23 100644 --- a/lib/src/rlc/rlc_um_nr.cc +++ b/lib/src/rlc/rlc_um_nr.cc @@ -304,9 +304,10 @@ void rlc_um_nr::rlc_um_nr_rx::timer_expired(uint32_t timeout_id) { std::lock_guard lock(mutex); if (reassembly_timer.id() == timeout_id) { - logger.info("%s reassembly timeout expiry - updating RX_Next_Reassembly and reassembling", rb_name.c_str()); + logger.info("%s reassembly timeout expiry for SN=%d - updating RX_Next_Reassembly and reassembling", + rb_name.c_str(), + RX_Next_Reassembly); - logger.info("Lost PDU SN=%d", RX_Next_Reassembly); metrics.num_lost_pdus++; if (rx_sdu != nullptr) { @@ -333,6 +334,7 @@ void rlc_um_nr::rlc_um_nr_rx::timer_expired(uint32_t timeout_id) if (RX_MOD_NR_BASE(RX_Next_Highest) > RX_MOD_NR_BASE(RX_Next_Reassembly + 1) || ((RX_MOD_NR_BASE(RX_Next_Highest) == RX_MOD_NR_BASE(RX_Next_Reassembly + 1) && has_missing_byte_segment(RX_Next_Reassembly)))) { + logger.info("%s Starting reassembly timer for SN=%d", rb_name.c_str(), RX_Next_Reassembly); reassembly_timer.run(); RX_Timer_Trigger = RX_Next_Highest; } @@ -385,6 +387,8 @@ bool rlc_um_nr::rlc_um_nr_rx::has_missing_byte_segment(const uint32_t sn) void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) { if (rx_window.find(sn) != rx_window.end()) { + bool sdu_complete = false; + // iterate over received segments and try to assemble full SDU auto& pdu = rx_window.at(sn); for (auto it = pdu.segments.begin(); it != pdu.segments.end();) { @@ -427,25 +431,9 @@ void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) it = pdu.segments.erase(it); if (pdu.next_expected_so == pdu.total_sdu_length) { - // deliver full SDU to upper layers - logger.info("Delivering %s SDU SN=%d (%d B)", rb_name.c_str(), sn, pdu.sdu->N_bytes); - pdcp->write_pdu(lcid, std::move(pdu.sdu)); - - // find next SN in rx buffer - if (sn == RX_Next_Reassembly) { - for (auto it = rx_window.begin(); it != rx_window.end(); ++it) { - logger.debug("SN=%d has %zd segments", it->first, it->second.segments.size()); - if (RX_MOD_NR_BASE(it->first) > RX_MOD_NR_BASE(RX_Next_Reassembly)) { - RX_Next_Reassembly = it->first; - break; - } - } - logger.debug("Updating RX_Next_Reassembly=%d", RX_Next_Reassembly); - } - - // delete PDU from rx_window - rx_window.erase(sn); - return; + // entire SDU has been received, it will be passed up the stack outside the loop + sdu_complete = true; + break; } } } else { @@ -454,10 +442,34 @@ void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) } } - // check for SN outside of rx window - if (not sn_in_reassembly_window(sn)) { - // update RX_Next_highest - RX_Next_Highest = sn + 1; + if (sdu_complete) { + // deliver full SDU to upper layers + logger.info("%s Delivering SDU SN=%d (%d B)", rb_name.c_str(), sn, pdu.sdu->N_bytes); + pdcp->write_pdu(lcid, std::move(pdu.sdu)); + + // delete PDU from rx_window + rx_window.erase(sn); + + // find next SN in rx buffer + if (sn == RX_Next_Reassembly) { + if (rx_window.empty()) { + // no further segments received + RX_Next_Reassembly = RX_Next_Highest; + } else { + for (auto it = rx_window.begin(); it != rx_window.end(); ++it) { + logger.debug("SN=%d has %zd segments", it->first, it->second.segments.size()); + if (RX_MOD_NR_BASE(it->first) > RX_MOD_NR_BASE(RX_Next_Reassembly)) { + RX_Next_Reassembly = it->first; + break; + } + } + } + logger.debug("Updating RX_Next_Reassembly=%d", RX_Next_Reassembly); + } + } else if (not sn_in_reassembly_window(sn)) { + // SN outside of rx window + + RX_Next_Highest = sn + 1; // update RX_Next_highest logger.debug("Updating RX_Next_Highest=%d", RX_Next_Highest); // drop all SNs outside of new rx window @@ -480,29 +492,32 @@ void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) for (const auto& rx_pdu : rx_window) { if (rx_pdu.first >= RX_MOD_NR_BASE(RX_Next_Highest - UM_Window_Size)) { RX_Next_Reassembly = rx_pdu.first; - logger.debug("Updating RX_Next_Reassembly=%d", RX_Next_Reassembly); + logger.debug("%s Updating RX_Next_Reassembly=%d", rb_name.c_str(), RX_Next_Reassembly); break; } } } + } - if (reassembly_timer.is_running()) { - if (RX_Timer_Trigger <= RX_Next_Reassembly || - (not sn_in_reassembly_window(RX_Timer_Trigger) and RX_Timer_Trigger != RX_Next_Highest) || - ((RX_Next_Highest == RX_Next_Reassembly + 1) && not has_missing_byte_segment(sn))) { - reassembly_timer.stop(); - } + if (reassembly_timer.is_running()) { + if (RX_Timer_Trigger <= RX_Next_Reassembly || + (not sn_in_reassembly_window(RX_Timer_Trigger) and RX_Timer_Trigger != RX_Next_Highest) || + ((RX_Next_Highest == RX_Next_Reassembly + 1) && not has_missing_byte_segment(RX_Next_Reassembly))) { + logger.debug("%s stopping reassembly timer", rb_name.c_str()); + reassembly_timer.stop(); } + } - if (not reassembly_timer.is_running() && has_missing_byte_segment(sn)) { - if (RX_Next_Highest > RX_Next_Reassembly + 1) { - reassembly_timer.run(); - RX_Timer_Trigger = RX_Next_Highest; - } + if (not reassembly_timer.is_running()) { + if ((RX_Next_Highest > RX_Next_Reassembly + 1) || + ((RX_Next_Highest == RX_Next_Reassembly + 1) && has_missing_byte_segment(RX_Next_Reassembly))) { + logger.info("%s Starting reassembly timer for SN=%d", rb_name.c_str(), sn); + reassembly_timer.run(); + RX_Timer_Trigger = RX_Next_Highest; } } } else { - logger.error("SN=%d does not exist in Rx buffer", sn); + logger.error("%s SN=%d does not exist in Rx buffer", rb_name.c_str(), sn); } } From 813adb9567df830d5da3d8aee01d911863790f17 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 8 Nov 2021 21:08:40 +0100 Subject: [PATCH 26/77] rlc_um_nr: reduce verbosity in info mode this will only print the most relevant messages in info mode. also streamlines some messages with RLC AM entity. --- lib/src/rlc/rlc_um_nr.cc | 50 +++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/src/rlc/rlc_um_nr.cc b/lib/src/rlc/rlc_um_nr.cc index 45a395f23..8736d4e23 100644 --- a/lib/src/rlc/rlc_um_nr.cc +++ b/lib/src/rlc/rlc_um_nr.cc @@ -304,9 +304,9 @@ void rlc_um_nr::rlc_um_nr_rx::timer_expired(uint32_t timeout_id) { std::lock_guard lock(mutex); if (reassembly_timer.id() == timeout_id) { - logger.info("%s reassembly timeout expiry for SN=%d - updating RX_Next_Reassembly and reassembling", - rb_name.c_str(), - RX_Next_Reassembly); + logger.debug("%s reassembly timeout expiry for SN=%d - updating RX_Next_Reassembly and reassembling", + rb_name.c_str(), + RX_Next_Reassembly); metrics.num_lost_pdus++; @@ -334,7 +334,7 @@ void rlc_um_nr::rlc_um_nr_rx::timer_expired(uint32_t timeout_id) if (RX_MOD_NR_BASE(RX_Next_Highest) > RX_MOD_NR_BASE(RX_Next_Reassembly + 1) || ((RX_MOD_NR_BASE(RX_Next_Highest) == RX_MOD_NR_BASE(RX_Next_Reassembly + 1) && has_missing_byte_segment(RX_Next_Reassembly)))) { - logger.info("%s Starting reassembly timer for SN=%d", rb_name.c_str(), RX_Next_Reassembly); + logger.debug("%s starting reassembly timer for SN=%d", rb_name.c_str(), RX_Next_Reassembly); reassembly_timer.run(); RX_Timer_Trigger = RX_Next_Highest; } @@ -444,7 +444,7 @@ void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) if (sdu_complete) { // deliver full SDU to upper layers - logger.info("%s Delivering SDU SN=%d (%d B)", rb_name.c_str(), sn, pdu.sdu->N_bytes); + logger.info("%s Rx SDU (%d B)", rb_name.c_str(), pdu.sdu->N_bytes); pdcp->write_pdu(lcid, std::move(pdu.sdu)); // delete PDU from rx_window @@ -511,7 +511,7 @@ void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) if (not reassembly_timer.is_running()) { if ((RX_Next_Highest > RX_Next_Reassembly + 1) || ((RX_Next_Highest == RX_Next_Reassembly + 1) && has_missing_byte_segment(RX_Next_Reassembly))) { - logger.info("%s Starting reassembly timer for SN=%d", rb_name.c_str(), sn); + logger.debug("%s Starting reassembly timer for SN=%d", rb_name.c_str(), sn); reassembly_timer.run(); RX_Timer_Trigger = RX_Next_Highest; } @@ -526,10 +526,10 @@ inline void rlc_um_nr::rlc_um_nr_rx::update_total_sdu_length(rlc_umd_pdu_segment { if (rx_pdu.header.si == rlc_nr_si_field_t::last_segment) { pdu_segments.total_sdu_length = rx_pdu.header.so + rx_pdu.buf->N_bytes; - logger.info("%s updating total SDU length for SN=%d to %d B", - rb_name.c_str(), - rx_pdu.header.sn, - pdu_segments.total_sdu_length); + logger.debug("%s updating total SDU length for SN=%d to %d B", + rb_name.c_str(), + rx_pdu.header.sn, + pdu_segments.total_sdu_length); } }; @@ -540,7 +540,7 @@ void rlc_um_nr::rlc_um_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_byt rlc_um_nr_pdu_header_t header = {}; rlc_um_nr_read_data_pdu_header(payload, nof_bytes, cfg.um_nr.sn_field_length, &header); - logger.debug(payload, nof_bytes, "RX %s Rx data PDU (%d B)", rb_name.c_str(), nof_bytes); + logger.debug(payload, nof_bytes, "%s Rx data PDU (%d B)", rb_name.c_str(), nof_bytes); // check if PDU contains a SN if (header.si == rlc_nr_si_field_t::full_sdu) { @@ -548,7 +548,7 @@ void rlc_um_nr::rlc_um_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_byt unique_byte_buffer_t sdu = rlc_um_nr_strip_pdu_header(header, payload, nof_bytes); // deliver to PDCP - logger.info("Delivering %s SDU (%d B)", rb_name.c_str(), sdu->N_bytes); + logger.info("%s Rx SDU (%d B)", rb_name.c_str(), sdu->N_bytes); pdcp->write_pdu(lcid, std::move(sdu)); } else if (sn_invalid_for_rx_buffer(header.sn)) { logger.info("%s Discarding SN=%d", rb_name.c_str(), header.sn); @@ -562,24 +562,26 @@ void rlc_um_nr::rlc_um_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_byt // check if this SN is already present in rx buffer if (rx_window.find(header.sn) == rx_window.end()) { // first received segment of this SN, add to rx buffer - logger.info(rx_pdu.buf->msg, - rx_pdu.buf->N_bytes, - "%s placing %s segment of SN=%d (%d B) in Rx buffer", - rb_name.c_str(), - to_string_short(header.si).c_str(), - header.sn, - rx_pdu.buf->N_bytes); + logger.debug(rx_pdu.buf->msg, + rx_pdu.buf->N_bytes, + "%s placing %s segment of SN=%d (%d B) in Rx buffer", + rb_name.c_str(), + to_string_short(header.si).c_str(), + header.sn, + rx_pdu.buf->N_bytes); rlc_umd_pdu_segments_nr_t pdu_segments = {}; update_total_sdu_length(pdu_segments, rx_pdu); pdu_segments.segments.emplace(header.so, std::move(rx_pdu)); rx_window[header.sn] = std::move(pdu_segments); } else { // other segment for this SN already present, update received data - logger.info("%s updating SN=%d at SO=%d with %d B", - rb_name.c_str(), - rx_pdu.header.sn, - rx_pdu.header.so, - rx_pdu.buf->N_bytes); + logger.debug(rx_pdu.buf->msg, + rx_pdu.buf->N_bytes, + "%s updating SN=%d at SO=%d with %d B", + rb_name.c_str(), + rx_pdu.header.sn, + rx_pdu.header.so, + rx_pdu.buf->N_bytes); auto& pdu_segments = rx_window.at(header.sn); From 853d870c5258c33d66d3b741bd29a31d77869f2c Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Tue, 9 Nov 2021 10:50:12 +0100 Subject: [PATCH 27/77] rlc_um_nr: fix SN wrap-around in a few places --- lib/src/rlc/rlc_um_nr.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/src/rlc/rlc_um_nr.cc b/lib/src/rlc/rlc_um_nr.cc index 8736d4e23..51b931c75 100644 --- a/lib/src/rlc/rlc_um_nr.cc +++ b/lib/src/rlc/rlc_um_nr.cc @@ -323,7 +323,7 @@ void rlc_um_nr::rlc_um_nr_rx::timer_expired(uint32_t timeout_id) // discard all segments with SN < updated RX_Next_Reassembly for (auto it = rx_window.begin(); it != rx_window.end();) { - if (it->first < RX_Next_Reassembly) { + if (RX_MOD_NR_BASE(it->first) < RX_MOD_NR_BASE(RX_Next_Reassembly)) { it = rx_window.erase(it); } else { ++it; @@ -469,7 +469,7 @@ void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) } else if (not sn_in_reassembly_window(sn)) { // SN outside of rx window - RX_Next_Highest = sn + 1; // update RX_Next_highest + RX_Next_Highest = (sn + 1) % mod; // update RX_Next_highest logger.debug("Updating RX_Next_Highest=%d", RX_Next_Highest); // drop all SNs outside of new rx window @@ -509,8 +509,9 @@ void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn) } if (not reassembly_timer.is_running()) { - if ((RX_Next_Highest > RX_Next_Reassembly + 1) || - ((RX_Next_Highest == RX_Next_Reassembly + 1) && has_missing_byte_segment(RX_Next_Reassembly))) { + if ((RX_MOD_NR_BASE(RX_Next_Highest) > RX_MOD_NR_BASE(RX_Next_Reassembly + 1)) || + ((RX_MOD_NR_BASE(RX_Next_Highest) == RX_MOD_NR_BASE(RX_Next_Reassembly + 1)) && + has_missing_byte_segment(RX_Next_Reassembly))) { logger.debug("%s Starting reassembly timer for SN=%d", rb_name.c_str(), sn); reassembly_timer.run(); RX_Timer_Trigger = RX_Next_Highest; From 40809fb10e5c69bf3e72612eb7d9bfc7d93e81db Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Sun, 14 Nov 2021 20:46:40 +0100 Subject: [PATCH 28/77] gnb,rrc: fix compilation of RRC test on 32bit --- srsgnb/src/stack/rrc/test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srsgnb/src/stack/rrc/test/CMakeLists.txt b/srsgnb/src/stack/rrc/test/CMakeLists.txt index 9716fdaac..3e5ab83cf 100644 --- a/srsgnb/src/stack/rrc/test/CMakeLists.txt +++ b/srsgnb/src/stack/rrc/test/CMakeLists.txt @@ -9,5 +9,5 @@ add_library(rrc_nr_test_helpers rrc_nr_test_helpers.cc) add_executable(rrc_nr_test rrc_nr_test.cc) -target_link_libraries(rrc_nr_test srsgnb_rrc test_helpers rrc_nr_test_helpers) +target_link_libraries(rrc_nr_test srsgnb_rrc test_helpers rrc_nr_test_helpers ${ATOMIC_LIBS}) add_test(rrc_nr_test rrc_nr_test -i ${CMAKE_CURRENT_SOURCE_DIR}/../..) \ No newline at end of file From 779bfcf79118b36c873758a9b6679f6d3360a899 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Tue, 9 Nov 2021 19:21:16 +0100 Subject: [PATCH 29/77] phy_common: fix symbol size derivation for NR-only cells make sure that 52 PRB cell with LTE rates gives 15.36e6 as sample rate --- lib/src/phy/common/phy_common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/phy/common/phy_common.c b/lib/src/phy/common/phy_common.c index 48dafa568..2abcb954f 100644 --- a/lib/src/phy/common/phy_common.c +++ b/lib/src/phy/common/phy_common.c @@ -338,9 +338,9 @@ int srsran_symbol_sz_power2(uint32_t nof_prb) return 256; } else if (nof_prb <= 25) { return 512; - } else if (nof_prb <= 50) { + } else if (nof_prb <= 52) { return 1024; - } else if (nof_prb <= 75) { + } else if (nof_prb <= 79) { return 1536; } else if (nof_prb <= 110) { return 2048; From f708635a5d8fe2fc79d3bc3cd5f2095ae0aa7658 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Tue, 9 Nov 2021 20:43:59 +0100 Subject: [PATCH 30/77] rrc_nr_cfg: set default PLMN for NR cell to 00101 --- lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h b/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h index 003ed238f..e3d0f0e4b 100644 --- a/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h +++ b/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h @@ -24,7 +24,7 @@ struct basic_cell_args_t { uint32_t scs = 15; bool is_standalone = true; bool is_fdd = true; - std::string plmn = "90170"; + std::string plmn = "00101"; uint32_t tac = 7; }; From 36a287edd8964f188ce5971ec772b44c61eb70ac Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Tue, 9 Nov 2021 20:44:37 +0100 Subject: [PATCH 31/77] ue_dl_nr_file_test: expose various coreset and search space related parameters --- lib/src/phy/ue/test/ue_dl_nr_file_test.c | 53 ++++++++++++++++++------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.c b/lib/src/phy/ue/test/ue_dl_nr_file_test.c index d2f122e51..8aaa334a8 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.c +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.c @@ -28,6 +28,10 @@ static srsran_slot_cfg_t slot_cfg = {}; static srsran_softbuffer_rx_t softbuffer = {}; static uint8_t* data = NULL; +static uint32_t coreset_index = 0; +static uint32_t coreset_offset_rb = 0; +static srsran_search_space_type_t ss_type = srsran_search_space_type_common_0; + static void usage(char* prog) { printf("Usage: %s [pTLR] \n", prog); @@ -36,7 +40,10 @@ static void usage(char* prog) printf("\t-i Physical cell identifier [Default %d]\n", carrier.pci); printf("\t-n Slot index [Default %d]\n", slot_cfg.idx); printf("\t-R RNTI in hexadecimal [Default 0x%x]\n", rnti); - printf("\t-T RNTI type (c, ra) [Default %s]\n", srsran_rnti_type_str(rnti_type)); + printf("\t-T RNTI type (c, ra, si) [Default %s]\n", srsran_rnti_type_str(rnti_type)); + printf("\t-s Search space type (common0, common3) [Default %s]\n", srsran_ss_type_str(ss_type)); + printf("\t-c Coreset index (i.e. 0 for CoresetZero) [Default %d]\n", coreset_index); + printf("\t-o Coreset PRB offset [Default %d]\n", coreset_offset_rb); printf("\t-S Use standard rates [Default %s]\n", srsran_symbol_size_is_standard() ? "yes" : "no"); printf("\t-v [set srsran_verbose to debug, default none]\n"); @@ -45,7 +52,7 @@ static void usage(char* prog) static int parse_args(int argc, char** argv) { int opt; - while ((opt = getopt(argc, argv, "fPivnSRT")) != -1) { + while ((opt = getopt(argc, argv, "fPivnSRTsco")) != -1) { switch (opt) { case 'f': filename = argv[optind]; @@ -70,12 +77,31 @@ static int parse_args(int argc, char** argv) rnti_type = srsran_rnti_type_c; } else if (strcmp(argv[optind], "ra") == 0) { rnti_type = srsran_rnti_type_ra; + } else if (strcmp(argv[optind], "si") == 0) { + rnti_type = srsran_rnti_type_si; } else { printf("Invalid RNTI type '%s'\n", argv[optind]); usage(argv[0]); return SRSRAN_ERROR; } break; + case 's': + if (strcmp(argv[optind], "common0") == 0) { + ss_type = srsran_search_space_type_common_0; + } else if (strcmp(argv[optind], "common3") == 0) { + ss_type = srsran_search_space_type_common_3; + } else { + printf("Invalid SS type '%s'\n", argv[optind]); + usage(argv[0]); + return SRSRAN_ERROR; + } + break; + case 'c': + coreset_index = (uint16_t)strtol(argv[optind], NULL, 16); + break; + case 'o': + coreset_offset_rb = (uint16_t)strtol(argv[optind], NULL, 16); + break; case 'S': srsran_use_standard_symbol_size(true); break; @@ -161,6 +187,11 @@ int main(int argc, char** argv) srsran_ue_dl_nr_t ue_dl = {}; cf_t* buffer[SRSRAN_MAX_PORTS] = {}; + // parse args + if (parse_args(argc, argv) < SRSRAN_SUCCESS) { + goto clean_exit; + } + uint32_t sf_len = SRSRAN_SF_LEN_PRB(carrier.nof_prb); buffer[0] = srsran_vec_cf_malloc(sf_len); if (buffer[0] == NULL) { @@ -180,6 +211,7 @@ int main(int argc, char** argv) goto clean_exit; } + // Set default PDSCH configuration srsran_ue_dl_nr_args_t ue_dl_args = {}; ue_dl_args.nof_rx_antennas = 1; ue_dl_args.pdsch.sch.disable_simd = false; @@ -189,11 +221,6 @@ int main(int argc, char** argv) ue_dl_args.pdcch.measure_evm = true; ue_dl_args.nof_max_prb = carrier.nof_prb; - // Set default PDSCH configuration - if (parse_args(argc, argv) < SRSRAN_SUCCESS) { - goto clean_exit; - } - // Check for filename if (filename == NULL) { ERROR("Filename was not provided"); @@ -208,19 +235,21 @@ int main(int argc, char** argv) } // Configure CORESET - srsran_coreset_t* coreset = &pdcch_cfg.coreset[1]; - pdcch_cfg.coreset_present[1] = true; - coreset->duration = 2; + srsran_coreset_t* coreset = &pdcch_cfg.coreset[coreset_index]; + pdcch_cfg.coreset_present[coreset_index] = true; + coreset->duration = 2; + coreset->offset_rb = coreset_offset_rb; for (uint32_t i = 0; i < SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { coreset->freq_resources[i] = i < carrier.nof_prb / 6; } + // coreset->precoder_granularity = srsran_coreset_precoder_granularity_reg_bundle; // Configure Search Space srsran_search_space_t* search_space = &pdcch_cfg.search_space[0]; pdcch_cfg.search_space_present[0] = true; search_space->id = 0; - search_space->coreset_id = 1; - search_space->type = srsran_search_space_type_common_3; + search_space->coreset_id = coreset_index; + search_space->type = ss_type; search_space->formats[0] = srsran_dci_format_nr_0_0; search_space->formats[1] = srsran_dci_format_nr_1_0; search_space->nof_formats = 2; From 279d82aa317e04927adf35099fd0cd5e1eca959b Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Tue, 9 Nov 2021 20:47:02 +0100 Subject: [PATCH 32/77] ue_dl_nr: debug to print PDCCH received symbols --- lib/src/phy/ue/ue_dl_nr.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/src/phy/ue/ue_dl_nr.c b/lib/src/phy/ue/ue_dl_nr.c index 5a167f75e..88b728b02 100644 --- a/lib/src/phy/ue/ue_dl_nr.c +++ b/lib/src/phy/ue/ue_dl_nr.c @@ -293,6 +293,16 @@ static int ue_dl_nr_find_dci_ncce(srsran_ue_dl_nr_t* q, return SRSRAN_ERROR; } +#if 0 + static uint32_t num_pdcch = 0; + char tmpstr[64]; + snprintf(tmpstr, 64, "pdcch_symbols%d.dat", num_pdcch); + printf("save %d syms to %s\n", q->pdcch.M / 4, tmpstr); + srsran_vec_save_file(tmpstr, q->pdcch.symbols, q->pdcch.M / 4 * sizeof(cf_t)); + // srsran_vec_fprint_c(stdout, q->pdcch.symbols, q->pdcch.M/4); + num_pdcch++; +#endif + // Save information pdcch_info->result = *pdcch_res; From 9987b9e70bd539e889ee01ef8e0cfe56db1c49fa Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 10 Nov 2021 11:14:07 +0100 Subject: [PATCH 33/77] phy_common: fix number of antenna port for NR-only configs --- srsenb/hdr/phy/phy_common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srsenb/hdr/phy/phy_common.h b/srsenb/hdr/phy/phy_common.h index aab5675c3..e929c1a46 100644 --- a/srsenb/hdr/phy/phy_common.h +++ b/srsenb/hdr/phy/phy_common.h @@ -91,8 +91,8 @@ public: if (cc_idx < cell_list_lte.size()) { ret = cell_list_lte[cc_idx].cell.nof_ports; - } else if (cc_idx == 1 && !cell_list_nr.empty()) { - // one RF port for basic NSA config + } else if ((cc_idx == 0 || cc_idx == 1) && !cell_list_nr.empty()) { + // one RF port for basic NSA/SA config ret = 1; } From 2117aa93e22358792f26ebd2ab0db87625bb0d7e Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 10 Nov 2021 11:15:09 +0100 Subject: [PATCH 34/77] slot_worker: add compile time option to write baseband signal to file --- srsenb/src/phy/nr/slot_worker.cc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/srsenb/src/phy/nr/slot_worker.cc b/srsenb/src/phy/nr/slot_worker.cc index b2389f0c2..fd67f4537 100644 --- a/srsenb/src/phy/nr/slot_worker.cc +++ b/srsenb/src/phy/nr/slot_worker.cc @@ -14,6 +14,13 @@ #include "srsran/common/buffer_pool.h" #include "srsran/common/common.h" +#define DEBUG_WRITE_FILE + +#ifdef DEBUG_WRITE_FILE +FILE* f; +static uint32_t num_slots = 0; +#endif + namespace srsenb { namespace nr { slot_worker::slot_worker(srsran::phy_common_interface& common_, @@ -86,6 +93,12 @@ bool slot_worker::init(const args_t& args) return false; } +#ifdef DEBUG_WRITE_FILE + const char* filename = "nr_baseband.dat"; + printf("Opening %s to dump baseband\n", filename); + f = fopen(filename, "w"); +#endif + return true; } @@ -407,6 +420,18 @@ void slot_worker::work_imp() } common.worker_end(context, true, tx_rf_buffer); + +#ifdef DEBUG_WRITE_FILE + fwrite(tx_rf_buffer.get(0), tx_rf_buffer.get_nof_samples() * sizeof(cf_t), 1, f); +#endif + +#ifdef DEBUG_WRITE_FILE + if (num_slots == 30) { + fclose(f); + exit(-1); + } + num_slots++; +#endif } bool slot_worker::set_common_cfg(const srsran_carrier_nr_t& carrier, From 8c99d7a3bd6b5926e2941938e05646f24464b082 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 10 Nov 2021 12:56:45 +0100 Subject: [PATCH 35/77] ue_dl_nr_file_test: use common helper function to derive coreset0 params --- lib/src/phy/ue/test/ue_dl_nr_file_test.c | 73 ++++++++++++++++++------ 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.c b/lib/src/phy/ue/test/ue_dl_nr_file_test.c index 8aaa334a8..ce8596d32 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.c +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.c @@ -28,8 +28,11 @@ static srsran_slot_cfg_t slot_cfg = {}; static srsran_softbuffer_rx_t softbuffer = {}; static uint8_t* data = NULL; -static uint32_t coreset_index = 0; +static uint32_t coreset0_idx = 0; // if ss_type=si coreset0 is used and this is the index static uint32_t coreset_offset_rb = 0; + +static uint32_t coreset_n_rb = 48; +static uint32_t coreset_len = 1; static srsran_search_space_type_t ss_type = srsran_search_space_type_common_0; static void usage(char* prog) @@ -42,8 +45,10 @@ static void usage(char* prog) printf("\t-R RNTI in hexadecimal [Default 0x%x]\n", rnti); printf("\t-T RNTI type (c, ra, si) [Default %s]\n", srsran_rnti_type_str(rnti_type)); printf("\t-s Search space type (common0, common3) [Default %s]\n", srsran_ss_type_str(ss_type)); - printf("\t-c Coreset index (i.e. 0 for CoresetZero) [Default %d]\n", coreset_index); - printf("\t-o Coreset PRB offset [Default %d]\n", coreset_offset_rb); + printf("\t-c Coreset0 index (only used if SS type is common0 for SIB) [Default %d]\n", coreset0_idx); + printf("\t-o Coreset RB offset [Default %d]\n", coreset_offset_rb); + printf("\t-N Coreset N_RB [Default %d]\n", coreset_n_rb); + printf("\t-l Coreset duration in symbols [Default %d]\n", coreset_len); printf("\t-S Use standard rates [Default %s]\n", srsran_symbol_size_is_standard() ? "yes" : "no"); printf("\t-v [set srsran_verbose to debug, default none]\n"); @@ -52,7 +57,7 @@ static void usage(char* prog) static int parse_args(int argc, char** argv) { int opt; - while ((opt = getopt(argc, argv, "fPivnSRTsco")) != -1) { + while ((opt = getopt(argc, argv, "fPivnSRTscoNl")) != -1) { switch (opt) { case 'f': filename = argv[optind]; @@ -97,10 +102,16 @@ static int parse_args(int argc, char** argv) } break; case 'c': - coreset_index = (uint16_t)strtol(argv[optind], NULL, 16); + coreset0_idx = (uint16_t)strtol(argv[optind], NULL, 10); break; case 'o': - coreset_offset_rb = (uint16_t)strtol(argv[optind], NULL, 16); + coreset_offset_rb = (uint16_t)strtol(argv[optind], NULL, 10); + break; + case 'N': + coreset_n_rb = (uint16_t)strtol(argv[optind], NULL, 10); + break; + case 'l': + coreset_len = (uint16_t)strtol(argv[optind], NULL, 10); break; case 'S': srsran_use_standard_symbol_size(true); @@ -147,7 +158,7 @@ static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot) if (nof_found_dci < 1) { printf("No DCI found :'(\n"); - return SRSRAN_SUCCESS; + return SRSRAN_ERROR; } char str[1024] = {}; @@ -178,6 +189,9 @@ static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot) return SRSRAN_ERROR; } + printf("Decoded PDSCH (%d B)\n", pdsch_cfg.grant.tb[0].tbs / 8); + srsran_vec_fprint_byte(stdout, pdsch_res.tb[0].payload, pdsch_cfg.grant.tb[0].tbs / 8); + return SRSRAN_SUCCESS; } @@ -234,22 +248,47 @@ int main(int argc, char** argv) goto clean_exit; } + srsran_coreset_t* coreset = NULL; + // Configure CORESET - srsran_coreset_t* coreset = &pdcch_cfg.coreset[coreset_index]; - pdcch_cfg.coreset_present[coreset_index] = true; - coreset->duration = 2; - coreset->offset_rb = coreset_offset_rb; - for (uint32_t i = 0; i < SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { - coreset->freq_resources[i] = i < carrier.nof_prb / 6; + if (rnti_type == srsran_rnti_type_si) { + // configure to use coreset0 + coreset = &pdcch_cfg.coreset[0]; + pdcch_cfg.coreset_present[0] = true; + + srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_15kHz; + srsran_subcarrier_spacing_t pdcch_scs = srsran_subcarrier_spacing_15kHz; + + uint32_t ssb_pointA_freq_offset_Hz = 0; + + if (srsran_coreset_zero(carrier.pci, ssb_pointA_freq_offset_Hz, ssb_scs, pdcch_scs, coreset0_idx, coreset) != + SRSRAN_SUCCESS) { + printf("Not possible to create CORESET Zero (ssb_scs=%s, pdcch_scs=%s, idx=%d)", + srsran_subcarrier_spacing_to_str(ssb_scs), + srsran_subcarrier_spacing_to_str(pdcch_scs), + coreset0_idx); + return false; + } + // FIXME: use ssb_pointA_freq_offset_Hz and let srsran_coreset_zero() calculate this + coreset->offset_rb = coreset_offset_rb; + } else { + // configure to use coreset1 + coreset = &pdcch_cfg.coreset[1]; + pdcch_cfg.coreset_present[1] = true; + coreset->duration = coreset_len; + coreset->offset_rb = coreset_offset_rb; + for (uint32_t i = 0; i < SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { + coreset->freq_resources[i] = i < coreset_n_rb / 6; + } } - // coreset->precoder_granularity = srsran_coreset_precoder_granularity_reg_bundle; // Configure Search Space srsran_search_space_t* search_space = &pdcch_cfg.search_space[0]; pdcch_cfg.search_space_present[0] = true; search_space->id = 0; - search_space->coreset_id = coreset_index; + search_space->coreset_id = (rnti_type == srsran_rnti_type_si) ? 0 : 1; search_space->type = ss_type; + // search_space->duration = coreset->duration; search_space->formats[0] = srsran_dci_format_nr_0_0; search_space->formats[1] = srsran_dci_format_nr_1_0; search_space->nof_formats = 2; @@ -288,9 +327,7 @@ int main(int argc, char** argv) } // Actual decode - work_ue_dl(&ue_dl, &slot_cfg); - - ret = SRSRAN_SUCCESS; + ret = work_ue_dl(&ue_dl, &slot_cfg); clean_exit: if (buffer[0] != NULL) { From 1684c56ca0e2262ee391b2be0fe76a26197ac68a Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 10 Nov 2021 12:57:44 +0100 Subject: [PATCH 36/77] phy: add two IQ dumps with coreset0 and coreset1 coreset0: 15:03:16.697 [PHY] DL - 01 ffff 86.0 PDSCH: harq=si prb=2:7 symb=2:12 CW0: tb_len=84 mod=2 rv_idx=1 cr=0.44 0000: 74 81 01 70 10 40 04 02 00 00 c8 00 24 68 a0 38 t..p.@......$h.8 0010: 05 01 02 60 24 00 00 06 6c 6d 92 21 f3 70 40 20 ...`$...lm.!.p@ 0020: 00 00 80 80 00 41 06 80 a0 90 9c 20 4c 29 21 00 .....A..... L)!. 0030: 00 00 33 a1 c6 d9 22 40 10 00 1e b8 94 63 c0 09 ..3..."@.....c.. 0040: 28 c4 1b 8a 36 fd 5b 1c 3a 00 bc 5b 46 14 00 00 (...6.[.:..[F... 0050: 00 00 00 00 .... 15:03:16.697 [PHY] DL - 01 ffff 86.0 PDCCH: ss_id=0 cce_index=0 al=4 dci=1_0 rb_alloc=0x120 time_domain_rsc=0 vrb_to_prb_map=0 mcs=6 rv_idx=1 si_indicator=0 dci_len=39 coreset1: 15:03:16.693 [PHY] DL 0001 01 0100 85.6 PDCCH: ss_id=1 cce_index=0 al=4 dci=1_0 rb_alloc=0x5f time_domain_rsc=0 vrb_to_prb_map=0 mcs=6 ndi=1 rv_idx=0 harq_process=0 dai=0 tpc_command=1 pucch_rsc=0 harq_feedback_timing=3 dci_len=39 15:03:16.693 [PHY] DL 0001 01 0100 85.6 PDSCH: harq=0 prb=2:48 symb=1:13 k1=4 CW0: tb_len=624 mod=2 rv_idx=0 cr=0.44 retx=0 0000: 43 02 6d 40 00 80 00 00 d6 5b 77 92 be 29 a1 5c C.m@.....[w..).\ 0010: 9d d9 a3 42 64 bf d7 c0 cc 20 a6 4f b3 5e f5 06 ...Bd.... .O.^.. 0020: 5f fc 03 02 83 ca e9 ee 04 e7 1a 1d 00 3f 9c 01 _............?.. 0030: ec 1c 32 bb 6b 0f e2 e9 dc 7c f6 84 41 b2 2b e8 ..2.k....|..A.+. 0040: 10 f0 23 2c 91 f1 5d c7 6f b5 6e ac b6 fb c2 e6 ..#,..].o.n..... 0050: 32 2d b2 8b 07 36 11 f3 81 78 5d ff 1a 85 8d 6c 2-...6...x]....l 0060: 18 ce ca 52 1f 81 0f 78 c6 1a ab b5 e8 71 50 34 ...R...x.....qP4 --- lib/src/phy/ue/test/CMakeLists.txt | 2 ++ ...ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat | Bin 0 -> 122880 bytes .../test/ue_dl_nr_pci500_rb52_si_s15.36e6.dat | Bin 0 -> 122880 bytes 3 files changed, 2 insertions(+) create mode 100644 lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat create mode 100644 lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_s15.36e6.dat diff --git a/lib/src/phy/ue/test/CMakeLists.txt b/lib/src/phy/ue/test/CMakeLists.txt index 5fea23954..ba0d0889f 100644 --- a/lib/src/phy/ue/test/CMakeLists.txt +++ b/lib/src/phy/ue/test/CMakeLists.txt @@ -55,3 +55,5 @@ foreach (n RANGE 0 9) add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) endforeach () add_test(ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0.dat -i 1 -P 52 -n 4 -R 7f) +add_test(ue_dl_nr_pci500_rb52_si ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 7 -o 2 -s common0) +add_test(ue_dl_nr_pci500_rb52_pdsch ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -o 2 -s common3) \ No newline at end of file diff --git a/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat b/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat new file mode 100644 index 0000000000000000000000000000000000000000..848f5bada9912a45aa7d0dbeb116c2c9acf0332d GIT binary patch literal 122880 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�-K?Y$Jy*GdIS$Nq&Smgb*+uv4Pt&Ny`q=Qun>8jM6yNtB zz;qs_u$>ns67TVn3V+|F!;f6>{8T%VSju6Zj5#hp)Q?;2y&WAk-w>zP3GV2ozv4!x zP=2!3V!=nGhsDtc#ea+z;K3eU(gSs>4vJ@AbM4soT?d)B>@%uVEE3$M-?>jpc^EyJ zhr(a)z&Te93$3NudhJyx(c45FOE+Ntu>-tOj0|41l!lFQWwg&o@G;CgzzpZF!EPZB zS+hjo-ClHt;;SWzxNJYzO+%0(R;{CUPP&d3aGa#hU7nghNf#X zSeU&Ozg&3`^U2vE__elj%^~6Ned{J}>=iQ{6ZDcoITgIs#yL>8czbkGp%_X&L8!$+d!-~dC z?%YK+jNTMR*^=u-iyQ5sUCx>_co&cMYZ2~0&&ISvhiUu48Tjz;P1=!qRLGeXfb=DA z_#`8NGBQ`7ro)r6x7naY`cT$rxCiUz)RDEKDmovip^Gv_T+p`pU#X*`; zI@ImR=Numxi-oG@nn<3{7+_R~WpyeK8k?DCwS)*vb_ zu!$riibzx{D`LP>NR9#~3gW8GLV)-JCddF15E#!nGCN5K_=1X#NF<5q3Q~%@%1lbn zcj#X~#F;sB-`6=;`IsKXNi)&l`wA9DdWXyq9GVGvvD}~2Pf(O!r0Be~jgHi0LX^W< zY>HuwuPWn)CZk#Q2{o4cqyS&1$M{x(MGdG>sy?Ubz_&-33wUcSe< z3PmO*nt~v=z5uV=-G>_IL2!3Bf$Qyi5N2y=&*sPB*;o-)I(^S%LGj?^b&w5oTCl9l zL2ku?N1U@?FLvl}gjDW40_Lwh*}D&CvXgaA?9`Pi)QV;FVbln2`R*ijD6CkRu-l9X z{H3T!8}c}{2D3GFyz8xRux+O|p8Oz%jTn@$W_mUIsq7-?GTx?NZ=^xmOMjAUHGF7c z9}NC$Mw~Ffg)MWa^yk$SDqac}z4=snw2DR1MtmVY!%kRcwol#t14NB7Qmqhkz7-1U zvR(zjyZceTL8mPK@;++i*RZKp_?6%7gL~(n$7OOyme@3a%YPHxs4q6rtWWNMsB$H3 z&pL(+>&G~|yPl*9Q!$U>4ak)Su^l%qg6*+iz<8wvG_eYJocIdo@s7A+1R!QSVe zQCF-5pKG?}H52xfDapVjeKD!qr1V_BlnzB62KCiVoK9TLx;nf#Rdfj}R}^7){S%Na zS<7#(&gWM|2Jm*3B1&@!Bcs0!Su~`RA~Xl8zF5X}3-04OHDil|tH4)!2rMeY$*pBG zE*5gtk|ldkEP0=ozL3ylUhq;r?ZAV$?TSQ2Gj|hT$0|G0`Hm7zIYZ5+YMv=a3AP~RNU4Df_Kq;3I1O0 zh{-MXtakk#5=T7dx+=EQrDzSU`=|y-ZfeQ%rPcv{-kH-}r{Hn0bVp3=eLeV zu!fj%O#SpM$XmPH+w#pAZ@CvL+FQ|;ZNNeICU||=gSwTY@Q=M0$>wzm{EP=p`0wN2 N^#5=A|Nqnf{|B0o;L`vA literal 0 HcmV?d00001 diff --git a/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_s15.36e6.dat b/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_s15.36e6.dat new file mode 100644 index 0000000000000000000000000000000000000000..e7afecae3b1937764689b52aab41c1d81727d4a6 GIT binary patch literal 122880 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�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 P|C|5+-~9i7Gyng8gK%Mb literal 0 HcmV?d00001 From 77269c055dfbad2d01daf36b7b7f6af39eec4829 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 10 Nov 2021 15:12:55 +0100 Subject: [PATCH 37/77] ue_dl_nr_file_test: expose ARFCN for center and SSB and use it to derive RB offset --- lib/src/phy/ue/test/CMakeLists.txt | 6 +- ...l_nr_file_test.c => ue_dl_nr_file_test.cc} | 122 +++++++++++------- 2 files changed, 79 insertions(+), 49 deletions(-) rename lib/src/phy/ue/test/{ue_dl_nr_file_test.c => ue_dl_nr_file_test.cc} (78%) diff --git a/lib/src/phy/ue/test/CMakeLists.txt b/lib/src/phy/ue/test/CMakeLists.txt index ba0d0889f..a55e339a3 100644 --- a/lib/src/phy/ue/test/CMakeLists.txt +++ b/lib/src/phy/ue/test/CMakeLists.txt @@ -44,8 +44,8 @@ if(RF_FOUND) endif(SRSGUI_FOUND) endif(RF_FOUND) -add_executable(ue_dl_nr_file_test ue_dl_nr_file_test.c) -target_link_libraries(ue_dl_nr_file_test srsran_phy pthread) +add_executable(ue_dl_nr_file_test ue_dl_nr_file_test.cc) +target_link_libraries(ue_dl_nr_file_test srsran_phy srsran_common pthread) foreach (n RANGE 0 9) add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) @@ -55,5 +55,5 @@ foreach (n RANGE 0 9) add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) endforeach () add_test(ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0.dat -i 1 -P 52 -n 4 -R 7f) -add_test(ue_dl_nr_pci500_rb52_si ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 7 -o 2 -s common0) +add_test(ue_dl_nr_pci500_rb52_si ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 7 -o 2 -s common0 -A 161200 -a 161290) add_test(ue_dl_nr_pci500_rb52_pdsch ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -o 2 -s common3) \ No newline at end of file diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.c b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc similarity index 78% rename from lib/src/phy/ue/test/ue_dl_nr_file_test.c rename to lib/src/phy/ue/test/ue_dl_nr_file_test.cc index ce8596d32..31cfbd0ea 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.c +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc @@ -10,10 +10,16 @@ * */ +#ifdef __cplusplus +extern "C" { #include "srsran/phy/io/filesource.h" #include "srsran/phy/phch/ra_nr.h" #include "srsran/phy/ue/ue_dl_nr.h" #include "srsran/phy/utils/debug.h" +} +#endif // __cplusplus + +#include "srsran/common/band_helper.h" #include static srsran_carrier_nr_t carrier = SRSRAN_DEFAULT_CARRIER_NR; @@ -25,15 +31,21 @@ static uint16_t rnti = 0x1234; static srsran_rnti_type_t rnti_type = srsran_rnti_type_c; static srsran_slot_cfg_t slot_cfg = {}; -static srsran_softbuffer_rx_t softbuffer = {}; -static uint8_t* data = NULL; +static srsran_filesource_t filesource = {}; +static srsran_ue_dl_nr_t ue_dl = {}; +static cf_t* buffer[SRSRAN_MAX_PORTS] = {}; +static srsran_softbuffer_rx_t softbuffer = {}; +static uint8_t* data = NULL; + +static uint32_t coreset0_idx = 0; // if ss_type=si coreset0 is used and this is the index +static uint32_t coreset_offset_rb = 0; -static uint32_t coreset0_idx = 0; // if ss_type=si coreset0 is used and this is the index -static uint32_t coreset_offset_rb = 0; +static uint32_t dl_arfcn = 161200; // center of the NR carrier (default at 806e6 Hz) +static uint32_t ssb_arfcn = 161290; // center of the SSB within the carrier (default at 806.45e6) -static uint32_t coreset_n_rb = 48; -static uint32_t coreset_len = 1; -static srsran_search_space_type_t ss_type = srsran_search_space_type_common_0; +static uint32_t coreset_n_rb = 48; +static uint32_t coreset_len = 1; +static srsran_search_space_type_t ss_type = srsran_search_space_type_common_0; static void usage(char* prog) { @@ -49,6 +61,10 @@ static void usage(char* prog) printf("\t-o Coreset RB offset [Default %d]\n", coreset_offset_rb); printf("\t-N Coreset N_RB [Default %d]\n", coreset_n_rb); printf("\t-l Coreset duration in symbols [Default %d]\n", coreset_len); + + printf("\t-A ARFCN of the NR carrier (center) [Default %d]\n", dl_arfcn); + printf("\t-a center of the SSB within the carrier [Default %d]\n", ssb_arfcn); + printf("\t-S Use standard rates [Default %s]\n", srsran_symbol_size_is_standard() ? "yes" : "no"); printf("\t-v [set srsran_verbose to debug, default none]\n"); @@ -57,7 +73,7 @@ static void usage(char* prog) static int parse_args(int argc, char** argv) { int opt; - while ((opt = getopt(argc, argv, "fPivnSRTscoNl")) != -1) { + while ((opt = getopt(argc, argv, "fPivnSRTscoNlAa")) != -1) { switch (opt) { case 'f': filename = argv[optind]; @@ -113,6 +129,12 @@ static int parse_args(int argc, char** argv) case 'l': coreset_len = (uint16_t)strtol(argv[optind], NULL, 10); break; + case 'A': + dl_arfcn = (uint16_t)strtol(argv[optind], NULL, 10); + break; + case 'a': + ssb_arfcn = (uint16_t)strtol(argv[optind], NULL, 10); + break; case 'S': srsran_use_standard_symbol_size(true); break; @@ -195,34 +217,47 @@ static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot) return SRSRAN_SUCCESS; } +// helper to avoid goto in C++ +int clean_exit(int ret) +{ + if (buffer[0] != NULL) { + free(buffer[0]); + } + if (data != NULL) { + free(data); + } + srsran_ue_dl_nr_free(&ue_dl); + srsran_filesource_free(&filesource); + srsran_softbuffer_rx_free(&softbuffer); + return ret; +} + int main(int argc, char** argv) { - int ret = SRSRAN_ERROR; - srsran_ue_dl_nr_t ue_dl = {}; - cf_t* buffer[SRSRAN_MAX_PORTS] = {}; + int ret = SRSRAN_ERROR; // parse args if (parse_args(argc, argv) < SRSRAN_SUCCESS) { - goto clean_exit; + return clean_exit(ret); } uint32_t sf_len = SRSRAN_SF_LEN_PRB(carrier.nof_prb); buffer[0] = srsran_vec_cf_malloc(sf_len); if (buffer[0] == NULL) { ERROR("Error malloc"); - goto clean_exit; + return clean_exit(ret); } if (srsran_softbuffer_rx_init_guru(&softbuffer, SRSRAN_SCH_NR_MAX_NOF_CB_LDPC, SRSRAN_LDPC_MAX_LEN_ENCODED_CB) < SRSRAN_SUCCESS) { ERROR("Error init soft-buffer"); - goto clean_exit; + return clean_exit(ret); } data = srsran_vec_u8_malloc(SRSRAN_SLOT_MAX_NOF_BITS_NR); if (data == NULL) { ERROR("Error malloc"); - goto clean_exit; + return clean_exit(ret); } // Set default PDSCH configuration @@ -238,14 +273,13 @@ int main(int argc, char** argv) // Check for filename if (filename == NULL) { ERROR("Filename was not provided"); - goto clean_exit; + return clean_exit(ret); } // Open filesource - srsran_filesource_t filesource = {}; if (srsran_filesource_init(&filesource, filename, SRSRAN_COMPLEX_FLOAT_BIN) < SRSRAN_SUCCESS) { ERROR("Error opening filesource"); - goto clean_exit; + return clean_exit(ret); } srsran_coreset_t* coreset = NULL; @@ -256,21 +290,28 @@ int main(int argc, char** argv) coreset = &pdcch_cfg.coreset[0]; pdcch_cfg.coreset_present[0] = true; - srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_15kHz; - srsran_subcarrier_spacing_t pdcch_scs = srsran_subcarrier_spacing_15kHz; - - uint32_t ssb_pointA_freq_offset_Hz = 0; - - if (srsran_coreset_zero(carrier.pci, ssb_pointA_freq_offset_Hz, ssb_scs, pdcch_scs, coreset0_idx, coreset) != + // derive absolute frequencies from ARFCNs + srsran::srsran_band_helper band_helper; + carrier.ssb_center_freq_hz = band_helper.nr_arfcn_to_freq(ssb_arfcn); + carrier.dl_center_frequency_hz = band_helper.nr_arfcn_to_freq(dl_arfcn); + + // Get pointA and SSB absolute frequencies + double pointA_abs_freq_Hz = + carrier.dl_center_frequency_hz - carrier.nof_prb * SRSRAN_NRE * SRSRAN_SUBC_SPACING_NR(carrier.scs) / 2; + double ssb_abs_freq_Hz = 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; + + // derive coreset0 parameters + if (srsran_coreset_zero(carrier.pci, ssb_pointA_freq_offset_Hz, carrier.scs, carrier.scs, coreset0_idx, coreset) != SRSRAN_SUCCESS) { printf("Not possible to create CORESET Zero (ssb_scs=%s, pdcch_scs=%s, idx=%d)", - srsran_subcarrier_spacing_to_str(ssb_scs), - srsran_subcarrier_spacing_to_str(pdcch_scs), + srsran_subcarrier_spacing_to_str(carrier.scs), + srsran_subcarrier_spacing_to_str(carrier.scs), coreset0_idx); - return false; + return clean_exit(ret); } - // FIXME: use ssb_pointA_freq_offset_Hz and let srsran_coreset_zero() calculate this - coreset->offset_rb = coreset_offset_rb; } else { // configure to use coreset1 coreset = &pdcch_cfg.coreset[1]; @@ -288,7 +329,6 @@ int main(int argc, char** argv) search_space->id = 0; search_space->coreset_id = (rnti_type == srsran_rnti_type_si) ? 0 : 1; search_space->type = ss_type; - // search_space->duration = coreset->duration; search_space->formats[0] = srsran_dci_format_nr_0_0; search_space->formats[1] = srsran_dci_format_nr_1_0; search_space->nof_formats = 2; @@ -303,18 +343,18 @@ int main(int argc, char** argv) if (srsran_ue_dl_nr_init(&ue_dl, buffer, &ue_dl_args)) { ERROR("Error UE DL"); - goto clean_exit; + return clean_exit(ret); } if (srsran_ue_dl_nr_set_carrier(&ue_dl, &carrier)) { ERROR("Error setting SCH NR carrier"); - goto clean_exit; + return clean_exit(ret); } // Read baseband from file if (srsran_filesource_read(&filesource, buffer[0], (int)ue_dl.fft->sf_sz) < SRSRAN_SUCCESS) { ERROR("Error reading baseband"); - goto clean_exit; + return clean_exit(ret); } srsran_dci_cfg_nr_t dci_cfg = {}; @@ -323,22 +363,12 @@ int main(int argc, char** argv) dci_cfg.monitor_common_0_0 = true; if (srsran_ue_dl_nr_set_pdcch_config(&ue_dl, &pdcch_cfg, &dci_cfg)) { ERROR("Error setting CORESET"); - goto clean_exit; + return clean_exit(ret); } // Actual decode ret = work_ue_dl(&ue_dl, &slot_cfg); -clean_exit: - if (buffer[0] != NULL) { - free(buffer[0]); - } - if (data != NULL) { - free(data); - } - srsran_ue_dl_nr_free(&ue_dl); - srsran_filesource_free(&filesource); - srsran_softbuffer_rx_free(&softbuffer); - - return ret; + // free memory and return last value of ret + return clean_exit(ret); } From 830cbcde08cdf00ba7ac57989e9b52059d6b8407 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Thu, 11 Nov 2021 12:06:33 +0100 Subject: [PATCH 38/77] ue_dl_nr_file_test: add all zero PDSCH check and let test fail in this case --- lib/src/phy/ue/test/ue_dl_nr_file_test.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc index 31cfbd0ea..e7b671294 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc @@ -214,6 +214,19 @@ static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot) printf("Decoded PDSCH (%d B)\n", pdsch_cfg.grant.tb[0].tbs / 8); srsran_vec_fprint_byte(stdout, pdsch_res.tb[0].payload, pdsch_cfg.grant.tb[0].tbs / 8); + // check payload is not all null + bool all_zero = true; + for (uint32_t i = 0; i < pdsch_cfg.grant.tb[0].tbs / 8; ++i) { + if (pdsch_res.tb[0].payload[i] != 0x0) { + all_zero = false; + break; + } + } + if (all_zero) { + ERROR("PDSCH payload is all zeros"); + return SRSRAN_ERROR; + } + return SRSRAN_SUCCESS; } From d2fc11fbec4a2851c6e6f21d81aa1c6fa8a32fac Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Thu, 11 Nov 2021 12:06:58 +0100 Subject: [PATCH 39/77] ue_dl_nr_file_test: add missing parameter to existing file tests --- lib/src/phy/ue/test/CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/phy/ue/test/CMakeLists.txt b/lib/src/phy/ue/test/CMakeLists.txt index a55e339a3..6dab8aa99 100644 --- a/lib/src/phy/ue/test/CMakeLists.txt +++ b/lib/src/phy/ue/test/CMakeLists.txt @@ -47,12 +47,12 @@ endif(RF_FOUND) add_executable(ue_dl_nr_file_test ue_dl_nr_file_test.cc) target_link_libraries(ue_dl_nr_file_test srsran_phy srsran_common pthread) foreach (n RANGE 0 9) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234) + add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) endforeach () add_test(ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0.dat -i 1 -P 52 -n 4 -R 7f) add_test(ue_dl_nr_pci500_rb52_si ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 7 -o 2 -s common0 -A 161200 -a 161290) From 16b5f4c3db52cc656bcc1d99b7ad96c02c586202 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Thu, 11 Nov 2021 13:12:02 +0100 Subject: [PATCH 40/77] ue_dl_nr_file_test: add further IQ capture with coreset0_idx=6 Cell 0x01: nr_arfcn=368500 ul_nr_arfcn=349500 pci=500 mode=FDD rat=nr n_rb_dl=52 n_rb_ul=52 dl_mu=0 ul_mu=0 ssb_mu=0 ssb_arfcn=368410 ssb_prb=13:21 k_ssb=6 coreset0_prb=1:48 coreset0_idx=6 12:58:46.197 [PHY] DL - 01 ffff 556.0 PDSCH: harq=si prb=1:7 symb=2:12 CW0: tb_len=84 mod=2 rv_idx=2 cr=0.44 0000: 74 81 01 70 10 40 04 02 00 00 c8 00 24 68 a0 38 t..p.@......$h.8 0010: 05 01 00 40 1a 00 00 06 6c 6d 92 21 f3 70 40 20 ...@....lm.!.p@ 0020: 00 00 80 80 00 41 06 80 a0 90 9c 20 08 55 19 40 .....A..... .U.@ 0030: 00 00 33 a1 c6 d9 22 40 10 00 1e b8 94 63 c0 09 ..3..."@.....c.. 0040: 28 c4 1b 8a 36 e1 5b 1c 3a 01 3c 5b 46 14 00 00 (...6.[.:.<[F... 0050: 00 00 00 00 .... 12:58:46.197 [PHY] DL - 01 ffff 556.0 PDCCH: ss_id=0 cce_index=0 al=4 dci=1_0 rb_alloc=0x120 time_domain_rsc=0 vrb_to_prb_map=0 mcs=6 rv_idx=2 si_indicator=0 dci_len=39 --- ...r_pci500_rb52_si_coreset0_idx6_s15.36e6.dat | Bin 0 -> 122880 bytes ..._pci500_rb52_si_coreset0_idx7_s15.36e6.dat} | Bin 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat rename lib/src/phy/ue/test/{ue_dl_nr_pci500_rb52_si_s15.36e6.dat => ue_dl_nr_pci500_rb52_si_coreset0_idx7_s15.36e6.dat} (100%) diff --git a/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat b/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat new file mode 100644 index 0000000000000000000000000000000000000000..69a6e594d21de7f049219e2d924cf82c3c48b7bd GIT binary patch literal 122880 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 literal 0 HcmV?d00001 diff --git a/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_s15.36e6.dat b/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx7_s15.36e6.dat similarity index 100% rename from lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_s15.36e6.dat rename to lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx7_s15.36e6.dat From 7a64163aa4b1830ea87f664189b0af7b69226faf Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Thu, 11 Nov 2021 13:16:09 +0100 Subject: [PATCH 41/77] ue_dl_nr_file_test: enable coreset0_idx=6 file test --- lib/src/phy/ue/test/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/phy/ue/test/CMakeLists.txt b/lib/src/phy/ue/test/CMakeLists.txt index 6dab8aa99..9daaddf9c 100644 --- a/lib/src/phy/ue/test/CMakeLists.txt +++ b/lib/src/phy/ue/test/CMakeLists.txt @@ -55,5 +55,6 @@ foreach (n RANGE 0 9) add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) endforeach () add_test(ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0.dat -i 1 -P 52 -n 4 -R 7f) -add_test(ue_dl_nr_pci500_rb52_si ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 7 -o 2 -s common0 -A 161200 -a 161290) +add_test(ue_dl_nr_pci500_rb52_si_coreset0_idx6 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 6 -s common0 -A 368500 -a 368410) +add_test(ue_dl_nr_pci500_rb52_si_coreset0_idx7 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_coreset0_idx7_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 7 -s common0 -A 161200 -a 161290) add_test(ue_dl_nr_pci500_rb52_pdsch ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -o 2 -s common3) \ No newline at end of file From 972e080e489b779a48d475a388b29aecb8cae705 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Thu, 11 Nov 2021 20:52:05 +0100 Subject: [PATCH 42/77] ue_dl_nr_file_test: update SSB/SIB TV update capture and make sure its rv=0 // Cell 0x01: nr_arfcn=368500 ul_nr_arfcn=349500 pci=500 mode=FDD rat=nr n_rb_dl=52 n_rb_ul=52 dl_mu=0 ul_mu=0 ssb_mu=0 ssb_arfcn=368410 ssb_prb=13:21 k_ssb=6 coreset0_prb=1:48 coreset0_idx=6 20:43:31.997 [PHY] DL - 01 ffff 784.0 PDSCH: harq=si prb=1:7 symb=2:12 CW0: tb_len=84 mod=2 rv_idx=0 cr=0.44 0000: 74 81 01 70 10 40 04 02 00 00 c8 00 24 68 a0 38 t..p.@......$h.8 0010: 05 01 00 40 1a 00 00 06 6c 6d 92 21 f3 70 40 20 ...@....lm.!.p@ 0020: 00 00 80 80 00 41 06 80 a0 90 9c 20 08 55 19 40 .....A..... .U.@ 0030: 00 00 33 a1 c6 d9 22 40 10 00 1e b8 94 63 c0 09 ..3..."@.....c.. 0040: 28 c4 1b 8a 36 e1 5b 1c 3a 01 3c 5b 46 14 00 00 (...6.[.:.<[F... 0050: 00 00 00 00 .... 20:43:31.997 [PHY] DL - 01 ffff 784.0 PDCCH: ss_id=0 cce_index=0 al=4 dci=1_0 rb_alloc=0x120 time_domain_rsc=0 vrb_to_prb_map=0 mcs=6 rv_idx=0 si_indicator=0 dci_len=39 --- ..._pci500_rb52_si_coreset0_idx6_s15.36e6.dat | Bin 122880 -> 122880 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat b/lib/src/phy/ue/test/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat index 69a6e594d21de7f049219e2d924cf82c3c48b7bd..9c35a565f37300e9188af4d1446e5d1cf2dc3ca5 100644 GIT binary patch delta 88464 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

)r@a5jRPjpiMkj~jdd5=8TQzjM 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 V_>cJk{(lV+{Kx!&%>M-l{s-ccB^m$# delta 88464 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ś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℞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 Date: Fri, 12 Nov 2021 10:17:21 +0100 Subject: [PATCH 43/77] ue_dl_nr_file_test: fix coreset0 bandwidth when decoding SI-RNTI --- lib/src/phy/ue/test/ue_dl_nr_file_test.cc | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc index e7b671294..eef8b4b68 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc @@ -211,6 +211,11 @@ static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot) return SRSRAN_ERROR; } + if (!pdsch_res.tb[0].crc) { + ERROR("Error decoding PDSCH"); + return SRSRAN_ERROR; + } + printf("Decoded PDSCH (%d B)\n", pdsch_cfg.grant.tb[0].tbs / 8); srsran_vec_fprint_byte(stdout, pdsch_res.tb[0].payload, pdsch_cfg.grant.tb[0].tbs / 8); @@ -295,6 +300,12 @@ int main(int argc, char** argv) return clean_exit(ret); } + // initial DCI config + srsran_dci_cfg_nr_t dci_cfg = {}; + dci_cfg.bwp_dl_initial_bw = carrier.nof_prb; + dci_cfg.bwp_ul_initial_bw = carrier.nof_prb; + dci_cfg.monitor_common_0_0 = true; + srsran_coreset_t* coreset = NULL; // Configure CORESET @@ -325,6 +336,12 @@ int main(int argc, char** argv) coreset0_idx); return clean_exit(ret); } + + // Setup PDSCH DMRS (also signaled through MIB) + pdsch_hl_cfg.typeA_pos = srsran_dmrs_sch_typeA_pos_2; + + // set coreset0 bandwidth + dci_cfg.coreset0_bw = srsran_coreset_get_bw(coreset); } else { // configure to use coreset1 coreset = &pdcch_cfg.coreset[1]; @@ -370,10 +387,6 @@ int main(int argc, char** argv) return clean_exit(ret); } - srsran_dci_cfg_nr_t dci_cfg = {}; - dci_cfg.bwp_dl_initial_bw = carrier.nof_prb; - dci_cfg.bwp_ul_initial_bw = carrier.nof_prb; - dci_cfg.monitor_common_0_0 = true; if (srsran_ue_dl_nr_set_pdcch_config(&ue_dl, &pdcch_cfg, &dci_cfg)) { ERROR("Error setting CORESET"); return clean_exit(ret); From 800933c38e11a2fb77a491a19fa9c323a8d3b2ca Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 12 Nov 2021 10:22:08 +0100 Subject: [PATCH 44/77] slot_worker: disable baseband dump by default and don't exit after finishing --- srsenb/src/phy/nr/slot_worker.cc | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/srsenb/src/phy/nr/slot_worker.cc b/srsenb/src/phy/nr/slot_worker.cc index fd67f4537..8bfc578be 100644 --- a/srsenb/src/phy/nr/slot_worker.cc +++ b/srsenb/src/phy/nr/slot_worker.cc @@ -14,11 +14,12 @@ #include "srsran/common/buffer_pool.h" #include "srsran/common/common.h" -#define DEBUG_WRITE_FILE +//#define DEBUG_WRITE_FILE #ifdef DEBUG_WRITE_FILE FILE* f; static uint32_t num_slots = 0; +static uint32_t slots_to_dump = 10; #endif namespace srsenb { @@ -422,15 +423,13 @@ void slot_worker::work_imp() common.worker_end(context, true, tx_rf_buffer); #ifdef DEBUG_WRITE_FILE - fwrite(tx_rf_buffer.get(0), tx_rf_buffer.get_nof_samples() * sizeof(cf_t), 1, f); -#endif - -#ifdef DEBUG_WRITE_FILE - if (num_slots == 30) { + if (num_slots++ < slots_to_dump) { + printf("Writing slot %d\n", dl_slot_cfg.idx); + fwrite(tx_rf_buffer.get(0), tx_rf_buffer.get_nof_samples() * sizeof(cf_t), 1, f); + } else if (num_slots == slots_to_dump) { + printf("Baseband signaled dump finished. Please close app.\n"); fclose(f); - exit(-1); } - num_slots++; #endif } From 573b2f657a71fd7b8857a928510ce8ce72bdc7b8 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 12 Nov 2021 13:16:18 +0100 Subject: [PATCH 45/77] ssb_file_test: make duplex and SSB SCS configurable, add new unit test for FDD+15kHz SCS --- lib/src/phy/sync/test/CMakeLists.txt | 4 ++- lib/src/phy/sync/test/ssb_file_test.c | 37 ++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lib/src/phy/sync/test/CMakeLists.txt b/lib/src/phy/sync/test/CMakeLists.txt index 83741dd5a..bba52f5ea 100644 --- a/lib/src/phy/sync/test/CMakeLists.txt +++ b/lib/src/phy/sync/test/CMakeLists.txt @@ -164,4 +164,6 @@ target_link_libraries(ssb_file_test srsran_phy) # File test 1 # 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 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) +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 diff --git a/lib/src/phy/sync/test/ssb_file_test.c b/lib/src/phy/sync/test/ssb_file_test.c index 377c4aa24..46baed8f3 100644 --- a/lib/src/phy/sync/test/ssb_file_test.c +++ b/lib/src/phy/sync/test/ssb_file_test.c @@ -29,7 +29,7 @@ static char* filename = NULL; static double srate_hz = 23.04e6; // Base-band sampling rate in Hz static double center_freq_hz = NAN; // Center frequency in Hz static double ssb_freq_hz = NAN; // SSB frequency in Hz -static uint32_t nof_samples = 0; // Number of half-frames +static uint32_t nof_samples = 0; // Number of samples // Assertion static bool assert = false; @@ -44,9 +44,11 @@ static void usage(char* prog) { printf("Usage: %s -i filename [rv]\n", prog); printf("\t-r sampling rate in Hz [Default %.2f MHz]\n", srate_hz / 1e6); + printf("\t-n number of samples [Default %d]\n", nof_samples); + printf("\t-s SSB subcarrier spacing (15, 30) [Default %s]\n", srsran_subcarrier_spacing_to_str(ssb_scs)); + printf("\t-d duplex mode [Default %s]\n", duplex_mode == SRSRAN_DUPLEX_MODE_FDD ? "FDD" : "TDD"); printf("\t-f absolute baseband center frequency in Hz [Default %.2f MHz]\n", center_freq_hz / 1e3); printf("\t-F absolute SSB center freuqency in Hz [Default %.2f MHz]\n", ssb_freq_hz / 1e3); - printf("\t-F absolute SSB center freuqency in Hz [Default %.2f MHz]\n", ssb_freq_hz / 1e3); printf("\t-A Assert: PCI t_offset sfn_lsb ssb_idx ssb_k hrf"); printf("\t-v [set srsran_verbose to debug, default none]\n"); } @@ -54,7 +56,7 @@ static void usage(char* prog) static void parse_args(int argc, char** argv) { int opt; - while ((opt = getopt(argc, argv, "inrfFAv")) != -1) { + while ((opt = getopt(argc, argv, "insdrfFAv")) != -1) { switch (opt) { case 'i': filename = argv[optind]; @@ -62,6 +64,24 @@ static void parse_args(int argc, char** argv) case 'n': nof_samples = (uint32_t)strtol(argv[optind], NULL, 10); break; + case 's': + if ((uint32_t)strtol(argv[optind], NULL, 10) == 15) { + ssb_scs = srsran_subcarrier_spacing_15kHz; + } else { + ssb_scs = srsran_subcarrier_spacing_30kHz; + } + break; + case 'd': + if (strcmp(argv[optind], "tdd") == 0) { + duplex_mode = SRSRAN_DUPLEX_MODE_TDD; + } else if (strcmp(argv[optind], "fdd") == 0) { + duplex_mode = SRSRAN_DUPLEX_MODE_FDD; + } else { + printf("Invalid duplex mode '%s'\n", argv[optind]); + usage(argv[0]); + exit(-1); + } + break; case 'r': srate_hz = strtod(argv[optind], NULL); break; @@ -195,6 +215,17 @@ int main(int argc, char** argv) str, search_res.pbch_msg.crc ? "OK" : "KO"); + // unpack MIB + srsran_mib_nr_t mib = {}; + if (srsran_pbch_msg_nr_mib_unpack(&search_res.pbch_msg, &mib) < SRSRAN_SUCCESS) { + ERROR("Error unpacking PBCH-MIB"); + goto clean_exit; + } + + char mib_info[512] = {}; + srsran_pbch_msg_nr_mib_info(&mib, mib_info, sizeof(mib_info)); + INFO("PBCH-MIB: %s", mib_info); + // Assert search if (assert) { if (assert_search(&search_res)) { From d5a00fcdadf5bba3f9f70c792f8d479bf7d47213 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 12 Nov 2021 13:26:20 +0100 Subject: [PATCH 46/77] dci_nr_test: add test for DCI 1_0 with SI-RNTI --- lib/src/phy/phch/test/dci_nr_test.c | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/src/phy/phch/test/dci_nr_test.c b/lib/src/phy/phch/test/dci_nr_test.c index 48b54f238..b480241a8 100644 --- a/lib/src/phy/phch/test/dci_nr_test.c +++ b/lib/src/phy/phch/test/dci_nr_test.c @@ -91,8 +91,10 @@ static int test_52prb_base() TESTASSERT(srsran_dci_nr_size(&dci, srsran_search_space_type_ue, srsran_dci_format_nr_0_1) == 36); TESTASSERT(srsran_dci_nr_size(&dci, srsran_search_space_type_ue, srsran_dci_format_nr_1_1) == 41); TESTASSERT(srsran_dci_nr_size(&dci, srsran_search_space_type_rar, srsran_dci_format_nr_rar) == 27); + TESTASSERT(srsran_dci_nr_size(&dci, srsran_search_space_type_common_0, srsran_dci_format_nr_1_0) == 39); srsran_dci_ctx_t ctx = {}; + ctx.rnti = 0x1234; ctx.ss_type = srsran_search_space_type_common_3; ctx.rnti_type = srsran_rnti_type_c; @@ -349,6 +351,43 @@ static int test_52prb_base() TESTASSERT(memcmp(&dci_tx, &dci_rx, sizeof(srsran_dci_dl_nr_t)) == 0); } + // Test DL DCI 1_0 Packing/Unpacking and info for SI-RNTI + ctx.format = srsran_dci_format_nr_1_0; + ctx.rnti = 0xffff; + ctx.ss_type = srsran_search_space_type_common_0; + ctx.rnti_type = srsran_rnti_type_si; + dci.cfg.coreset0_bw = 48; + + for (uint32_t i = 0; i < nof_repetitions; i++) { + srsran_dci_dl_nr_t dci_tx = {}; + dci_tx.ctx = ctx; + dci_tx.freq_domain_assigment = 0x120; + dci_tx.time_domain_assigment = 0; + dci_tx.vrb_to_prb_mapping = 0; + dci_tx.mcs = srsran_random_uniform_int_dist(random_gen, 0, 31); + dci_tx.rv = srsran_random_uniform_int_dist(random_gen, 0, 3); + dci_tx.sii = 1; // bit set to 1 indicates SI message other than SIB1 + dci_tx.coreset0_bw = 48; + + // Pack + srsran_dci_msg_nr_t dci_msg = {}; + TESTASSERT(srsran_dci_nr_dl_pack(&dci, &dci_tx, &dci_msg) == SRSRAN_SUCCESS); + + // Unpack + srsran_dci_dl_nr_t dci_rx = {}; + TESTASSERT(srsran_dci_nr_dl_unpack(&dci, &dci_msg, &dci_rx) == SRSRAN_SUCCESS); + + // To string + char str[512]; + TESTASSERT(srsran_dci_dl_nr_to_str(&dci, &dci_tx, str, (uint32_t)sizeof(str)) != 0); + INFO("Tx: %s", str); + TESTASSERT(srsran_dci_dl_nr_to_str(&dci, &dci_rx, str, (uint32_t)sizeof(str)) != 0); + INFO("Rx: %s", str); + + // Assert + TESTASSERT(memcmp(&dci_tx, &dci_rx, sizeof(srsran_dci_dl_nr_t)) == 0); + } + return SRSRAN_SUCCESS; } From 9c6f9f5949584a9bf002dbf1c06941491fb94e0a Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 12 Nov 2021 13:27:27 +0100 Subject: [PATCH 47/77] dci_nr: print coreset0_bw when DCI is scrambled with SI-RNTI --- lib/src/phy/phch/dci_nr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/phy/phch/dci_nr.c b/lib/src/phy/phch/dci_nr.c index b617b8ce9..7e71f41ea 100644 --- a/lib/src/phy/phch/dci_nr.c +++ b/lib/src/phy/phch/dci_nr.c @@ -1273,6 +1273,7 @@ static uint32_t dci_nr_format_1_0_to_str(const srsran_dci_dl_nr_t* dci, char* st // System information indicator – 1 bit if (rnti_type == srsran_rnti_type_si) { len = srsran_print_check(str, str_len, len, "sii=%d ", dci->sii); + len = srsran_print_check(str, str_len, len, "coreset0_bw=%d ", dci->coreset0_bw); } // Downlink assignment index – 2 bits From e7c2cea032cac798f773c6f2f1b41300bdc60a35 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 12 Nov 2021 20:23:34 +0100 Subject: [PATCH 48/77] Fix data conversion parse in ue_dl_nr_file_test --- lib/src/phy/ue/test/ue_dl_nr_file_test.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc index eef8b4b68..cc85dbfa6 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc @@ -130,10 +130,10 @@ static int parse_args(int argc, char** argv) coreset_len = (uint16_t)strtol(argv[optind], NULL, 10); break; case 'A': - dl_arfcn = (uint16_t)strtol(argv[optind], NULL, 10); + dl_arfcn = (uint32_t)strtol(argv[optind], NULL, 10); break; case 'a': - ssb_arfcn = (uint16_t)strtol(argv[optind], NULL, 10); + ssb_arfcn = (uint32_t)strtol(argv[optind], NULL, 10); break; case 'S': srsran_use_standard_symbol_size(true); @@ -221,7 +221,7 @@ static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot) // check payload is not all null bool all_zero = true; - for (uint32_t i = 0; i < pdsch_cfg.grant.tb[0].tbs / 8; ++i) { + for (int i = 0; i < pdsch_cfg.grant.tb[0].tbs / 8; ++i) { if (pdsch_res.tb[0].payload[i] != 0x0) { all_zero = false; break; From 798e9b7a5e0ac75f6005b2422eb23c865b7bdb24 Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 12 Nov 2021 20:24:52 +0100 Subject: [PATCH 49/77] ue_dl: reference point for DMRS varies for SIB --- lib/include/srsran/phy/phch/phch_cfg_nr.h | 2 ++ lib/src/phy/ch_estimation/dmrs_sch.c | 6 ++++-- lib/src/phy/ue/test/ue_dl_nr_file_test.cc | 8 ++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/include/srsran/phy/phch/phch_cfg_nr.h b/lib/include/srsran/phy/phch/phch_cfg_nr.h index cd76ecc0b..09b4ea114 100644 --- a/lib/include/srsran/phy/phch/phch_cfg_nr.h +++ b/lib/include/srsran/phy/phch/phch_cfg_nr.h @@ -77,6 +77,8 @@ typedef struct { srsran_dmrs_sch_typeA_pos_t typeA_pos; bool lte_CRS_to_match_around; + uint32_t reference_point_k_rb; + /// Parameters provided by FeatureSetDownlink-v1540 bool additional_DMRS_DL_Alt; diff --git a/lib/src/phy/ch_estimation/dmrs_sch.c b/lib/src/phy/ch_estimation/dmrs_sch.c index 629e61b2f..3956e08fd 100644 --- a/lib/src/phy/ch_estimation/dmrs_sch.c +++ b/lib/src/phy/ch_estimation/dmrs_sch.c @@ -216,7 +216,8 @@ static int srsran_dmrs_sch_put_symbol(srsran_dmrs_sch_t* q, // ... save first consecutive PRB in the group prb_start = prb_idx; - // ... discard unused pilots and reset counter + // ... discard unused pilots and reset counter unless the PDSCH transmission carries SIB + prb_skip = SRSRAN_MAX(0, (int)prb_skip - (int)dmrs_cfg->reference_point_k_rb); srsran_sequence_state_advance(&sequence_state, prb_skip * nof_pilots_x_prb * 2); prb_skip = 0; } @@ -704,7 +705,8 @@ static int srsran_dmrs_sch_get_symbol(srsran_dmrs_sch_t* q, // ... save first consecutive PRB in the group prb_start = prb_idx; - // ... discard unused pilots and reset counter + // ... discard unused pilots and reset counter unless the PDSCH transmission carries SIB + prb_skip = SRSRAN_MAX(0, (int)prb_skip - (int)dmrs_cfg->reference_point_k_rb); srsran_sequence_state_advance(&sequence_state, prb_skip * nof_pilots_x_prb * 2); prb_skip = 0; } diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc index cc85dbfa6..2f41bac4b 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc @@ -205,6 +205,14 @@ static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot) srsran_pdsch_res_nr_t pdsch_res = {}; pdsch_res.tb[0].payload = data; + // See 7.4.1.1.2 38.211 The reference point for k is + // - for PDSCH transmission carrying SIB1, subcarrier 0 of the lowest-numbered common resource block in the + // CORESET configured by the PBCH + //- otherwise, subcarrier 0 in common resource block 0 + if (rnti_type == srsran_rnti_type_si) { + pdsch_cfg.dmrs.reference_point_k_rb = pdcch_cfg.coreset[0].offset_rb; + } + // Decode PDSCH if (srsran_ue_dl_nr_decode_pdsch(ue_dl, slot, &pdsch_cfg, &pdsch_res) < SRSRAN_SUCCESS) { ERROR("Error decoding PDSCH search"); From 66871b1e8ed635d225c3e71e080dcceebb79508d Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 12 Nov 2021 21:05:22 +0100 Subject: [PATCH 50/77] ue_dl_nr_file_test: disable failing file tests temporarily they will be enabled again as soon as the decoding issues are solved --- lib/src/phy/ue/test/CMakeLists.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/src/phy/ue/test/CMakeLists.txt b/lib/src/phy/ue/test/CMakeLists.txt index 9daaddf9c..9f611f57e 100644 --- a/lib/src/phy/ue/test/CMakeLists.txt +++ b/lib/src/phy/ue/test/CMakeLists.txt @@ -47,14 +47,14 @@ endif(RF_FOUND) add_executable(ue_dl_nr_file_test ue_dl_nr_file_test.cc) target_link_libraries(ue_dl_nr_file_test srsran_phy srsran_common pthread) foreach (n RANGE 0 9) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) - add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + #add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + #add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + #add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + #add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + #add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) + #add_test(ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci1_rb25_n${n}_common_L1_ncce0.dat -i 1 -P 25 -n ${n} -R 1234 -l 2) endforeach () -add_test(ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0.dat -i 1 -P 52 -n 4 -R 7f) +#add_test(ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_n4_ra_L2_ncce0.dat -i 1 -P 52 -n 4 -R 7f) add_test(ue_dl_nr_pci500_rb52_si_coreset0_idx6 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_coreset0_idx6_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 6 -s common0 -A 368500 -a 368410) -add_test(ue_dl_nr_pci500_rb52_si_coreset0_idx7 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_coreset0_idx7_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 7 -s common0 -A 161200 -a 161290) -add_test(ue_dl_nr_pci500_rb52_pdsch ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -o 2 -s common3) \ No newline at end of file +#add_test(ue_dl_nr_pci500_rb52_si_coreset0_idx7 ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_si_coreset0_idx7_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -c 7 -s common0 -A 161200 -a 161290) +#add_test(ue_dl_nr_pci500_rb52_pdsch ue_dl_nr_file_test -f ${CMAKE_CURRENT_SOURCE_DIR}/ue_dl_nr_pci500_rb52_rnti0x100_s15.36e6.dat -S -i 500 -P 52 -n 0 -R ffff -T si -o 2 -s common3) \ No newline at end of file From 883ddef4a68beae6ae089267860deab664a36c23 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Fri, 12 Nov 2021 21:38:37 +0100 Subject: [PATCH 51/77] ue_dl_nr_file_test: fix compilation on gcc 4.8 --- lib/src/phy/ue/test/ue_dl_nr_file_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc index 2f41bac4b..d144c4274 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc @@ -11,6 +11,7 @@ */ #ifdef __cplusplus +#include extern "C" { #include "srsran/phy/io/filesource.h" #include "srsran/phy/phch/ra_nr.h" From 4836e90b2c843c5971c111c0fa475e8783d9670c Mon Sep 17 00:00:00 2001 From: Ismael Gomez Date: Fri, 12 Nov 2021 22:28:16 +0100 Subject: [PATCH 52/77] Move place to apply reference_point_k_rb to dmrs --- lib/src/phy/phch/ra_nr.c | 8 ++++++++ lib/src/phy/ue/test/ue_dl_nr_file_test.cc | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/phy/phch/ra_nr.c b/lib/src/phy/phch/ra_nr.c index f265578ea..a94f65949 100644 --- a/lib/src/phy/phch/ra_nr.c +++ b/lib/src/phy/phch/ra_nr.c @@ -559,6 +559,14 @@ static int ra_dl_dmrs(const srsran_sch_hl_cfg_nr_t* hl_cfg, const srsran_dci_dl_ (cfg->grant.mapping == srsran_sch_mapping_type_A) ? hl_cfg->dmrs_typeA.present : hl_cfg->dmrs_typeB.present; if (dci->ctx.format == srsran_dci_format_nr_1_0 || !dedicated_dmrs_present) { + // The reference point for k is + // - for PDSCH transmission carrying SIB1, subcarrier 0 of the lowest-numbered common resource block in the + // CORESET configured by the PBCH + //- otherwise, subcarrier 0 in common resource block 0 + if (dci->ctx.rnti_type == srsran_rnti_type_si) { + cfg->dmrs.reference_point_k_rb = dci->ctx.coreset_start_rb; + } + if (cfg->grant.mapping == srsran_sch_mapping_type_A) { // Absent default values are defined is TS 38.331 - DMRS-DownlinkConfig cfg->dmrs.additional_pos = srsran_dmrs_sch_add_pos_2; diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc index d144c4274..1358b8c10 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc @@ -206,14 +206,6 @@ static int work_ue_dl(srsran_ue_dl_nr_t* ue_dl, srsran_slot_cfg_t* slot) srsran_pdsch_res_nr_t pdsch_res = {}; pdsch_res.tb[0].payload = data; - // See 7.4.1.1.2 38.211 The reference point for k is - // - for PDSCH transmission carrying SIB1, subcarrier 0 of the lowest-numbered common resource block in the - // CORESET configured by the PBCH - //- otherwise, subcarrier 0 in common resource block 0 - if (rnti_type == srsran_rnti_type_si) { - pdsch_cfg.dmrs.reference_point_k_rb = pdcch_cfg.coreset[0].offset_rb; - } - // Decode PDSCH if (srsran_ue_dl_nr_decode_pdsch(ue_dl, slot, &pdsch_cfg, &pdsch_res) < SRSRAN_SUCCESS) { ERROR("Error decoding PDSCH search"); From e68bdf37184bd92064d80986485fe1f8e8a39121 Mon Sep 17 00:00:00 2001 From: Pedro Alvarez Date: Mon, 9 Aug 2021 17:48:50 +0100 Subject: [PATCH 53/77] Added support to sending error indication when receiving some S1AP messages in the wrong state --- srsenb/src/stack/s1ap/s1ap.cc | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/srsenb/src/stack/s1ap/s1ap.cc b/srsenb/src/stack/s1ap/s1ap.cc index 12d598e30..928686110 100644 --- a/srsenb/src/stack/s1ap/s1ap.cc +++ b/srsenb/src/stack/s1ap/s1ap.cc @@ -712,6 +712,13 @@ bool s1ap::handle_unsuccessfuloutcome(const unsuccessful_outcome_s& msg) bool s1ap::handle_s1setupresponse(const asn1::s1ap::s1_setup_resp_s& msg) { + if (s1setup_proc.is_idle()) { + asn1::s1ap::cause_c cause; + cause.set_protocol().value = cause_protocol_opts::msg_not_compatible_with_receiver_state; + send_error_indication(cause); + return false; + } + s1setupresponse = msg; mme_connected = true; s1_setup_proc_t::s1setupresult res; @@ -1086,6 +1093,13 @@ bool s1ap::handle_uectxtreleasecommand(const ue_context_release_cmd_s& msg) bool s1ap::handle_s1setupfailure(const asn1::s1ap::s1_setup_fail_s& msg) { + if (s1setup_proc.is_idle()) { + asn1::s1ap::cause_c cause; + cause.set_protocol().value = cause_protocol_opts::msg_not_compatible_with_receiver_state; + send_error_indication(cause); + return false; + } + std::string cause = get_cause(msg.protocol_ies.cause.value); logger.error("S1 Setup Failure. Cause: %s", cause.c_str()); srsran::console("S1 Setup Failure. Cause: %s\n", cause.c_str()); @@ -1099,6 +1113,14 @@ bool s1ap::handle_handover_preparation_failure(const ho_prep_fail_s& msg) if (u == nullptr) { return false; } + + if (u->ho_prep_proc.is_idle()) { + asn1::s1ap::cause_c cause; + cause.set_protocol().value = cause_protocol_opts::msg_not_compatible_with_receiver_state; + send_error_indication(cause); + return false; + } + u->ho_prep_proc.trigger(msg); return true; } @@ -1110,6 +1132,13 @@ bool s1ap::handle_handover_command(const asn1::s1ap::ho_cmd_s& msg) if (u == nullptr) { return false; } + + if (u->ho_prep_proc.is_idle()) { + asn1::s1ap::cause_c cause; + cause.set_protocol().value = cause_protocol_opts::msg_not_compatible_with_receiver_state; + send_error_indication(cause); + return false; + } u->ho_prep_proc.trigger(msg); return true; } From 92c4e6e2d0443bac4affafb9705a98d1368c20ba Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 11 Nov 2021 20:50:00 +0000 Subject: [PATCH 54/77] nr,gnb: account for CORESET#0 RB offset (See 38.213, table 13-1) in computation of SSB center frequency --- lib/include/srsran/common/band_helper.h | 19 ++++++++++++-- lib/src/common/band_helper.cc | 24 +++++++++++------ lib/src/common/test/band_helper_test.cc | 2 ++ srsenb/src/enb_cfg_parser.cc | 35 ++++++++++++++++++++----- srsgnb/src/stack/rrc/rrc_nr.cc | 2 +- 5 files changed, 64 insertions(+), 18 deletions(-) diff --git a/lib/include/srsran/common/band_helper.h b/lib/include/srsran/common/band_helper.h index 4712ff13d..e59eeaff1 100644 --- a/lib/include/srsran/common/band_helper.h +++ b/lib/include/srsran/common/band_helper.h @@ -126,14 +126,29 @@ public: /** * @brief Compute the absolute frequency of the SSB for a DL ARFCN and a band. This selects an SSB center frequency + * following the band SS/PBCH frequency raster provided by TS38.104 table 5.4.3.1-1, which is the upper bound + * of the provided center frequency + * + * @param scs ssb subcarrier spacing. + * @param min_center_freq_hz center frequency above which the SSB absolute frequency must be. + * @return absolute frequency of the SSB in arfcn notation. + */ + uint32_t find_lower_bound_abs_freq_ssb(uint16_t band, srsran_subcarrier_spacing_t scs, uint32_t min_center_freq_hz); + + /** + * @brief Compute the absolute frequency of the SSB for a DL ARFCN and a band. This finds an SSB center frequency * following the band SS/PBCH frequency raster provided by TS38.104 table 5.4.3.1-1 as close as possible to PointA - * without letting any SS/PBCH subcarrier below PointA + * without letting any SS/PBCH subcarrier and CORESET#0 subcarrier (if RB offset is defined) below PointA * * @param scs ssb subcarrier spacing. * @param freq_point_a_arfcn frequency point a in arfcn notation. + * @param coreset0_offset_rb CORESET#0 RB offset. See TS 38.213, Table 13-1,2,3 * @return absolute frequency of the SSB in arfcn notation. */ - uint32_t get_abs_freq_ssb_arfcn(uint16_t band, srsran_subcarrier_spacing_t scs, uint32_t freq_point_a_arfcn); + uint32_t get_abs_freq_ssb_arfcn(uint16_t band, + srsran_subcarrier_spacing_t scs, + uint32_t freq_point_a_arfcn, + uint32_t coreset0_offset_rb = 0); /** * @brief Compute SSB center frequency for NR carrier diff --git a/lib/src/common/band_helper.cc b/lib/src/common/band_helper.cc index e4dd86756..5513837a0 100644 --- a/lib/src/common/band_helper.cc +++ b/lib/src/common/band_helper.cc @@ -133,23 +133,20 @@ double srsran_band_helper::get_abs_freq_point_a_from_center_freq(uint32_t nof_pr SRSRAN_NRE); } -uint32_t -srsran_band_helper::get_abs_freq_ssb_arfcn(uint16_t band, srsran_subcarrier_spacing_t scs, uint32_t freq_point_a_arfcn) +uint32_t srsran_band_helper::find_lower_bound_abs_freq_ssb(uint16_t band, + srsran_subcarrier_spacing_t scs, + uint32_t min_center_freq_hz) { sync_raster_t sync_raster = get_sync_raster(band, scs); if (!sync_raster.valid()) { return 0; } - // double abs_freq_ssb_hz = sync_raster.get_frequency(); - double freq_point_a_hz = nr_arfcn_to_freq(freq_point_a_arfcn); - double ssb_bw_hz = SRSRAN_SSB_BW_SUBC * SRSRAN_SUBC_SPACING_NR(scs); - while (!sync_raster.end()) { double abs_freq_ssb_hz = sync_raster.get_frequency(); - if ((abs_freq_ssb_hz > (freq_point_a_hz + ssb_bw_hz / 2)) and - ((uint32_t)std::round(abs_freq_ssb_hz - freq_point_a_hz) % SRSRAN_SUBC_SPACING_NR(scs) == 0)) { + if ((abs_freq_ssb_hz > min_center_freq_hz) and + ((uint32_t)std::round(abs_freq_ssb_hz - min_center_freq_hz) % SRSRAN_SUBC_SPACING_NR(scs) == 0)) { return freq_to_nr_arfcn(abs_freq_ssb_hz); } @@ -158,6 +155,17 @@ srsran_band_helper::get_abs_freq_ssb_arfcn(uint16_t band, srsran_subcarrier_spac return 0; } +uint32_t srsran_band_helper::get_abs_freq_ssb_arfcn(uint16_t band, + srsran_subcarrier_spacing_t scs, + uint32_t freq_point_a_arfcn, + uint32_t coreset0_offset_rb) +{ + double freq_point_a_hz = nr_arfcn_to_freq(freq_point_a_arfcn); + double ssb_bw_hz = SRSRAN_SSB_BW_SUBC * SRSRAN_SUBC_SPACING_NR(scs); + double coreset0_offset_hz = coreset0_offset_rb * SRSRAN_NRE * SRSRAN_SUBC_SPACING_NR(scs); + return find_lower_bound_abs_freq_ssb(band, scs, freq_point_a_hz + coreset0_offset_hz / 2 + ssb_bw_hz / 2); +} + srsran_ssb_patern_t srsran_band_helper::get_ssb_pattern(uint16_t band, srsran_subcarrier_spacing_t scs) const { // Look for the given band and SCS diff --git a/lib/src/common/test/band_helper_test.cc b/lib/src/common/test/band_helper_test.cc index 2ef31ced7..1ade62532 100644 --- a/lib/src/common/test/band_helper_test.cc +++ b/lib/src/common/test/band_helper_test.cc @@ -47,9 +47,11 @@ int bands_test_nr() TESTASSERT(bands.nr_arfcn_to_freq(342000) == 1710.0e6); TESTASSERT(bands.nr_arfcn_to_freq(348000) == 1740.0e6); TESTASSERT(bands.nr_arfcn_to_freq(361000) == 1805.0e6); + TESTASSERT_EQ(1842.5e6, bands.nr_arfcn_to_freq(368500)); TESTASSERT(bands.nr_arfcn_to_freq(376000) == 1880.0e6); TESTASSERT(bands.get_abs_freq_point_a_arfcn(52, 368500) == 367564); TESTASSERT(bands.get_abs_freq_ssb_arfcn(3, srsran_subcarrier_spacing_15kHz, 367564) > 367924); + TESTASSERT_EQ(368410, bands.get_abs_freq_ssb_arfcn(3, srsran_subcarrier_spacing_15kHz, 367564, 16)); // n5 TESTASSERT(bands.get_duplex_mode(5) == SRSRAN_DUPLEX_MODE_FDD); TESTASSERT(bands.nr_arfcn_to_freq(176300) == 881.5e6); diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 68f847d79..9be7d392f 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -1161,6 +1161,21 @@ int parse_cfg_files(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr return SRSRAN_ERROR; } + // update number of NR cells + rrc_cfg_->num_nr_cells = rrc_nr_cfg_->cell_list.size(); + args_->rf.nof_carriers = rrc_cfg_->cell_list.size() + rrc_nr_cfg_->cell_list.size(); + ASSERT_VALID_CFG(args_->rf.nof_carriers > 0, "There must be at least one NR or LTE cell"); + if (rrc_nr_cfg_->cell_list.size() > 0) { + // NR cells available. + if (rrc_cfg_->cell_list.size() == 0) { + // SA mode. + rrc_nr_cfg_->is_standalone = true; + } else { + // NSA mode. + rrc_nr_cfg_->is_standalone = false; + } + } + // Set fields derived from others, and check for correctness of the parsed configuration if (enb_conf_sections::set_derived_args(args_, rrc_cfg_, phy_cfg_, cell_common_cfg) != SRSRAN_SUCCESS) { fprintf(stderr, "Error deriving EUTRA cell parameters\n"); @@ -1174,20 +1189,15 @@ int parse_cfg_files(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr } // update number of NR cells - rrc_cfg_->num_nr_cells = rrc_nr_cfg_->cell_list.size(); - args_->rf.nof_carriers = rrc_cfg_->cell_list.size() + rrc_nr_cfg_->cell_list.size(); - ASSERT_VALID_CFG(args_->rf.nof_carriers > 0, "There must be at least one NR or LTE cell"); if (rrc_nr_cfg_->cell_list.size() > 0) { // NR cells available. - if (rrc_cfg_->cell_list.size() == 0) { + if (rrc_nr_cfg_->is_standalone) { // SA mode. Update NGAP args - rrc_nr_cfg_->is_standalone = true; args_->nr_stack.ngap.gnb_id = args_->enb.enb_id; args_->nr_stack.ngap.cell_id = rrc_nr_cfg_->cell_list[0].phy_cell.cell_id; args_->nr_stack.ngap.tac = rrc_nr_cfg_->cell_list[0].tac; } else { // NSA mode. - rrc_nr_cfg_->is_standalone = false; // update EUTRA RRC params for ENDC rrc_cfg_->endc_cfg.abs_frequency_ssb = rrc_nr_cfg_->cell_list.at(0).ssb_absolute_freq_point; rrc_cfg_->endc_cfg.nr_band = rrc_nr_cfg_->cell_list.at(0).band; @@ -1633,8 +1643,19 @@ int set_derived_args_nr(all_args_t* args_, rrc_nr_cfg_t* rrc_nr_cfg_, phy_cfg_t* } // fill remaining SSB fields + uint32_t coreset0_rb_offset = 0; + if (rrc_nr_cfg_->is_standalone) { + // Taken from TS 38.213, Table 13-1 + if (cfg.phy_cell.carrier.nof_prb > 96) { + coreset0_rb_offset = 96; + } else if (cfg.phy_cell.carrier.nof_prb > 48) { + coreset0_rb_offset = 16; + } else { + coreset0_rb_offset = 4; + } + } cfg.ssb_absolute_freq_point = - band_helper.get_abs_freq_ssb_arfcn(cfg.band, cfg.ssb_cfg.scs, cfg.dl_absolute_freq_point_a); + band_helper.get_abs_freq_ssb_arfcn(cfg.band, cfg.ssb_cfg.scs, cfg.dl_absolute_freq_point_a, coreset0_rb_offset); if (cfg.ssb_absolute_freq_point == 0) { ERROR("Can't derive SSB freq point for dl_arfcn %d and band %d", cfg.dl_arfcn, cfg.band); return SRSRAN_ERROR; diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index b7430d63c..7c11d055e 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -51,7 +51,7 @@ int rrc_nr::init(const rrc_nr_cfg_t& cfg_, cfg = cfg_; if (cfg.is_standalone) { // Generate parameters of Coreset#0 and SS#0 - const uint32_t coreset0_idx = 7; + const uint32_t coreset0_idx = 6; cfg.cell_list[0].phy_cell.pdcch.coreset_present[0] = true; // Get pointA and SSB absolute frequencies double pointA_abs_freq_Hz = cfg.cell_list[0].phy_cell.carrier.dl_center_frequency_hz - From 0b8a6970f052256f0d45373296ddf14c77bab4f1 Mon Sep 17 00:00:00 2001 From: Francisco Date: Sun, 14 Nov 2021 18:59:01 +0000 Subject: [PATCH 55/77] nr,gnb,rrc: fix ssb frequency derivation when coreset0 is active --- lib/include/srsran/phy/common/phy_common_nr.h | 10 +++++ lib/src/common/band_helper.cc | 2 +- lib/src/common/test/band_helper_test.cc | 2 +- lib/src/phy/common/phy_common_nr.c | 42 +++++++++++++++++++ srsenb/src/enb_cfg_parser.cc | 23 +++++----- srsgnb/src/stack/rrc/rrc_nr.cc | 2 +- 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/lib/include/srsran/phy/common/phy_common_nr.h b/lib/include/srsran/phy/common/phy_common_nr.h index c8612c317..8e6c6cea8 100644 --- a/lib/include/srsran/phy/common/phy_common_nr.h +++ b/lib/include/srsran/phy/common/phy_common_nr.h @@ -704,6 +704,16 @@ SRSRAN_API int srsran_coreset_zero(uint32_t n_cell_id, uint32_t idx, srsran_coreset_t* coreset); +/** + * @brief Obtain offset in RBs between CoresetZero and SSB. See TS 38.213, Tables 13-{1,...,10} + * @param idx Index of 13-{1,...10} table + * @param ssb_scs SS/PBCH block subcarrier spacing + * @param pdcch_scs PDCCH subcarrier spacing + * @return offset in RBs, or -1 in case of invalid inputs + */ +SRSRAN_API int +srsran_coreset0_ssb_offset(uint32_t idx, srsran_subcarrier_spacing_t ssb_scs, srsran_subcarrier_spacing_t pdcch_scs); + /** * @brief Convert SSB pattern to string * @param pattern diff --git a/lib/src/common/band_helper.cc b/lib/src/common/band_helper.cc index 5513837a0..5eee021a8 100644 --- a/lib/src/common/band_helper.cc +++ b/lib/src/common/band_helper.cc @@ -163,7 +163,7 @@ uint32_t srsran_band_helper::get_abs_freq_ssb_arfcn(uint16_t double freq_point_a_hz = nr_arfcn_to_freq(freq_point_a_arfcn); double ssb_bw_hz = SRSRAN_SSB_BW_SUBC * SRSRAN_SUBC_SPACING_NR(scs); double coreset0_offset_hz = coreset0_offset_rb * SRSRAN_NRE * SRSRAN_SUBC_SPACING_NR(scs); - return find_lower_bound_abs_freq_ssb(band, scs, freq_point_a_hz + coreset0_offset_hz / 2 + ssb_bw_hz / 2); + return find_lower_bound_abs_freq_ssb(band, scs, freq_point_a_hz + coreset0_offset_hz + ssb_bw_hz / 2); } srsran_ssb_patern_t srsran_band_helper::get_ssb_pattern(uint16_t band, srsran_subcarrier_spacing_t scs) const diff --git a/lib/src/common/test/band_helper_test.cc b/lib/src/common/test/band_helper_test.cc index 1ade62532..4e5b86c00 100644 --- a/lib/src/common/test/band_helper_test.cc +++ b/lib/src/common/test/band_helper_test.cc @@ -51,7 +51,7 @@ int bands_test_nr() TESTASSERT(bands.nr_arfcn_to_freq(376000) == 1880.0e6); TESTASSERT(bands.get_abs_freq_point_a_arfcn(52, 368500) == 367564); TESTASSERT(bands.get_abs_freq_ssb_arfcn(3, srsran_subcarrier_spacing_15kHz, 367564) > 367924); - TESTASSERT_EQ(368410, bands.get_abs_freq_ssb_arfcn(3, srsran_subcarrier_spacing_15kHz, 367564, 16)); + TESTASSERT_EQ(368410, bands.get_abs_freq_ssb_arfcn(3, srsran_subcarrier_spacing_15kHz, 367564, 12)); // n5 TESTASSERT(bands.get_duplex_mode(5) == SRSRAN_DUPLEX_MODE_FDD); TESTASSERT(bands.nr_arfcn_to_freq(176300) == 881.5e6); diff --git a/lib/src/phy/common/phy_common_nr.c b/lib/src/phy/common/phy_common_nr.c index b3b2e3360..acb3f7fdf 100644 --- a/lib/src/phy/common/phy_common_nr.c +++ b/lib/src/phy/common/phy_common_nr.c @@ -682,6 +682,48 @@ int srsran_coreset_zero(uint32_t n_cell_id, return SRSRAN_SUCCESS; } +int srsran_coreset0_ssb_offset(uint32_t idx, srsran_subcarrier_spacing_t ssb_scs, srsran_subcarrier_spacing_t pdcch_scs) +{ + // Verify inputs + if (idx >= 16) { + ERROR("Invalid CORESET Zero input. idx=%d", idx); + return SRSRAN_ERROR_INVALID_INPUTS; + } + + // Default entry to NULL + const coreset_zero_entry_t* entry = NULL; + + // Table 13-1: Set of resource blocks and slot symbols of CORESET for Type0-PDCCH search space set + // when {SS/PBCH block, PDCCH} SCS is {15, 15} kHz for frequency bands with minimum channel + // bandwidth 5 MHz or 10 MHz + if (ssb_scs == srsran_subcarrier_spacing_15kHz && pdcch_scs == srsran_subcarrier_spacing_15kHz) { + entry = &coreset_zero_15_15[idx]; + } + // Table 13-2: Set of resource blocks and slot symbols of CORESET for Type0-PDCCH search space set + // when {SS/PBCH block, PDCCH} SCS is {15, 30} kHz for frequency bands with minimum channel + // bandwidth 5 MHz or 10 MHz + if (ssb_scs == srsran_subcarrier_spacing_15kHz && pdcch_scs == srsran_subcarrier_spacing_30kHz) { + entry = &coreset_zero_15_30[idx]; + } + + // Table 13-3: Set of resource blocks and slot symbols of CORESET for Type0-PDCCH search space set + // when {SS/PBCH block, PDCCH} SCS is {30, 15} kHz for frequency bands with minimum channel + // bandwidth 5 MHz or 10 MHz + if (ssb_scs == srsran_subcarrier_spacing_30kHz && pdcch_scs == srsran_subcarrier_spacing_15kHz) { + entry = &coreset_zero_30_15[idx]; + } + + // Check a valid entry has been selected + if (entry == NULL) { + ERROR("Unhandled case ssb_scs=%s, pdcch_scs=%s", + srsran_subcarrier_spacing_to_str(ssb_scs), + srsran_subcarrier_spacing_to_str(pdcch_scs)); + return SRSRAN_ERROR; + } + + return entry->offset_rb; +} + const char* srsran_ssb_pattern_to_str(srsran_ssb_patern_t pattern) { switch (pattern) { diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 9be7d392f..d30357f2b 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -1643,23 +1643,20 @@ int set_derived_args_nr(all_args_t* args_, rrc_nr_cfg_t* rrc_nr_cfg_, phy_cfg_t* } // fill remaining SSB fields - uint32_t coreset0_rb_offset = 0; + int coreset0_rb_offset = 0; if (rrc_nr_cfg_->is_standalone) { - // Taken from TS 38.213, Table 13-1 - if (cfg.phy_cell.carrier.nof_prb > 96) { - coreset0_rb_offset = 96; - } else if (cfg.phy_cell.carrier.nof_prb > 48) { - coreset0_rb_offset = 16; - } else { - coreset0_rb_offset = 4; - } + const uint32_t coreset0_idx = 6; // See TS 38.331 - controlResourceSetZero / Table 13-1 index + cfg.phy_cell.pdcch.coreset_present[0] = true; + // Get offset in RBs between CORESET#0 and SSB + coreset0_rb_offset = srsran_coreset0_ssb_offset(coreset0_idx, cfg.ssb_cfg.scs, cfg.phy_cell.carrier.scs); + srsran_assert(coreset0_rb_offset >= 0, "Failed to compute RB offset between CORESET#0 and SSB"); } cfg.ssb_absolute_freq_point = band_helper.get_abs_freq_ssb_arfcn(cfg.band, cfg.ssb_cfg.scs, cfg.dl_absolute_freq_point_a, coreset0_rb_offset); - if (cfg.ssb_absolute_freq_point == 0) { - ERROR("Can't derive SSB freq point for dl_arfcn %d and band %d", cfg.dl_arfcn, cfg.band); - return SRSRAN_ERROR; - } + srsran_assert(cfg.ssb_absolute_freq_point > 0, + "Can't derive SSB freq point for dl_arfcn %d and band %d", + cfg.dl_arfcn, + cfg.band); // Convert to frequency for PHY cfg.phy_cell.carrier.ssb_center_freq_hz = band_helper.nr_arfcn_to_freq(cfg.ssb_absolute_freq_point); diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index 7c11d055e..f26fa7b2c 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -51,7 +51,7 @@ int rrc_nr::init(const rrc_nr_cfg_t& cfg_, cfg = cfg_; if (cfg.is_standalone) { // Generate parameters of Coreset#0 and SS#0 - const uint32_t coreset0_idx = 6; + const uint32_t coreset0_idx = 6; // See TS 38.331 - controlResourceSetZero cfg.cell_list[0].phy_cell.pdcch.coreset_present[0] = true; // Get pointA and SSB absolute frequencies double pointA_abs_freq_Hz = cfg.cell_list[0].phy_cell.carrier.dl_center_frequency_hz - From f2a5f00bb33c0c777a586496519b25c75c7e9caf Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 15 Nov 2021 09:40:54 +0000 Subject: [PATCH 56/77] nr,gnb: fix abs freq ssb arfcn derivation --- lib/src/common/band_helper.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/common/band_helper.cc b/lib/src/common/band_helper.cc index 5eee021a8..29f9a7251 100644 --- a/lib/src/common/band_helper.cc +++ b/lib/src/common/band_helper.cc @@ -142,10 +142,11 @@ uint32_t srsran_band_helper::find_lower_bound_abs_freq_ssb(uint16_t return 0; } + double ssb_bw_hz = SRSRAN_SSB_BW_SUBC * SRSRAN_SUBC_SPACING_NR(scs); while (!sync_raster.end()) { double abs_freq_ssb_hz = sync_raster.get_frequency(); - if ((abs_freq_ssb_hz > min_center_freq_hz) and + if ((abs_freq_ssb_hz > min_center_freq_hz + ssb_bw_hz / 2) and ((uint32_t)std::round(abs_freq_ssb_hz - min_center_freq_hz) % SRSRAN_SUBC_SPACING_NR(scs) == 0)) { return freq_to_nr_arfcn(abs_freq_ssb_hz); } @@ -161,9 +162,8 @@ uint32_t srsran_band_helper::get_abs_freq_ssb_arfcn(uint16_t uint32_t coreset0_offset_rb) { double freq_point_a_hz = nr_arfcn_to_freq(freq_point_a_arfcn); - double ssb_bw_hz = SRSRAN_SSB_BW_SUBC * SRSRAN_SUBC_SPACING_NR(scs); double coreset0_offset_hz = coreset0_offset_rb * SRSRAN_NRE * SRSRAN_SUBC_SPACING_NR(scs); - return find_lower_bound_abs_freq_ssb(band, scs, freq_point_a_hz + coreset0_offset_hz + ssb_bw_hz / 2); + return find_lower_bound_abs_freq_ssb(band, scs, freq_point_a_hz + coreset0_offset_hz); } srsran_ssb_patern_t srsran_band_helper::get_ssb_pattern(uint16_t band, srsran_subcarrier_spacing_t scs) const From fec97689a25ec381ea9f129399962aa1f67ebce4 Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 15 Nov 2021 20:31:15 +0000 Subject: [PATCH 57/77] rrc,nr,gnb: refactored rrc nr cfg default and derived param generation to be mostly independent of parser --- lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h | 2 +- lib/src/rrc/nr/rrc_nr_cfg_utils.cc | 7 +- srsenb/src/CMakeLists.txt | 2 +- srsenb/src/enb_cfg_parser.cc | 128 +------- srsgnb/hdr/stack/rrc/cell_asn1_config.h | 2 +- srsgnb/hdr/stack/rrc/rrc_nr.h | 2 +- .../rrc/{rrc_config_nr.h => rrc_nr_config.h} | 7 +- srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h | 28 ++ srsgnb/src/stack/rrc/CMakeLists.txt | 6 +- srsgnb/src/stack/rrc/cell_asn1_config.cc | 6 +- srsgnb/src/stack/rrc/rrc_nr.cc | 31 -- srsgnb/src/stack/rrc/rrc_nr_config_utils.cc | 292 ++++++++++++++++++ srsgnb/src/stack/rrc/test/CMakeLists.txt | 2 +- srsgnb/src/stack/rrc/test/rrc_nr_test.cc | 2 +- 14 files changed, 354 insertions(+), 163 deletions(-) rename srsgnb/hdr/stack/rrc/{rrc_config_nr.h => rrc_nr_config.h} (92%) create mode 100644 srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h create mode 100644 srsgnb/src/stack/rrc/rrc_nr_config_utils.cc diff --git a/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h b/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h index e3d0f0e4b..745742d26 100644 --- a/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h +++ b/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h @@ -32,7 +32,7 @@ void generate_default_pdcch_cfg_common(asn1::rrc_nr::pdcch_cfg_common_s& cfg, co void generate_default_init_dl_bwp(asn1::rrc_nr::bwp_dl_common_s& cfg, const basic_cell_args_t& args = {}); void generate_default_dl_cfg_common(asn1::rrc_nr::dl_cfg_common_s& cfg, const basic_cell_args_t& args = {}); -void generate_default_mib(const basic_cell_args_t& args, asn1::rrc_nr::mib_s& cfg); +void generate_default_mib(uint32_t pdcch_scs, uint32_t coreset0_idx, asn1::rrc_nr::mib_s& cfg); void generate_default_serv_cell_cfg_common_sib(const basic_cell_args_t& args, asn1::rrc_nr::serving_cell_cfg_common_sib_s& cfg); diff --git a/lib/src/rrc/nr/rrc_nr_cfg_utils.cc b/lib/src/rrc/nr/rrc_nr_cfg_utils.cc index 205ac9ba9..5327e997b 100644 --- a/lib/src/rrc/nr/rrc_nr_cfg_utils.cc +++ b/lib/src/rrc/nr/rrc_nr_cfg_utils.cc @@ -172,14 +172,15 @@ void generate_default_serv_cell_cfg_common_sib(const basic_cell_args_t& args, se cfg.ss_pbch_block_pwr = -16; } -void generate_default_mib(const basic_cell_args_t& args, mib_s& cfg) +void generate_default_mib(uint32_t pdcch_scs, uint32_t coreset0_idx, mib_s& cfg) { - asn1::number_to_enum(cfg.sub_carrier_spacing_common, args.scs); + bool ret = asn1::number_to_enum(cfg.sub_carrier_spacing_common, pdcch_scs); + srsran_assert(ret, "Invalid SCS=%d kHz", pdcch_scs); cfg.ssb_subcarrier_offset = 0; cfg.intra_freq_resel.value = mib_s::intra_freq_resel_opts::allowed; cfg.cell_barred.value = mib_s::cell_barred_opts::not_barred; cfg.pdcch_cfg_sib1.search_space_zero = 0; - cfg.pdcch_cfg_sib1.ctrl_res_set_zero = 0; + cfg.pdcch_cfg_sib1.ctrl_res_set_zero = coreset0_idx; cfg.dmrs_type_a_position.value = mib_s::dmrs_type_a_position_opts::pos2; cfg.sys_frame_num.from_number(0); } diff --git a/srsenb/src/CMakeLists.txt b/srsenb/src/CMakeLists.txt index ae3250670..2db4196cb 100644 --- a/srsenb/src/CMakeLists.txt +++ b/srsenb/src/CMakeLists.txt @@ -22,7 +22,7 @@ if (RPATH) endif (RPATH) add_library(enb_cfg_parser STATIC parser.cc enb_cfg_parser.cc) -target_link_libraries(enb_cfg_parser srsran_common ${LIBCONFIGPP_LIBRARIES}) +target_link_libraries(enb_cfg_parser srsran_common srsgnb_rrc_config_utils ${LIBCONFIGPP_LIBRARIES}) add_executable(srsenb main.cc enb.cc metrics_stdout.cc metrics_csv.cc metrics_json.cc) diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index d30357f2b..7a771886c 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -12,6 +12,7 @@ #include "enb_cfg_parser.h" #include "srsenb/hdr/enb.h" +#include "srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h" #include "srsran/asn1/rrc_utils.h" #include "srsran/common/band_helper.h" #include "srsran/common/multiqueue.h" @@ -961,8 +962,10 @@ static int parse_cell_list(all_args_t* args, rrc_cfg_t* rrc_cfg, Setting& root) static int parse_nr_cell_list(all_args_t* args, rrc_nr_cfg_t* rrc_cfg_nr, rrc_cfg_t* rrc_cfg_eutra, Setting& root) { for (uint32_t n = 0; n < (uint32_t)root.getLength(); ++n) { + auto& cellroot = root[n]; + rrc_cell_cfg_nr_t cell_cfg = {}; - auto& cellroot = root[n]; + generate_default_nr_cell(cell_cfg); parse_opt_field(cell_cfg.phy_cell.rf_port, cellroot, "rf_port"); HANDLEPARSERCODE(parse_required_field(cell_cfg.phy_cell.carrier.pci, cellroot, "pci")); @@ -1530,7 +1533,8 @@ int set_derived_args_nr(all_args_t* args_, rrc_nr_cfg_t* rrc_nr_cfg_, phy_cfg_t* // Create NR dedicated cell configuration from RRC configuration for (auto it = rrc_nr_cfg_->cell_list.begin(); it != rrc_nr_cfg_->cell_list.end(); ++it) { - auto& cfg = *it; + auto& cfg = *it; + cfg.phy_cell.carrier.max_mimo_layers = args_->enb.nof_ports; // NR cells have the same bandwidth as EUTRA cells, adjust PRB sizes @@ -1548,130 +1552,22 @@ int set_derived_args_nr(all_args_t* args_, rrc_nr_cfg_t* rrc_nr_cfg_, phy_cfg_t* ERROR("The only accepted number of PRB is: 25, 50, 100"); return SRSRAN_ERROR; } - // phy_cell_cfg.root_seq_idx = cfg.root_seq_idx; - cfg.phy_cell.num_ra_preambles = 52; // FIXME: read from config - - if (cfg.phy_cell.dl_freq_hz == 0) { - cfg.phy_cell.dl_freq_hz = band_helper.nr_arfcn_to_freq(cfg.dl_arfcn); - } - if (cfg.phy_cell.ul_freq_hz == 0) { - // auto-detect UL frequency - if (cfg.ul_arfcn == 0) { - // derive UL ARFCN from given DL ARFCN - cfg.ul_arfcn = band_helper.get_ul_arfcn_from_dl_arfcn(cfg.dl_arfcn); - if (cfg.ul_arfcn == 0) { - ERROR("Can't derive UL ARFCN from DL ARFCN %d", cfg.dl_arfcn); - return SRSRAN_ERROR; - } - } - cfg.phy_cell.ul_freq_hz = band_helper.nr_arfcn_to_freq(cfg.ul_arfcn); + // Derive cross-dependent cell params + if (set_derived_nr_cell_params(rrc_nr_cfg_->is_standalone, cfg) != SRSRAN_SUCCESS) { + ERROR("Failed to derive NR cell params."); + return SRSRAN_ERROR; } - // duplex mode - cfg.duplex_mode = band_helper.get_duplex_mode(cfg.band); + // phy_cell_cfg.root_seq_idx = cfg.root_seq_idx; // PRACH - cfg.phy_cell.prach.is_nr = true; - cfg.phy_cell.prach.config_idx = 8; - cfg.phy_cell.prach.root_seq_idx = 0; - cfg.phy_cell.prach.freq_offset = 1; - cfg.phy_cell.prach.num_ra_preambles = cfg.phy_cell.num_ra_preambles; - cfg.phy_cell.prach.hs_flag = phy_cfg_->prach_cnfg.prach_cfg_info.high_speed_flag; - cfg.phy_cell.prach.tdd_config.configured = (cfg.duplex_mode == SRSRAN_DUPLEX_MODE_TDD); - - // PDCCH - // Configure CORESET ID 1 - cfg.phy_cell.pdcch.coreset_present[1] = true; - cfg.phy_cell.pdcch.coreset[1].id = 1; - cfg.phy_cell.pdcch.coreset[1].duration = 1; - cfg.phy_cell.pdcch.coreset[1].mapping_type = srsran_coreset_mapping_type_non_interleaved; - cfg.phy_cell.pdcch.coreset[1].precoder_granularity = srsran_coreset_precoder_granularity_reg_bundle; - - // Generate frequency resources for the full BW - for (uint32_t i = 0; i < SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { - cfg.phy_cell.pdcch.coreset[1].freq_resources[i] = i < SRSRAN_FLOOR(cfg.phy_cell.carrier.nof_prb, 6); - } - - // Configure Search Space 1 as common - cfg.phy_cell.pdcch.search_space_present[1] = true; - cfg.phy_cell.pdcch.search_space[1].id = 1; - cfg.phy_cell.pdcch.search_space[1].coreset_id = 1; - cfg.phy_cell.pdcch.search_space[1].duration = 1; - cfg.phy_cell.pdcch.search_space[1].formats[0] = srsran_dci_format_nr_0_0; // DCI format for PUSCH - cfg.phy_cell.pdcch.search_space[1].formats[1] = srsran_dci_format_nr_1_0; // DCI format for PDSCH - cfg.phy_cell.pdcch.search_space[1].nof_formats = 2; - cfg.phy_cell.pdcch.search_space[1].type = srsran_search_space_type_common_3; - - // Generate 1 candidate for each aggregation level if possible - for (uint32_t L = 0; L < SRSRAN_SEARCH_SPACE_NOF_AGGREGATION_LEVELS_NR; L++) { - cfg.phy_cell.pdcch.search_space[1].nof_candidates[L] = - SRSRAN_MIN(2, srsran_pdcch_nr_max_candidates_coreset(&cfg.phy_cell.pdcch.coreset[1], L)); - } - - cfg.phy_cell.pdcch.ra_search_space_present = true; - cfg.phy_cell.pdcch.ra_search_space = cfg.phy_cell.pdcch.search_space[1]; - cfg.phy_cell.pdcch.ra_search_space.type = srsran_search_space_type_common_1; + cfg.phy_cell.prach.hs_flag = phy_cfg_->prach_cnfg.prach_cfg_info.high_speed_flag; // PDSCH cfg.phy_cell.pdsch.rs_power = phy_cfg_->pdsch_cnfg.ref_sig_pwr; cfg.phy_cell.pdsch.p_b = phy_cfg_->pdsch_cnfg.p_b; - // copy center frequencies - cfg.phy_cell.carrier.dl_center_frequency_hz = cfg.phy_cell.dl_freq_hz; - cfg.phy_cell.carrier.ul_center_frequency_hz = cfg.phy_cell.ul_freq_hz; - - cfg.dl_absolute_freq_point_a = band_helper.get_abs_freq_point_a_arfcn(cfg.phy_cell.carrier.nof_prb, cfg.dl_arfcn); - cfg.ul_absolute_freq_point_a = band_helper.get_abs_freq_point_a_arfcn(cfg.phy_cell.carrier.nof_prb, cfg.ul_arfcn); - - // Calculate SSB params depending on band/duplex - cfg.ssb_cfg.duplex_mode = band_helper.get_duplex_mode(cfg.band); - cfg.ssb_cfg.pattern = band_helper.get_ssb_pattern(cfg.band, srsran_subcarrier_spacing_15kHz); - if (cfg.ssb_cfg.pattern == SRSRAN_SSB_PATTERN_A) { - // 15kHz SSB SCS - cfg.ssb_cfg.scs = srsran_subcarrier_spacing_15kHz; - } else { - // try to optain SSB pattern for same band with 30kHz SCS - cfg.ssb_cfg.pattern = band_helper.get_ssb_pattern(cfg.band, srsran_subcarrier_spacing_30kHz); - if (cfg.ssb_cfg.pattern == SRSRAN_SSB_PATTERN_B || cfg.ssb_cfg.pattern == SRSRAN_SSB_PATTERN_C) { - // SSB SCS is 30 kHz - cfg.ssb_cfg.scs = srsran_subcarrier_spacing_30kHz; - } else { - ERROR("Can't derive SSB pattern for band %d", cfg.band); - return SRSRAN_ERROR; - } - } - - // fill remaining SSB fields - int coreset0_rb_offset = 0; - if (rrc_nr_cfg_->is_standalone) { - const uint32_t coreset0_idx = 6; // See TS 38.331 - controlResourceSetZero / Table 13-1 index - cfg.phy_cell.pdcch.coreset_present[0] = true; - // Get offset in RBs between CORESET#0 and SSB - coreset0_rb_offset = srsran_coreset0_ssb_offset(coreset0_idx, cfg.ssb_cfg.scs, cfg.phy_cell.carrier.scs); - srsran_assert(coreset0_rb_offset >= 0, "Failed to compute RB offset between CORESET#0 and SSB"); - } - cfg.ssb_absolute_freq_point = - band_helper.get_abs_freq_ssb_arfcn(cfg.band, cfg.ssb_cfg.scs, cfg.dl_absolute_freq_point_a, coreset0_rb_offset); - srsran_assert(cfg.ssb_absolute_freq_point > 0, - "Can't derive SSB freq point for dl_arfcn %d and band %d", - cfg.dl_arfcn, - cfg.band); - - // Convert to frequency for PHY - cfg.phy_cell.carrier.ssb_center_freq_hz = band_helper.nr_arfcn_to_freq(cfg.ssb_absolute_freq_point); - - cfg.ssb_cfg.center_freq_hz = cfg.phy_cell.carrier.dl_center_frequency_hz; - cfg.ssb_cfg.ssb_freq_hz = cfg.phy_cell.carrier.ssb_center_freq_hz; - cfg.ssb_cfg.periodicity_ms = 10; // TODO: make a param - cfg.ssb_cfg.beta_pss = 0.0; - cfg.ssb_cfg.beta_sss = 0.0; - cfg.ssb_cfg.beta_pbch = 0.0; - cfg.ssb_cfg.beta_pbch_dmrs = 0.0; - // set by PHY layer in worker_pool::set_common_cfg - cfg.ssb_cfg.srate_hz = 0.0; - cfg.ssb_cfg.scaling = 0.0; - phy_cfg_->phy_cell_cfg_nr.push_back(cfg.phy_cell); } diff --git a/srsgnb/hdr/stack/rrc/cell_asn1_config.h b/srsgnb/hdr/stack/rrc/cell_asn1_config.h index b9a2d8f06..202f18a49 100644 --- a/srsgnb/hdr/stack/rrc/cell_asn1_config.h +++ b/srsgnb/hdr/stack/rrc/cell_asn1_config.h @@ -13,7 +13,7 @@ #ifndef SRSRAN_CELL_ASN1_CONFIG_H #define SRSRAN_CELL_ASN1_CONFIG_H -#include "rrc_config_nr.h" +#include "rrc_nr_config.h" #include "srsran/asn1/rrc_nr.h" namespace srsenb { diff --git a/srsgnb/hdr/stack/rrc/rrc_nr.h b/srsgnb/hdr/stack/rrc/rrc_nr.h index ab3cd2a3b..9f3b3c39c 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr.h @@ -16,7 +16,7 @@ #include "srsenb/hdr/stack/enb_stack_base.h" #include "srsenb/hdr/stack/rrc/rrc_config_common.h" #include "srsenb/hdr/stack/rrc/rrc_metrics.h" -#include "srsgnb/hdr/stack/rrc/rrc_config_nr.h" +#include "srsgnb/hdr/stack/rrc/rrc_nr_config.h" #include "srsran/asn1/rrc_nr.h" #include "srsran/common/block_queue.h" #include "srsran/common/buffer_pool.h" diff --git a/srsgnb/hdr/stack/rrc/rrc_config_nr.h b/srsgnb/hdr/stack/rrc/rrc_nr_config.h similarity index 92% rename from srsgnb/hdr/stack/rrc/rrc_config_nr.h rename to srsgnb/hdr/stack/rrc/rrc_nr_config.h index 5a67958f5..fcae77a11 100644 --- a/srsgnb/hdr/stack/rrc/rrc_config_nr.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_config.h @@ -10,8 +10,8 @@ * */ -#ifndef SRSRAN_RRC_CONFIG_NR_H -#define SRSRAN_RRC_CONFIG_NR_H +#ifndef SRSRAN_RRC_NR_CONFIG_H +#define SRSRAN_RRC_NR_CONFIG_H #include "srsenb/hdr/phy/phy_interfaces.h" #include "srsenb/hdr/stack/rrc/rrc_config_common.h" @@ -40,6 +40,7 @@ struct rrc_cell_cfg_nr_t { uint32_t ul_absolute_freq_point_a; // derived from UL ARFCN uint32_t ssb_absolute_freq_point; // derived from DL ARFCN uint32_t band; + uint32_t coreset0_idx; // Table 13-{1,...15} row index srsran_duplex_mode_t duplex_mode; srsran_ssb_cfg_t ssb_cfg; }; @@ -61,4 +62,4 @@ struct rrc_nr_cfg_t { } // namespace srsenb -#endif // SRSRAN_RRC_CONFIG_NR_H +#endif // SRSRAN_RRC_NR_CONFIG_H diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h b/srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h new file mode 100644 index 000000000..6d63646f6 --- /dev/null +++ b/srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h @@ -0,0 +1,28 @@ +/** + * + * \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 SRSRAN_RRC_NR_CONFIG_DEFAULT_H +#define SRSRAN_RRC_NR_CONFIG_DEFAULT_H + +#include "rrc_nr_config.h" + +namespace srsenb { + +void generate_default_nr_cell(rrc_cell_cfg_nr_t& cell); + +int set_derived_nr_cell_params(bool is_sa, rrc_cell_cfg_nr_t& cell); + +int check_nr_cell_cfg_valid(const rrc_cell_cfg_nr_t& cell, bool is_sa); + +} // namespace srsenb + +#endif // SRSRAN_RRC_NR_CONFIG_DEFAULT_H diff --git a/srsgnb/src/stack/rrc/CMakeLists.txt b/srsgnb/src/stack/rrc/CMakeLists.txt index 0e63798ee..3b1cdb05d 100644 --- a/srsgnb/src/stack/rrc/CMakeLists.txt +++ b/srsgnb/src/stack/rrc/CMakeLists.txt @@ -6,9 +6,13 @@ # the distribution. # +set(SOURCES rrc_nr_config_utils.cc) +add_library(srsgnb_rrc_config_utils STATIC ${SOURCES}) +target_link_libraries(srsgnb_rrc_config_utils srsran_rrc_nr srsran_phy) + set(SOURCES rrc_nr.cc rrc_nr_ue.cc cell_asn1_config.cc) add_library(srsgnb_rrc STATIC ${SOURCES}) -target_link_libraries(srsgnb_rrc srsran_rrc_nr) +target_link_libraries(srsgnb_rrc srsran_rrc_nr srsgnb_rrc_config_utils) include_directories(${PROJECT_SOURCE_DIR}) diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index 56a329d89..16db18d9d 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -586,9 +586,9 @@ int fill_sp_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, sp_cell_ int fill_mib_from_enb_cfg(const rrc_nr_cfg_t& cfg, asn1::rrc_nr::mib_s& mib) { - srsran::basic_cell_args_t args; - args.scs = cfg.cell_list[0].phy_cell.carrier.scs; - srsran::generate_default_mib(args, mib); + uint32_t scs = + subcarrier_spacing_e{(subcarrier_spacing_opts::options)cfg.cell_list[0].phy_cell.carrier.scs}.to_number(); + srsran::generate_default_mib(scs, cfg.cell_list[0].coreset0_idx, mib); return SRSRAN_SUCCESS; } diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index f26fa7b2c..89babc3c7 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -49,37 +49,6 @@ int rrc_nr::init(const rrc_nr_cfg_t& cfg_, rrc_eutra = rrc_eutra_; cfg = cfg_; - if (cfg.is_standalone) { - // Generate parameters of Coreset#0 and SS#0 - const uint32_t coreset0_idx = 6; // See TS 38.331 - controlResourceSetZero - cfg.cell_list[0].phy_cell.pdcch.coreset_present[0] = true; - // Get pointA and SSB absolute frequencies - double pointA_abs_freq_Hz = cfg.cell_list[0].phy_cell.carrier.dl_center_frequency_hz - - cfg.cell_list[0].phy_cell.carrier.nof_prb * SRSRAN_NRE * - SRSRAN_SUBC_SPACING_NR(cfg.cell_list[0].phy_cell.carrier.scs) / 2; - double ssb_abs_freq_Hz = cfg.cell_list[0].phy_cell.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; - int ret = srsran_coreset_zero(cfg.cell_list[0].phy_cell.cell_id, - ssb_pointA_freq_offset_Hz, - cfg.cell_list[0].ssb_cfg.scs, - cfg.cell_list[0].phy_cell.carrier.scs, - coreset0_idx, - &cfg.cell_list[0].phy_cell.pdcch.coreset[0]); - srsran_assert(ret == SRSRAN_SUCCESS, "Failed to generate CORESET#0"); - cfg.cell_list[0].phy_cell.pdcch.search_space_present[0] = true; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].id = 0; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].coreset_id = 0; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].type = srsran_search_space_type_common_0; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].nof_candidates[0] = 1; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].nof_candidates[1] = 1; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].nof_candidates[2] = 1; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].formats[0] = srsran_dci_format_nr_1_0; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].nof_formats = 1; - cfg.cell_list[0].phy_cell.pdcch.search_space[0].duration = 1; - } - cell_ctxt.reset(new cell_ctxt_t{}); // derived diff --git a/srsgnb/src/stack/rrc/rrc_nr_config_utils.cc b/srsgnb/src/stack/rrc/rrc_nr_config_utils.cc new file mode 100644 index 000000000..625c2c54f --- /dev/null +++ b/srsgnb/src/stack/rrc/rrc_nr_config_utils.cc @@ -0,0 +1,292 @@ +/** + * + * \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 "srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h" +#include "srsran/common/band_helper.h" + +#define INVALID_PARAM(x, fmt, ...) \ + do { \ + if (not(x)) { \ + get_logger().error("ERROR: " fmt, ##__VA_ARGS__); \ + return SRSRAN_ERROR; \ + } \ + } while (0) + +namespace srsenb { + +static srslog::basic_logger& get_logger() +{ + static srslog::basic_logger& logger = srslog::fetch_basic_logger("ALL"); + return logger; +} + +/// Generate default phy cell configuration +void generate_default_nr_phy_cell(phy_cell_cfg_nr_t& phy_cell) +{ + phy_cell = {}; + + phy_cell.carrier.scs = srsran_subcarrier_spacing_15kHz; + phy_cell.carrier.nof_prb = 52; + phy_cell.carrier.max_mimo_layers = 1; + + phy_cell.dl_freq_hz = 0; // auto set + phy_cell.ul_freq_hz = 0; + phy_cell.num_ra_preambles = 52; + + // PRACH + phy_cell.prach.is_nr = true; + phy_cell.prach.config_idx = 8; + phy_cell.prach.root_seq_idx = 0; + phy_cell.prach.freq_offset = 1; + phy_cell.prach.num_ra_preambles = phy_cell.num_ra_preambles; + phy_cell.prach.hs_flag = false; + phy_cell.prach.tdd_config.configured = false; + + // PDCCH + // Configure CORESET ID 1 + phy_cell.pdcch.coreset_present[1] = true; + phy_cell.pdcch.coreset[1].id = 1; + phy_cell.pdcch.coreset[1].duration = 1; + phy_cell.pdcch.coreset[1].mapping_type = srsran_coreset_mapping_type_non_interleaved; + phy_cell.pdcch.coreset[1].precoder_granularity = srsran_coreset_precoder_granularity_reg_bundle; + + // Generate frequency resources for the full BW + for (uint32_t i = 0; i < SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { + phy_cell.pdcch.coreset[1].freq_resources[i] = i < SRSRAN_FLOOR(phy_cell.carrier.nof_prb, 6); + } + + // Configure Search Space 1 as common + phy_cell.pdcch.search_space_present[1] = true; + phy_cell.pdcch.search_space[1].id = 1; + phy_cell.pdcch.search_space[1].coreset_id = 1; + phy_cell.pdcch.search_space[1].duration = 1; + phy_cell.pdcch.search_space[1].formats[0] = srsran_dci_format_nr_0_0; // DCI format for PUSCH + phy_cell.pdcch.search_space[1].formats[1] = srsran_dci_format_nr_1_0; // DCI format for PDSCH + phy_cell.pdcch.search_space[1].nof_formats = 2; + phy_cell.pdcch.search_space[1].type = srsran_search_space_type_common_3; + + // Generate 1 candidate for each aggregation level if possible + for (uint32_t L = 0; L < SRSRAN_SEARCH_SPACE_NOF_AGGREGATION_LEVELS_NR; L++) { + phy_cell.pdcch.search_space[1].nof_candidates[L] = + SRSRAN_MIN(2, srsran_pdcch_nr_max_candidates_coreset(&phy_cell.pdcch.coreset[1], L)); + } + + phy_cell.pdcch.ra_search_space_present = true; + phy_cell.pdcch.ra_search_space = phy_cell.pdcch.search_space[1]; + phy_cell.pdcch.ra_search_space.type = srsran_search_space_type_common_1; + + // PDSCH + phy_cell.pdsch.rs_power = 0; + phy_cell.pdsch.p_b = 0; +} + +/// Generate default rrc nr cell configuration +void generate_default_nr_cell(rrc_cell_cfg_nr_t& cell) +{ + cell = {}; + cell.coreset0_idx = 6; + cell.ssb_absolute_freq_point = 0; // auto derived + generate_default_nr_phy_cell(cell.phy_cell); +} + +/// Generate CORESET#0 and SSB absolute frequency (if not specified) +int derive_coreset0_params(rrc_cell_cfg_nr_t& cell) +{ + // Generate CORESET#0 + cell.phy_cell.pdcch.coreset_present[0] = true; + // Get pointA and SSB absolute frequencies + double pointA_abs_freq_Hz = + cell.phy_cell.carrier.dl_center_frequency_hz - + cell.phy_cell.carrier.nof_prb * SRSRAN_NRE * SRSRAN_SUBC_SPACING_NR(cell.phy_cell.carrier.scs) / 2; + double ssb_abs_freq_Hz = cell.phy_cell.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; + int ret = srsran_coreset_zero(cell.phy_cell.cell_id, + ssb_pointA_freq_offset_Hz, + cell.ssb_cfg.scs, + cell.phy_cell.carrier.scs, + cell.coreset0_idx, + &cell.phy_cell.pdcch.coreset[0]); + INVALID_PARAM(ret == SRSRAN_SUCCESS, "Failed to generate CORESET#0"); + return SRSRAN_SUCCESS; +} + +int derive_ssb_params(bool is_sa, + uint32_t dl_arfcn, + uint32_t band, + srsran_subcarrier_spacing_t pdcch_scs, + uint32_t coreset0_idx, + uint32_t nof_prb, + srsran_ssb_cfg_t& ssb) +{ + // Verify essential parameters are specified and valid + INVALID_PARAM(dl_arfcn > 0, "Invalid DL ARFCN=%d", dl_arfcn); + INVALID_PARAM(band > 0, "Band is a mandatory parameter"); + INVALID_PARAM(pdcch_scs < srsran_subcarrier_spacing_invalid, "Invalid carrier SCS"); + INVALID_PARAM(coreset0_idx < 15, "Invalid controlResourceSetZero"); + INVALID_PARAM(nof_prb > 0, "Invalid DL number of PRBS=%d", nof_prb); + + srsran::srsran_band_helper band_helper; + + double dl_freq_hz = band_helper.nr_arfcn_to_freq(dl_arfcn); + uint32_t dl_absolute_freq_point_a = band_helper.get_abs_freq_point_a_arfcn(nof_prb, dl_arfcn); + + ssb.center_freq_hz = dl_freq_hz; + ssb.duplex_mode = band_helper.get_duplex_mode(band); + + // derive SSB pattern and scs + ssb.pattern = band_helper.get_ssb_pattern(band, srsran_subcarrier_spacing_15kHz); + if (ssb.pattern == SRSRAN_SSB_PATTERN_A) { + // 15kHz SSB SCS + ssb.scs = srsran_subcarrier_spacing_15kHz; + } else { + // try to optain SSB pattern for same band with 30kHz SCS + ssb.pattern = band_helper.get_ssb_pattern(band, srsran_subcarrier_spacing_30kHz); + if (ssb.pattern == SRSRAN_SSB_PATTERN_B || ssb.pattern == SRSRAN_SSB_PATTERN_C) { + // SSB SCS is 30 kHz + ssb.scs = srsran_subcarrier_spacing_30kHz; + } else { + srsran_terminate("Can't derive SSB pattern from band %d", band); + } + } + + // derive SSB position + int coreset0_rb_offset = 0; + if (is_sa) { + // Get offset in RBs between CORESET#0 and SSB + coreset0_rb_offset = srsran_coreset0_ssb_offset(coreset0_idx, ssb.scs, pdcch_scs); + INVALID_PARAM(coreset0_rb_offset >= 0, "Failed to compute RB offset between CORESET#0 and SSB"); + } else { + // TODO: Verify if specified SSB frequency is valid + } + uint32_t ssb_abs_freq_point = + band_helper.get_abs_freq_ssb_arfcn(band, ssb.scs, dl_absolute_freq_point_a, coreset0_rb_offset); + INVALID_PARAM(ssb_abs_freq_point > 0, + "Can't derive SSB freq point for dl_arfcn=%d and band %d", + band_helper.freq_to_nr_arfcn(dl_freq_hz), + band); + + // Convert to frequency for PHY + ssb.ssb_freq_hz = band_helper.nr_arfcn_to_freq(ssb_abs_freq_point); + + ssb.periodicity_ms = 10; // TODO: make a param + ssb.beta_pss = 0.0; + ssb.beta_sss = 0.0; + ssb.beta_pbch = 0.0; + ssb.beta_pbch_dmrs = 0.0; + // set by PHY layer in worker_pool::set_common_cfg + ssb.srate_hz = 0.0; + ssb.scaling = 0.0; + + return SRSRAN_SUCCESS; +} + +int derive_phy_cell_freq_params(uint32_t dl_arfcn, uint32_t ul_arfcn, phy_cell_cfg_nr_t& phy_cell) +{ + // Verify essential parameters are specified and valid + INVALID_PARAM(dl_arfcn > 0, "DL ARFCN is a mandatory parameter"); + + // Use helper class to derive NR carrier parameters + srsran::srsran_band_helper band_helper; + + // derive DL freq from ARFCN + if (phy_cell.dl_freq_hz == 0) { + phy_cell.dl_freq_hz = band_helper.nr_arfcn_to_freq(dl_arfcn); + } + + // derive UL freq from ARFCN + if (phy_cell.ul_freq_hz == 0) { + // auto-detect UL frequency + if (ul_arfcn == 0) { + // derive UL ARFCN from given DL ARFCN + ul_arfcn = band_helper.get_ul_arfcn_from_dl_arfcn(dl_arfcn); + INVALID_PARAM(ul_arfcn > 0, "Can't derive UL ARFCN from DL ARFCN %d", dl_arfcn); + } + phy_cell.ul_freq_hz = band_helper.nr_arfcn_to_freq(ul_arfcn); + } + + // copy center frequencies + phy_cell.carrier.dl_center_frequency_hz = phy_cell.dl_freq_hz; + phy_cell.carrier.ul_center_frequency_hz = phy_cell.ul_freq_hz; + + return SRSRAN_SUCCESS; +} + +int set_derived_nr_cell_params(bool is_sa, rrc_cell_cfg_nr_t& cell) +{ + // Verify essential parameters are specified and valid + INVALID_PARAM(cell.dl_arfcn > 0, "DL ARFCN is a mandatory parameter"); + INVALID_PARAM(cell.band > 0, "Band is a mandatory parameter"); + INVALID_PARAM(cell.phy_cell.carrier.nof_prb > 0, "Number of PRBs is a mandatory parameter"); + + // Use helper class to derive NR carrier parameters + srsran::srsran_band_helper band_helper; + + if (cell.ul_arfcn == 0) { + // derive UL ARFCN from given DL ARFCN + cell.ul_arfcn = band_helper.get_ul_arfcn_from_dl_arfcn(cell.dl_arfcn); + INVALID_PARAM(cell.ul_arfcn > 0, "Can't derive UL ARFCN from DL ARFCN %d", cell.dl_arfcn); + } + + // duplex mode + cell.duplex_mode = band_helper.get_duplex_mode(cell.band); + + // PointA + cell.dl_absolute_freq_point_a = band_helper.get_abs_freq_point_a_arfcn(cell.phy_cell.carrier.nof_prb, cell.dl_arfcn); + cell.ul_absolute_freq_point_a = band_helper.get_abs_freq_point_a_arfcn(cell.phy_cell.carrier.nof_prb, cell.ul_arfcn); + + // Derive phy_cell parameters that depend on ARFCNs + derive_phy_cell_freq_params(cell.dl_arfcn, cell.ul_arfcn, cell.phy_cell); + + // Derive SSB params + derive_ssb_params(is_sa, + cell.dl_arfcn, + cell.band, + cell.phy_cell.carrier.scs, + cell.coreset0_idx, + cell.phy_cell.carrier.nof_prb, + cell.ssb_cfg); + cell.phy_cell.carrier.ssb_center_freq_hz = cell.ssb_cfg.ssb_freq_hz; + cell.ssb_absolute_freq_point = band_helper.freq_to_nr_arfcn(cell.ssb_cfg.ssb_freq_hz); + + // Derive remaining config params + if (is_sa) { + derive_coreset0_params(cell); + cell.phy_cell.pdcch.search_space_present[0] = true; + cell.phy_cell.pdcch.search_space[0].id = 0; + cell.phy_cell.pdcch.search_space[0].coreset_id = 0; + cell.phy_cell.pdcch.search_space[0].type = srsran_search_space_type_common_0; + cell.phy_cell.pdcch.search_space[0].nof_candidates[0] = 1; + cell.phy_cell.pdcch.search_space[0].nof_candidates[1] = 1; + cell.phy_cell.pdcch.search_space[0].nof_candidates[2] = 1; + cell.phy_cell.pdcch.search_space[0].formats[0] = srsran_dci_format_nr_1_0; + cell.phy_cell.pdcch.search_space[0].nof_formats = 1; + cell.phy_cell.pdcch.search_space[0].duration = 1; + } + + // Derive remaining PHY cell params + cell.phy_cell.prach.num_ra_preambles = cell.phy_cell.num_ra_preambles; + cell.phy_cell.prach.tdd_config.configured = (cell.duplex_mode == SRSRAN_DUPLEX_MODE_TDD); + + return check_nr_cell_cfg_valid(cell, is_sa); +} + +int check_nr_cell_cfg_valid(const rrc_cell_cfg_nr_t& cell, bool is_sa) +{ + // verify SSB params are consistent + INVALID_PARAM(cell.ssb_cfg.center_freq_hz == cell.phy_cell.dl_freq_hz, "Invalid SSB param generation"); + + return SRSRAN_SUCCESS; +} + +} // namespace srsenb \ No newline at end of file diff --git a/srsgnb/src/stack/rrc/test/CMakeLists.txt b/srsgnb/src/stack/rrc/test/CMakeLists.txt index 3e5ab83cf..45df0892d 100644 --- a/srsgnb/src/stack/rrc/test/CMakeLists.txt +++ b/srsgnb/src/stack/rrc/test/CMakeLists.txt @@ -9,5 +9,5 @@ add_library(rrc_nr_test_helpers rrc_nr_test_helpers.cc) add_executable(rrc_nr_test rrc_nr_test.cc) -target_link_libraries(rrc_nr_test srsgnb_rrc test_helpers rrc_nr_test_helpers ${ATOMIC_LIBS}) +target_link_libraries(rrc_nr_test srsgnb_rrc srsgnb_rrc_config_utils test_helpers rrc_nr_test_helpers ${ATOMIC_LIBS}) add_test(rrc_nr_test rrc_nr_test -i ${CMAKE_CURRENT_SOURCE_DIR}/../..) \ No newline at end of file diff --git a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc index 828ecd988..02bcbedea 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc @@ -168,7 +168,7 @@ int main(int argc, char** argv) auto& logger = srslog::fetch_basic_logger("ASN1"); logger.set_level(srslog::basic_levels::info); auto& rrc_logger = srslog::fetch_basic_logger("RRC-NR"); - rrc_logger.set_level(srslog::basic_levels::info); + rrc_logger.set_level(srslog::basic_levels::debug); srslog::init(); From 25a8ea53d3d41ae3c8d964297f5f0b86899813d2 Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 15 Nov 2021 21:00:08 +0000 Subject: [PATCH 58/77] nr,gnb,sched: pass mib config to scheduler so it is used during SSB allocation --- lib/include/srsran/asn1/rrc_nr_utils.h | 2 ++ lib/src/asn1/rrc_nr_utils.cc | 17 +++++++++++++++++ srsgnb/hdr/stack/mac/sched_nr_interface.h | 1 + srsgnb/hdr/stack/mac/sched_nr_signalling.h | 8 ++++++-- srsgnb/src/stack/mac/sched_nr_signalling.cc | 15 +++++++-------- .../stack/mac/test/sched_nr_cfg_generators.h | 8 +++++--- srsgnb/src/stack/rrc/rrc_nr.cc | 2 ++ 7 files changed, 40 insertions(+), 13 deletions(-) diff --git a/lib/include/srsran/asn1/rrc_nr_utils.h b/lib/include/srsran/asn1/rrc_nr_utils.h index 18d284646..60c38cfa5 100644 --- a/lib/include/srsran/asn1/rrc_nr_utils.h +++ b/lib/include/srsran/asn1/rrc_nr_utils.h @@ -58,6 +58,7 @@ struct serving_cell_cfg_common_s; struct serving_cell_cfg_s; struct pdcch_cfg_common_s; struct pdcch_cfg_s; +struct mib_s; } // namespace rrc_nr } // namespace asn1 @@ -115,6 +116,7 @@ bool make_phy_carrier_cfg(const asn1::rrc_nr::freq_info_dl_s& freq_info_dl, srsr bool make_phy_ssb_cfg(const srsran_carrier_nr_t& carrier, const asn1::rrc_nr::serving_cell_cfg_common_s& serv_cell_cfg, phy_cfg_nr_t::ssb_cfg_t* ssb); +bool make_phy_mib(const asn1::rrc_nr::mib_s& mib_cfg, srsran_mib_nr_t* mib); bool make_pdsch_cfg_from_serv_cell(const asn1::rrc_nr::serving_cell_cfg_s& serv_cell, srsran_sch_hl_cfg_nr_t* sch_hl); bool make_csi_cfg_from_serv_cell(const asn1::rrc_nr::serving_cell_cfg_s& serv_cell, srsran_csi_hl_cfg_t* csi_hl); bool make_duplex_cfg_from_serv_cell(const asn1::rrc_nr::serving_cell_cfg_common_s& serv_cell, diff --git a/lib/src/asn1/rrc_nr_utils.cc b/lib/src/asn1/rrc_nr_utils.cc index e11633519..e27321582 100644 --- a/lib/src/asn1/rrc_nr_utils.cc +++ b/lib/src/asn1/rrc_nr_utils.cc @@ -1480,6 +1480,23 @@ bool make_phy_ssb_cfg(const srsran_carrier_nr_t& carrier, return true; } +bool make_phy_mib(const asn1::rrc_nr::mib_s& mib_cfg, srsran_mib_nr_t* mib) +{ + mib->sfn = 0; + mib->ssb_idx = 0; + mib->hrf = 0; + mib->scs_common = + mib_cfg.sub_carrier_spacing_common.value == asn1::rrc_nr::mib_s::sub_carrier_spacing_common_opts::scs15or60 + ? srsran_subcarrier_spacing_15kHz + : srsran_subcarrier_spacing_30kHz; + mib->ssb_offset = mib_cfg.ssb_subcarrier_offset; + mib->dmrs_typeA_pos = (srsran_dmrs_sch_typeA_pos_t)mib_cfg.dmrs_type_a_position.value; + mib->coreset0_idx = mib_cfg.pdcch_cfg_sib1.ctrl_res_set_zero; + mib->ss0_idx = mib_cfg.pdcch_cfg_sib1.search_space_zero; + mib->cell_barred = mib_cfg.cell_barred.value == asn1::rrc_nr::mib_s::cell_barred_opts::barred; + mib->intra_freq_reselection = mib_cfg.intra_freq_resel.value == asn1::rrc_nr::mib_s::intra_freq_resel_opts::allowed; +} + bool make_pdsch_cfg_from_serv_cell(const asn1::rrc_nr::serving_cell_cfg_s& serv_cell, srsran_sch_hl_cfg_nr_t* sch_hl) { if (serv_cell.csi_meas_cfg_present and diff --git a/srsgnb/hdr/stack/mac/sched_nr_interface.h b/srsgnb/hdr/stack/mac/sched_nr_interface.h index fc4a10766..edfef63e5 100644 --- a/srsgnb/hdr/stack/mac/sched_nr_interface.h +++ b/srsgnb/hdr/stack/mac/sched_nr_interface.h @@ -75,6 +75,7 @@ public: srsran_duplex_config_nr_t duplex = {}; srsran::phy_cfg_nr_t::ssb_cfg_t ssb = {}; srsran::bounded_vector bwps{1}; // idx0 for BWP-common + srsran_mib_nr_t mib; srsran::bounded_vector sibs; }; diff --git a/srsgnb/hdr/stack/mac/sched_nr_signalling.h b/srsgnb/hdr/stack/mac/sched_nr_signalling.h index 2ddf60eb1..e7cf95677 100644 --- a/srsgnb/hdr/stack/mac/sched_nr_signalling.h +++ b/srsgnb/hdr/stack/mac/sched_nr_signalling.h @@ -36,14 +36,18 @@ void sched_nzp_csi_rs(srsran::const_span nzp_csi_rs_set * * @param[in] sl_point Slot point carrying information about current slot. * @param[in] ssb_periodicity Periodicity of SSB in ms. - * @param[out] ssb_list List of SSB messages to be sent to PHY. + * @param[in] mib MIB message content + * @param[out] ssb_list List of SSB messages to be sent to PHY. * * @remark This function a is basic scheduling function that uses the following simplified assumption: * 1) Subcarrier spacing: 15kHz * 2) Frequency below 3GHz * 3) Position in Burst is 1000, i.e., Only the first SSB of the 4 opportunities gets scheduled */ -void sched_ssb_basic(const slot_point& sl_point, uint32_t ssb_periodicity, ssb_list& ssb_list); +void sched_ssb_basic(const slot_point& sl_point, + uint32_t ssb_periodicity, + const srsran_mib_nr_t& mib, + ssb_list& ssb_list); /// Fill DCI fields with SIB info bool fill_dci_sib(prb_interval interv, uint32_t sib_idx, const bwp_params_t& bwp_cfg, srsran_dci_dl_nr_t& dci); diff --git a/srsgnb/src/stack/mac/sched_nr_signalling.cc b/srsgnb/src/stack/mac/sched_nr_signalling.cc index 020d0aff1..3da24f9cc 100644 --- a/srsgnb/src/stack/mac/sched_nr_signalling.cc +++ b/srsgnb/src/stack/mac/sched_nr_signalling.cc @@ -46,7 +46,10 @@ void sched_nzp_csi_rs(srsran::const_span nzp_csi_rs_set } } -void sched_ssb_basic(const slot_point& sl_point, uint32_t ssb_periodicity, ssb_list& ssb_list) +void sched_ssb_basic(const slot_point& sl_point, + uint32_t ssb_periodicity, + const srsran_mib_nr_t& mib, + ssb_list& ssb_list) { if (ssb_list.full()) { srslog::fetch_basic_logger("MAC-NR").error("SCHED: Failed to allocate SSB"); @@ -66,17 +69,13 @@ void sched_ssb_basic(const slot_point& sl_point, uint32_t ssb_periodicity, ssb_l // code below is simplified, it assumes 15kHz subcarrier spacing and sub 3GHz carrier if (sl_point_mod == 0) { ssb_t ssb_msg = {}; - srsran_mib_nr_t mib_msg = {}; + srsran_mib_nr_t mib_msg = mib; mib_msg.sfn = sl_point.sfn(); mib_msg.hrf = (sl_point.slot_idx() % SRSRAN_NSLOTS_PER_FRAME_NR(srsran_subcarrier_spacing_15kHz) >= SRSRAN_NSLOTS_PER_FRAME_NR(srsran_subcarrier_spacing_15kHz) / 2); // This corresponds to "Position in Burst" = 1000 mib_msg.ssb_idx = 0; - // Setting the following 4 parameters is redundant, but it makes it explicit what values are passed to PHY - mib_msg.dmrs_typeA_pos = srsran_dmrs_sch_typeA_pos_2; - mib_msg.scs_common = srsran_subcarrier_spacing_15kHz; - mib_msg.coreset0_idx = 0; - mib_msg.ss0_idx = 0; + // Remaining MIB parameters remain constant // Pack mib message to be sent to PHY int packing_ret_code = srsran_pbch_msg_nr_mib_pack(&mib_msg, &ssb_msg.pbch_msg); @@ -95,7 +94,7 @@ void sched_dl_signalling(bwp_slot_allocator& bwp_alloc) cfg.idx = sl_pdcch.to_uint(); // Schedule SSB - sched_ssb_basic(sl_pdcch, bwp_params.cell_cfg.ssb.periodicity_ms, sl_grid.dl.phy.ssb); + sched_ssb_basic(sl_pdcch, bwp_params.cell_cfg.ssb.periodicity_ms, bwp_params.cell_cfg.mib, sl_grid.dl.phy.ssb); // Schedule NZP-CSI-RS sched_nzp_csi_rs(bwp_params.cfg.pdsch.nzp_csi_rs_sets, cfg, sl_grid.dl.phy.nzp_csi_rs); diff --git a/srsgnb/src/stack/mac/test/sched_nr_cfg_generators.h b/srsgnb/src/stack/mac/test/sched_nr_cfg_generators.h index fdbd02589..1ed5b0ca4 100644 --- a/srsgnb/src/stack/mac/test/sched_nr_cfg_generators.h +++ b/srsgnb/src/stack/mac/test/sched_nr_cfg_generators.h @@ -35,9 +35,11 @@ inline sched_nr_interface::cell_cfg_t get_default_cell_cfg( { sched_nr_interface::cell_cfg_t cell_cfg{}; - cell_cfg.carrier = phy_cfg.carrier; - cell_cfg.duplex = phy_cfg.duplex; - cell_cfg.ssb = phy_cfg.ssb; + cell_cfg.carrier = phy_cfg.carrier; + cell_cfg.duplex = phy_cfg.duplex; + cell_cfg.ssb = phy_cfg.ssb; + cell_cfg.mib.coreset0_idx = 6; + cell_cfg.mib.scs_common = srsran_subcarrier_spacing_15kHz; cell_cfg.bwps.resize(1); cell_cfg.bwps[0].pdcch = phy_cfg.pdcch; diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index 89babc3c7..f9f2e06ba 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -270,6 +270,8 @@ void rrc_nr::config_mac() srsran_assert(ret2, "Invalid NR cell configuration."); ret2 = srsran::make_duplex_cfg_from_serv_cell(base_sp_cell_cfg.recfg_with_sync.sp_cell_cfg_common, &cell.duplex); srsran_assert(ret2, "Invalid NR cell configuration."); + ret2 = srsran::make_phy_mib(cell_ctxt->mib, &cell.mib); + srsran_assert(ret2, "Invalid NR cell MIB configuration."); // Set SIB1 and SI messages cell.sibs.resize(cell_ctxt->sib_buffer.size()); From 5a3e99fb58b788d1c41a21b8c5369b838c7e30bc Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 15 Nov 2021 22:22:55 +0000 Subject: [PATCH 59/77] nr,gnb,rrc: fix missing return for mib generation --- lib/src/asn1/rrc_nr_utils.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/asn1/rrc_nr_utils.cc b/lib/src/asn1/rrc_nr_utils.cc index e27321582..9d6c02744 100644 --- a/lib/src/asn1/rrc_nr_utils.cc +++ b/lib/src/asn1/rrc_nr_utils.cc @@ -1495,6 +1495,7 @@ bool make_phy_mib(const asn1::rrc_nr::mib_s& mib_cfg, srsran_mib_nr_t* mib) mib->ss0_idx = mib_cfg.pdcch_cfg_sib1.search_space_zero; mib->cell_barred = mib_cfg.cell_barred.value == asn1::rrc_nr::mib_s::cell_barred_opts::barred; mib->intra_freq_reselection = mib_cfg.intra_freq_resel.value == asn1::rrc_nr::mib_s::intra_freq_resel_opts::allowed; + return true; } bool make_pdsch_cfg_from_serv_cell(const asn1::rrc_nr::serving_cell_cfg_s& serv_cell, srsran_sch_hl_cfg_nr_t* sch_hl) From 4aa5fe41dfb51bbd9a798f0104174c44bdd57699 Mon Sep 17 00:00:00 2001 From: Francisco Date: Sun, 14 Nov 2021 17:50:18 +0000 Subject: [PATCH 60/77] nr,gnb,rrc: add extra fields to RRC setup and RRC setup complete messages. --- srsgnb/hdr/stack/rrc/cell_asn1_config.h | 6 + srsgnb/hdr/stack/rrc/rrc_nr.h | 11 +- srsgnb/hdr/stack/rrc/rrc_nr_ue.h | 17 +- srsgnb/src/stack/rrc/cell_asn1_config.cc | 292 +++++++++++++++++- srsgnb/src/stack/rrc/rrc_nr.cc | 8 + srsgnb/src/stack/rrc/rrc_nr_ue.cc | 158 +++++----- srsgnb/src/stack/rrc/test/rrc_nr_test.cc | 5 +- .../src/stack/rrc/test/rrc_nr_test_helpers.cc | 8 + 8 files changed, 406 insertions(+), 99 deletions(-) diff --git a/srsgnb/hdr/stack/rrc/cell_asn1_config.h b/srsgnb/hdr/stack/rrc/cell_asn1_config.h index 202f18a49..df14ed406 100644 --- a/srsgnb/hdr/stack/rrc/cell_asn1_config.h +++ b/srsgnb/hdr/stack/rrc/cell_asn1_config.h @@ -18,8 +18,14 @@ namespace srsenb { +using rlc_bearer_list_t = asn1::rrc_nr::cell_group_cfg_s::rlc_bearer_to_add_mod_list_l_; + +// NSA helpers int fill_sp_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::sp_cell_cfg_s& sp_cell); +// SA helpers +int fill_master_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::cell_group_cfg_s& out); + int fill_mib_from_enb_cfg(const rrc_nr_cfg_t& cfg, asn1::rrc_nr::mib_s& mib); int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, asn1::rrc_nr::sib1_s& sib1); diff --git a/srsgnb/hdr/stack/rrc/rrc_nr.h b/srsgnb/hdr/stack/rrc/rrc_nr.h index 9f3b3c39c..560165396 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr.h @@ -138,11 +138,12 @@ private: // vars struct cell_ctxt_t { - asn1::rrc_nr::mib_s mib; - asn1::rrc_nr::sib1_s sib1; - asn1::rrc_nr::sys_info_ies_s::sib_type_and_info_l_ sibs; - srsran::unique_byte_buffer_t mib_buffer = nullptr; - std::vector sib_buffer; + asn1::rrc_nr::mib_s mib; + asn1::rrc_nr::sib1_s sib1; + asn1::rrc_nr::sys_info_ies_s::sib_type_and_info_l_ sibs; + srsran::unique_byte_buffer_t mib_buffer = nullptr; + std::vector sib_buffer; + std::unique_ptr master_cell_group; }; std::unique_ptr cell_ctxt; rnti_map_t > users; diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h index f79c01712..1b3a588d2 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h @@ -66,9 +66,6 @@ public: void handle_ul_information_transfer(const asn1::rrc_nr::ul_info_transfer_s& msg); private: - rrc_nr* parent = nullptr; - uint16_t rnti = SRSRAN_INVALID_RNTI; - void send_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_ccch_msg); void send_dl_dcch(srsran::nr_srb srb, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg); @@ -93,7 +90,6 @@ private: int pack_sp_cell_cfg_ded(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack); int pack_sp_cell_cfg_ded_init_dl_bwp(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack); - int pack_sp_cell_cfg_ded_init_dl_bwp_pdsch_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack); int pack_sp_cell_cfg_ded_init_dl_bwp_radio_link_monitoring(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack); int pack_sp_cell_cfg_ded_ul_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack); @@ -123,6 +119,8 @@ private: int add_drb(); + bool init_pucch(); + // logging helpers template void log_rrc_message(srsran::nr_srb srb, @@ -133,6 +131,11 @@ private: template void log_rrc_container(const direction_t dir, srsran::const_byte_span pdu, const M& msg, const char* msg_type); + // args + rrc_nr* parent = nullptr; + srslog::basic_logger& logger; + uint16_t rnti = SRSRAN_INVALID_RNTI; + // state rrc_nr_state_t state = rrc_nr_state_t::RRC_IDLE; uint8_t transaction_id = 0; @@ -147,6 +150,12 @@ private: const uint32_t drb1_lcid = 4; + // SA specific variables + struct ctxt_t { + uint64_t setup_ue_id = -1; + asn1::rrc_nr::establishment_cause_opts connection_cause; + } ctxt; + // NSA specific variables bool endc = false; uint16_t eutra_rnti = SRSRAN_INVALID_RNTI; diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index 16db18d9d..93ddfdc2a 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -228,7 +228,8 @@ int fill_pdcch_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, pdcch_cfg_ { auto& cell_cfg = cfg.cell_list.at(cc); - for (uint32_t cs_idx = 0; cs_idx < SRSRAN_UE_DL_NR_MAX_NOF_CORESET; cs_idx++) { + // Note: Skip CORESET#0 + for (uint32_t cs_idx = 1; cs_idx < SRSRAN_UE_DL_NR_MAX_NOF_CORESET; cs_idx++) { if (cell_cfg.phy_cell.pdcch.coreset_present[cs_idx]) { auto& coreset_cfg = cell_cfg.phy_cell.pdcch.coreset[cs_idx]; @@ -263,7 +264,8 @@ int fill_pdcch_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, pdcch_cfg_ } } - for (uint32_t ss_idx = 0; ss_idx < SRSRAN_UE_DL_NR_MAX_NOF_SEARCH_SPACE; ss_idx++) { + // Note: Skip SearchSpace#0 + for (uint32_t ss_idx = 1; ss_idx < SRSRAN_UE_DL_NR_MAX_NOF_SEARCH_SPACE; ss_idx++) { if (cell_cfg.phy_cell.pdcch.search_space_present[ss_idx]) { // search spaces auto& search_space_cfg = cell_cfg.phy_cell.pdcch.search_space[ss_idx]; @@ -309,17 +311,204 @@ int fill_pdcch_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, pdcch_cfg_ return SRSRAN_SUCCESS; } +void fill_pdsch_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, pdsch_cfg_s& out) +{ + out.dmrs_dl_for_pdsch_map_type_a_present = true; + out.dmrs_dl_for_pdsch_map_type_a.set_setup(); + out.dmrs_dl_for_pdsch_map_type_a.setup().dmrs_add_position_present = true; + out.dmrs_dl_for_pdsch_map_type_a.setup().dmrs_add_position = dmrs_dl_cfg_s::dmrs_add_position_opts::pos1; + + out.tci_states_to_add_mod_list_present = true; + out.tci_states_to_add_mod_list.resize(1); + out.tci_states_to_add_mod_list[0].tci_state_id = 0; + out.tci_states_to_add_mod_list[0].qcl_type1.ref_sig.set_ssb(); + out.tci_states_to_add_mod_list[0].qcl_type1.ref_sig.ssb() = 0; + out.tci_states_to_add_mod_list[0].qcl_type1.qcl_type = asn1::rrc_nr::qcl_info_s::qcl_type_opts::type_d; + + out.res_alloc = pdsch_cfg_s::res_alloc_opts::res_alloc_type1; + out.rbg_size = pdsch_cfg_s::rbg_size_opts::cfg1; + out.prb_bundling_type.set_static_bundling(); + out.prb_bundling_type.static_bundling().bundle_size_present = true; + out.prb_bundling_type.static_bundling().bundle_size = + pdsch_cfg_s::prb_bundling_type_c_::static_bundling_s_::bundle_size_opts::wideband; + + // ZP-CSI + out.zp_csi_rs_res_to_add_mod_list_present = false; // TEMP + 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; + out.zp_csi_rs_res_to_add_mod_list[0].res_map.freq_domain_alloc.set_row4(); + out.zp_csi_rs_res_to_add_mod_list[0].res_map.freq_domain_alloc.row4().from_number(0b100); + out.zp_csi_rs_res_to_add_mod_list[0].res_map.nrof_ports = asn1::rrc_nr::csi_rs_res_map_s::nrof_ports_opts::p4; + + out.zp_csi_rs_res_to_add_mod_list[0].res_map.first_ofdm_symbol_in_time_domain = 8; + out.zp_csi_rs_res_to_add_mod_list[0].res_map.cdm_type = asn1::rrc_nr::csi_rs_res_map_s::cdm_type_opts::fd_cdm2; + out.zp_csi_rs_res_to_add_mod_list[0].res_map.density.set_one(); + + out.zp_csi_rs_res_to_add_mod_list[0].res_map.freq_band.start_rb = 0; + out.zp_csi_rs_res_to_add_mod_list[0].res_map.freq_band.nrof_rbs = cfg.cell_list[cc].phy_cell.carrier.nof_prb; + out.zp_csi_rs_res_to_add_mod_list[0].periodicity_and_offset_present = true; + out.zp_csi_rs_res_to_add_mod_list[0].periodicity_and_offset.set_slots80() = 1; + + out.p_zp_csi_rs_res_set_present = false; // TEMP + out.p_zp_csi_rs_res_set.set_setup(); + out.p_zp_csi_rs_res_set.setup().zp_csi_rs_res_set_id = 0; + out.p_zp_csi_rs_res_set.setup().zp_csi_rs_res_id_list.resize(1); + out.p_zp_csi_rs_res_set.setup().zp_csi_rs_res_id_list[0] = 0; +} + /// Fill InitDlBwp with gNB config int fill_init_dl_bwp_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, bwp_dl_ded_s& init_dl_bwp) { init_dl_bwp.pdcch_cfg_present = true; HANDLE_ERROR(fill_pdcch_cfg_from_enb_cfg(cfg, cc, init_dl_bwp.pdcch_cfg.set_setup())); + init_dl_bwp.pdsch_cfg_present = true; + fill_pdsch_cfg_from_enb_cfg(cfg, cc, init_dl_bwp.pdsch_cfg.set_setup()); + // TODO: ADD missing fields return SRSRAN_SUCCESS; } +void fill_pucch_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, pucch_cfg_s& out) +{ + // Make 2 PUCCH resource sets + out.res_set_to_add_mod_list_present = true; + out.res_set_to_add_mod_list.resize(2); + + // Make PUCCH resource set for 1-2 bit + for (uint32_t set_id = 0; set_id < out.res_set_to_add_mod_list.size(); ++set_id) { + auto& res_set = out.res_set_to_add_mod_list[set_id]; + res_set.pucch_res_set_id = set_id; + res_set.res_list.resize(8); + for (uint32_t i = 0; i < res_set.res_list.size(); ++i) { + if (cfg.is_standalone) { + res_set.res_list[i] = i + set_id * 8; + } else { + res_set.res_list[i] = set_id; + } + } + } + + // Make 3 possible resources + out.res_to_add_mod_list_present = true; + out.res_to_add_mod_list.resize(18); + uint32_t j = 0, j2 = 0; + for (uint32_t i = 0; i < out.res_to_add_mod_list.size(); ++i) { + out.res_to_add_mod_list[i].pucch_res_id = i; + out.res_to_add_mod_list[i].intra_slot_freq_hop_present = true; + out.res_to_add_mod_list[i].second_hop_prb_present = true; + if (i < 8 or i == 16) { + out.res_to_add_mod_list[i].start_prb = 51; + out.res_to_add_mod_list[i].second_hop_prb = 0; + out.res_to_add_mod_list[i].format.set_format1().init_cyclic_shift = (4 * (j % 3)); + out.res_to_add_mod_list[i].format.format1().nrof_symbols = 14; + out.res_to_add_mod_list[i].format.format1().start_symbol_idx = 0; + out.res_to_add_mod_list[i].format.format1().time_domain_occ = j / 3; + j++; + } else if (i < 15) { + out.res_to_add_mod_list[i].start_prb = 1; + out.res_to_add_mod_list[i].second_hop_prb = 50; + out.res_to_add_mod_list[i].format.set_format2().nrof_prbs = 1; + out.res_to_add_mod_list[i].format.format2().nrof_symbols = 2; + out.res_to_add_mod_list[i].format.format2().start_symbol_idx = 2 * (j2 % 7); + j2++; + } else { + out.res_to_add_mod_list[i].start_prb = 50; + out.res_to_add_mod_list[i].second_hop_prb = 1; + out.res_to_add_mod_list[i].format.set_format2().nrof_prbs = 1; + out.res_to_add_mod_list[i].format.format2().nrof_symbols = 2; + out.res_to_add_mod_list[i].format.format2().start_symbol_idx = 2 * (j2 % 7); + j2++; + } + } + + out.format1_present = true; + out.format1.set_setup(); + + out.format2_present = true; + out.format2.set_setup(); + out.format2.setup().max_code_rate_present = true; + out.format2.setup().max_code_rate = pucch_max_code_rate_opts::zero_dot25; + + // SR resources + out.sched_request_res_to_add_mod_list_present = true; + out.sched_request_res_to_add_mod_list.resize(1); + auto& sr_res1 = out.sched_request_res_to_add_mod_list[0]; + sr_res1.sched_request_res_id = 1; + sr_res1.sched_request_id = 0; + sr_res1.periodicity_and_offset_present = true; + sr_res1.periodicity_and_offset.set_sl40() = 0; + sr_res1.res_present = true; + sr_res1.res = 16; + + // DL data + out.dl_data_to_ul_ack_present = true; + if (cfg.cell_list[cc].duplex_mode == SRSRAN_DUPLEX_MODE_FDD) { + out.dl_data_to_ul_ack.resize(1); + out.dl_data_to_ul_ack[0] = 4; + } else { + out.dl_data_to_ul_ack.resize(6); + out.dl_data_to_ul_ack[0] = 6; + out.dl_data_to_ul_ack[1] = 5; + out.dl_data_to_ul_ack[2] = 4; + out.dl_data_to_ul_ack[3] = 4; + out.dl_data_to_ul_ack[4] = 4; + out.dl_data_to_ul_ack[5] = 4; + } +} + +void fill_pusch_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, pusch_cfg_s& out) +{ + out.dmrs_ul_for_pusch_map_type_a_present = true; + out.dmrs_ul_for_pusch_map_type_a.set_setup(); + out.dmrs_ul_for_pusch_map_type_a.setup().dmrs_add_position_present = true; + out.dmrs_ul_for_pusch_map_type_a.setup().dmrs_add_position = dmrs_ul_cfg_s::dmrs_add_position_opts::pos1; + // PUSH power control skipped + out.res_alloc = pusch_cfg_s::res_alloc_opts::res_alloc_type1; + + // UCI + out.uci_on_pusch_present = true; + out.uci_on_pusch.set_setup(); + out.uci_on_pusch.setup().beta_offsets_present = true; + out.uci_on_pusch.setup().beta_offsets.set_semi_static(); + auto& beta_offset_semi_static = out.uci_on_pusch.setup().beta_offsets.semi_static(); + beta_offset_semi_static.beta_offset_ack_idx1_present = true; + beta_offset_semi_static.beta_offset_ack_idx1 = 9; + beta_offset_semi_static.beta_offset_ack_idx2_present = true; + beta_offset_semi_static.beta_offset_ack_idx2 = 9; + beta_offset_semi_static.beta_offset_ack_idx3_present = true; + beta_offset_semi_static.beta_offset_ack_idx3 = 9; + beta_offset_semi_static.beta_offset_csi_part1_idx1_present = true; + beta_offset_semi_static.beta_offset_csi_part1_idx1 = 6; + beta_offset_semi_static.beta_offset_csi_part1_idx2_present = true; + beta_offset_semi_static.beta_offset_csi_part1_idx2 = 6; + beta_offset_semi_static.beta_offset_csi_part2_idx1_present = true; + beta_offset_semi_static.beta_offset_csi_part2_idx1 = 6; + beta_offset_semi_static.beta_offset_csi_part2_idx2_present = true; + beta_offset_semi_static.beta_offset_csi_part2_idx2 = 6; + + out.uci_on_pusch.setup().scaling = uci_on_pusch_s::scaling_opts::f1; +} + +void fill_init_ul_bwp_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, bwp_ul_ded_s& out) +{ + if (cfg.is_standalone) { + out.pucch_cfg_present = true; + fill_pucch_cfg_from_enb_cfg(cfg, cc, out.pucch_cfg.set_setup()); + + out.pusch_cfg_present = true; + fill_pusch_cfg_from_enb_cfg(cfg, cc, out.pusch_cfg.set_setup()); + } +} + +/// Fill InitUlBwp with gNB config +void fill_ul_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, ul_cfg_s& out) +{ + out.init_ul_bwp_present = true; + fill_init_ul_bwp_from_enb_cfg(cfg, cc, out.init_ul_bwp); +} + /// Fill ServingCellConfig with gNB config int fill_serv_cell_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, serving_cell_cfg_s& serv_cell) { @@ -329,6 +518,16 @@ int fill_serv_cell_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, serving_ce serv_cell.init_dl_bwp_present = true; fill_init_dl_bwp_from_enb_cfg(cfg, cc, serv_cell.init_dl_bwp); + serv_cell.first_active_dl_bwp_id_present = true; + if (cfg.cell_list[0].duplex_mode == SRSRAN_DUPLEX_MODE_FDD) { + serv_cell.first_active_dl_bwp_id = 0; + } else { + serv_cell.first_active_dl_bwp_id = 1; + } + + serv_cell.ul_cfg_present = true; + fill_ul_cfg_from_enb_cfg(cfg, cc, serv_cell.ul_cfg); + // TODO: remaining fields return SRSRAN_SUCCESS; @@ -584,6 +783,95 @@ int fill_sp_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, sp_cell_ return SRSRAN_SUCCESS; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void fill_srb1(const rrc_nr_cfg_t& cfg, rlc_bearer_cfg_s& srb1) +{ + srb1.lc_ch_id = 1; + srb1.served_radio_bearer_present = true; + srb1.served_radio_bearer.set_srb_id() = 1; + srb1.rlc_cfg_present = true; + ul_am_rlc_s& am_ul = srb1.rlc_cfg.set_am().ul_am_rlc; + am_ul.sn_field_len_present = true; + am_ul.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; + am_ul.t_poll_retx.value = asn1::rrc_nr::t_poll_retx_opts::ms45; + am_ul.poll_pdu.value = asn1::rrc_nr::poll_pdu_opts::infinity; + am_ul.poll_byte.value = asn1::rrc_nr::poll_byte_opts::infinity; + am_ul.max_retx_thres.value = asn1::rrc_nr::ul_am_rlc_s::max_retx_thres_opts::t8; + dl_am_rlc_s& am_dl = srb1.rlc_cfg.am().dl_am_rlc; + am_dl.sn_field_len_present = true; + am_dl.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; + am_dl.t_reassembly.value = t_reassembly_opts::ms35; + am_dl.t_status_prohibit.value = asn1::rrc_nr::t_status_prohibit_opts::ms0; + + // mac-LogicalChannelConfig -- Cond LCH-Setup + srb1.mac_lc_ch_cfg_present = true; + srb1.mac_lc_ch_cfg.ul_specific_params_present = true; + srb1.mac_lc_ch_cfg.ul_specific_params.prio = 1; + srb1.mac_lc_ch_cfg.ul_specific_params.prioritised_bit_rate.value = + lc_ch_cfg_s::ul_specific_params_s_::prioritised_bit_rate_opts::infinity; + srb1.mac_lc_ch_cfg.ul_specific_params.bucket_size_dur.value = + lc_ch_cfg_s::ul_specific_params_s_::bucket_size_dur_opts::ms5; + srb1.mac_lc_ch_cfg.ul_specific_params.lc_ch_group_present = true; + srb1.mac_lc_ch_cfg.ul_specific_params.lc_ch_group = 0; + srb1.mac_lc_ch_cfg.ul_specific_params.sched_request_id_present = true; + srb1.mac_lc_ch_cfg.ul_specific_params.sched_request_id = 0; + srb1.mac_lc_ch_cfg.ul_specific_params.lc_ch_sr_mask = false; + srb1.mac_lc_ch_cfg.ul_specific_params.lc_ch_sr_delay_timer_applied = false; +} + +/// Fill MasterCellConfig with gNB config +int fill_master_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::cell_group_cfg_s& out) +{ + out.cell_group_id = 0; + out.rlc_bearer_to_add_mod_list_present = true; + out.rlc_bearer_to_add_mod_list.resize(1); + fill_srb1(cfg, out.rlc_bearer_to_add_mod_list[0]); + + // mac-CellGroupConfig -- Need M + out.mac_cell_group_cfg_present = true; + out.mac_cell_group_cfg.sched_request_cfg_present = true; + out.mac_cell_group_cfg.sched_request_cfg.sched_request_to_add_mod_list_present = true; + out.mac_cell_group_cfg.sched_request_cfg.sched_request_to_add_mod_list.resize(1); + out.mac_cell_group_cfg.sched_request_cfg.sched_request_to_add_mod_list[0].sched_request_id = 0; + out.mac_cell_group_cfg.sched_request_cfg.sched_request_to_add_mod_list[0].sr_trans_max.value = + sched_request_to_add_mod_s::sr_trans_max_opts::n64; + out.mac_cell_group_cfg.bsr_cfg_present = true; + out.mac_cell_group_cfg.bsr_cfg.periodic_bsr_timer.value = bsr_cfg_s::periodic_bsr_timer_opts::sf20; + out.mac_cell_group_cfg.bsr_cfg.retx_bsr_timer.value = bsr_cfg_s::retx_bsr_timer_opts::sf320; + out.mac_cell_group_cfg.tag_cfg_present = true; + out.mac_cell_group_cfg.tag_cfg.tag_to_add_mod_list_present = true; + out.mac_cell_group_cfg.tag_cfg.tag_to_add_mod_list.resize(1); + out.mac_cell_group_cfg.tag_cfg.tag_to_add_mod_list[0].tag_id = 0; + out.mac_cell_group_cfg.tag_cfg.tag_to_add_mod_list[0].time_align_timer.value = time_align_timer_opts::infinity; + out.mac_cell_group_cfg.phr_cfg_present = true; + phr_cfg_s& phr = out.mac_cell_group_cfg.phr_cfg.set_setup(); + phr.phr_periodic_timer.value = asn1::rrc_nr::phr_cfg_s::phr_periodic_timer_opts::sf500; + phr.phr_prohibit_timer.value = asn1::rrc_nr::phr_cfg_s::phr_prohibit_timer_opts::sf200; + phr.phr_tx_pwr_factor_change.value = asn1::rrc_nr::phr_cfg_s::phr_tx_pwr_factor_change_opts::db3; + phr.multiple_phr = false; + phr.dummy = false; + phr.phr_type2_other_cell = false; + phr.phr_mode_other_cg.value = asn1::rrc_nr::phr_cfg_s::phr_mode_other_cg_opts::real; + out.mac_cell_group_cfg.skip_ul_tx_dynamic = false; + + // physicalCellGroupConfig -- Need M + out.phys_cell_group_cfg_present = true; + out.phys_cell_group_cfg.p_nr_fr1_present = true; + out.phys_cell_group_cfg.p_nr_fr1 = 10; + out.phys_cell_group_cfg.pdsch_harq_ack_codebook.value = + phys_cell_group_cfg_s::pdsch_harq_ack_codebook_opts::dynamic_value; + + // spCellConfig -- Need M + out.sp_cell_cfg_present = true; + fill_sp_cell_cfg_from_enb_cfg(cfg, cc, out.sp_cell_cfg); + out.sp_cell_cfg.recfg_with_sync_present = false; + + return SRSRAN_SUCCESS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + int fill_mib_from_enb_cfg(const rrc_nr_cfg_t& cfg, asn1::rrc_nr::mib_s& mib) { uint32_t scs = diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index f9f2e06ba..ada6213ef 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -49,7 +49,15 @@ int rrc_nr::init(const rrc_nr_cfg_t& cfg_, rrc_eutra = rrc_eutra_; cfg = cfg_; + + // Generate cell config structs cell_ctxt.reset(new cell_ctxt_t{}); + if (cfg.is_standalone) { + std::unique_ptr master_cell_group{new cell_group_cfg_s{}}; + int ret = fill_master_cell_cfg_from_enb_cfg(cfg, 0, *master_cell_group); + srsran_assert(ret == SRSRAN_SUCCESS, "Failed to configure MasterCellGroup"); + cell_ctxt->master_cell_group = std::move(master_cell_group); + } // derived slot_dur_ms = 1; diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index 77fc9e430..4778bdbaa 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -26,7 +26,7 @@ Every function in UE class is called from a mutex environment thus does not need extra protection. *******************************************************************************/ rrc_nr::ue::ue(rrc_nr* parent_, uint16_t rnti_, const sched_nr_ue_cfg_t& uecfg_, bool start_msg3_timer) : - parent(parent_), rnti(rnti_), uecfg(uecfg_) + parent(parent_), logger(parent_->logger), rnti(rnti_), uecfg(uecfg_) { // Derive UE cfg from rrc_cfg_nr_t uecfg.phy_cfg.pdcch = parent->cfg.cell_list[0].phy_cell.pdcch; @@ -56,12 +56,12 @@ void rrc_nr::ue::set_activity_timeout(activity_timeout_type_t type) deadline_ms = 10000; break; default: - parent->logger.error("Unknown timeout type %d", type); + logger.error("Unknown timeout type %d", type); return; } activity_timer.set(deadline_ms, [this, type](uint32_t tid) { activity_timer_expired(type); }); - parent->logger.debug("Setting timer for %s for rnti=0x%x to %dms", to_string(type).c_str(), rnti, deadline_ms); + logger.debug("Setting timer for %s for rnti=0x%x to %dms", to_string(type).c_str(), rnti, deadline_ms); set_activity(); } @@ -70,7 +70,7 @@ void rrc_nr::ue::set_activity(bool enabled) { if (not enabled) { if (activity_timer.is_running()) { - parent->logger.debug("Inactivity timer interrupted for rnti=0x%x", rnti); + logger.debug("Inactivity timer interrupted for rnti=0x%x", rnti); } activity_timer.stop(); return; @@ -78,12 +78,12 @@ void rrc_nr::ue::set_activity(bool enabled) // re-start activity timer with current timeout value activity_timer.run(); - parent->logger.debug("Activity registered for rnti=0x%x (timeout_value=%dms)", rnti, activity_timer.duration()); + logger.debug("Activity registered for rnti=0x%x (timeout_value=%dms)", rnti, activity_timer.duration()); } void rrc_nr::ue::activity_timer_expired(const activity_timeout_type_t type) { - parent->logger.info("Activity timer for rnti=0x%x expired after %d ms", rnti, activity_timer.time_elapsed()); + logger.info("Activity timer for rnti=0x%x expired after %d ms", rnti, activity_timer.time_elapsed()); switch (type) { case MSG5_RX_TIMEOUT: @@ -101,7 +101,7 @@ void rrc_nr::ue::activity_timer_expired(const activity_timeout_type_t type) default: // Unhandled activity timeout, just remove UE and log an error parent->rem_user(rnti); - parent->logger.error( + logger.error( "Unhandled reason for activity timer expiration. rnti=0x%x, cause %d", rnti, static_cast(type)); } } @@ -117,7 +117,7 @@ void rrc_nr::ue::send_dl_ccch(const dl_ccch_msg_s& dl_ccch_msg) // Allocate a new PDU buffer, pack the message and send to PDCP srsran::unique_byte_buffer_t pdu = parent->pack_into_pdu(dl_ccch_msg); if (pdu == nullptr) { - parent->logger.error("Failed to send DL-CCCH"); + logger.error("Failed to send DL-CCCH"); return; } fmt::memory_buffer fmtbuf; @@ -131,7 +131,7 @@ void rrc_nr::ue::send_dl_dcch(srsran::nr_srb srb, const asn1::rrc_nr::dl_dcch_ms // Allocate a new PDU buffer, pack the message and send to PDCP srsran::unique_byte_buffer_t pdu = parent->pack_into_pdu(dl_dcch_msg); if (pdu == nullptr) { - parent->logger.error("Failed to send DL-DCCH"); + logger.error("Failed to send DL-DCCH"); return; } fmt::memory_buffer fmtbuf; @@ -213,7 +213,6 @@ int rrc_nr::ue::pack_sp_cell_cfg_ded_init_dl_bwp(asn1::rrc_nr::cell_group_cfg_s& { cell_group_cfg_pack.sp_cell_cfg.sp_cell_cfg_ded.init_dl_bwp_present = true; - pack_sp_cell_cfg_ded_init_dl_bwp_pdsch_cfg(cell_group_cfg_pack); pack_sp_cell_cfg_ded_init_dl_bwp_radio_link_monitoring(cell_group_cfg_pack); return SRSRAN_SUCCESS; @@ -236,58 +235,6 @@ int rrc_nr::ue::pack_sp_cell_cfg_ded_init_dl_bwp_radio_link_monitoring( return SRSRAN_SUCCESS; } -int rrc_nr::ue::pack_sp_cell_cfg_ded_init_dl_bwp_pdsch_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack) -{ - cell_group_cfg_pack.sp_cell_cfg.sp_cell_cfg_ded.init_dl_bwp.pdsch_cfg_present = true; - auto& pdsch_cfg_dedicated = cell_group_cfg_pack.sp_cell_cfg.sp_cell_cfg_ded.init_dl_bwp.pdsch_cfg; - - pdsch_cfg_dedicated.set_setup(); - pdsch_cfg_dedicated.setup().dmrs_dl_for_pdsch_map_type_a_present = true; - pdsch_cfg_dedicated.setup().dmrs_dl_for_pdsch_map_type_a.set_setup(); - pdsch_cfg_dedicated.setup().dmrs_dl_for_pdsch_map_type_a.setup().dmrs_add_position_present = true; - pdsch_cfg_dedicated.setup().dmrs_dl_for_pdsch_map_type_a.setup().dmrs_add_position = - asn1::rrc_nr::dmrs_dl_cfg_s::dmrs_add_position_opts::pos1; - pdsch_cfg_dedicated.setup().tci_states_to_add_mod_list_present = true; - pdsch_cfg_dedicated.setup().tci_states_to_add_mod_list.resize(1); - pdsch_cfg_dedicated.setup().tci_states_to_add_mod_list[0].tci_state_id = 0; - pdsch_cfg_dedicated.setup().tci_states_to_add_mod_list[0].qcl_type1.ref_sig.set_ssb(); - pdsch_cfg_dedicated.setup().tci_states_to_add_mod_list[0].qcl_type1.ref_sig.ssb() = 0; - pdsch_cfg_dedicated.setup().tci_states_to_add_mod_list[0].qcl_type1.qcl_type = - asn1::rrc_nr::qcl_info_s::qcl_type_opts::type_d; - pdsch_cfg_dedicated.setup().res_alloc = pdsch_cfg_s::res_alloc_opts::res_alloc_type1; - pdsch_cfg_dedicated.setup().rbg_size = asn1::rrc_nr::pdsch_cfg_s::rbg_size_opts::cfg1; - pdsch_cfg_dedicated.setup().prb_bundling_type.set_static_bundling(); - pdsch_cfg_dedicated.setup().prb_bundling_type.static_bundling().bundle_size_present = true; - pdsch_cfg_dedicated.setup().prb_bundling_type.static_bundling().bundle_size = - asn1::rrc_nr::pdsch_cfg_s::prb_bundling_type_c_::static_bundling_s_::bundle_size_opts::wideband; - - // ZP-CSI - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list_present = false; - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list.resize(1); - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].zp_csi_rs_res_id = 0; - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].res_map.freq_domain_alloc.set_row4(); - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].res_map.freq_domain_alloc.row4().from_number(0b100); - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].res_map.nrof_ports = - asn1::rrc_nr::csi_rs_res_map_s::nrof_ports_opts::p4; - - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].res_map.first_ofdm_symbol_in_time_domain = 8; - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].res_map.cdm_type = - asn1::rrc_nr::csi_rs_res_map_s::cdm_type_opts::fd_cdm2; - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].res_map.density.set_one(); - - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].res_map.freq_band.start_rb = 0; - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].res_map.freq_band.nrof_rbs = 52; - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].periodicity_and_offset_present = true; - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].periodicity_and_offset.set_slots80(); - pdsch_cfg_dedicated.setup().zp_csi_rs_res_to_add_mod_list[0].periodicity_and_offset.slots80() = 1; - pdsch_cfg_dedicated.setup().p_zp_csi_rs_res_set_present = false; - pdsch_cfg_dedicated.setup().p_zp_csi_rs_res_set.set_setup(); - pdsch_cfg_dedicated.setup().p_zp_csi_rs_res_set.setup().zp_csi_rs_res_set_id = 0; - pdsch_cfg_dedicated.setup().p_zp_csi_rs_res_set.setup().zp_csi_rs_res_id_list.resize(1); - - return SRSRAN_SUCCESS; -} - int rrc_nr::ue::pack_sp_cell_cfg_ded_ul_cfg_init_ul_bwp_pucch_cfg(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack) { // PUCCH @@ -357,13 +304,13 @@ int rrc_nr::ue::pack_sp_cell_cfg_ded_ul_cfg_init_ul_bwp_pucch_cfg(asn1::rrc_nr:: pucch_cfg.setup().res_to_add_mod_list_present = true; pucch_cfg.setup().res_to_add_mod_list.resize(3); if (not srsran::make_phy_res_config(resource_small, pucch_cfg.setup().res_to_add_mod_list[0], 0)) { - parent->logger.warning("Failed to create 1-2 bit NR PUCCH resource"); + logger.warning("Failed to create 1-2 bit NR PUCCH resource"); } if (not srsran::make_phy_res_config(resource_big, pucch_cfg.setup().res_to_add_mod_list[1], 1)) { - parent->logger.warning("Failed to create >2 bit NR PUCCH resource"); + logger.warning("Failed to create >2 bit NR PUCCH resource"); } if (not srsran::make_phy_res_config(resource_sr, pucch_cfg.setup().res_to_add_mod_list[2], 2)) { - parent->logger.warning("Failed to create SR NR PUCCH resource"); + logger.warning("Failed to create SR NR PUCCH resource"); } // Make 2 PUCCH resource sets @@ -467,14 +414,7 @@ int rrc_nr::ue::pack_sp_cell_cfg_ded_pdcch_serving_cell_cfg(asn1::rrc_nr::cell_g int rrc_nr::ue::pack_sp_cell_cfg_ded(asn1::rrc_nr::cell_group_cfg_s& cell_group_cfg_pack) { // SP Cell Dedicated config - cell_group_cfg_pack.sp_cell_cfg.sp_cell_cfg_ded_present = true; - cell_group_cfg_pack.sp_cell_cfg.sp_cell_cfg_ded.first_active_dl_bwp_id_present = true; - - if (parent->cfg.cell_list[0].duplex_mode == SRSRAN_DUPLEX_MODE_FDD) { - cell_group_cfg_pack.sp_cell_cfg.sp_cell_cfg_ded.first_active_dl_bwp_id = 0; - } else { - cell_group_cfg_pack.sp_cell_cfg.sp_cell_cfg_ded.first_active_dl_bwp_id = 1; - } + cell_group_cfg_pack.sp_cell_cfg.sp_cell_cfg_ded_present = true; pack_sp_cell_cfg_ded_ul_cfg(cell_group_cfg_pack); pack_sp_cell_cfg_ded_init_dl_bwp(cell_group_cfg_pack); @@ -484,7 +424,7 @@ int rrc_nr::ue::pack_sp_cell_cfg_ded(asn1::rrc_nr::cell_group_cfg_s& cell_group_ // spCellConfig if (fill_sp_cell_cfg_from_enb_cfg(parent->cfg, UE_PSCELL_CC_IDX, cell_group_cfg_pack.sp_cell_cfg) != SRSRAN_SUCCESS) { - parent->logger.error("Failed to pack spCellConfig for rnti=0x%x", rnti); + logger.error("Failed to pack spCellConfig for rnti=0x%x", rnti); } return SRSRAN_SUCCESS; @@ -664,7 +604,7 @@ int rrc_nr::ue::pack_secondary_cell_group_cfg(asn1::dyn_octstring& packed_second packed_secondary_cell_config.resize(256); asn1::bit_ref bref_pack(packed_secondary_cell_config.data(), packed_secondary_cell_config.size()); if (cell_group_cfg_pack.pack(bref_pack) != asn1::SRSASN_SUCCESS) { - parent->logger.error("Failed to pack NR secondary cell config"); + logger.error("Failed to pack NR secondary cell config"); return SRSRAN_ERROR; } packed_secondary_cell_config.resize(bref_pack.distance_bytes()); @@ -685,7 +625,7 @@ int rrc_nr::ue::pack_rrc_reconfiguration(asn1::dyn_octstring& packed_rrc_reconfi recfg_ies.secondary_cell_group_present = true; if (pack_secondary_cell_group_cfg(recfg_ies.secondary_cell_group) == SRSRAN_ERROR) { - parent->logger.error("Failed to pack secondary cell group"); + logger.error("Failed to pack secondary cell group"); return SRSRAN_ERROR; } @@ -693,7 +633,7 @@ int rrc_nr::ue::pack_rrc_reconfiguration(asn1::dyn_octstring& packed_rrc_reconfi packed_rrc_reconfig.resize(512); asn1::bit_ref bref_pack(packed_rrc_reconfig.data(), packed_rrc_reconfig.size()); if (reconfig.pack(bref_pack) != asn1::SRSASN_SUCCESS) { - parent->logger.error("Failed to pack RRC Reconfiguration"); + logger.error("Failed to pack RRC Reconfiguration"); return SRSRAN_ERROR; } packed_rrc_reconfig.resize(bref_pack.distance_bytes()); @@ -719,7 +659,7 @@ int rrc_nr::ue::pack_nr_radio_bearer_config(asn1::dyn_octstring& packed_nr_beare packed_nr_bearer_config.resize(128); asn1::bit_ref bref_pack(packed_nr_bearer_config.data(), packed_nr_bearer_config.size()); if (radio_bearer_cfg_pack.pack(bref_pack) != asn1::SRSASN_SUCCESS) { - parent->logger.error("Failed to pack NR radio bearer config"); + logger.error("Failed to pack NR radio bearer config"); return SRSRAN_ERROR; } @@ -883,25 +823,36 @@ int rrc_nr::ue::add_drb() void rrc_nr::ue::handle_rrc_setup_request(const asn1::rrc_nr::rrc_setup_request_s& msg) { + const uint8_t max_wait_time_secs = 16; if (not parent->ngap->is_amf_connected()) { - parent->logger.error("MME isn't connected. Sending Connection Reject"); - const uint8_t max_wait_time_secs = 16; - send_rrc_reject(max_wait_time_secs); // See TS 38.331, RejectWaitTime + logger.error("MME isn't connected. Sending Connection Reject"); + send_rrc_reject(max_wait_time_secs); return; } - // TODO: Allocate PUCCH resources and reject if not available + // Allocate PUCCH resources and reject if not available + if (not init_pucch()) { + logger.warning("Could not allocate PUCCH resources for rnti=0x%x. Sending Connection Reject", rnti); + send_rrc_reject(max_wait_time_secs); + return; + } - switch (msg.rrc_setup_request.ue_id.type().value) { - case asn1::rrc_nr::init_ue_id_c::types_opts::ng_minus5_g_s_tmsi_part1: - // TODO: communicate with NGAP + const rrc_setup_request_ies_s& ies = msg.rrc_setup_request; + + switch (ies.ue_id.type().value) { + case init_ue_id_c::types_opts::ng_minus5_g_s_tmsi_part1: + ctxt.setup_ue_id = ies.ue_id.ng_minus5_g_s_tmsi_part1().to_number(); break; case asn1::rrc_nr::init_ue_id_c::types_opts::random_value: + ctxt.setup_ue_id = ies.ue_id.random_value().to_number(); // TODO: communicate with NGAP break; default: - parent->logger.error("Unsupported RRCSetupRequest"); + logger.error("Unsupported RRCSetupRequest"); + send_rrc_reject(max_wait_time_secs); + return; } + ctxt.connection_cause.value = ies.establishment_cause.value; send_rrc_setup(); set_activity_timeout(UE_INACTIVITY_TIMEOUT); @@ -912,16 +863,22 @@ void rrc_nr::ue::send_rrc_reject(uint8_t reject_wait_time_secs) { dl_ccch_msg_s msg; rrc_reject_ies_s& reject = msg.msg.set_c1().set_rrc_reject().crit_exts.set_rrc_reject(); + + // See TS 38.331, RejectWaitTime if (reject_wait_time_secs > 0) { reject.wait_time_present = true; reject.wait_time = reject_wait_time_secs; } send_dl_ccch(msg); + + // TODO: remove user } /// TS 38.331, RRCSetup void rrc_nr::ue::send_rrc_setup() { + const uint8_t max_wait_time_secs = 16; + dl_ccch_msg_s msg; rrc_setup_s& setup = msg.msg.set_c1().set_rrc_setup(); setup.rrc_transaction_id = (uint8_t)((transaction_id++) % 4); @@ -934,6 +891,25 @@ void rrc_nr::ue::send_rrc_setup() srb_to_add_mod_s& srb1 = setup_ies.radio_bearer_cfg.srb_to_add_mod_list[0]; srb1.srb_id = 1; + { + srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); + asn1::bit_ref bref{pdu->data(), pdu->get_tailroom()}; + if (parent->cell_ctxt->master_cell_group->pack(bref) != SRSRAN_SUCCESS) { + logger.error("Failed to pack master cell group"); + send_rrc_reject(max_wait_time_secs); + return; + } + pdu->N_bytes = bref.distance_bytes(); + + setup_ies.master_cell_group.resize(pdu->N_bytes); + memcpy(setup_ies.master_cell_group.data(), pdu->data(), pdu->N_bytes); + } + if (logger.debug.enabled()) { + asn1::json_writer js; + parent->cell_ctxt->master_cell_group->to_json(js); + logger.debug("Containerized MasterCellGroup: %s", js.to_string().c_str()); + } + send_dl_ccch(msg); } @@ -941,6 +917,9 @@ void rrc_nr::ue::send_rrc_setup() void rrc_nr::ue::handle_rrc_setup_complete(const asn1::rrc_nr::rrc_setup_complete_s& msg) { // TODO: handle RRCSetupComplete + using ngap_cause_t = asn1::ngap_nr::rrcestablishment_cause_opts::options; + auto ngap_cause = (ngap_cause_t)ctxt.connection_cause.value; // NGAP and RRC causes seem to have a 1-1 mapping + parent->ngap->initial_ue(rnti, uecfg.carriers[0].cc, ngap_cause, {}, ctxt.setup_ue_id); send_security_mode_command(); } @@ -991,6 +970,13 @@ void rrc_nr::ue::handle_ul_information_transfer(const asn1::rrc_nr::ul_info_tran // TODO: handle UL information transfer } +bool rrc_nr::ue::init_pucch() +{ + // TODO: Allocate PUCCH resources + + return true; +} + /** * @brief Deactivate all Bearers (MAC logical channel) for this specific RNTI * diff --git a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc index 02bcbedea..7e4a05f93 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc @@ -146,8 +146,9 @@ void test_rrc_sa_connection() rrc_nr_cfg_t rrc_cfg_nr = rrc_nr_cfg_t{}; rrc_cfg_nr.cell_list.emplace_back(); rrc_cfg_nr.cell_list[0].phy_cell.carrier.pci = 500; - rrc_cfg_nr.cell_list[0].dl_arfcn = 634240; - rrc_cfg_nr.cell_list[0].band = 78; + rrc_cfg_nr.cell_list[0].dl_arfcn = 368500; + rrc_cfg_nr.cell_list[0].band = 3; + rrc_cfg_nr.cell_list[0].duplex_mode = SRSRAN_DUPLEX_MODE_FDD; rrc_cfg_nr.is_standalone = true; args.enb.n_prb = 50; enb_conf_sections::set_derived_args_nr(&args, &rrc_cfg_nr, &phy_cfg); 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 7d4a8a62a..14485b158 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.cc +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test_helpers.cc @@ -63,6 +63,14 @@ void test_rrc_nr_connection_establishment(srsran::task_scheduler& task_sched, rrc_setup_complete_s& complete = ul_dcch_msg.msg.set_c1().set_rrc_setup_complete(); complete.rrc_transaction_id = dl_ccch_msg.msg.c1().rrc_setup().rrc_transaction_id; rrc_setup_complete_ies_s& complete_ies = complete.crit_exts.set_rrc_setup_complete(); + complete_ies.sel_plmn_id = 1; // First PLMN in list + complete_ies.registered_amf_present = true; + complete_ies.registered_amf.amf_id.from_number(0x800101); + complete_ies.guami_type_present = true; + complete_ies.guami_type.value = rrc_setup_complete_ies_s::guami_type_opts::native; + complete_ies.ded_nas_msg.from_string("7E01280E534C337E004109000BF200F110800101347B80802E02F07071002D7E004109000BF200F" + "110800101347B80801001002E02F0702F0201015200F11000006418010174000090530101"); + { pdu = srsran::make_byte_buffer(); asn1::bit_ref bref{pdu->data(), pdu->get_tailroom()}; From b7f9dd5d75a985c362f63b4c66a45fa08795ad87 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 15 Nov 2021 16:55:29 +0100 Subject: [PATCH 61/77] phy_common_nr: add helper method to print coreset info to string --- lib/include/srsran/phy/common/phy_common_nr.h | 12 +++++ lib/src/phy/common/phy_common_nr.c | 51 +++++++++++++++++++ lib/src/phy/phch/pdcch_nr.c | 13 ----- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/lib/include/srsran/phy/common/phy_common_nr.h b/lib/include/srsran/phy/common/phy_common_nr.h index 8e6c6cea8..d868e2470 100644 --- a/lib/include/srsran/phy/common/phy_common_nr.h +++ b/lib/include/srsran/phy/common/phy_common_nr.h @@ -212,6 +212,8 @@ typedef enum SRSRAN_API { srsran_coreset_bundle_size_n6, } srsran_coreset_bundle_size_t; +uint32_t pdcch_nr_bundle_size(srsran_coreset_bundle_size_t x); + typedef enum SRSRAN_API { srsran_coreset_precoder_granularity_contiguous = 0, srsran_coreset_precoder_granularity_reg_bundle @@ -714,6 +716,16 @@ SRSRAN_API int srsran_coreset_zero(uint32_t n_cell_id, SRSRAN_API int srsran_coreset0_ssb_offset(uint32_t idx, srsran_subcarrier_spacing_t ssb_scs, srsran_subcarrier_spacing_t pdcch_scs); +/** + * @brief Convert Coreset to string + * + * @param coreset The coreset structure as input + * @param str The string to write to + * @param str_len Maximum string length + * @return SRSRAN_API + */ +SRSRAN_API int srsran_coreset_to_str(srsran_coreset_t* coreset, char* str, uint32_t str_len); + /** * @brief Convert SSB pattern to string * @param pattern diff --git a/lib/src/phy/common/phy_common_nr.c b/lib/src/phy/common/phy_common_nr.c index acb3f7fdf..1d2c405f2 100644 --- a/lib/src/phy/common/phy_common_nr.c +++ b/lib/src/phy/common/phy_common_nr.c @@ -528,6 +528,19 @@ void srsran_combine_csi_trs_measurements(const srsran_csi_trs_measurements_t* a, dst->nof_re = nof_re_sum; } +uint32_t pdcch_nr_bundle_size(srsran_coreset_bundle_size_t x) +{ + switch (x) { + case srsran_coreset_bundle_size_n2: + return 2; + case srsran_coreset_bundle_size_n3: + return 3; + case srsran_coreset_bundle_size_n6: + return 6; + } + return 0; +} + typedef struct { uint32_t mux_pattern; uint32_t nof_prb; @@ -596,6 +609,44 @@ static const coreset_zero_entry_t coreset_zero_30_15[16] = { {}, }; +int srsran_coreset_to_str(srsran_coreset_t* coreset, char* str, uint32_t str_len) +{ + if (coreset == NULL || str == NULL || str_len == 0) { + return 0; + } + + char freq_res_str[SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE] = {}; + srsran_vec_sprint_bin( + freq_res_str, sizeof(freq_res_str), (uint8_t*)coreset->freq_resources, SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE); + + return srsran_print_check( + str, + str_len, + 0, + "\n" + " - coreset_id=%d\n" + " - mapping_type=%s\n" + " - duration=%d\n" + " - freq_res=%s\n" + " - dmrs_scrambling_present=%s (id=%d)\n" + " - precoder_granularity=%s\n" + " - interleaver_size=%d\n" + " - reg_bundle_size=%d\n" + " - shift_index=%d\n" + " - offset_rb=%d\n", + coreset->id, + coreset->mapping_type == srsran_coreset_mapping_type_non_interleaved ? "non-interleaved" : "interleaved", + coreset->duration, + freq_res_str, + coreset->dmrs_scrambling_id_present ? "true" : "false", + coreset->dmrs_scrambling_id, + coreset->precoder_granularity == srsran_coreset_precoder_granularity_contiguous ? "contiguous" : "reg_bundle", + pdcch_nr_bundle_size(coreset->interleaver_size), + pdcch_nr_bundle_size(coreset->reg_bundle_size), + coreset->shift_index, + coreset->offset_rb); +} + int srsran_coreset_zero(uint32_t n_cell_id, uint32_t ssb_pointA_freq_offset_Hz, srsran_subcarrier_spacing_t ssb_scs, diff --git a/lib/src/phy/phch/pdcch_nr.c b/lib/src/phy/phch/pdcch_nr.c index 61977b856..deee7bbcf 100644 --- a/lib/src/phy/phch/pdcch_nr.c +++ b/lib/src/phy/phch/pdcch_nr.c @@ -304,19 +304,6 @@ int srsran_pdcch_nr_set_carrier(srsran_pdcch_nr_t* q, return SRSRAN_SUCCESS; } -static uint32_t pdcch_nr_bundle_size(srsran_coreset_bundle_size_t x) -{ - switch (x) { - case srsran_coreset_bundle_size_n2: - return 2; - case srsran_coreset_bundle_size_n3: - return 3; - case srsran_coreset_bundle_size_n6: - return 6; - } - return 0; -} - static int pdcch_nr_cce_to_reg_mapping_non_interleaved(const srsran_coreset_t* coreset, const srsran_dci_location_t* dci_location, bool rb_mask[SRSRAN_MAX_PRB_NR]) From b687f58ff0c7b93937c604d759c1116edefdf586 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 15 Nov 2021 14:10:33 +0100 Subject: [PATCH 62/77] ue,cc_worker: print coreset info in debug mode --- srsue/src/phy/nr/cc_worker.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/srsue/src/phy/nr/cc_worker.cc b/srsue/src/phy/nr/cc_worker.cc index 8fbcaeb9a..7b1f81663 100644 --- a/srsue/src/phy/nr/cc_worker.cc +++ b/srsue/src/phy/nr/cc_worker.cc @@ -156,6 +156,14 @@ void cc_worker::decode_pdcch_dl() logger.info("PDCCH: cc=%d, %s", cc_idx, str.data()); } + if (logger.debug.enabled()) { + // log coreset info + srsran_coreset_t* coreset = &ue_dl.cfg.coreset[dci_rx[i].ctx.coreset_id]; + std::array coreset_str; + srsran_coreset_to_str(coreset, coreset_str.data(), coreset_str.size()); + logger.info("PDCCH: coreset=%d, %s", cc_idx, coreset_str.data()); + } + // Enqueue UL grants phy.set_dl_pending_grant(cfg, dl_slot_cfg, dci_rx[i]); } From 0ee82ed0f657b3a605582a92ca259445ecc2a39d Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Mon, 15 Nov 2021 14:29:32 +0100 Subject: [PATCH 63/77] ue_dl_nr_file_test: add option to select ue specific search space * add option to select search space: ue and common1 * print coreset info --- lib/src/phy/ue/test/ue_dl_nr_file_test.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc index 1358b8c10..103e48545 100644 --- a/lib/src/phy/ue/test/ue_dl_nr_file_test.cc +++ b/lib/src/phy/ue/test/ue_dl_nr_file_test.cc @@ -110,8 +110,12 @@ static int parse_args(int argc, char** argv) case 's': if (strcmp(argv[optind], "common0") == 0) { ss_type = srsran_search_space_type_common_0; + } else if (strcmp(argv[optind], "common1") == 0) { + ss_type = srsran_search_space_type_common_1; } else if (strcmp(argv[optind], "common3") == 0) { ss_type = srsran_search_space_type_common_3; + } else if (strcmp(argv[optind], "ue") == 0) { + ss_type = srsran_search_space_type_ue; } else { printf("Invalid SS type '%s'\n", argv[optind]); usage(argv[0]); @@ -346,14 +350,20 @@ int main(int argc, char** argv) } else { // configure to use coreset1 coreset = &pdcch_cfg.coreset[1]; + coreset->id = 1; pdcch_cfg.coreset_present[1] = true; coreset->duration = coreset_len; coreset->offset_rb = coreset_offset_rb; for (uint32_t i = 0; i < SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { coreset->freq_resources[i] = i < coreset_n_rb / 6; } + pdsch_hl_cfg.nof_common_time_ra = 1; } + char coreset_info[512] = {}; + srsran_coreset_to_str(coreset, coreset_info, sizeof(coreset_info)); + INFO("Coreset parameter: %s", coreset_info); + // Configure Search Space srsran_search_space_t* search_space = &pdcch_cfg.search_space[0]; pdcch_cfg.search_space_present[0] = true; From 453a7760b3aaaa69eb741403fa03c671d3eeffe8 Mon Sep 17 00:00:00 2001 From: faluco Date: Fri, 12 Nov 2021 15:11:09 +0100 Subject: [PATCH 64/77] Port misc SSN changes to dev. --- lib/include/srsran/adt/circular_buffer.h | 4 +- lib/include/srsran/common/block_queue.h | 12 ++++- lib/src/common/threads.c | 4 +- srsenb/hdr/phy/phy_metrics.h | 16 +++++- srsenb/hdr/stack/mac/common/mac_metrics.h | 19 ++++--- srsenb/hdr/stack/mac/sched.h | 1 + srsenb/hdr/stack/mac/sched_ue.h | 2 + .../stack/mac/sched_ue_ctrl/sched_ue_cell.h | 2 + srsenb/src/metrics_json.cc | 42 ++++++++++++--- srsenb/src/metrics_stdout.cc | 9 ++-- srsenb/src/stack/mac/mac.cc | 29 +++++++--- srsenb/src/stack/mac/sched.cc | 6 +++ srsenb/src/stack/mac/sched_grid.cc | 53 ++++++++++--------- srsenb/src/stack/mac/sched_ue.cc | 7 +++ srsenb/src/stack/upper/rlc.cc | 7 ++- 15 files changed, 154 insertions(+), 59 deletions(-) diff --git a/lib/include/srsran/adt/circular_buffer.h b/lib/include/srsran/adt/circular_buffer.h index 72f636dff..156abc99c 100644 --- a/lib/include/srsran/adt/circular_buffer.h +++ b/lib/include/srsran/adt/circular_buffer.h @@ -291,7 +291,7 @@ public: } return obj; } - bool pop_wait_until(T& obj, const std::chrono::system_clock::time_point& until) { return pop_(obj, true, &until); } + bool pop_wait_until(T& obj, const std::chrono::steady_clock::time_point& until) { return pop_(obj, true, &until); } void clear() { T obj; @@ -405,7 +405,7 @@ protected: return {}; } - bool pop_(T& obj, bool block, const std::chrono::system_clock::time_point* until = nullptr) + bool pop_(T& obj, bool block, const std::chrono::steady_clock::time_point* until = nullptr) { std::unique_lock lock(mutex); if (not active) { diff --git a/lib/include/srsran/common/block_queue.h b/lib/include/srsran/common/block_queue.h index 7442c9a0b..a53bd67c0 100644 --- a/lib/include/srsran/common/block_queue.h +++ b/lib/include/srsran/common/block_queue.h @@ -95,6 +95,8 @@ public: return value; } + bool timedwait_pop(myobj* value, const struct timespec* abstime) { return pop_(value, true, abstime); } + bool empty() { // queue is empty? pthread_mutex_lock(&mutex); @@ -130,7 +132,7 @@ public: } private: - bool pop_(myobj* value, bool block) + bool pop_(myobj* value, bool block, const struct timespec* abstime = nullptr) { if (!enable) { return false; @@ -142,7 +144,13 @@ private: goto exit; } while (q.empty() && enable) { - pthread_cond_wait(&cv_empty, &mutex); + if (abstime == nullptr) { + pthread_cond_wait(&cv_empty, &mutex); + } else { + if (pthread_cond_timedwait(&cv_empty, &mutex, abstime)) { + goto exit; + } + } } if (!enable) { goto exit; diff --git a/lib/src/common/threads.c b/lib/src/common/threads.c index bad3d05d7..8ae786848 100644 --- a/lib/src/common/threads.c +++ b/lib/src/common/threads.c @@ -80,7 +80,9 @@ bool threads_new_rt_cpu(pthread_t* thread, void* (*start_routine)(void*), void* #else // All threads have normal priority except prio_offset=0,1,2,3,4 if (prio_offset >= 0 && prio_offset < 5) { - param.sched_priority = sched_get_priority_max(SCHED_FIFO) - prio_offset; + // Subtract one to the priority offset to avoid scheduling threads with the highest priority that could contend with + // OS critical tasks. + param.sched_priority = sched_get_priority_max(SCHED_FIFO) - prio_offset - 1; if (pthread_attr_init(&attr)) { perror("pthread_attr_init"); } else { diff --git a/srsenb/hdr/phy/phy_metrics.h b/srsenb/hdr/phy/phy_metrics.h index 2207de864..0b27a5417 100644 --- a/srsenb/hdr/phy/phy_metrics.h +++ b/srsenb/hdr/phy/phy_metrics.h @@ -13,6 +13,8 @@ #ifndef SRSENB_PHY_METRICS_H #define SRSENB_PHY_METRICS_H +#include + namespace srsenb { // PHY metrics per user @@ -20,7 +22,15 @@ namespace srsenb { struct ul_metrics_t { float n; float pusch_sinr; - float pucch_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 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; @@ -30,7 +40,9 @@ struct ul_metrics_t { struct dl_metrics_t { float mcs; - int n_samples; + // Initialize this member with an invalid value as this field is optional. + int64_t pucch_tpc = 0; + int n_samples; }; struct phy_metrics_t { diff --git a/srsenb/hdr/stack/mac/common/mac_metrics.h b/srsenb/hdr/stack/mac/common/mac_metrics.h index 78165ed1b..4fb47770d 100644 --- a/srsenb/hdr/stack/mac/common/mac_metrics.h +++ b/srsenb/hdr/stack/mac/common/mac_metrics.h @@ -21,6 +21,7 @@ namespace srsenb { /// MAC metrics per user struct mac_ue_metrics_t { uint16_t rnti; + uint32_t pci; uint32_t nof_tti; uint32_t cc_idx; int tx_pkts; @@ -35,16 +36,18 @@ struct mac_ue_metrics_t { float dl_ri; float dl_pmi; float phr; + float dl_cqi_offset; + float ul_snr_offset; // NR-only UL PHY metrics - float pusch_sinr; - float pucch_sinr; - float ul_rssi; - float fec_iters; - float dl_mcs; - int dl_mcs_samples; - float ul_mcs; - int ul_mcs_samples; + float pusch_sinr; + float pucch_sinr; + float ul_rssi; + float fec_iters; + float dl_mcs; + int dl_mcs_samples; + float ul_mcs; + int ul_mcs_samples; }; /// MAC misc information for each cc. struct mac_cc_info_t { diff --git a/srsenb/hdr/stack/mac/sched.h b/srsenb/hdr/stack/mac/sched.h index 4e0ab9c68..31c076d1c 100644 --- a/srsenb/hdr/stack/mac/sched.h +++ b/srsenb/hdr/stack/mac/sched.h @@ -77,6 +77,7 @@ public: std::array get_enb_ue_cc_map(uint16_t rnti) final; std::array get_enb_ue_activ_cc_map(uint16_t rnti) final; int ul_buffer_add(uint16_t rnti, uint32_t lcid, uint32_t bytes) final; + int metrics_read(uint16_t rnti, mac_ue_metrics_t& metrics); class carrier_sched; diff --git a/srsenb/hdr/stack/mac/sched_ue.h b/srsenb/hdr/stack/mac/sched_ue.h index fc68c70c8..312fcc3c9 100644 --- a/srsenb/hdr/stack/mac/sched_ue.h +++ b/srsenb/hdr/stack/mac/sched_ue.h @@ -18,6 +18,7 @@ #include "sched_ue_ctrl/sched_ue_cell.h" #include "sched_ue_ctrl/tpc.h" #include "srsenb/hdr/common/common_enb.h" +#include "srsenb/hdr/stack/mac/common/mac_metrics.h" #include "srsran/srslog/srslog.h" #include #include @@ -73,6 +74,7 @@ public: const ue_cfg_t& get_ue_cfg() const { return cfg; } uint32_t get_aggr_level(uint32_t enb_cc_idx, uint32_t nof_bits); void ul_buffer_add(uint8_t lcid, uint32_t bytes); + void metrics_read(mac_ue_metrics_t& metrics); /******************************************************* * Functions used by scheduler metric objects diff --git a/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h b/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h index b0f34d5c4..830ae628c 100644 --- a/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h +++ b/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h @@ -43,6 +43,8 @@ struct sched_ue_cell { const ue_cc_cfg* get_ue_cc_cfg() const { return configured() ? &ue_cfg->supported_cc_list[ue_cc_idx] : nullptr; } const sched_interface::ue_cfg_t* get_ue_cfg() const { return configured() ? ue_cfg : nullptr; } cc_st cc_state() const { return cc_state_; } + float get_ul_snr_offset() const { return ul_snr_coeff; } + float get_dl_cqi_offset() const { return dl_cqi_coeff; } int get_dl_cqi() const; int get_dl_cqi(const rbgmask_t& rbgs) const; diff --git a/srsenb/src/metrics_json.cc b/srsenb/src/metrics_json.cc index 2e2505a48..0e129f712 100644 --- a/srsenb/src/metrics_json.cc +++ b/srsenb/src/metrics_json.cc @@ -45,6 +45,13 @@ DECLARE_METRIC("dl_bitrate", metric_dl_bitrate, float, ""); DECLARE_METRIC("dl_bler", metric_dl_bler, float, ""); DECLARE_METRIC("ul_snr", metric_ul_snr, float, ""); DECLARE_METRIC("ul_mcs", metric_ul_mcs, float, ""); +DECLARE_METRIC("ul_pusch_rssi", metric_ul_pusch_rssi, float, ""); +DECLARE_METRIC("ul_pucch_rssi", metric_ul_pucch_rssi, float, ""); +DECLARE_METRIC("ul_pucch_ni", metric_ul_pucch_ni, float, ""); +DECLARE_METRIC("ul_pusch_tpc", metric_ul_pusch_tpc, int64_t, ""); +DECLARE_METRIC("ul_pucch_tpc", metric_ul_pucch_tpc, int64_t, ""); +DECLARE_METRIC("dl_cqi_offset", metric_dl_cqi_offset, float, ""); +DECLARE_METRIC("ul_snr_offset", metric_ul_snr_offset, float, ""); DECLARE_METRIC("ul_bitrate", metric_ul_bitrate, float, ""); DECLARE_METRIC("ul_bler", metric_ul_bler, float, ""); DECLARE_METRIC("ul_phr", metric_ul_phr, float, ""); @@ -55,6 +62,13 @@ DECLARE_METRIC_SET("ue_container", metric_ue_rnti, metric_dl_cqi, metric_dl_mcs, + metric_ul_pusch_rssi, + metric_ul_pucch_rssi, + metric_ul_pucch_ni, + metric_ul_pusch_tpc, + metric_ul_pucch_tpc, + metric_dl_cqi_offset, + metric_ul_snr_offset, metric_dl_bitrate, metric_dl_bler, metric_ul_snr, @@ -88,24 +102,40 @@ static void fill_ue_metrics(mset_ue_container& ue, const enb_metrics_t& m, unsig ue.write(m.stack.mac.ues[i].rnti); ue.write(std::max(0.1f, m.stack.mac.ues[i].dl_cqi)); if (!std::isnan(m.phy[i].dl.mcs)) { - ue.write(std::max(0.1f, m.phy[i].dl.mcs)); + ue.write(m.phy[i].dl.mcs); } if (m.stack.mac.ues[i].tx_brate > 0 && m.stack.mac.ues[i].nof_tti > 0) { ue.write( std::max(0.1f, (float)m.stack.mac.ues[i].tx_brate / (m.stack.mac.ues[i].nof_tti * 0.001f))); } if (m.stack.mac.ues[i].tx_pkts > 0 && m.stack.mac.ues[i].tx_errors > 0) { - ue.write(std::max(0.1f, (float)100 * m.stack.mac.ues[i].tx_errors / m.stack.mac.ues[i].tx_pkts)); + ue.write((float)100 * m.stack.mac.ues[i].tx_errors / m.stack.mac.ues[i].tx_pkts); } if (!std::isnan(m.phy[i].ul.pusch_sinr)) { - ue.write(std::max(0.1f, m.phy[i].ul.pusch_sinr)); + ue.write(m.phy[i].ul.pusch_sinr); + } + if (!std::isnan(m.phy[i].ul.pusch_rssi)) { + ue.write(m.phy[i].ul.pusch_rssi); + } + if (!std::isnan(m.phy[i].ul.pucch_rssi)) { + ue.write(m.phy[i].ul.pucch_rssi); + } + if (!std::isnan(m.phy[i].ul.pucch_ni)) { + ue.write(m.phy[i].ul.pucch_ni); + } + ue.write(m.phy[i].ul.pusch_tpc); + ue.write(m.phy[i].dl.pucch_tpc); + if (!std::isnan(m.stack.mac.ues[i].dl_cqi_offset)) { + ue.write(m.stack.mac.ues[i].dl_cqi_offset); + } + if (!std::isnan(m.stack.mac.ues[i].ul_snr_offset)) { + ue.write(m.stack.mac.ues[i].ul_snr_offset); } if (!std::isnan(m.phy[i].ul.mcs)) { - ue.write(std::max(0.1f, m.phy[i].ul.mcs)); + ue.write(m.phy[i].ul.mcs); } if (m.stack.mac.ues[i].rx_brate > 0 && m.stack.mac.ues[i].nof_tti > 0) { - ue.write( - std::max(0.1f, (float)m.stack.mac.ues[i].rx_brate / (m.stack.mac.ues[i].nof_tti * 0.001f))); + ue.write((float)m.stack.mac.ues[i].rx_brate / (m.stack.mac.ues[i].nof_tti * 0.001f)); } if (m.stack.mac.ues[i].rx_pkts > 0 && m.stack.mac.ues[i].rx_errors > 0) { ue.write(std::max(0.1f, (float)100 * m.stack.mac.ues[i].rx_errors / m.stack.mac.ues[i].rx_pkts)); diff --git a/srsenb/src/metrics_stdout.cc b/srsenb/src/metrics_stdout.cc index 2a1a213f2..29fcc70a0 100644 --- a/srsenb/src/metrics_stdout.cc +++ b/srsenb/src/metrics_stdout.cc @@ -88,7 +88,8 @@ void metrics_stdout::set_metrics_helper(uint32_t num_ue fmt::print("rx caution errors {} > {}\n", mac.ues[i].rx_errors, mac.ues[i].rx_pkts); } - fmt::print("{:>3.5}", (is_nr) ? "nr" : "lte"); + fmt::print("{:>4}", mac.ues[i].pci); + fmt::print(" {:>3.5}", (is_nr) ? "nr" : "lte"); fmt::print("{:>5x}", mac.ues[i].rnti); if (not iszero(mac.ues[i].dl_cqi)) { fmt::print(" {:>3}", int(mac.ues[i].dl_cqi)); @@ -185,8 +186,10 @@ void metrics_stdout::set_metrics(const enb_metrics_t& metrics, const uint32_t pe if (++n_reports > 10) { n_reports = 0; fmt::print("\n"); - fmt::print(" -----------------DL----------------|-------------------------UL-------------------------\n"); - fmt::print("rat rnti cqi ri mcs brate ok nok (%) | pusch pucch phr mcs brate ok nok (%) bsr\n"); + fmt::print( + " -----------------DL----------------|-------------------------UL-------------------------\n"); + fmt::print( + " pci rat rnti cqi ri mcs brate ok nok (%) | pusch pucch phr mcs brate ok nok (%) bsr\n"); } set_metrics_helper(metrics.stack.rrc.ues.size(), metrics.stack.mac, metrics.phy, false); diff --git a/srsenb/src/stack/mac/mac.cc b/srsenb/src/stack/mac/mac.cc index b451f77ec..34f37373f 100644 --- a/srsenb/src/stack/mac/mac.cc +++ b/srsenb/src/stack/mac/mac.cc @@ -241,7 +241,11 @@ void mac::get_metrics(mac_metrics_t& metrics) continue; } metrics.ues.emplace_back(); - u.second->metrics_read(&metrics.ues.back()); + auto& ue_metrics = metrics.ues.back(); + + u.second->metrics_read(&ue_metrics); + scheduler.metrics_read(u.first, ue_metrics); + ue_metrics.pci = (ue_metrics.cc_idx < cell_config.size()) ? cell_config[ue_metrics.cc_idx].cell.id : 0; } metrics.cc_info.resize(detected_rachs.size()); for (unsigned cc = 0, e = detected_rachs.size(); cc != e; ++cc) { @@ -570,11 +574,22 @@ void mac::rach_detected(uint32_t tti, uint32_t enb_cc_idx, uint32_t preamble_idx // Trigger scheduler RACH scheduler.dl_rach_info(enb_cc_idx, rar_info); - logger.info( - "RACH: tti=%d, cc=%d, preamble=%d, offset=%d, temp_crnti=0x%x", tti, enb_cc_idx, preamble_idx, time_adv, rnti); - srsran::console("RACH: tti=%d, cc=%d, preamble=%d, offset=%d, temp_crnti=0x%x\n", + auto get_pci = [this, enb_cc_idx]() { + srsran::rwlock_read_guard lock(rwlock); + return (enb_cc_idx < cell_config.size()) ? cell_config[enb_cc_idx].cell.id : 0; + }; + uint32_t pci = get_pci(); + logger.info("RACH: tti=%d, cc=%d, pci=%d, preamble=%d, offset=%d, temp_crnti=0x%x", + tti, + enb_cc_idx, + pci, + preamble_idx, + time_adv, + rnti); + srsran::console("RACH: tti=%d, cc=%d, pci=%d, preamble=%d, offset=%d, temp_crnti=0x%x\n", tti, enb_cc_idx, + pci, preamble_idx, time_adv, rnti); @@ -831,9 +846,9 @@ int mac::get_mch_sched(uint32_t tti, bool is_mcch, dl_sched_list_t& dl_sched_res int requested_bytes = (mcs_data.tbs / 8 > (int)mch.mtch_sched[mtch_index].lcid_buffer_size) ? (mch.mtch_sched[mtch_index].lcid_buffer_size) : ((mcs_data.tbs / 8) - 2); - int bytes_received = ue_db[SRSRAN_MRNTI]->read_pdu(current_lcid, mtch_payload_buffer, requested_bytes); - mch.pdu[0].lcid = current_lcid; - mch.pdu[0].nbytes = bytes_received; + int bytes_received = ue_db[SRSRAN_MRNTI]->read_pdu(current_lcid, mtch_payload_buffer, requested_bytes); + mch.pdu[0].lcid = current_lcid; + mch.pdu[0].nbytes = bytes_received; mch.mtch_sched[0].mtch_payload = mtch_payload_buffer; dl_sched_res->pdsch[0].dci.rnti = SRSRAN_MRNTI; if (bytes_received) { diff --git a/srsenb/src/stack/mac/sched.cc b/srsenb/src/stack/mac/sched.cc index d836fdbd2..bfa4aa278 100644 --- a/srsenb/src/stack/mac/sched.cc +++ b/srsenb/src/stack/mac/sched.cc @@ -361,6 +361,12 @@ bool sched::is_generated(srsran::tti_point tti_rx, uint32_t enb_cc_idx) const return sched_results.has_sf(tti_rx) and sched_results.get_sf(tti_rx)->is_generated(enb_cc_idx); } +int sched::metrics_read(uint16_t rnti, mac_ue_metrics_t& metrics) +{ + return ue_db_access_locked( + rnti, [&metrics](sched_ue& ue) { ue.metrics_read(metrics); }, "metrics_read"); +} + // Common way to access ue_db elements in a read locking way template int sched::ue_db_access_locked(uint16_t rnti, Func&& f, const char* func_name, bool log_fail) diff --git a/srsenb/src/stack/mac/sched_grid.cc b/srsenb/src/stack/mac/sched_grid.cc index fba1acf28..ff0178fb9 100644 --- a/srsenb/src/stack/mac/sched_grid.cc +++ b/srsenb/src/stack/mac/sched_grid.cc @@ -78,8 +78,8 @@ void sf_grid_t::init(const sched_cell_params_t& cell_params_) // Compute reserved PRBs for CQI, SR and HARQ-ACK, and store it in a bitmask pucch_mask.resize(cc_cfg->nof_prb()); - pucch_nrb = (cc_cfg->cfg.nrb_pucch > 0) ? (uint32_t)cc_cfg->cfg.nrb_pucch : 0; - srsran_pucch_cfg_t pucch_cfg = cell_params_.pucch_cfg_common; + pucch_nrb = (cc_cfg->cfg.nrb_pucch > 0) ? (uint32_t)cc_cfg->cfg.nrb_pucch : 0; + srsran_pucch_cfg_t pucch_cfg = cell_params_.pucch_cfg_common; uint32_t harq_pucch = 0; if (cc_cfg->sched_cfg->pucch_harq_max_rb > 0) { harq_pucch = cc_cfg->sched_cfg->pucch_harq_max_rb; @@ -822,13 +822,13 @@ void sf_sched::set_ul_sched_result(const sf_cch_allocator::alloc_result_t& dci_r sched_interface::ul_sched_data_t& pusch = ul_result->pusch.back(); uint32_t total_data_before = user->get_pending_ul_data_total(get_tti_tx_ul(), cc_cfg->enb_cc_idx); int tbs = user->generate_format0(&pusch, - get_tti_tx_ul(), - cc_cfg->enb_cc_idx, - ul_alloc.alloc, - ul_alloc.needs_pdcch(), - cce_range, - ul_alloc.msg3_mcs, - uci_type); + get_tti_tx_ul(), + cc_cfg->enb_cc_idx, + ul_alloc.alloc, + ul_alloc.needs_pdcch(), + cce_range, + ul_alloc.msg3_mcs, + uci_type); ul_harq_proc* h = user->get_ul_harq(get_tti_tx_ul(), cc_cfg->enb_cc_idx); uint32_t new_pending_bytes = user->get_pending_ul_new_data(get_tti_tx_ul(), cc_cfg->enb_cc_idx); @@ -854,23 +854,24 @@ void sf_sched::set_ul_sched_result(const sf_cch_allocator::alloc_result_t& dci_r uint32_t old_pending_bytes = user->get_pending_ul_old_data(); if (logger.info.enabled()) { fmt::memory_buffer str_buffer; - fmt::format_to( - str_buffer, - "SCHED: {} {} rnti=0x{:x}, cc={}, pid={}, dci=({},{}), prb={}, n_rtx={}, cfi={}, tbs={}, bsr={} ({}-{})", - ul_alloc.is_msg3 ? "Msg3" : "UL", - ul_alloc.is_retx() ? "retx" : "tx", - user->get_rnti(), - cc_cfg->enb_cc_idx, - h->get_id(), - pusch.dci.location.L, - pusch.dci.location.ncce, - ul_alloc.alloc, - h->nof_retx(0), - tti_alloc.get_cfi(), - tbs, - new_pending_bytes, - total_data_before, - old_pending_bytes); + fmt::format_to(str_buffer, + "SCHED: {} {} rnti=0x{:x}, cc={}, pid={}, dci=({},{}), prb={}, n_rtx={}, cfi={}, tbs={}, bsr={} " + "({}-{}), tti_tx_ul={}", + ul_alloc.is_msg3 ? "Msg3" : "UL", + ul_alloc.is_retx() ? "retx" : "tx", + user->get_rnti(), + cc_cfg->enb_cc_idx, + h->get_id(), + pusch.dci.location.L, + pusch.dci.location.ncce, + ul_alloc.alloc, + h->nof_retx(0), + tti_alloc.get_cfi(), + tbs, + new_pending_bytes, + total_data_before, + old_pending_bytes, + get_tti_tx_ul().to_uint()); logger.info("%s", srsran::to_c_str(str_buffer)); } diff --git a/srsenb/src/stack/mac/sched_ue.cc b/srsenb/src/stack/mac/sched_ue.cc index fb4d15d3f..d34529c94 100644 --- a/srsenb/src/stack/mac/sched_ue.cc +++ b/srsenb/src/stack/mac/sched_ue.cc @@ -164,6 +164,13 @@ void sched_ue::unset_sr() sr = false; } +void sched_ue::metrics_read(mac_ue_metrics_t& metrics) +{ + sched_ue_cell& pcell = cells[cfg.supported_cc_list[0].enb_cc_idx]; + metrics.ul_snr_offset = pcell.get_ul_snr_offset(); + metrics.dl_cqi_offset = pcell.get_dl_cqi_offset(); +} + tti_point prev_meas_gap_start(tti_point tti, uint32_t period, uint32_t offset) { return tti_point{static_cast(floor(static_cast((tti - offset).to_uint()) / period)) * period + diff --git a/srsenb/src/stack/upper/rlc.cc b/srsenb/src/stack/upper/rlc.cc index 38ccd6a35..016d59deb 100644 --- a/srsenb/src/stack/upper/rlc.cc +++ b/srsenb/src/stack/upper/rlc.cc @@ -75,14 +75,17 @@ void rlc::add_user(uint16_t rnti) void rlc::rem_user(uint16_t rnti) { - pthread_rwlock_wrlock(&rwlock); + pthread_rwlock_rdlock(&rwlock); if (users.count(rnti)) { users[rnti].rlc->stop(); - users.erase(rnti); } else { logger.error("Removing rnti=0x%x. Already removed", rnti); } pthread_rwlock_unlock(&rwlock); + + pthread_rwlock_wrlock(&rwlock); + users.erase(rnti); + pthread_rwlock_unlock(&rwlock); } void rlc::clear_buffer(uint16_t rnti) From 86d2eb853c8ca45071df7a265c1d7886fafa8752 Mon Sep 17 00:00:00 2001 From: faluco Date: Tue, 16 Nov 2021 10:41:07 +0100 Subject: [PATCH 65/77] Change the column order for the enb stdout metrics so that rat is first and pci second. --- srsenb/src/metrics_stdout.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/srsenb/src/metrics_stdout.cc b/srsenb/src/metrics_stdout.cc index 29fcc70a0..64557377c 100644 --- a/srsenb/src/metrics_stdout.cc +++ b/srsenb/src/metrics_stdout.cc @@ -88,8 +88,8 @@ void metrics_stdout::set_metrics_helper(uint32_t num_ue fmt::print("rx caution errors {} > {}\n", mac.ues[i].rx_errors, mac.ues[i].rx_pkts); } - fmt::print("{:>4}", mac.ues[i].pci); - fmt::print(" {:>3.5}", (is_nr) ? "nr" : "lte"); + fmt::print("{:>3.5}", (is_nr) ? "nr" : "lte"); + fmt::print(" {:>4}", mac.ues[i].pci); fmt::print("{:>5x}", mac.ues[i].rnti); if (not iszero(mac.ues[i].dl_cqi)) { fmt::print(" {:>3}", int(mac.ues[i].dl_cqi)); @@ -189,7 +189,7 @@ void metrics_stdout::set_metrics(const enb_metrics_t& metrics, const uint32_t pe fmt::print( " -----------------DL----------------|-------------------------UL-------------------------\n"); fmt::print( - " pci rat rnti cqi ri mcs brate ok nok (%) | pusch pucch phr mcs brate ok nok (%) bsr\n"); + "rat pci rnti cqi ri mcs brate ok nok (%) | pusch pucch phr mcs brate ok nok (%) bsr\n"); } set_metrics_helper(metrics.stack.rrc.ues.size(), metrics.stack.mac, metrics.phy, false); From f705445f21efecde4eb1865b949b70fc7aae56d7 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 14:42:49 +0000 Subject: [PATCH 66/77] nr,gnb,rrc: use rrc_nr_cfg to generate sib1 and mib --- lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h | 2 - lib/src/rrc/nr/rrc_nr_cfg_utils.cc | 13 - srsgnb/hdr/stack/rrc/cell_asn1_config.h | 4 +- srsgnb/src/stack/rrc/cell_asn1_config.cc | 242 ++++++++++++++++++- srsgnb/src/stack/rrc/rrc_nr.cc | 4 +- 5 files changed, 236 insertions(+), 29 deletions(-) diff --git a/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h b/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h index 745742d26..ca17b64ae 100644 --- a/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h +++ b/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h @@ -32,8 +32,6 @@ void generate_default_pdcch_cfg_common(asn1::rrc_nr::pdcch_cfg_common_s& cfg, co void generate_default_init_dl_bwp(asn1::rrc_nr::bwp_dl_common_s& cfg, const basic_cell_args_t& args = {}); void generate_default_dl_cfg_common(asn1::rrc_nr::dl_cfg_common_s& cfg, const basic_cell_args_t& args = {}); -void generate_default_mib(uint32_t pdcch_scs, uint32_t coreset0_idx, asn1::rrc_nr::mib_s& cfg); - void generate_default_serv_cell_cfg_common_sib(const basic_cell_args_t& args, asn1::rrc_nr::serving_cell_cfg_common_sib_s& cfg); void generate_default_sib1(const basic_cell_args_t& args, asn1::rrc_nr::sib1_s& cfg); diff --git a/lib/src/rrc/nr/rrc_nr_cfg_utils.cc b/lib/src/rrc/nr/rrc_nr_cfg_utils.cc index 5327e997b..a0d971415 100644 --- a/lib/src/rrc/nr/rrc_nr_cfg_utils.cc +++ b/lib/src/rrc/nr/rrc_nr_cfg_utils.cc @@ -172,19 +172,6 @@ void generate_default_serv_cell_cfg_common_sib(const basic_cell_args_t& args, se cfg.ss_pbch_block_pwr = -16; } -void generate_default_mib(uint32_t pdcch_scs, uint32_t coreset0_idx, mib_s& cfg) -{ - bool ret = asn1::number_to_enum(cfg.sub_carrier_spacing_common, pdcch_scs); - srsran_assert(ret, "Invalid SCS=%d kHz", pdcch_scs); - cfg.ssb_subcarrier_offset = 0; - cfg.intra_freq_resel.value = mib_s::intra_freq_resel_opts::allowed; - cfg.cell_barred.value = mib_s::cell_barred_opts::not_barred; - cfg.pdcch_cfg_sib1.search_space_zero = 0; - cfg.pdcch_cfg_sib1.ctrl_res_set_zero = coreset0_idx; - cfg.dmrs_type_a_position.value = mib_s::dmrs_type_a_position_opts::pos2; - cfg.sys_frame_num.from_number(0); -} - void generate_default_sib1(const basic_cell_args_t& args, sib1_s& cfg) { cfg.cell_sel_info_present = true; diff --git a/srsgnb/hdr/stack/rrc/cell_asn1_config.h b/srsgnb/hdr/stack/rrc/cell_asn1_config.h index df14ed406..5ee4d3ce1 100644 --- a/srsgnb/hdr/stack/rrc/cell_asn1_config.h +++ b/srsgnb/hdr/stack/rrc/cell_asn1_config.h @@ -26,8 +26,8 @@ int fill_sp_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rr // SA helpers int fill_master_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::cell_group_cfg_s& out); -int fill_mib_from_enb_cfg(const rrc_nr_cfg_t& cfg, asn1::rrc_nr::mib_s& mib); -int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, asn1::rrc_nr::sib1_s& sib1); +int fill_mib_from_enb_cfg(const rrc_cell_cfg_nr_t& cell_cfg, asn1::rrc_nr::mib_s& mib); +int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::sib1_s& sib1); } // namespace srsenb diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index 93ddfdc2a..b6e47412f 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -11,6 +11,8 @@ */ #include "srsgnb/hdr/stack/rrc/cell_asn1_config.h" +#include "srsran/asn1/rrc_nr_utils.h" +#include "srsran/common/band_helper.h" #include "srsran/rrc/nr/rrc_nr_cfg_utils.h" #include @@ -872,21 +874,241 @@ int fill_master_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -int fill_mib_from_enb_cfg(const rrc_nr_cfg_t& cfg, asn1::rrc_nr::mib_s& mib) +int fill_mib_from_enb_cfg(const rrc_cell_cfg_nr_t& cell_cfg, asn1::rrc_nr::mib_s& mib) { - uint32_t scs = - subcarrier_spacing_e{(subcarrier_spacing_opts::options)cfg.cell_list[0].phy_cell.carrier.scs}.to_number(); - srsran::generate_default_mib(scs, cfg.cell_list[0].coreset0_idx, mib); + mib.sys_frame_num.from_number(0); + switch (cell_cfg.phy_cell.carrier.scs) { + case srsran_subcarrier_spacing_15kHz: + case srsran_subcarrier_spacing_60kHz: + mib.sub_carrier_spacing_common.value = asn1::rrc_nr::mib_s::sub_carrier_spacing_common_opts::scs15or60; + break; + case srsran_subcarrier_spacing_30kHz: + case srsran_subcarrier_spacing_120kHz: + mib.sub_carrier_spacing_common.value = asn1::rrc_nr::mib_s::sub_carrier_spacing_common_opts::scs30or120; + break; + default: + srsran_terminate("Invalid carrier SCS=%d Hz", SRSRAN_SUBC_SPACING_NR(cell_cfg.phy_cell.carrier.scs)); + } + mib.ssb_subcarrier_offset = 0; + mib.dmrs_type_a_position.value = mib_s::dmrs_type_a_position_opts::pos2; + mib.pdcch_cfg_sib1.search_space_zero = 0; + mib.pdcch_cfg_sib1.ctrl_res_set_zero = cell_cfg.coreset0_idx; + mib.cell_barred.value = mib_s::cell_barred_opts::not_barred; + mib.intra_freq_resel.value = mib_s::intra_freq_resel_opts::allowed; + mib.spare.from_number(0); return SRSRAN_SUCCESS; } -int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, asn1::rrc_nr::sib1_s& sib1) +void fill_pdcch_cfg_common(const rrc_cell_cfg_nr_t& cell_cfg, pdcch_cfg_common_s& cfg) +{ + cfg.ctrl_res_set_zero_present = true; // may be disabled later if called by sib1 generation + cfg.ctrl_res_set_zero = 0; + cfg.common_ctrl_res_set_present = false; + + cfg.search_space_zero_present = true; + cfg.search_space_zero = 0; + + cfg.common_search_space_list_present = true; + cfg.common_search_space_list.resize(1); + search_space_s& ss = cfg.common_search_space_list[0]; + ss.search_space_id = 1; + ss.ctrl_res_set_id_present = true; + ss.ctrl_res_set_id = 0; + ss.monitoring_slot_periodicity_and_offset_present = true; + ss.monitoring_slot_periodicity_and_offset.set_sl1(); + ss.monitoring_symbols_within_slot_present = true; + ss.monitoring_symbols_within_slot.from_number(0x2000); + ss.nrof_candidates_present = true; + ss.nrof_candidates.aggregation_level1.value = search_space_s::nrof_candidates_s_::aggregation_level1_opts::n0; + ss.nrof_candidates.aggregation_level2.value = search_space_s::nrof_candidates_s_::aggregation_level2_opts::n0; + ss.nrof_candidates.aggregation_level4.value = search_space_s::nrof_candidates_s_::aggregation_level4_opts::n1; + ss.nrof_candidates.aggregation_level8.value = search_space_s::nrof_candidates_s_::aggregation_level8_opts::n0; + ss.nrof_candidates.aggregation_level16.value = search_space_s::nrof_candidates_s_::aggregation_level16_opts::n0; + ss.search_space_type_present = true; + auto& common = ss.search_space_type.set_common(); + common.dci_format0_minus0_and_format1_minus0_present = true; + + cfg.search_space_sib1_present = true; + cfg.search_space_sib1 = 0; + cfg.search_space_other_sys_info_present = true; + cfg.search_space_other_sys_info = 1; + cfg.paging_search_space_present = true; + cfg.paging_search_space = 1; + cfg.ra_search_space_present = true; + cfg.ra_search_space = 1; +} + +void fill_pdsch_cfg_common(const rrc_cell_cfg_nr_t& cell_cfg, pdsch_cfg_common_s& cfg) +{ + cfg.pdsch_time_domain_alloc_list_present = true; + cfg.pdsch_time_domain_alloc_list.resize(1); + cfg.pdsch_time_domain_alloc_list[0].map_type.value = pdsch_time_domain_res_alloc_s::map_type_opts::type_a; + cfg.pdsch_time_domain_alloc_list[0].start_symbol_and_len = 40; +} + +void fill_init_dl_bwp(const rrc_cell_cfg_nr_t& cell_cfg, bwp_dl_common_s& cfg) +{ + cfg.generic_params.location_and_bw = 14025; + cfg.generic_params.subcarrier_spacing = (subcarrier_spacing_opts::options)cell_cfg.phy_cell.carrier.scs; + + cfg.pdcch_cfg_common_present = true; + fill_pdcch_cfg_common(cell_cfg, cfg.pdcch_cfg_common.set_setup()); + cfg.pdsch_cfg_common_present = true; + fill_pdsch_cfg_common(cell_cfg, cfg.pdsch_cfg_common.set_setup()); +} + +void fill_dl_cfg_common_sib(const rrc_cell_cfg_nr_t& cell_cfg, dl_cfg_common_sib_s& cfg) +{ + srsran::srsran_band_helper band_helper; + cfg.freq_info_dl.freq_band_list.resize(1); + cfg.freq_info_dl.freq_band_list[0].freq_band_ind_nr_present = true; + cfg.freq_info_dl.freq_band_list[0].freq_band_ind_nr = cell_cfg.band; + uint32_t offset_point_a_hz = + cell_cfg.phy_cell.dl_freq_hz - band_helper.nr_arfcn_to_freq(cell_cfg.dl_absolute_freq_point_a); + uint32_t offset_point_a_rbs = offset_point_a_hz / SRSRAN_SUBC_SPACING_NR(cell_cfg.phy_cell.carrier.scs) / SRSRAN_NRE; + cfg.freq_info_dl.offset_to_point_a = offset_point_a_rbs; + cfg.freq_info_dl.scs_specific_carrier_list.resize(1); + cfg.freq_info_dl.scs_specific_carrier_list[0].offset_to_carrier = 0; + cfg.freq_info_dl.scs_specific_carrier_list[0].subcarrier_spacing = + (subcarrier_spacing_opts::options)cell_cfg.phy_cell.carrier.scs; + cfg.freq_info_dl.scs_specific_carrier_list[0].carrier_bw = cell_cfg.phy_cell.carrier.nof_prb; + + fill_init_dl_bwp(cell_cfg, cfg.init_dl_bwp); + // disable InitialBWP-Only fields + cfg.init_dl_bwp.pdcch_cfg_common.setup().ctrl_res_set_zero_present = false; + cfg.init_dl_bwp.pdcch_cfg_common.setup().search_space_zero_present = false; + + cfg.bcch_cfg.mod_period_coeff.value = bcch_cfg_s::mod_period_coeff_opts::n4; + + cfg.pcch_cfg.default_paging_cycle.value = paging_cycle_opts::rf128; + cfg.pcch_cfg.nand_paging_frame_offset.set_one_t(); + cfg.pcch_cfg.ns.value = pcch_cfg_s::ns_opts::one; +} + +void fill_rach_cfg_common(const rrc_cell_cfg_nr_t& cell_cfg, rach_cfg_common_s& cfg) { - srsran::basic_cell_args_t args; - args.is_standalone = cfg.is_standalone; - args.scs = subcarrier_spacing_e{(subcarrier_spacing_opts::options)cfg.cell_list[0].phy_cell.carrier.scs}.to_number(); - args.is_fdd = cfg.cell_list[0].duplex_mode == SRSRAN_DUPLEX_MODE_FDD; - srsran::generate_default_sib1(args, sib1); + cfg.rach_cfg_generic.prach_cfg_idx = 16; + cfg.rach_cfg_generic.msg1_fdm.value = rach_cfg_generic_s::msg1_fdm_opts::one; + cfg.rach_cfg_generic.msg1_freq_start = 0; + cfg.rach_cfg_generic.zero_correlation_zone_cfg = 15; + cfg.rach_cfg_generic.preamb_rx_target_pwr = -110; + cfg.rach_cfg_generic.preamb_trans_max.value = rach_cfg_generic_s::preamb_trans_max_opts::n7; + cfg.rach_cfg_generic.pwr_ramp_step.value = rach_cfg_generic_s::pwr_ramp_step_opts::db4; + cfg.rach_cfg_generic.ra_resp_win.value = rach_cfg_generic_s::ra_resp_win_opts::sl10; + cfg.ssb_per_rach_occasion_and_cb_preambs_per_ssb_present = true; + cfg.ssb_per_rach_occasion_and_cb_preambs_per_ssb.set_one().value = + rach_cfg_common_s::ssb_per_rach_occasion_and_cb_preambs_per_ssb_c_::one_opts::n8; + cfg.ra_contention_resolution_timer.value = rach_cfg_common_s::ra_contention_resolution_timer_opts::sf64; + cfg.prach_root_seq_idx.set_l839() = 1; + cfg.restricted_set_cfg.value = rach_cfg_common_s::restricted_set_cfg_opts::unrestricted_set; +} + +void fill_ul_cfg_common_sib(const rrc_cell_cfg_nr_t& cell_cfg, ul_cfg_common_sib_s& cfg) +{ + cfg.freq_info_ul.scs_specific_carrier_list.resize(1); + cfg.freq_info_ul.scs_specific_carrier_list[0].offset_to_carrier = 0; + cfg.freq_info_ul.scs_specific_carrier_list[0].subcarrier_spacing = + (subcarrier_spacing_opts::options)cell_cfg.phy_cell.carrier.scs; + cfg.freq_info_ul.scs_specific_carrier_list[0].carrier_bw = cell_cfg.phy_cell.carrier.nof_prb; + + cfg.init_ul_bwp.generic_params.location_and_bw = 14025; + cfg.init_ul_bwp.generic_params.subcarrier_spacing.value = + (subcarrier_spacing_opts::options)cell_cfg.phy_cell.carrier.scs; + + cfg.init_ul_bwp.rach_cfg_common_present = true; + fill_rach_cfg_common(cell_cfg, cfg.init_ul_bwp.rach_cfg_common.set_setup()); + + cfg.init_ul_bwp.pusch_cfg_common_present = true; + pusch_cfg_common_s& pusch = cfg.init_ul_bwp.pusch_cfg_common.set_setup(); + pusch.pusch_time_domain_alloc_list_present = true; + pusch.pusch_time_domain_alloc_list.resize(1); + pusch.pusch_time_domain_alloc_list[0].k2_present = true; + pusch.pusch_time_domain_alloc_list[0].k2 = 4; + pusch.pusch_time_domain_alloc_list[0].map_type.value = pusch_time_domain_res_alloc_s::map_type_opts::type_a; + pusch.pusch_time_domain_alloc_list[0].start_symbol_and_len = 27; + pusch.p0_nominal_with_grant_present = true; + pusch.p0_nominal_with_grant = -76; + + cfg.init_ul_bwp.pucch_cfg_common_present = true; + pucch_cfg_common_s& pucch = cfg.init_ul_bwp.pucch_cfg_common.set_setup(); + pucch.pucch_res_common_present = true; + pucch.pucch_res_common = 11; + pucch.pucch_group_hop.value = pucch_cfg_common_s::pucch_group_hop_opts::neither; + pucch.p0_nominal_present = true; + pucch.p0_nominal = -90; + + cfg.time_align_timer_common.value = time_align_timer_opts::infinity; +} + +void fill_serv_cell_cfg_common_sib(const rrc_cell_cfg_nr_t& cell_cfg, serving_cell_cfg_common_sib_s& cfg) +{ + fill_dl_cfg_common_sib(cell_cfg, cfg.dl_cfg_common); + + cfg.ul_cfg_common_present = true; + fill_ul_cfg_common_sib(cell_cfg, cfg.ul_cfg_common); + + cfg.ssb_positions_in_burst.in_one_group.from_number(0x80); + + cfg.ssb_periodicity_serving_cell.value = serving_cell_cfg_common_sib_s::ssb_periodicity_serving_cell_opts::ms20; + + cfg.ss_pbch_block_pwr = -16; +} + +int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::sib1_s& sib1) +{ + std::string plmn_str = "00101"; + uint32_t cell_id = 0x19B01; + const rrc_cell_cfg_nr_t& cell_cfg = cfg.cell_list[cc]; + + sib1.cell_sel_info_present = true; + sib1.cell_sel_info.q_rx_lev_min = -70; + sib1.cell_sel_info.q_qual_min_present = true; + sib1.cell_sel_info.q_qual_min = -20; + + sib1.cell_access_related_info.plmn_id_list.resize(1); + sib1.cell_access_related_info.plmn_id_list[0].plmn_id_list.resize(1); + srsran::plmn_id_t plmn; + plmn.from_string(plmn_str); + srsran::to_asn1(&sib1.cell_access_related_info.plmn_id_list[0].plmn_id_list[0], plmn); + sib1.cell_access_related_info.plmn_id_list[0].tac_present = true; + sib1.cell_access_related_info.plmn_id_list[0].tac.from_number(cell_cfg.tac); + sib1.cell_access_related_info.plmn_id_list[0].cell_id.from_number(cell_id); + sib1.cell_access_related_info.plmn_id_list[0].cell_reserved_for_oper.value = + plmn_id_info_s::cell_reserved_for_oper_opts::not_reserved; + + sib1.conn_est_fail_ctrl_present = true; + sib1.conn_est_fail_ctrl.conn_est_fail_count.value = conn_est_fail_ctrl_s::conn_est_fail_count_opts::n1; + sib1.conn_est_fail_ctrl.conn_est_fail_offset_validity.value = + conn_est_fail_ctrl_s::conn_est_fail_offset_validity_opts::s30; + sib1.conn_est_fail_ctrl.conn_est_fail_offset_present = true; + sib1.conn_est_fail_ctrl.conn_est_fail_offset = 1; + + // sib1.si_sched_info_present = true; + // sib1.si_sched_info.si_request_cfg.rach_occasions_si_present = true; + // sib1.si_sched_info.si_request_cfg.rach_occasions_si.rach_cfg_si.ra_resp_win.value = + // rach_cfg_generic_s::ra_resp_win_opts::sl8; + // sib1.si_sched_info.si_win_len.value = si_sched_info_s::si_win_len_opts::s20; + // sib1.si_sched_info.sched_info_list.resize(1); + // sib1.si_sched_info.sched_info_list[0].si_broadcast_status.value = + // sched_info_s::si_broadcast_status_opts::broadcasting; sib1.si_sched_info.sched_info_list[0].si_periodicity.value = + // sched_info_s::si_periodicity_opts::rf16; sib1.si_sched_info.sched_info_list[0].sib_map_info.resize(1); + // // scheduling of SI messages + // sib1.si_sched_info.sched_info_list[0].sib_map_info[0].type.value = sib_type_info_s::type_opts::sib_type2; + // sib1.si_sched_info.sched_info_list[0].sib_map_info[0].value_tag_present = true; + // sib1.si_sched_info.sched_info_list[0].sib_map_info[0].value_tag = 0; + + sib1.serving_cell_cfg_common_present = true; + fill_serv_cell_cfg_common_sib(cell_cfg, sib1.serving_cell_cfg_common); + + sib1.ue_timers_and_consts_present = true; + sib1.ue_timers_and_consts.t300.value = ue_timers_and_consts_s::t300_opts::ms1000; + sib1.ue_timers_and_consts.t301.value = ue_timers_and_consts_s::t301_opts::ms1000; + sib1.ue_timers_and_consts.t310.value = ue_timers_and_consts_s::t310_opts::ms1000; + sib1.ue_timers_and_consts.n310.value = ue_timers_and_consts_s::n310_opts::n1; + sib1.ue_timers_and_consts.t311.value = ue_timers_and_consts_s::t311_opts::ms30000; + sib1.ue_timers_and_consts.n311.value = ue_timers_and_consts_s::n311_opts::n1; + sib1.ue_timers_and_consts.t319.value = ue_timers_and_consts_s::t319_opts::ms1000; + return SRSRAN_SUCCESS; } diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index ada6213ef..9fc2bf6ad 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -301,7 +301,7 @@ void rrc_nr::config_mac() int32_t rrc_nr::generate_sibs() { // MIB packing - fill_mib_from_enb_cfg(cfg, cell_ctxt->mib); + fill_mib_from_enb_cfg(cfg.cell_list[0], cell_ctxt->mib); bcch_bch_msg_s mib_msg; mib_msg.msg.set_mib() = cell_ctxt->mib; { @@ -325,7 +325,7 @@ int32_t rrc_nr::generate_sibs() } // SIB1 packing - fill_sib1_from_enb_cfg(cfg, cell_ctxt->sib1); + fill_sib1_from_enb_cfg(cfg, 0, cell_ctxt->sib1); si_sched_info_s::sched_info_list_l_& sched_info = cell_ctxt->sib1.si_sched_info.sched_info_list; // SI messages packing From 84a60da1468916c30abd24d259fb99ea5492dad2 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 15:23:17 +0000 Subject: [PATCH 67/77] nr,gnb,rrc: fix sib1 offsetToPointA derivation --- srsgnb/src/stack/rrc/cell_asn1_config.cc | 11 +++++++---- srsgnb/src/stack/rrc/test/rrc_nr_test.cc | 7 ++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index b6e47412f..d0362f2e6 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -959,14 +959,17 @@ void fill_init_dl_bwp(const rrc_cell_cfg_nr_t& cell_cfg, bwp_dl_common_s& cfg) void fill_dl_cfg_common_sib(const rrc_cell_cfg_nr_t& cell_cfg, dl_cfg_common_sib_s& cfg) { + uint32_t scs_hz = SRSRAN_SUBC_SPACING_NR(cell_cfg.phy_cell.carrier.scs); + uint32_t prb_bw = scs_hz * SRSRAN_NRE; + srsran::srsran_band_helper band_helper; cfg.freq_info_dl.freq_band_list.resize(1); cfg.freq_info_dl.freq_band_list[0].freq_band_ind_nr_present = true; cfg.freq_info_dl.freq_band_list[0].freq_band_ind_nr = cell_cfg.band; - uint32_t offset_point_a_hz = - cell_cfg.phy_cell.dl_freq_hz - band_helper.nr_arfcn_to_freq(cell_cfg.dl_absolute_freq_point_a); - uint32_t offset_point_a_rbs = offset_point_a_hz / SRSRAN_SUBC_SPACING_NR(cell_cfg.phy_cell.carrier.scs) / SRSRAN_NRE; - cfg.freq_info_dl.offset_to_point_a = offset_point_a_rbs; + double ssb_freq_start = cell_cfg.ssb_cfg.ssb_freq_hz - SRSRAN_SSB_BW_SUBC * scs_hz / 2; + double offset_point_a_hz = ssb_freq_start - band_helper.nr_arfcn_to_freq(cell_cfg.dl_absolute_freq_point_a); + uint32_t offset_point_a_prbs = offset_point_a_hz / prb_bw; + cfg.freq_info_dl.offset_to_point_a = offset_point_a_prbs; cfg.freq_info_dl.scs_specific_carrier_list.resize(1); cfg.freq_info_dl.scs_specific_carrier_list[0].offset_to_carrier = 0; cfg.freq_info_dl.scs_specific_carrier_list[0].subcarrier_spacing = diff --git a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc index 7e4a05f93..8500996c2 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc @@ -56,8 +56,9 @@ void test_sib_generation() rrc_nr_cfg_t rrc_cfg_nr = {}; rrc_cfg_nr.cell_list.emplace_back(); rrc_cfg_nr.cell_list[0].phy_cell.carrier.pci = 500; - rrc_cfg_nr.cell_list[0].dl_arfcn = 634240; - rrc_cfg_nr.cell_list[0].band = 78; + rrc_cfg_nr.cell_list[0].dl_arfcn = 368500; + rrc_cfg_nr.cell_list[0].band = 3; + rrc_cfg_nr.cell_list[0].duplex_mode = SRSRAN_DUPLEX_MODE_FDD; rrc_cfg_nr.is_standalone = true; args.enb.n_prb = 50; enb_conf_sections::set_derived_args_nr(&args, &rrc_cfg_nr, &phy_cfg); @@ -179,8 +180,8 @@ int main(int argc, char** argv) } argparse::parse_args(argc, argv); - srsenb::test_sib_generation(); TESTASSERT(srsenb::test_rrc_setup() == SRSRAN_SUCCESS); + srsenb::test_sib_generation(); srsenb::test_rrc_sa_connection(); return SRSRAN_SUCCESS; From 2b68ea322db161058d3f8155cf1e622adfa8a949 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 15:32:44 +0000 Subject: [PATCH 68/77] nr,gnb,rrc: fix uplinkConfigCommon of SIB1 --- srsgnb/src/stack/rrc/cell_asn1_config.cc | 11 +++++++++++ srsgnb/src/stack/rrc/test/rrc_nr_test.cc | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index d0362f2e6..d53f2ff3b 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -1008,6 +1008,17 @@ void fill_rach_cfg_common(const rrc_cell_cfg_nr_t& cell_cfg, rach_cfg_common_s& void fill_ul_cfg_common_sib(const rrc_cell_cfg_nr_t& cell_cfg, ul_cfg_common_sib_s& cfg) { + srsran::srsran_band_helper band_helper; + + cfg.freq_info_ul.freq_band_list_present = true; + cfg.freq_info_ul.freq_band_list.resize(1); + cfg.freq_info_ul.freq_band_list[0].freq_band_ind_nr_present = true; + cfg.freq_info_ul.freq_band_list[0].freq_band_ind_nr = cell_cfg.band; + + cfg.freq_info_ul.absolute_freq_point_a_present = true; + cfg.freq_info_ul.absolute_freq_point_a = + band_helper.get_abs_freq_point_a_arfcn(cell_cfg.phy_cell.carrier.nof_prb, cell_cfg.ul_arfcn); + cfg.freq_info_ul.scs_specific_carrier_list.resize(1); cfg.freq_info_ul.scs_specific_carrier_list[0].offset_to_carrier = 0; cfg.freq_info_ul.scs_specific_carrier_list[0].subcarrier_spacing = diff --git a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc index 8500996c2..14911d33f 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc @@ -180,8 +180,8 @@ int main(int argc, char** argv) } argparse::parse_args(argc, argv); - TESTASSERT(srsenb::test_rrc_setup() == SRSRAN_SUCCESS); srsenb::test_sib_generation(); + TESTASSERT(srsenb::test_rrc_setup() == SRSRAN_SUCCESS); srsenb::test_rrc_sa_connection(); return SRSRAN_SUCCESS; From b2a4ff1bbbe805e5a89eab2a9a539974ef137d48 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 15:34:05 +0000 Subject: [PATCH 69/77] nr,gnb,rrc: add pmax config to SIB1 --- srsgnb/src/stack/rrc/cell_asn1_config.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index d53f2ff3b..b5e4f0cb6 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -1025,6 +1025,9 @@ void fill_ul_cfg_common_sib(const rrc_cell_cfg_nr_t& cell_cfg, ul_cfg_common_sib (subcarrier_spacing_opts::options)cell_cfg.phy_cell.carrier.scs; cfg.freq_info_ul.scs_specific_carrier_list[0].carrier_bw = cell_cfg.phy_cell.carrier.nof_prb; + cfg.freq_info_ul.p_max_present = true; + cfg.freq_info_ul.p_max = 10; + cfg.init_ul_bwp.generic_params.location_and_bw = 14025; cfg.init_ul_bwp.generic_params.subcarrier_spacing.value = (subcarrier_spacing_opts::options)cell_cfg.phy_cell.carrier.scs; From 3b3630d4446956fea276d2324c629cf129f97ff5 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 15:52:24 +0000 Subject: [PATCH 70/77] nr,gnb,rrc: remove redundant code, and update rrc_nr_test to use default rrc nr cfg generator --- lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h | 41 ---- lib/src/CMakeLists.txt | 1 - lib/src/rrc/CMakeLists.txt | 10 - lib/src/rrc/nr/rrc_nr_cfg_utils.cc | 227 ------------------- srsgnb/src/stack/rrc/CMakeLists.txt | 4 +- srsgnb/src/stack/rrc/cell_asn1_config.cc | 1 - srsgnb/src/stack/rrc/test/rrc_nr_test.cc | 4 + 7 files changed, 6 insertions(+), 282 deletions(-) delete mode 100644 lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h delete mode 100644 lib/src/rrc/CMakeLists.txt delete mode 100644 lib/src/rrc/nr/rrc_nr_cfg_utils.cc diff --git a/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h b/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h deleted file mode 100644 index ca17b64ae..000000000 --- a/lib/include/srsran/rrc/nr/rrc_nr_cfg_utils.h +++ /dev/null @@ -1,41 +0,0 @@ -/** - * - * \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 SRSRAN_RRC_NR_CFG_UTILS_H -#define SRSRAN_RRC_NR_CFG_UTILS_H - -#include "srsran/asn1/rrc_nr.h" -#include "srsran/interfaces/gnb_rrc_nr_interfaces.h" - -namespace srsran { - -struct basic_cell_args_t { - uint32_t cell_id = 0x19B01; - uint32_t nof_prbs = 52; - uint32_t scs = 15; - bool is_standalone = true; - bool is_fdd = true; - std::string plmn = "00101"; - uint32_t tac = 7; -}; - -void generate_default_pdcch_cfg_common(asn1::rrc_nr::pdcch_cfg_common_s& cfg, const basic_cell_args_t& args = {}); -void generate_default_init_dl_bwp(asn1::rrc_nr::bwp_dl_common_s& cfg, const basic_cell_args_t& args = {}); -void generate_default_dl_cfg_common(asn1::rrc_nr::dl_cfg_common_s& cfg, const basic_cell_args_t& args = {}); - -void generate_default_serv_cell_cfg_common_sib(const basic_cell_args_t& args, - asn1::rrc_nr::serving_cell_cfg_common_sib_s& cfg); -void generate_default_sib1(const basic_cell_args_t& args, asn1::rrc_nr::sib1_s& cfg); - -} // namespace srsran - -#endif // SRSRAN_RRC_NR_CFG_UTILS_H diff --git a/lib/src/CMakeLists.txt b/lib/src/CMakeLists.txt index 603cb6f0b..d6bb50635 100644 --- a/lib/src/CMakeLists.txt +++ b/lib/src/CMakeLists.txt @@ -11,7 +11,6 @@ add_subdirectory(common) add_subdirectory(mac) add_subdirectory(phy) add_subdirectory(radio) -add_subdirectory(rrc) add_subdirectory(rlc) add_subdirectory(pdcp) add_subdirectory(gtpu) diff --git a/lib/src/rrc/CMakeLists.txt b/lib/src/rrc/CMakeLists.txt deleted file mode 100644 index 4eceb4106..000000000 --- a/lib/src/rrc/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# -# 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. -# - -set(SOURCES nr/rrc_nr_cfg_utils.cc) -add_library(srsran_rrc_nr STATIC ${SOURCES}) diff --git a/lib/src/rrc/nr/rrc_nr_cfg_utils.cc b/lib/src/rrc/nr/rrc_nr_cfg_utils.cc deleted file mode 100644 index a0d971415..000000000 --- a/lib/src/rrc/nr/rrc_nr_cfg_utils.cc +++ /dev/null @@ -1,227 +0,0 @@ -/** - * - * \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/rrc/nr/rrc_nr_cfg_utils.h" -#include "srsran/asn1/rrc_nr.h" -#include "srsran/asn1/rrc_nr_utils.h" - -using namespace asn1::rrc_nr; - -namespace srsran { - -void generate_default_pdcch_cfg_common(const basic_cell_args_t& args, pdcch_cfg_common_s& cfg) -{ - cfg.ctrl_res_set_zero_present = true; - cfg.ctrl_res_set_zero = 0; - cfg.common_ctrl_res_set_present = false; - - cfg.search_space_zero_present = true; - cfg.search_space_zero = 0; - - cfg.common_search_space_list_present = true; - cfg.common_search_space_list.resize(1); - search_space_s& ss = cfg.common_search_space_list[0]; - ss.search_space_id = 1; - ss.ctrl_res_set_id_present = true; - ss.ctrl_res_set_id = 0; - ss.monitoring_slot_periodicity_and_offset_present = true; - ss.monitoring_slot_periodicity_and_offset.set_sl1(); - ss.monitoring_symbols_within_slot_present = true; - ss.monitoring_symbols_within_slot.from_number(0x2000); - ss.nrof_candidates_present = true; - ss.nrof_candidates.aggregation_level1.value = search_space_s::nrof_candidates_s_::aggregation_level1_opts::n0; - ss.nrof_candidates.aggregation_level2.value = search_space_s::nrof_candidates_s_::aggregation_level2_opts::n0; - ss.nrof_candidates.aggregation_level4.value = search_space_s::nrof_candidates_s_::aggregation_level4_opts::n1; - ss.nrof_candidates.aggregation_level8.value = search_space_s::nrof_candidates_s_::aggregation_level8_opts::n0; - ss.nrof_candidates.aggregation_level16.value = search_space_s::nrof_candidates_s_::aggregation_level16_opts::n0; - ss.search_space_type_present = true; - auto& common = ss.search_space_type.set_common(); - common.dci_format0_minus0_and_format1_minus0_present = true; - - cfg.search_space_sib1_present = true; - cfg.search_space_sib1 = 0; - cfg.search_space_other_sys_info_present = true; - cfg.search_space_other_sys_info = 1; - cfg.paging_search_space_present = true; - cfg.paging_search_space = 1; - cfg.ra_search_space_present = true; - cfg.ra_search_space = 1; -} - -void generate_default_pdsch_cfg_common(const basic_cell_args_t& args, pdsch_cfg_common_s& cfg) -{ - cfg.pdsch_time_domain_alloc_list_present = true; - cfg.pdsch_time_domain_alloc_list.resize(1); - cfg.pdsch_time_domain_alloc_list[0].map_type.value = pdsch_time_domain_res_alloc_s::map_type_opts::type_a; - cfg.pdsch_time_domain_alloc_list[0].start_symbol_and_len = 40; -} - -void generate_default_init_dl_bwp(const basic_cell_args_t& args, bwp_dl_common_s& cfg) -{ - cfg.generic_params.location_and_bw = 14025; - asn1::number_to_enum(cfg.generic_params.subcarrier_spacing, args.scs); - - cfg.pdcch_cfg_common_present = true; - generate_default_pdcch_cfg_common(args, cfg.pdcch_cfg_common.set_setup()); - cfg.pdsch_cfg_common_present = true; - generate_default_pdsch_cfg_common(args, cfg.pdsch_cfg_common.set_setup()); -} - -void generate_default_dl_cfg_common(dl_cfg_common_s& cfg, const basic_cell_args_t& args) -{ - cfg.init_dl_bwp_present = true; - generate_default_init_dl_bwp(args, cfg.init_dl_bwp); -} - -void generate_default_dl_cfg_common_sib(const basic_cell_args_t& args, dl_cfg_common_sib_s& cfg) -{ - cfg.freq_info_dl.freq_band_list.resize(1); - cfg.freq_info_dl.freq_band_list[0].freq_band_ind_nr_present = true; - cfg.freq_info_dl.freq_band_list[0].freq_band_ind_nr = 20; - cfg.freq_info_dl.offset_to_point_a = 24; - cfg.freq_info_dl.scs_specific_carrier_list.resize(1); - cfg.freq_info_dl.scs_specific_carrier_list[0].offset_to_carrier = 0; - asn1::number_to_enum(cfg.freq_info_dl.scs_specific_carrier_list[0].subcarrier_spacing, args.scs); - cfg.freq_info_dl.scs_specific_carrier_list[0].carrier_bw = args.nof_prbs; - - generate_default_init_dl_bwp(args, cfg.init_dl_bwp); - // disable InitialBWP-Only fields - cfg.init_dl_bwp.pdcch_cfg_common.setup().ctrl_res_set_zero_present = false; - cfg.init_dl_bwp.pdcch_cfg_common.setup().search_space_zero_present = false; - - cfg.bcch_cfg.mod_period_coeff.value = bcch_cfg_s::mod_period_coeff_opts::n4; - - cfg.pcch_cfg.default_paging_cycle.value = paging_cycle_opts::rf128; - cfg.pcch_cfg.nand_paging_frame_offset.set_one_t(); - cfg.pcch_cfg.ns.value = pcch_cfg_s::ns_opts::one; -} - -void generate_default_rach_cfg_common(const basic_cell_args_t& args, rach_cfg_common_s& cfg) -{ - cfg.rach_cfg_generic.prach_cfg_idx = 16; - cfg.rach_cfg_generic.msg1_fdm.value = rach_cfg_generic_s::msg1_fdm_opts::one; - cfg.rach_cfg_generic.msg1_freq_start = 0; - cfg.rach_cfg_generic.zero_correlation_zone_cfg = 15; - cfg.rach_cfg_generic.preamb_rx_target_pwr = -110; - cfg.rach_cfg_generic.preamb_trans_max.value = rach_cfg_generic_s::preamb_trans_max_opts::n7; - cfg.rach_cfg_generic.pwr_ramp_step.value = rach_cfg_generic_s::pwr_ramp_step_opts::db4; - cfg.rach_cfg_generic.ra_resp_win.value = rach_cfg_generic_s::ra_resp_win_opts::sl10; - cfg.ssb_per_rach_occasion_and_cb_preambs_per_ssb_present = true; - cfg.ssb_per_rach_occasion_and_cb_preambs_per_ssb.set_one().value = - rach_cfg_common_s::ssb_per_rach_occasion_and_cb_preambs_per_ssb_c_::one_opts::n8; - cfg.ra_contention_resolution_timer.value = rach_cfg_common_s::ra_contention_resolution_timer_opts::sf64; - cfg.prach_root_seq_idx.set_l839() = 1; - cfg.restricted_set_cfg.value = rach_cfg_common_s::restricted_set_cfg_opts::unrestricted_set; -} - -void generate_default_ul_cfg_common_sib(const basic_cell_args_t& args, ul_cfg_common_sib_s& cfg) -{ - cfg.freq_info_ul.scs_specific_carrier_list.resize(1); - cfg.freq_info_ul.scs_specific_carrier_list[0].offset_to_carrier = 0; - asn1::number_to_enum(cfg.freq_info_ul.scs_specific_carrier_list[0].subcarrier_spacing, args.scs); - cfg.freq_info_ul.scs_specific_carrier_list[0].carrier_bw = args.nof_prbs; - - cfg.init_ul_bwp.generic_params.location_and_bw = 14025; - asn1::number_to_enum(cfg.init_ul_bwp.generic_params.subcarrier_spacing, args.scs); - - cfg.init_ul_bwp.rach_cfg_common_present = true; - generate_default_rach_cfg_common(args, cfg.init_ul_bwp.rach_cfg_common.set_setup()); - - cfg.init_ul_bwp.pusch_cfg_common_present = true; - pusch_cfg_common_s& pusch = cfg.init_ul_bwp.pusch_cfg_common.set_setup(); - pusch.pusch_time_domain_alloc_list_present = true; - pusch.pusch_time_domain_alloc_list.resize(1); - pusch.pusch_time_domain_alloc_list[0].k2_present = true; - pusch.pusch_time_domain_alloc_list[0].k2 = 4; - pusch.pusch_time_domain_alloc_list[0].map_type.value = pusch_time_domain_res_alloc_s::map_type_opts::type_a; - pusch.pusch_time_domain_alloc_list[0].start_symbol_and_len = 27; - pusch.p0_nominal_with_grant_present = true; - pusch.p0_nominal_with_grant = -76; - - cfg.init_ul_bwp.pucch_cfg_common_present = true; - pucch_cfg_common_s& pucch = cfg.init_ul_bwp.pucch_cfg_common.set_setup(); - pucch.pucch_res_common_present = true; - pucch.pucch_res_common = 11; - pucch.pucch_group_hop.value = pucch_cfg_common_s::pucch_group_hop_opts::neither; - pucch.p0_nominal_present = true; - pucch.p0_nominal = -90; - - cfg.time_align_timer_common.value = time_align_timer_opts::infinity; -} - -void generate_default_serv_cell_cfg_common_sib(const basic_cell_args_t& args, serving_cell_cfg_common_sib_s& cfg) -{ - generate_default_dl_cfg_common_sib(args, cfg.dl_cfg_common); - - cfg.ul_cfg_common_present = true; - generate_default_ul_cfg_common_sib(args, cfg.ul_cfg_common); - - cfg.ssb_positions_in_burst.in_one_group.from_number(0x80); - - cfg.ssb_periodicity_serving_cell.value = serving_cell_cfg_common_sib_s::ssb_periodicity_serving_cell_opts::ms20; - - cfg.ss_pbch_block_pwr = -16; -} - -void generate_default_sib1(const basic_cell_args_t& args, sib1_s& cfg) -{ - cfg.cell_sel_info_present = true; - cfg.cell_sel_info.q_rx_lev_min = -70; - cfg.cell_sel_info.q_qual_min_present = true; - cfg.cell_sel_info.q_qual_min = -20; - - cfg.cell_access_related_info.plmn_id_list.resize(1); - cfg.cell_access_related_info.plmn_id_list[0].plmn_id_list.resize(1); - srsran::plmn_id_t plmn; - plmn.from_string(args.plmn); - srsran::to_asn1(&cfg.cell_access_related_info.plmn_id_list[0].plmn_id_list[0], plmn); - cfg.cell_access_related_info.plmn_id_list[0].tac_present = true; - cfg.cell_access_related_info.plmn_id_list[0].tac.from_number(args.tac); - cfg.cell_access_related_info.plmn_id_list[0].cell_id.from_number(args.cell_id); - cfg.cell_access_related_info.plmn_id_list[0].cell_reserved_for_oper.value = - plmn_id_info_s::cell_reserved_for_oper_opts::not_reserved; - - cfg.conn_est_fail_ctrl_present = true; - cfg.conn_est_fail_ctrl.conn_est_fail_count.value = conn_est_fail_ctrl_s::conn_est_fail_count_opts::n1; - cfg.conn_est_fail_ctrl.conn_est_fail_offset_validity.value = - conn_est_fail_ctrl_s::conn_est_fail_offset_validity_opts::s30; - cfg.conn_est_fail_ctrl.conn_est_fail_offset_present = true; - cfg.conn_est_fail_ctrl.conn_est_fail_offset = 1; - - // cfg.si_sched_info_present = true; - // cfg.si_sched_info.si_request_cfg.rach_occasions_si_present = true; - // cfg.si_sched_info.si_request_cfg.rach_occasions_si.rach_cfg_si.ra_resp_win.value = - // rach_cfg_generic_s::ra_resp_win_opts::sl8; - // cfg.si_sched_info.si_win_len.value = si_sched_info_s::si_win_len_opts::s20; - // cfg.si_sched_info.sched_info_list.resize(1); - // cfg.si_sched_info.sched_info_list[0].si_broadcast_status.value = - // sched_info_s::si_broadcast_status_opts::broadcasting; cfg.si_sched_info.sched_info_list[0].si_periodicity.value = - // sched_info_s::si_periodicity_opts::rf16; cfg.si_sched_info.sched_info_list[0].sib_map_info.resize(1); - // // scheduling of SI messages - // cfg.si_sched_info.sched_info_list[0].sib_map_info[0].type.value = sib_type_info_s::type_opts::sib_type2; - // cfg.si_sched_info.sched_info_list[0].sib_map_info[0].value_tag_present = true; - // cfg.si_sched_info.sched_info_list[0].sib_map_info[0].value_tag = 0; - - cfg.serving_cell_cfg_common_present = true; - generate_default_serv_cell_cfg_common_sib(args, cfg.serving_cell_cfg_common); - - cfg.ue_timers_and_consts_present = true; - cfg.ue_timers_and_consts.t300.value = ue_timers_and_consts_s::t300_opts::ms1000; - cfg.ue_timers_and_consts.t301.value = ue_timers_and_consts_s::t301_opts::ms1000; - cfg.ue_timers_and_consts.t310.value = ue_timers_and_consts_s::t310_opts::ms1000; - cfg.ue_timers_and_consts.n310.value = ue_timers_and_consts_s::n310_opts::n1; - cfg.ue_timers_and_consts.t311.value = ue_timers_and_consts_s::t311_opts::ms30000; - cfg.ue_timers_and_consts.n311.value = ue_timers_and_consts_s::n311_opts::n1; - cfg.ue_timers_and_consts.t319.value = ue_timers_and_consts_s::t319_opts::ms1000; -} - -} // namespace srsran diff --git a/srsgnb/src/stack/rrc/CMakeLists.txt b/srsgnb/src/stack/rrc/CMakeLists.txt index 3b1cdb05d..8161ba69c 100644 --- a/srsgnb/src/stack/rrc/CMakeLists.txt +++ b/srsgnb/src/stack/rrc/CMakeLists.txt @@ -8,11 +8,11 @@ set(SOURCES rrc_nr_config_utils.cc) add_library(srsgnb_rrc_config_utils STATIC ${SOURCES}) -target_link_libraries(srsgnb_rrc_config_utils srsran_rrc_nr srsran_phy) +target_link_libraries(srsgnb_rrc_config_utils srsran_phy) set(SOURCES rrc_nr.cc rrc_nr_ue.cc cell_asn1_config.cc) add_library(srsgnb_rrc STATIC ${SOURCES}) -target_link_libraries(srsgnb_rrc srsran_rrc_nr srsgnb_rrc_config_utils) +target_link_libraries(srsgnb_rrc srsgnb_rrc_config_utils) include_directories(${PROJECT_SOURCE_DIR}) diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index b5e4f0cb6..e7cd90b1c 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -13,7 +13,6 @@ #include "srsgnb/hdr/stack/rrc/cell_asn1_config.h" #include "srsran/asn1/rrc_nr_utils.h" #include "srsran/common/band_helper.h" -#include "srsran/rrc/nr/rrc_nr_cfg_utils.h" #include using namespace asn1::rrc_nr; diff --git a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc index 14911d33f..d231b335a 100644 --- a/srsgnb/src/stack/rrc/test/rrc_nr_test.cc +++ b/srsgnb/src/stack/rrc/test/rrc_nr_test.cc @@ -16,6 +16,7 @@ #include "srsenb/test/rrc/test_helpers.h" #include "srsgnb/hdr/stack/common/test/dummy_nr_classes.h" #include "srsgnb/hdr/stack/rrc/rrc_nr.h" +#include "srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h" #include "srsgnb/src/stack/mac/test/sched_nr_cfg_generators.h" #include "srsran/common/test_common.h" #include "srsran/interfaces/gnb_rrc_nr_interfaces.h" @@ -55,6 +56,7 @@ void test_sib_generation() phy_cfg_t phy_cfg{}; rrc_nr_cfg_t rrc_cfg_nr = {}; rrc_cfg_nr.cell_list.emplace_back(); + generate_default_nr_cell(rrc_cfg_nr.cell_list[0]); rrc_cfg_nr.cell_list[0].phy_cell.carrier.pci = 500; rrc_cfg_nr.cell_list[0].dl_arfcn = 368500; rrc_cfg_nr.cell_list[0].band = 3; @@ -108,6 +110,7 @@ int test_rrc_setup() phy_cfg_t phy_cfg{}; rrc_nr_cfg_t rrc_cfg_nr = rrc_nr_cfg_t{}; rrc_cfg_nr.cell_list.emplace_back(); + generate_default_nr_cell(rrc_cfg_nr.cell_list[0]); rrc_cfg_nr.cell_list[0].phy_cell.carrier.pci = 500; rrc_cfg_nr.cell_list[0].dl_arfcn = 634240; rrc_cfg_nr.cell_list[0].band = 78; @@ -146,6 +149,7 @@ void test_rrc_sa_connection() phy_cfg_t phy_cfg{}; rrc_nr_cfg_t rrc_cfg_nr = rrc_nr_cfg_t{}; rrc_cfg_nr.cell_list.emplace_back(); + generate_default_nr_cell(rrc_cfg_nr.cell_list[0]); rrc_cfg_nr.cell_list[0].phy_cell.carrier.pci = 500; rrc_cfg_nr.cell_list[0].dl_arfcn = 368500; rrc_cfg_nr.cell_list[0].band = 3; From 7f6abb8d6fe9a42e53eeae9f22d7a0172de274ec Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Tue, 16 Nov 2021 16:26:03 +0100 Subject: [PATCH 71/77] gnb,mac: add compile-time parameter to write SIBs to MAC PCAP set to disabled by default --- srsgnb/src/stack/mac/mac_nr.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/srsgnb/src/stack/mac/mac_nr.cc b/srsgnb/src/stack/mac/mac_nr.cc index fe59ff938..2033591d8 100644 --- a/srsgnb/src/stack/mac/mac_nr.cc +++ b/srsgnb/src/stack/mac/mac_nr.cc @@ -20,6 +20,8 @@ #include "srsran/common/time_prof.h" #include "srsran/mac/mac_rar_pdu_nr.h" +//#define WRITE_SIB_PCAP + namespace srsenb { class mac_nr_rx @@ -484,6 +486,15 @@ mac_nr::dl_sched_t* mac_nr::get_dl_sched(const srsran_slot_cfg_t& slot_cfg) } else if (pdsch.sch.grant.rnti_type == srsran_rnti_type_si) { uint32_t sib_idx = dl_res->sib_idxs[si_count++]; pdsch.data[0] = bcch_dlsch_payload[sib_idx].payload.get(); +#ifdef WRITE_SIB_PCAP + if (pcap != nullptr) { + pcap->write_dl_si_rnti_nr(bcch_dlsch_payload[sib_idx].payload->msg, + bcch_dlsch_payload[sib_idx].payload->N_bytes, + SI_RNTI, + 0, + slot_cfg.idx); + } +#endif } } for (auto& u : ue_db) { From e8beb2ef81fa6b72b0bb09c859facd362acd7e32 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 18:09:33 +0000 Subject: [PATCH 72/77] nr,rrc: redesign fetching of asn1 obj ids for comparison and addmod/rem to asn1 lists --- .../obj_id_cmp_utils.h} | 67 +++++++---- lib/include/srsran/asn1/rrc_utils.h | 33 ------ lib/src/asn1/rrc_nr_utils.cc | 11 ++ lib/src/asn1/rrc_utils.cc | 105 +++--------------- srsenb/src/stack/rrc/rrc_bearer_cfg.cc | 2 +- srsenb/src/stack/rrc/rrc_mobility.cc | 2 +- srsenb/src/stack/rrc/ue_meas_cfg.cc | 3 +- srsenb/src/stack/rrc/ue_rr_cfg.cc | 2 +- srsenb/test/rrc/rrc_meascfg_test.cc | 2 +- srsue/src/stack/rrc/rrc_meas.cc | 4 +- 10 files changed, 80 insertions(+), 151 deletions(-) rename lib/include/srsran/{rrc/rrc_cfg_utils.h => asn1/obj_id_cmp_utils.h} (76%) diff --git a/lib/include/srsran/rrc/rrc_cfg_utils.h b/lib/include/srsran/asn1/obj_id_cmp_utils.h similarity index 76% rename from lib/include/srsran/rrc/rrc_cfg_utils.h rename to lib/include/srsran/asn1/obj_id_cmp_utils.h index 695750a2b..ccc6835cd 100644 --- a/lib/include/srsran/rrc/rrc_cfg_utils.h +++ b/lib/include/srsran/asn1/obj_id_cmp_utils.h @@ -10,18 +10,37 @@ * */ -#ifndef SRSRAN_RRC_CFG_UTILS_H -#define SRSRAN_RRC_CFG_UTILS_H +#ifndef SRSRAN_OBJ_ID_CMP_UTILS_H +#define SRSRAN_OBJ_ID_CMP_UTILS_H -#include "srsran/asn1/rrc_utils.h" #include "srsran/common/common.h" #include #include namespace srsran { -template -using rrc_obj_id_t = decltype(asn1::rrc::get_rrc_obj_id(std::declval())); +using asn1_obj_id_t = uint8_t; + +/// Template function to generically obtain id of asn1 object (e.g. srb_id of srbs, drb_id of drbs, etc.) +template +uint8_t get_asn1_obj_id(const Asn1Obj& obj); + +/// Template function to generically set id of asn1 object (e.g. srb_id of srbs, drb_id of drbs, etc.) +template +void set_asn1_obj_id(Asn1Obj& obj, uint8_t id); + +/// helper macro to help define get_asn1_obj_id and set_asn1_obj_id for specific asn1 objects +#define ASN1_OBJ_ID_DEFINE(Asn1ObjType, member) \ + template <> \ + uint8_t get_asn1_obj_id(const Asn1ObjType& obj) \ + { \ + return obj.member; \ + } \ + template <> \ + void set_asn1_obj_id(Asn1ObjType & obj, uint8_t id) \ + { \ + obj.member = id; \ + } //! Functor to compare RRC config elements (e.g. SRB/measObj/Rep) based on ID struct rrc_obj_id_cmp { @@ -29,27 +48,27 @@ struct rrc_obj_id_cmp { typename std::enable_if::value and not std::is_integral::value, bool>::type operator()(const T& lhs, const U& rhs) const { - return asn1::rrc::get_rrc_obj_id(lhs) < asn1::rrc::get_rrc_obj_id(rhs); + return get_asn1_obj_id(lhs) < get_asn1_obj_id(rhs); } template - bool operator()(const T& lhs, rrc_obj_id_t id) const + bool operator()(const T& lhs, asn1_obj_id_t id) const { - return asn1::rrc::get_rrc_obj_id(lhs) < id; + return get_asn1_obj_id(lhs) < id; } template - bool operator()(rrc_obj_id_t id, const T& rhs) const + bool operator()(asn1_obj_id_t id, const T& rhs) const { - return id < asn1::rrc::get_rrc_obj_id(rhs); + return id < get_asn1_obj_id(rhs); } }; template struct unary_rrc_obj_id { - rrc_obj_id_t id; + asn1_obj_id_t id; template explicit unary_rrc_obj_id(T id_) : id(id_) {} - bool operator()(const typename Container::value_type& e) const { return asn1::rrc::get_rrc_obj_id(e) == id; } + bool operator()(const typename Container::value_type& e) const { return get_asn1_obj_id(e) == id; } }; /// Find rrc object in list based on ID @@ -69,13 +88,13 @@ template typename Container::iterator sorted_find_rrc_obj_id(Container& c, IdType id) { auto it = std::lower_bound(c.begin(), c.end(), id, rrc_obj_id_cmp{}); - return (it == c.end() or asn1::rrc::get_rrc_obj_id(*it) != id) ? c.end() : it; + return (it == c.end() or get_asn1_obj_id(*it) != id) ? c.end() : it; } template typename Container::const_iterator sorted_find_rrc_obj_id(const Container& c, IdType id) { auto it = std::lower_bound(c.begin(), c.end(), id, rrc_obj_id_cmp{}); - return (it == c.end() or asn1::rrc::get_rrc_obj_id(*it) != id) ? c.end() : it; + return (it == c.end() or get_asn1_obj_id(*it) != id) ? c.end() : it; } template @@ -86,7 +105,7 @@ bool equal_rrc_obj_ids(const Container& c, const Container2& c2) c2.begin(), c2.end(), [](const typename Container::value_type& e, const typename Container2::value_type& e2) { - return asn1::rrc::get_rrc_obj_id(e) == asn1::rrc::get_rrc_obj_id(e2); + return get_asn1_obj_id(e) == get_asn1_obj_id(e2); }); } @@ -98,7 +117,7 @@ typename Container::iterator add_rrc_obj_id(Container& c, IdType id) if (it == c.end()) { c.push_back({}); it = c.end() - 1; - asn1::rrc::set_rrc_obj_id(*it, id); + set_asn1_obj_id(*it, id); std::sort(c.begin(), c.end(), rrc_obj_id_cmp{}); it = sorted_find_rrc_obj_id(c, id); } @@ -108,11 +127,11 @@ typename Container::iterator add_rrc_obj_id(Container& c, IdType id) template typename Container::iterator add_rrc_obj(Container& c, const typename Container::value_type& v) { - auto it = sorted_find_rrc_obj_id(c, asn1::rrc::get_rrc_obj_id(v)); + auto it = sorted_find_rrc_obj_id(c, get_asn1_obj_id(v)); if (it == c.end()) { c.push_back(v); std::sort(c.begin(), c.end(), rrc_obj_id_cmp{}); - it = sorted_find_rrc_obj_id(c, asn1::rrc::get_rrc_obj_id(v)); + it = sorted_find_rrc_obj_id(c, get_asn1_obj_id(v)); } else { *it = v; } @@ -136,21 +155,21 @@ bool rem_rrc_obj_id(Container& c, IdType id) * @return id value */ template -auto find_rrc_obj_id_gap(const Container& c) -> decltype(asn1::rrc::get_rrc_obj_id(c[0])) +auto find_rrc_obj_id_gap(const Container& c) -> decltype(get_asn1_obj_id(c[0])) { auto id_cmp_op = rrc_obj_id_cmp{}; assert(std::is_sorted(c.begin(), c.end(), id_cmp_op)); auto prev_it = c.begin(); - if (prev_it != c.end() and asn1::rrc::get_rrc_obj_id(*prev_it) == 1) { + if (prev_it != c.end() and get_asn1_obj_id(*prev_it) == 1) { auto it = prev_it; for (++it; it != c.end(); prev_it = it, ++it) { - if (asn1::rrc::get_rrc_obj_id(*it) > asn1::rrc::get_rrc_obj_id(*prev_it) + 1) { + if (get_asn1_obj_id(*it) > get_asn1_obj_id(*prev_it) + 1) { break; } } } - return (prev_it == c.end()) ? 1 : asn1::rrc::get_rrc_obj_id(*prev_it) + 1; // starts at 1. + return (prev_it == c.end()) ? 1 : get_asn1_obj_id(*prev_it) + 1; // starts at 1. } /** @@ -307,7 +326,7 @@ void compute_cfg_diff(const toAddModList& src_list, } using it_t = typename toAddModList::const_iterator; - auto rem_func = [&rem_diff_list](it_t rem_it) { rem_diff_list.push_back(asn1::rrc::get_rrc_obj_id(*rem_it)); }; + auto rem_func = [&rem_diff_list](it_t rem_it) { rem_diff_list.push_back(get_asn1_obj_id(*rem_it)); }; auto add_func = [&add_diff_list](it_t add_it) { add_diff_list.push_back(*add_it); }; auto mod_func = [&add_diff_list](it_t src_it, it_t target_it) { if (not(*src_it == *target_it)) { @@ -319,4 +338,4 @@ void compute_cfg_diff(const toAddModList& src_list, } // namespace srsran -#endif // SRSRAN_RRC_CFG_UTILS_H +#endif // SRSRAN_OBJ_ID_CMP_UTILS_H diff --git a/lib/include/srsran/asn1/rrc_utils.h b/lib/include/srsran/asn1/rrc_utils.h index 58f3d58a5..de7832f5d 100644 --- a/lib/include/srsran/asn1/rrc_utils.h +++ b/lib/include/srsran/asn1/rrc_utils.h @@ -141,37 +141,4 @@ sib13_t make_sib13(const asn1::rrc::sib_type13_r9_s& asn1_type); } // namespace srsran -/************************ - * ASN1 RRC extensions - ***********************/ -namespace asn1 { -namespace rrc { - -/************************** - * RRC Obj Id - *************************/ - -uint8_t get_rrc_obj_id(const srb_to_add_mod_s& srb); -uint8_t get_rrc_obj_id(const drb_to_add_mod_s& drb); -uint8_t get_rrc_obj_id(const cells_to_add_mod_s& obj); -uint8_t get_rrc_obj_id(const cells_to_add_mod_nr_r15_s& obj); -uint8_t get_rrc_obj_id(const black_cells_to_add_mod_s& obj); -uint8_t get_rrc_obj_id(const meas_obj_to_add_mod_s& obj); -uint8_t get_rrc_obj_id(const report_cfg_to_add_mod_s& obj); -uint8_t get_rrc_obj_id(const meas_id_to_add_mod_s& obj); -uint8_t get_rrc_obj_id(const scell_to_add_mod_r10_s& obj); - -void set_rrc_obj_id(srb_to_add_mod_s& srb, uint8_t id); -void set_rrc_obj_id(drb_to_add_mod_s& drb, uint8_t id); -void set_rrc_obj_id(cells_to_add_mod_s& obj, uint8_t id); -void set_rrc_obj_id(cells_to_add_mod_nr_r15_s& obj, uint8_t id); -void set_rrc_obj_id(black_cells_to_add_mod_s& obj, uint8_t id); -void set_rrc_obj_id(meas_obj_to_add_mod_s& obj, uint8_t id); -void set_rrc_obj_id(report_cfg_to_add_mod_s& obj, uint8_t id); -void set_rrc_obj_id(meas_id_to_add_mod_s& obj, uint8_t id); -void set_rrc_obj_id(scell_to_add_mod_r10_s& obj, uint8_t id); - -} // namespace rrc -} // namespace asn1 - #endif // SRSRAN_RRC_UTILS_H diff --git a/lib/src/asn1/rrc_nr_utils.cc b/lib/src/asn1/rrc_nr_utils.cc index 9d6c02744..b44ecfc4e 100644 --- a/lib/src/asn1/rrc_nr_utils.cc +++ b/lib/src/asn1/rrc_nr_utils.cc @@ -11,6 +11,7 @@ */ #include "srsran/asn1/rrc_nr_utils.h" +#include "srsran/asn1/obj_id_cmp_utils.h" #include "srsran/asn1/rrc_nr.h" #include "srsran/common/band_helper.h" #include "srsran/config.h" @@ -1612,4 +1613,14 @@ bool fill_phy_pdcch_cfg_common(const asn1::rrc_nr::pdcch_cfg_common_s& pdcch_cfg return true; } +/************************** + * Asn1 Obj Id + *************************/ + +ASN1_OBJ_ID_DEFINE(asn1::rrc_nr::srb_to_add_mod_s, srb_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc_nr::drb_to_add_mod_s, drb_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc_nr::meas_obj_to_add_mod_s, meas_obj_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc_nr::report_cfg_to_add_mod_s, report_cfg_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc_nr::meas_id_to_add_mod_s, meas_id); + } // namespace srsran diff --git a/lib/src/asn1/rrc_utils.cc b/lib/src/asn1/rrc_utils.cc index 3296ea37b..cafe89082 100644 --- a/lib/src/asn1/rrc_utils.cc +++ b/lib/src/asn1/rrc_utils.cc @@ -11,6 +11,7 @@ */ #include "srsran/asn1/rrc_utils.h" +#include "srsran/asn1/obj_id_cmp_utils.h" #include "srsran/asn1/rrc.h" #include "srsran/config.h" #include @@ -129,12 +130,12 @@ srsran::rlc_config_t make_rlc_config_t(const asn1::rrc::rlc_cfg_c& asn1_type) srsran::rlc_config_t rlc_cfg; switch (asn1_type.type().value) { case asn1::rrc::rlc_cfg_c::types_opts::am: - rlc_cfg.rlc_mode = rlc_mode_t::am; - rlc_cfg.am.t_poll_retx = asn1_type.am().ul_am_rlc.t_poll_retx.to_number(); - rlc_cfg.am.poll_pdu = asn1_type.am().ul_am_rlc.poll_pdu.to_number(); - rlc_cfg.am.poll_byte = asn1_type.am().ul_am_rlc.poll_byte.to_number() < 0 - ? -1 - : asn1_type.am().ul_am_rlc.poll_byte.to_number() * 1000; // KB + rlc_cfg.rlc_mode = rlc_mode_t::am; + rlc_cfg.am.t_poll_retx = asn1_type.am().ul_am_rlc.t_poll_retx.to_number(); + rlc_cfg.am.poll_pdu = asn1_type.am().ul_am_rlc.poll_pdu.to_number(); + rlc_cfg.am.poll_byte = asn1_type.am().ul_am_rlc.poll_byte.to_number() < 0 + ? -1 + : asn1_type.am().ul_am_rlc.poll_byte.to_number() * 1000; // KB rlc_cfg.am.max_retx_thresh = asn1_type.am().ul_am_rlc.max_retx_thres.to_number(); rlc_cfg.am.t_reordering = asn1_type.am().dl_am_rlc.t_reordering.to_number(); rlc_cfg.am.t_status_prohibit = asn1_type.am().dl_am_rlc.t_status_prohibit.to_number(); @@ -1087,88 +1088,18 @@ sib13_t make_sib13(const asn1::rrc::sib_type13_r9_s& asn1_type) return sib13; } -} // namespace srsran - -namespace asn1 { -namespace rrc { - /************************** - * RRC Obj Id + * Asn1 Obj Id *************************/ -uint8_t get_rrc_obj_id(const srb_to_add_mod_s& srb) -{ - return srb.srb_id; -} -uint8_t get_rrc_obj_id(const drb_to_add_mod_s& drb) -{ - return drb.drb_id; -} -uint8_t get_rrc_obj_id(const black_cells_to_add_mod_s& obj) -{ - return obj.cell_idx; -} -uint8_t get_rrc_obj_id(const cells_to_add_mod_s& obj) -{ - return obj.cell_idx; -} -uint8_t get_rrc_obj_id(const cells_to_add_mod_nr_r15_s& obj) -{ - return obj.cell_idx_r15; -} -uint8_t get_rrc_obj_id(const meas_obj_to_add_mod_s& obj) -{ - return obj.meas_obj_id; -} -uint8_t get_rrc_obj_id(const report_cfg_to_add_mod_s& obj) -{ - return obj.report_cfg_id; -} -uint8_t get_rrc_obj_id(const meas_id_to_add_mod_s& obj) -{ - return obj.meas_id; -} -uint8_t get_rrc_obj_id(const scell_to_add_mod_r10_s& obj) -{ - return obj.scell_idx_r10; -} +ASN1_OBJ_ID_DEFINE(asn1::rrc::srb_to_add_mod_s, srb_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc::drb_to_add_mod_s, drb_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc::black_cells_to_add_mod_s, cell_idx); +ASN1_OBJ_ID_DEFINE(asn1::rrc::cells_to_add_mod_s, cell_idx); +ASN1_OBJ_ID_DEFINE(asn1::rrc::cells_to_add_mod_nr_r15_s, cell_idx_r15); +ASN1_OBJ_ID_DEFINE(asn1::rrc::meas_obj_to_add_mod_s, meas_obj_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc::report_cfg_to_add_mod_s, report_cfg_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc::meas_id_to_add_mod_s, meas_id); +ASN1_OBJ_ID_DEFINE(asn1::rrc::scell_to_add_mod_r10_s, scell_idx_r10); -void set_rrc_obj_id(srb_to_add_mod_s& srb, uint8_t id) -{ - srb.srb_id = id; -} -void set_rrc_obj_id(drb_to_add_mod_s& drb, uint8_t id) -{ - drb.drb_id = id; -} -void set_rrc_obj_id(black_cells_to_add_mod_s& obj, uint8_t id) -{ - obj.cell_idx = id; -} -void set_rrc_obj_id(cells_to_add_mod_s& obj, uint8_t id) -{ - obj.cell_idx = id; -} -void set_rrc_obj_id(cells_to_add_mod_nr_r15_s& obj, uint8_t id) -{ - obj.cell_idx_r15 = id; -} -void set_rrc_obj_id(meas_obj_to_add_mod_s& obj, uint8_t id) -{ - obj.meas_obj_id = id; -} -void set_rrc_obj_id(report_cfg_to_add_mod_s& obj, uint8_t id) -{ - obj.report_cfg_id = id; -} -void set_rrc_obj_id(meas_id_to_add_mod_s& obj, uint8_t id) -{ - obj.meas_id = id; -} -void set_rrc_obj_id(scell_to_add_mod_r10_s& obj, uint8_t id) -{ - obj.scell_idx_r10 = id; -} - -} // namespace rrc -} // namespace asn1 +} // namespace srsran diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index 57c7a3c70..7fd091b6c 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -12,8 +12,8 @@ #include "srsenb/hdr/stack/rrc/rrc_bearer_cfg.h" #include "srsenb/hdr/common/common_enb.h" +#include "srsran/asn1/obj_id_cmp_utils.h" #include "srsran/asn1/rrc_utils.h" -#include "srsran/rrc/rrc_cfg_utils.h" namespace srsenb { diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index b7db4f775..cff4df91f 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -15,6 +15,7 @@ #include "srsenb/hdr/stack/rrc/rrc_cell_cfg.h" #include "srsenb/hdr/stack/rrc/ue_meas_cfg.h" #include "srsenb/hdr/stack/rrc/ue_rr_cfg.h" +#include "srsran/asn1/obj_id_cmp_utils.h" #include "srsran/asn1/rrc_utils.h" #include "srsran/common/bcd_helpers.h" #include "srsran/common/common.h" @@ -25,7 +26,6 @@ #include "srsran/interfaces/enb_pdcp_interfaces.h" #include "srsran/interfaces/enb_rlc_interfaces.h" #include "srsran/interfaces/enb_s1ap_interfaces.h" -#include "srsran/rrc/rrc_cfg_utils.h" #include #include diff --git a/srsenb/src/stack/rrc/ue_meas_cfg.cc b/srsenb/src/stack/rrc/ue_meas_cfg.cc index f5fc06d4a..dc4b54d0f 100644 --- a/srsenb/src/stack/rrc/ue_meas_cfg.cc +++ b/srsenb/src/stack/rrc/ue_meas_cfg.cc @@ -12,7 +12,8 @@ #include "srsenb/hdr/stack/rrc/ue_meas_cfg.h" #include "srsenb/hdr/stack/rrc/rrc_cell_cfg.h" -#include "srsran/rrc/rrc_cfg_utils.h" +#include "srsran/asn1/obj_id_cmp_utils.h" +#include "srsran/asn1/rrc_utils.h" using namespace asn1::rrc; diff --git a/srsenb/src/stack/rrc/ue_rr_cfg.cc b/srsenb/src/stack/rrc/ue_rr_cfg.cc index c46f3e9f4..0b9b1b616 100644 --- a/srsenb/src/stack/rrc/ue_rr_cfg.cc +++ b/srsenb/src/stack/rrc/ue_rr_cfg.cc @@ -14,8 +14,8 @@ #include "srsenb/hdr/stack/rrc/rrc_bearer_cfg.h" #include "srsenb/hdr/stack/rrc/rrc_cell_cfg.h" #include "srsenb/hdr/stack/rrc/rrc_config.h" +#include "srsran/asn1/obj_id_cmp_utils.h" #include "srsran/asn1/rrc_utils.h" -#include "srsran/rrc/rrc_cfg_utils.h" #define SET_OPT_FIELD(fieldname, out, in) \ if (in.fieldname##_present) { \ diff --git a/srsenb/test/rrc/rrc_meascfg_test.cc b/srsenb/test/rrc/rrc_meascfg_test.cc index 4f39d1980..c5673955a 100644 --- a/srsenb/test/rrc/rrc_meascfg_test.cc +++ b/srsenb/test/rrc/rrc_meascfg_test.cc @@ -12,10 +12,10 @@ #include "srsenb/hdr/enb.h" #include "srsenb/hdr/stack/rrc/ue_meas_cfg.h" +#include "srsran/asn1/obj_id_cmp_utils.h" #include "srsran/asn1/rrc_utils.h" #include "srsran/common/test_common.h" #include "srsran/interfaces/enb_rrc_interface_types.h" -#include "srsran/rrc/rrc_cfg_utils.h" #include "test_helpers.h" using namespace asn1::rrc; diff --git a/srsue/src/stack/rrc/rrc_meas.cc b/srsue/src/stack/rrc/rrc_meas.cc index 0914a06ea..f9926e89d 100644 --- a/srsue/src/stack/rrc/rrc_meas.cc +++ b/srsue/src/stack/rrc/rrc_meas.cc @@ -11,9 +11,9 @@ */ #include "srsue/hdr/stack/rrc/rrc_meas.h" +#include "srsran/asn1/obj_id_cmp_utils.h" #include "srsran/asn1/rrc/dl_dcch_msg.h" #include "srsran/interfaces/ue_phy_interfaces.h" -#include "srsran/rrc/rrc_cfg_utils.h" #include "srsue/hdr/stack/rrc/rrc.h" /************************************************************************ @@ -403,7 +403,7 @@ void rrc::rrc_meas::var_meas_report_list::generate_report(const uint32_t measId) meas_results_s* report = &ul_dcch_msg.msg.c1().meas_report().crit_exts.c1().meas_report_r8().meas_results; - report->meas_id = (uint8_t)measId; + report->meas_id = (uint8_t)measId; report->meas_result_pcell.rsrp_result = rrc_value_to_range(quant_rsrp, serv_cell->get_rsrp()); report->meas_result_pcell.rsrq_result = rrc_value_to_range(quant_rsrq, serv_cell->get_rsrq()); From 0c733f7ea3df6da9e67b48fdd615048173c71e0b Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 18:18:09 +0000 Subject: [PATCH 73/77] nr,ngap: use const_byte_spans instead of unique_byte_buffers in ngap interface to avoid intermediate copies --- .../srsran/interfaces/gnb_ngap_interfaces.h | 10 +++++----- srsgnb/hdr/stack/common/test/dummy_nr_classes.h | 6 +++--- srsgnb/hdr/stack/ngap/ngap.h | 6 +++--- srsgnb/hdr/stack/ngap/ngap_ue.h | 6 +++--- srsgnb/src/stack/ngap/ngap.cc | 14 +++++++------- srsgnb/src/stack/ngap/ngap_ue.cc | 14 +++++++------- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/include/srsran/interfaces/gnb_ngap_interfaces.h b/lib/include/srsran/interfaces/gnb_ngap_interfaces.h index 0dc899e67..70472a69b 100644 --- a/lib/include/srsran/interfaces/gnb_ngap_interfaces.h +++ b/lib/include/srsran/interfaces/gnb_ngap_interfaces.h @@ -37,14 +37,14 @@ public: virtual void initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu) = 0; + srsran::const_byte_span pdu) = 0; virtual void initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu, - uint32_t m_tmsi) = 0; + srsran::const_byte_span pdu, + uint32_t m_tmsi) = 0; - virtual void write_pdu(uint16_t rnti, srsran::unique_byte_buffer_t pdu) = 0; + virtual void write_pdu(uint16_t rnti, srsran::const_byte_span pdu) = 0; virtual bool user_exists(uint16_t rnti) = 0; virtual void user_mod(uint16_t old_rnti, uint16_t new_rnti) = 0; virtual bool user_release(uint16_t rnti, asn1::ngap_nr::cause_radio_network_e cause_radio) = 0; @@ -54,4 +54,4 @@ public: } // namespace srsenb -#endif // SRSRAN_GNB_NGAP_INTERFACES_H \ No newline at end of file +#endif // SRSRAN_GNB_NGAP_INTERFACES_H diff --git a/srsgnb/hdr/stack/common/test/dummy_nr_classes.h b/srsgnb/hdr/stack/common/test/dummy_nr_classes.h index 90e4f9906..c7b18f703 100644 --- a/srsgnb/hdr/stack/common/test/dummy_nr_classes.h +++ b/srsgnb/hdr/stack/common/test/dummy_nr_classes.h @@ -24,16 +24,16 @@ class ngap_dummy : public ngap_interface_rrc_nr void initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu) + srsran::const_byte_span pdu) {} void initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu, + srsran::const_byte_span pdu, uint32_t m_tmsi) {} - void write_pdu(uint16_t rnti, srsran::unique_byte_buffer_t pdu) {} + void write_pdu(uint16_t rnti, srsran::const_byte_span pdu) {} bool user_exists(uint16_t rnti) { return true; } void user_mod(uint16_t old_rnti, uint16_t new_rnti) {} bool user_release(uint16_t rnti, asn1::ngap_nr::cause_radio_network_e cause_radio) { return true; } diff --git a/srsgnb/hdr/stack/ngap/ngap.h b/srsgnb/hdr/stack/ngap/ngap.h index 63bd0e423..ac106b755 100644 --- a/srsgnb/hdr/stack/ngap/ngap.h +++ b/srsgnb/hdr/stack/ngap/ngap.h @@ -50,14 +50,14 @@ public: void initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu); + srsran::const_byte_span pdu); void initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu, + srsran::const_byte_span pdu, uint32_t s_tmsi); - void write_pdu(uint16_t rnti, srsran::unique_byte_buffer_t pdu); + void write_pdu(uint16_t rnti, srsran::const_byte_span pdu); bool user_exists(uint16_t rnti) { return true; }; void user_mod(uint16_t old_rnti, uint16_t new_rnti){}; bool user_release(uint16_t rnti, asn1::ngap_nr::cause_radio_network_e cause_radio) { return true; }; diff --git a/srsgnb/hdr/stack/ngap/ngap_ue.h b/srsgnb/hdr/stack/ngap/ngap_ue.h index 03ba97dec..e2eb7ba7f 100644 --- a/srsgnb/hdr/stack/ngap/ngap_ue.h +++ b/srsgnb/hdr/stack/ngap/ngap_ue.h @@ -31,11 +31,11 @@ public: virtual ~ue(); // TS 38.413 - Section 9.2.5.1 - Initial UE Message bool send_initial_ue_message(asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu, + srsran::const_byte_span pdu, bool has_tmsi, uint32_t s_tmsi = 0); // TS 38.413 - Section 9.2.5.3 - Uplink NAS Transport - bool send_ul_nas_transport(srsran::unique_byte_buffer_t pdu); + bool send_ul_nas_transport(srsran::const_byte_span pdu); // TS 38.413 - Section 9.2.2.2 - Initial Context Setup Response bool send_initial_ctxt_setup_response(); // TS 38.413 - Section 9.2.2.3 - Initial Context Setup Failure @@ -77,4 +77,4 @@ private: }; } // namespace srsenb -#endif \ No newline at end of file +#endif diff --git a/srsgnb/src/stack/ngap/ngap.cc b/srsgnb/src/stack/ngap/ngap.cc index c57f13f39..fa53daff2 100644 --- a/srsgnb/src/stack/ngap/ngap.cc +++ b/srsgnb/src/stack/ngap/ngap.cc @@ -214,7 +214,7 @@ int ngap::build_tai_cgi() void ngap::initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu) + srsran::const_byte_span pdu) { std::unique_ptr ue_ptr{new ue{this, rrc, gtpu, logger}}; ue_ptr->ctxt.rnti = rnti; @@ -224,13 +224,13 @@ void ngap::initial_ue(uint16_t rnti, logger.error("Failed to add rnti=0x%x", rnti); return; } - u->send_initial_ue_message(cause, std::move(pdu), false); + u->send_initial_ue_message(cause, pdu, false); } void ngap::initial_ue(uint16_t rnti, uint32_t gnb_cc_idx, asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu, + srsran::const_byte_span pdu, uint32_t s_tmsi) { std::unique_ptr ue_ptr{new ue{this, rrc, gtpu, logger}}; @@ -241,7 +241,7 @@ void ngap::initial_ue(uint16_t rnti, logger.error("Failed to add rnti=0x%x", rnti); return; } - u->send_initial_ue_message(cause, std::move(pdu), true, s_tmsi); + u->send_initial_ue_message(cause, pdu, true, s_tmsi); } void ngap::ue_notify_rrc_reconf_complete(uint16_t rnti, bool outcome) @@ -253,16 +253,16 @@ void ngap::ue_notify_rrc_reconf_complete(uint16_t rnti, bool outcome) u->notify_rrc_reconf_complete(outcome); } -void ngap::write_pdu(uint16_t rnti, srsran::unique_byte_buffer_t pdu) +void ngap::write_pdu(uint16_t rnti, srsran::const_byte_span pdu) { - logger.info(pdu->msg, pdu->N_bytes, "Received RRC SDU"); + logger.info(pdu.data(), pdu.size(), "Received RRC SDU"); ue* u = users.find_ue_rnti(rnti); if (u == nullptr) { logger.info("The rnti=0x%x does not exist", rnti); return; } - u->send_ul_nas_transport(std::move(pdu)); + u->send_ul_nas_transport(pdu); } /********************************************************* diff --git a/srsgnb/src/stack/ngap/ngap_ue.cc b/srsgnb/src/stack/ngap/ngap_ue.cc index 1efcf8d4a..f83b446e9 100644 --- a/srsgnb/src/stack/ngap/ngap_ue.cc +++ b/srsgnb/src/stack/ngap/ngap_ue.cc @@ -46,7 +46,7 @@ ngap::ue::~ue() {} ********************************************************************************/ bool ngap::ue::send_initial_ue_message(asn1::ngap_nr::rrcestablishment_cause_e cause, - srsran::unique_byte_buffer_t pdu, + srsran::const_byte_span pdu, bool has_tmsi, uint32_t s_tmsi) { @@ -71,8 +71,8 @@ bool ngap::ue::send_initial_ue_message(asn1::ngap_nr::rrcestablishment_cause_e c container.ran_ue_ngap_id.value = ctxt.ran_ue_ngap_id; // NAS_PDU - container.nas_pdu.value.resize(pdu->N_bytes); - memcpy(container.nas_pdu.value.data(), pdu->msg, pdu->N_bytes); + container.nas_pdu.value.resize(pdu.size()); + memcpy(container.nas_pdu.value.data(), pdu.data(), pdu.size()); // RRC Establishment Cause container.rrcestablishment_cause.value = cause; @@ -93,7 +93,7 @@ bool ngap::ue::send_initial_ue_message(asn1::ngap_nr::rrcestablishment_cause_e c return ngap_ptr->sctp_send_ngap_pdu(tx_pdu, ctxt.rnti, "InitialUEMessage"); } -bool ngap::ue::send_ul_nas_transport(srsran::unique_byte_buffer_t pdu) +bool ngap::ue::send_ul_nas_transport(srsran::const_byte_span pdu) { if (not ngap_ptr->amf_connected) { logger.warning("AMF not connected"); @@ -116,8 +116,8 @@ bool ngap::ue::send_ul_nas_transport(srsran::unique_byte_buffer_t pdu) container.ran_ue_ngap_id.value = ctxt.ran_ue_ngap_id; // NAS PDU - container.nas_pdu.value.resize(pdu->N_bytes); - memcpy(container.nas_pdu.value.data(), pdu->msg, pdu->N_bytes); + container.nas_pdu.value.resize(pdu.size()); + memcpy(container.nas_pdu.value.data(), pdu.data(), pdu.size()); // User Location Info // userLocationInformationNR @@ -276,4 +276,4 @@ bool ngap::ue::handle_pdu_session_res_setup_request(const asn1::ngap_nr::pdu_ses return true; } -} // namespace srsenb \ No newline at end of file +} // namespace srsenb From 716ff982c8b92a1ee3fdd42990bc4ef1cb11b373 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 18:22:48 +0000 Subject: [PATCH 74/77] nr,gnb,rrc: add code to derive master cell config bearers from asn1 radio bearer cfg --- srsgnb/hdr/stack/rrc/cell_asn1_config.h | 6 ++ srsgnb/src/stack/rrc/cell_asn1_config.cc | 122 +++++++++++++++++------ srsgnb/src/stack/rrc/rrc_nr_ue.cc | 4 +- 3 files changed, 102 insertions(+), 30 deletions(-) diff --git a/srsgnb/hdr/stack/rrc/cell_asn1_config.h b/srsgnb/hdr/stack/rrc/cell_asn1_config.h index 5ee4d3ce1..5809f704f 100644 --- a/srsgnb/hdr/stack/rrc/cell_asn1_config.h +++ b/srsgnb/hdr/stack/rrc/cell_asn1_config.h @@ -15,6 +15,7 @@ #include "rrc_nr_config.h" #include "srsran/asn1/rrc_nr.h" +#include "srsran/common/common_nr.h" namespace srsenb { @@ -29,6 +30,11 @@ int fill_master_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1 int fill_mib_from_enb_cfg(const rrc_cell_cfg_nr_t& cell_cfg, asn1::rrc_nr::mib_s& mib); int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::sib1_s& sib1); +/// Apply radioBearerConfig updates to CellGroupConfig +void fill_cellgroup_with_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, + const asn1::rrc_nr::radio_bearer_cfg_s& bearers, + asn1::rrc_nr::cell_group_cfg_s& out); + } // namespace srsenb #endif // SRSRAN_CELL_ASN1_CONFIG_H diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index e7cd90b1c..bd2cfec42 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -786,48 +786,87 @@ int fill_sp_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, sp_cell_ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void fill_srb1(const rrc_nr_cfg_t& cfg, rlc_bearer_cfg_s& srb1) +/// Fill SRB with parameters derived from cfg +void fill_srb(const rrc_nr_cfg_t& cfg, srsran::nr_srb srb_id, asn1::rrc_nr::rlc_bearer_cfg_s& out) { - srb1.lc_ch_id = 1; - srb1.served_radio_bearer_present = true; - srb1.served_radio_bearer.set_srb_id() = 1; - srb1.rlc_cfg_present = true; - ul_am_rlc_s& am_ul = srb1.rlc_cfg.set_am().ul_am_rlc; - am_ul.sn_field_len_present = true; - am_ul.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; - am_ul.t_poll_retx.value = asn1::rrc_nr::t_poll_retx_opts::ms45; - am_ul.poll_pdu.value = asn1::rrc_nr::poll_pdu_opts::infinity; - am_ul.poll_byte.value = asn1::rrc_nr::poll_byte_opts::infinity; - am_ul.max_retx_thres.value = asn1::rrc_nr::ul_am_rlc_s::max_retx_thres_opts::t8; - dl_am_rlc_s& am_dl = srb1.rlc_cfg.am().dl_am_rlc; - am_dl.sn_field_len_present = true; - am_dl.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; - am_dl.t_reassembly.value = t_reassembly_opts::ms35; - am_dl.t_status_prohibit.value = asn1::rrc_nr::t_status_prohibit_opts::ms0; + srsran_assert(srb_id > srsran::nr_srb::srb0 and srb_id < srsran::nr_srb::count, "Invalid srb_id argument"); + + out.lc_ch_id = srsran::srb_to_lcid(srb_id); + out.served_radio_bearer_present = true; + out.served_radio_bearer.set_srb_id() = (uint8_t)srb_id; + + out.rlc_cfg_present = true; + auto& ul_am = out.rlc_cfg.set_am().ul_am_rlc; + ul_am.sn_field_len_present = true; + ul_am.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; + ul_am.t_poll_retx.value = asn1::rrc_nr::t_poll_retx_opts::ms45; + ul_am.poll_pdu.value = asn1::rrc_nr::poll_pdu_opts::infinity; + ul_am.poll_byte.value = asn1::rrc_nr::poll_byte_opts::infinity; + ul_am.max_retx_thres.value = asn1::rrc_nr::ul_am_rlc_s::max_retx_thres_opts::t8; + auto& dl_am = out.rlc_cfg.am().dl_am_rlc; + dl_am.sn_field_len_present = true; + dl_am.sn_field_len.value = asn1::rrc_nr::sn_field_len_am_opts::size12; + dl_am.t_reassembly.value = t_reassembly_opts::ms35; + dl_am.t_status_prohibit.value = asn1::rrc_nr::t_status_prohibit_opts::ms0; // mac-LogicalChannelConfig -- Cond LCH-Setup - srb1.mac_lc_ch_cfg_present = true; - srb1.mac_lc_ch_cfg.ul_specific_params_present = true; - srb1.mac_lc_ch_cfg.ul_specific_params.prio = 1; - srb1.mac_lc_ch_cfg.ul_specific_params.prioritised_bit_rate.value = + out.mac_lc_ch_cfg_present = true; + out.mac_lc_ch_cfg.ul_specific_params_present = true; + out.mac_lc_ch_cfg.ul_specific_params.prio = srb_id == srsran::nr_srb::srb1 ? 1 : 3; + out.mac_lc_ch_cfg.ul_specific_params.prioritised_bit_rate.value = lc_ch_cfg_s::ul_specific_params_s_::prioritised_bit_rate_opts::infinity; - srb1.mac_lc_ch_cfg.ul_specific_params.bucket_size_dur.value = + out.mac_lc_ch_cfg.ul_specific_params.bucket_size_dur.value = lc_ch_cfg_s::ul_specific_params_s_::bucket_size_dur_opts::ms5; - srb1.mac_lc_ch_cfg.ul_specific_params.lc_ch_group_present = true; - srb1.mac_lc_ch_cfg.ul_specific_params.lc_ch_group = 0; - srb1.mac_lc_ch_cfg.ul_specific_params.sched_request_id_present = true; - srb1.mac_lc_ch_cfg.ul_specific_params.sched_request_id = 0; - srb1.mac_lc_ch_cfg.ul_specific_params.lc_ch_sr_mask = false; - srb1.mac_lc_ch_cfg.ul_specific_params.lc_ch_sr_delay_timer_applied = false; + out.mac_lc_ch_cfg.ul_specific_params.lc_ch_group_present = true; + out.mac_lc_ch_cfg.ul_specific_params.lc_ch_group = 0; + out.mac_lc_ch_cfg.ul_specific_params.sched_request_id_present = true; + out.mac_lc_ch_cfg.ul_specific_params.sched_request_id = 0; + out.mac_lc_ch_cfg.ul_specific_params.lc_ch_sr_mask = false; + out.mac_lc_ch_cfg.ul_specific_params.lc_ch_sr_delay_timer_applied = false; } +/// 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) +{ + out.lc_ch_id = 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; + + // MAC logical channel config + out.mac_lc_ch_cfg_present = true; + out.mac_lc_ch_cfg.ul_specific_params_present = true; + out.mac_lc_ch_cfg.ul_specific_params.prio = 11; // TODO + out.mac_lc_ch_cfg.ul_specific_params.prioritised_bit_rate = + lc_ch_cfg_s::ul_specific_params_s_::prioritised_bit_rate_opts::kbps0; + out.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; + out.mac_lc_ch_cfg.ul_specific_params.lc_ch_group_present = true; + out.mac_lc_ch_cfg.ul_specific_params.lc_ch_group = 3; // TODO + out.mac_lc_ch_cfg.ul_specific_params.sched_request_id_present = true; + out.mac_lc_ch_cfg.ul_specific_params.sched_request_id = 0; // TODO + out.mac_lc_ch_cfg.ul_specific_params.lc_ch_sr_mask = false; + out.mac_lc_ch_cfg.ul_specific_params.lc_ch_sr_delay_timer_applied = false; + // TODO: add LC config to MAC +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// Fill MasterCellConfig with gNB config int fill_master_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::cell_group_cfg_s& out) { out.cell_group_id = 0; out.rlc_bearer_to_add_mod_list_present = true; out.rlc_bearer_to_add_mod_list.resize(1); - fill_srb1(cfg, out.rlc_bearer_to_add_mod_list[0]); + fill_srb(cfg, srsran::nr_srb::srb1, out.rlc_bearer_to_add_mod_list[0]); // mac-CellGroupConfig -- Need M out.mac_cell_group_cfg_present = true; @@ -1128,4 +1167,29 @@ int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::s return SRSRAN_SUCCESS; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void fill_cellgroup_with_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, + const asn1::rrc_nr::radio_bearer_cfg_s& bearers, + asn1::rrc_nr::cell_group_cfg_s& out) +{ + // Add SRBs + for (const srb_to_add_mod_s& srb : bearers.srb_to_add_mod_list) { + out.rlc_bearer_to_add_mod_list.push_back({}); + fill_srb(cfg, (srsran::nr_srb)srb.srb_id, out.rlc_bearer_to_add_mod_list.back()); + } + // 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({}); + fill_srb(cfg, (srsran::nr_srb)drb.drb_id, out.rlc_bearer_to_add_mod_list.back()); + } + out.rlc_bearer_to_add_mod_list_present = out.rlc_bearer_to_add_mod_list.size() > 0; + + // Release DRBs + for (uint8_t drb_id : bearers.drb_to_release_list) { + out.rlc_bearer_to_release_list.push_back(drb_id); + } + out.rlc_bearer_to_release_list_present = out.rlc_bearer_to_release_list.size() > 0; +} + } // namespace srsenb diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index 4778bdbaa..f7c03e1c3 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -943,6 +943,8 @@ void rrc_nr::ue::handle_security_mode_complete(const asn1::rrc_nr::security_mode { // TODO: handle SecurityModeComplete + // Note: Skip UE capabilities + send_rrc_reconfiguration(); } @@ -1016,4 +1018,4 @@ void rrc_nr::ue::log_rrc_container(const direction_t dir, parent->log_rrc_message(srsran::to_c_str(strbuf), Tx, pdu, msg, msg_type); } -} // namespace srsenb \ No newline at end of file +} // namespace srsenb From c75a161f03d26bf2ac52a72d7bcd366bea52239b Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 18:28:03 +0000 Subject: [PATCH 75/77] nr,gnb,rrc: implement DL/UL Information Transfer procedure in RRC --- srsgnb/hdr/stack/rrc/rrc_nr_ue.h | 6 ++++++ srsgnb/src/stack/rrc/rrc_nr.cc | 19 ++++++++++++++++++- srsgnb/src/stack/rrc/rrc_nr_ue.cc | 29 +++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h index 1b3a588d2..57dfd8400 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h @@ -62,9 +62,15 @@ public: /* TS 38.331 - 5.3.5 RRC reconfiguration */ void handle_rrc_reconfiguration_complete(const asn1::rrc_nr::rrc_recfg_complete_s& msg); + /* TS 38.331 - 5.7.1 DL information transfer */ + void send_dl_information_transfer(srsran::unique_byte_buffer_t sdu); + /* TS 38.331 - 5.7.2 UL information transfer */ 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); + private: void send_dl_ccch(const asn1::rrc_nr::dl_ccch_msg_s& dl_ccch_msg); void send_dl_dcch(srsran::nr_srb srb, const asn1::rrc_nr::dl_dcch_msg_s& dl_dcch_msg); diff --git a/srsgnb/src/stack/rrc/rrc_nr.cc b/srsgnb/src/stack/rrc/rrc_nr.cc index 9fc2bf6ad..58f4c0b5e 100644 --- a/srsgnb/src/stack/rrc/rrc_nr.cc +++ b/srsgnb/src/stack/rrc/rrc_nr.cc @@ -558,6 +558,12 @@ int rrc_nr::start_security_mode_procedure(uint16_t rnti) } int rrc_nr::establish_rrc_bearer(uint16_t rnti, uint16_t pdu_session_id, srsran::const_byte_span nas_pdu, uint32_t lcid) { + 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); return SRSRAN_SUCCESS; } @@ -571,7 +577,18 @@ int rrc_nr::allocate_lcid(uint16_t rnti) return SRSRAN_SUCCESS; } -void rrc_nr::write_dl_info(uint16_t rnti, srsran::unique_byte_buffer_t sdu) {} +void rrc_nr::write_dl_info(uint16_t rnti, srsran::unique_byte_buffer_t sdu) +{ + if (not users.contains(rnti)) { + logger.error("Received DL information transfer for inexistent rnti=0x%x", rnti); + return; + } + if (sdu == nullptr or sdu->size() == 0) { + logger.error("Received empty DL information transfer for rnti=0x%x", rnti); + return; + } + users[rnti]->send_dl_information_transfer(std::move(sdu)); +} /******************************************************************************* Interface for EUTRA RRC diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index f7c03e1c3..349425c89 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -891,10 +891,15 @@ void rrc_nr::ue::send_rrc_setup() srb_to_add_mod_s& srb1 = setup_ies.radio_bearer_cfg.srb_to_add_mod_list[0]; srb1.srb_id = 1; + asn1::rrc_nr::cell_group_cfg_s master_cell_group = *parent->cell_ctxt->master_cell_group; + + // Derive master cell group config bearers + fill_cellgroup_with_radio_bearer_cfg(parent->cfg, setup_ies.radio_bearer_cfg, master_cell_group); + { srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); asn1::bit_ref bref{pdu->data(), pdu->get_tailroom()}; - if (parent->cell_ctxt->master_cell_group->pack(bref) != SRSRAN_SUCCESS) { + if (master_cell_group.pack(bref) != SRSRAN_SUCCESS) { logger.error("Failed to pack master cell group"); send_rrc_reject(max_wait_time_secs); return; @@ -917,11 +922,11 @@ void rrc_nr::ue::send_rrc_setup() void rrc_nr::ue::handle_rrc_setup_complete(const asn1::rrc_nr::rrc_setup_complete_s& msg) { // TODO: handle RRCSetupComplete + + // Create UE context in NGAP using ngap_cause_t = asn1::ngap_nr::rrcestablishment_cause_opts::options; auto ngap_cause = (ngap_cause_t)ctxt.connection_cause.value; // NGAP and RRC causes seem to have a 1-1 mapping parent->ngap->initial_ue(rnti, uecfg.carriers[0].cc, ngap_cause, {}, ctxt.setup_ue_id); - - send_security_mode_command(); } /// TS 38.331, SecurityModeCommand @@ -967,11 +972,27 @@ void rrc_nr::ue::handle_rrc_reconfiguration_complete(const asn1::rrc_nr::rrc_rec // TODO: handle RRCReconfComplete } +void rrc_nr::ue::send_dl_information_transfer(srsran::unique_byte_buffer_t sdu) +{ + dl_dcch_msg_s dl_dcch_msg; + dl_dcch_msg.msg.set_c1().set_dl_info_transfer().rrc_transaction_id = (uint8_t)((transaction_id++) % 4); + dl_info_transfer_ies_s& ies = dl_dcch_msg.msg.c1().dl_info_transfer().crit_exts.set_dl_info_transfer(); + + ies.ded_nas_msg_present = true; + ies.ded_nas_msg.resize(sdu->N_bytes); + memcpy(ies.ded_nas_msg.data(), sdu->data(), ies.ded_nas_msg.size()); + + send_dl_dcch(srsran::nr_srb::srb1, dl_dcch_msg); +} + void rrc_nr::ue::handle_ul_information_transfer(const asn1::rrc_nr::ul_info_transfer_s& msg) { - // TODO: handle UL information transfer + // Forward dedicatedNAS-Message to NGAP + 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) {} + bool rrc_nr::ue::init_pucch() { // TODO: Allocate PUCCH resources From a58420a5304d68cc82cbc501680cc37957e29f38 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 18:36:15 +0000 Subject: [PATCH 76/77] nr,gnb,rrc: implement basic NR RRC bearer addition and reconf message sending --- lib/include/srsran/asn1/rrc_nr_utils.h | 15 ++++++ lib/src/asn1/rrc_nr_utils.cc | 26 ++++++++++ srsgnb/hdr/stack/rrc/cell_asn1_config.h | 9 ++++ srsgnb/hdr/stack/rrc/rrc_nr_ue.h | 2 +- srsgnb/src/stack/rrc/cell_asn1_config.cc | 23 +++++++++ srsgnb/src/stack/rrc/rrc_nr_ue.cc | 65 ++++++++++++++++++++---- 6 files changed, 128 insertions(+), 12 deletions(-) diff --git a/lib/include/srsran/asn1/rrc_nr_utils.h b/lib/include/srsran/asn1/rrc_nr_utils.h index 60c38cfa5..6114faa41 100644 --- a/lib/include/srsran/asn1/rrc_nr_utils.h +++ b/lib/include/srsran/asn1/rrc_nr_utils.h @@ -60,6 +60,9 @@ struct pdcch_cfg_common_s; struct pdcch_cfg_s; struct mib_s; +struct srb_to_add_mod_s; +struct drb_to_add_mod_s; + } // namespace rrc_nr } // namespace asn1 @@ -144,4 +147,16 @@ pdcp_config_t make_drb_pdcp_config_t(const uint8_t bearer_id, bool is_ue, const } // namespace srsran +/************************ + * ASN1 RRC extensions + ***********************/ +namespace asn1 { + +namespace rrc_nr { +bool operator==(const srb_to_add_mod_s& lhs, const srb_to_add_mod_s& rhs); +bool operator==(const drb_to_add_mod_s& lhs, const drb_to_add_mod_s& rhs); +} // namespace rrc_nr + +} // namespace asn1 + #endif // SRSRAN_RRC_NR_UTILS_H diff --git a/lib/src/asn1/rrc_nr_utils.cc b/lib/src/asn1/rrc_nr_utils.cc index b44ecfc4e..f345d84db 100644 --- a/lib/src/asn1/rrc_nr_utils.cc +++ b/lib/src/asn1/rrc_nr_utils.cc @@ -1624,3 +1624,29 @@ ASN1_OBJ_ID_DEFINE(asn1::rrc_nr::report_cfg_to_add_mod_s, report_cfg_id); ASN1_OBJ_ID_DEFINE(asn1::rrc_nr::meas_id_to_add_mod_s, meas_id); } // namespace srsran + +namespace asn1 { + +namespace rrc_nr { + +bool operator==(const srb_to_add_mod_s& lhs, const srb_to_add_mod_s& rhs) +{ + if (lhs.srb_id != rhs.srb_id or lhs.pdcp_cfg_present != rhs.pdcp_cfg_present) { + return false; + } + // TODO: check remaining fields + return true; +} +bool operator==(const drb_to_add_mod_s& lhs, const drb_to_add_mod_s& rhs) +{ + if (lhs.drb_id != rhs.drb_id or lhs.pdcp_cfg_present != rhs.pdcp_cfg_present or + lhs.cn_assoc_present != rhs.cn_assoc_present) { + return false; + } + // TODO: check remaining fields + return true; +} + +} // namespace rrc_nr + +} // namespace asn1 diff --git a/srsgnb/hdr/stack/rrc/cell_asn1_config.h b/srsgnb/hdr/stack/rrc/cell_asn1_config.h index 5809f704f..e94046d03 100644 --- a/srsgnb/hdr/stack/rrc/cell_asn1_config.h +++ b/srsgnb/hdr/stack/rrc/cell_asn1_config.h @@ -30,6 +30,15 @@ int fill_master_cell_cfg_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1 int fill_mib_from_enb_cfg(const rrc_cell_cfg_nr_t& cell_cfg, asn1::rrc_nr::mib_s& mib); int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::sib1_s& sib1); +/** + * Based on the previous and new radio bearer config, generate ASN1 diff + * @return if a change was detected + */ +bool compute_diff_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, + const asn1::rrc_nr::radio_bearer_cfg_s& prev_bearers, + const asn1::rrc_nr::radio_bearer_cfg_s& next_bearers, + asn1::rrc_nr::radio_bearer_cfg_s& diff); + /// Apply radioBearerConfig updates to CellGroupConfig void fill_cellgroup_with_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, const asn1::rrc_nr::radio_bearer_cfg_s& bearers, diff --git a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h index 57dfd8400..36e108679 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr_ue.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr_ue.h @@ -149,7 +149,7 @@ private: // RRC configs for UEs asn1::rrc_nr::cell_group_cfg_s cell_group_cfg; - asn1::rrc_nr::radio_bearer_cfg_s radio_bearer_cfg; + asn1::rrc_nr::radio_bearer_cfg_s radio_bearer_cfg, next_radio_bearer_cfg; // MAC controller sched_nr_interface::ue_cfg_t uecfg{}; diff --git a/srsgnb/src/stack/rrc/cell_asn1_config.cc b/srsgnb/src/stack/rrc/cell_asn1_config.cc index bd2cfec42..4838a3680 100644 --- a/srsgnb/src/stack/rrc/cell_asn1_config.cc +++ b/srsgnb/src/stack/rrc/cell_asn1_config.cc @@ -11,6 +11,7 @@ */ #include "srsgnb/hdr/stack/rrc/cell_asn1_config.h" +#include "srsran/asn1/obj_id_cmp_utils.h" #include "srsran/asn1/rrc_nr_utils.h" #include "srsran/common/band_helper.h" #include @@ -1169,6 +1170,28 @@ int fill_sib1_from_enb_cfg(const rrc_nr_cfg_t& cfg, uint32_t cc, asn1::rrc_nr::s //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool compute_diff_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, + const radio_bearer_cfg_s& prev_bearers, + const radio_bearer_cfg_s& next_bearers, + radio_bearer_cfg_s& diff) +{ + // Compute SRB differences + std::vector srbs_to_rem; + srsran::compute_cfg_diff( + prev_bearers.srb_to_add_mod_list, next_bearers.srb_to_add_mod_list, diff.srb_to_add_mod_list, srbs_to_rem); + diff.srb_to_add_mod_list_present = diff.srb_to_add_mod_list.size() > 0; + + // Compute DRB differences + srsran::compute_cfg_diff(prev_bearers.drb_to_add_mod_list, + next_bearers.drb_to_add_mod_list, + diff.drb_to_add_mod_list, + diff.drb_to_release_list); + diff.drb_to_add_mod_list_present = diff.drb_to_add_mod_list.size() > 0; + diff.drb_to_release_list_present = diff.drb_to_release_list.size() > 0; + + return diff.srb_to_add_mod_list_present or diff.drb_to_release_list_present or diff.drb_to_add_mod_list_present; +} + void fill_cellgroup_with_radio_bearer_cfg(const rrc_nr_cfg_t& cfg, const asn1::rrc_nr::radio_bearer_cfg_s& bearers, asn1::rrc_nr::cell_group_cfg_s& out) diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index 349425c89..6a5f743fe 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -879,23 +879,28 @@ void rrc_nr::ue::send_rrc_setup() { const uint8_t max_wait_time_secs = 16; + // Add SRB1 to UE context + // Note: See 5.3.5.6.3 - SRB addition/modification + next_radio_bearer_cfg.srb_to_add_mod_list_present = true; + next_radio_bearer_cfg.srb_to_add_mod_list.resize(1); + srb_to_add_mod_s& srb1 = next_radio_bearer_cfg.srb_to_add_mod_list[0]; + srb1.srb_id = 1; + + // Generate RRC setup message + dl_ccch_msg_s msg; rrc_setup_s& setup = msg.msg.set_c1().set_rrc_setup(); setup.rrc_transaction_id = (uint8_t)((transaction_id++) % 4); rrc_setup_ies_s& setup_ies = setup.crit_exts.set_rrc_setup(); // Fill RRC Setup - // Note: See 5.3.5.6.3 - SRB addition/modification - setup_ies.radio_bearer_cfg.srb_to_add_mod_list_present = true; - setup_ies.radio_bearer_cfg.srb_to_add_mod_list.resize(1); - srb_to_add_mod_s& srb1 = setup_ies.radio_bearer_cfg.srb_to_add_mod_list[0]; - srb1.srb_id = 1; + // - Setup SRB1 + compute_diff_radio_bearer_cfg(parent->cfg, radio_bearer_cfg, next_radio_bearer_cfg, setup_ies.radio_bearer_cfg); + // - Setup masterCellGroup asn1::rrc_nr::cell_group_cfg_s master_cell_group = *parent->cell_ctxt->master_cell_group; - - // Derive master cell group config bearers + // - Derive master cell group config bearers fill_cellgroup_with_radio_bearer_cfg(parent->cfg, setup_ies.radio_bearer_cfg, master_cell_group); - { srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer(); asn1::bit_ref bref{pdu->data(), pdu->get_tailroom()}; @@ -921,7 +926,8 @@ void rrc_nr::ue::send_rrc_setup() /// TS 38.331, RRCSetupComplete void rrc_nr::ue::handle_rrc_setup_complete(const asn1::rrc_nr::rrc_setup_complete_s& msg) { - // TODO: handle RRCSetupComplete + // Update current radio bearer cfg + radio_bearer_cfg = next_radio_bearer_cfg; // Create UE context in NGAP using ngap_cause_t = asn1::ngap_nr::rrcestablishment_cause_opts::options; @@ -959,6 +965,10 @@ void rrc_nr::ue::send_rrc_reconfiguration() dl_dcch_msg.msg.set_c1().set_rrc_recfg().rrc_transaction_id = (uint8_t)((transaction_id++) % 4); rrc_recfg_ies_s& ies = dl_dcch_msg.msg.c1().rrc_recfg().crit_exts.set_rrc_recfg(); + // Add new SRBs/DRBs + ies.radio_bearer_cfg_present = + compute_diff_radio_bearer_cfg(parent->cfg, radio_bearer_cfg, next_radio_bearer_cfg, ies.radio_bearer_cfg); + ies.non_crit_ext_present = true; ies.non_crit_ext.master_cell_group_present = false; // TODO @@ -969,7 +979,7 @@ void rrc_nr::ue::send_rrc_reconfiguration() void rrc_nr::ue::handle_rrc_reconfiguration_complete(const asn1::rrc_nr::rrc_recfg_complete_s& msg) { - // TODO: handle RRCReconfComplete + radio_bearer_cfg = next_radio_bearer_cfg; } void rrc_nr::ue::send_dl_information_transfer(srsran::unique_byte_buffer_t sdu) @@ -991,7 +1001,40 @@ 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) +{ + // Add SRB2, if not yet added + if (radio_bearer_cfg.srb_to_add_mod_list.size() <= 1) { + next_radio_bearer_cfg.srb_to_add_mod_list_present = true; + next_radio_bearer_cfg.srb_to_add_mod_list.resize(1); + next_radio_bearer_cfg.srb_to_add_mod_list[0].srb_id = 2; + } + + drb_to_add_mod_s drb; + drb.cn_assoc_present = true; + drb.cn_assoc.set_sdap_cfg().pdu_session = 1; + drb.cn_assoc.sdap_cfg().sdap_hdr_dl.value = asn1::rrc_nr::sdap_cfg_s::sdap_hdr_dl_opts::absent; + drb.cn_assoc.sdap_cfg().sdap_hdr_ul.value = asn1::rrc_nr::sdap_cfg_s::sdap_hdr_ul_opts::absent; + drb.cn_assoc.sdap_cfg().default_drb = true; + drb.cn_assoc.sdap_cfg().mapped_qos_flows_to_add_present = true; + 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.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; + + next_radio_bearer_cfg.drb_to_add_mod_list_present = true; + next_radio_bearer_cfg.drb_to_add_mod_list.push_back(drb); +} bool rrc_nr::ue::init_pucch() { From 91f47de3ccd1cb1883a0c2097d5875a6c384ed6d Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 16 Nov 2021 18:38:11 +0000 Subject: [PATCH 77/77] nr,gnb,rrc: forward nas pdu from rrc setup complete to ngap for the initial ue message --- srsgnb/src/stack/rrc/rrc_nr_ue.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/srsgnb/src/stack/rrc/rrc_nr_ue.cc b/srsgnb/src/stack/rrc/rrc_nr_ue.cc index 6a5f743fe..e60734d4b 100644 --- a/srsgnb/src/stack/rrc/rrc_nr_ue.cc +++ b/srsgnb/src/stack/rrc/rrc_nr_ue.cc @@ -932,7 +932,8 @@ void rrc_nr::ue::handle_rrc_setup_complete(const asn1::rrc_nr::rrc_setup_complet // Create UE context in NGAP using ngap_cause_t = asn1::ngap_nr::rrcestablishment_cause_opts::options; auto ngap_cause = (ngap_cause_t)ctxt.connection_cause.value; // NGAP and RRC causes seem to have a 1-1 mapping - parent->ngap->initial_ue(rnti, uecfg.carriers[0].cc, ngap_cause, {}, ctxt.setup_ue_id); + parent->ngap->initial_ue( + rnti, uecfg.carriers[0].cc, ngap_cause, msg.crit_exts.rrc_setup_complete().ded_nas_msg, ctxt.setup_ue_id); } /// TS 38.331, SecurityModeCommand