From ad03a147d3887034a27033e3f7974be517912dc3 Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 10 May 2021 14:02:11 +0100 Subject: [PATCH 01/13] avoid logging error message when UE sends CRNTI MAC CE for old rnti whose context has already been erased --- srsenb/hdr/stack/mac/sched.h | 2 +- srsenb/src/stack/mac/sched.cc | 15 +++++++++------ srsenb/src/stack/mac/ue.cc | 6 +++++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/srsenb/hdr/stack/mac/sched.h b/srsenb/hdr/stack/mac/sched.h index dc26d9015..e89b47acd 100644 --- a/srsenb/hdr/stack/mac/sched.h +++ b/srsenb/hdr/stack/mac/sched.h @@ -93,7 +93,7 @@ protected: bool is_generated(srsran::tti_point, uint32_t enb_cc_idx) const; // Helper methods template - int ue_db_access_locked(uint16_t rnti, Func&& f, const char* func_name = nullptr); + int ue_db_access_locked(uint16_t rnti, Func&& f, const char* func_name = nullptr, bool log_fail = true); // args rrc_interface_mac* rrc = nullptr; diff --git a/srsenb/src/stack/mac/sched.cc b/srsenb/src/stack/mac/sched.cc index 55fad203d..f3616ff45 100644 --- a/srsenb/src/stack/mac/sched.cc +++ b/srsenb/src/stack/mac/sched.cc @@ -134,7 +134,8 @@ int sched::ue_rem(uint16_t rnti) bool sched::ue_exists(uint16_t rnti) { - return ue_db_access_locked(rnti, [](sched_ue& ue) {}) >= 0; + return ue_db_access_locked( + rnti, [](sched_ue& ue) {}, nullptr, false) >= 0; } void sched::phy_config_enabled(uint16_t rnti, bool enabled) @@ -360,17 +361,19 @@ bool sched::is_generated(srsran::tti_point tti_rx, uint32_t enb_cc_idx) const // 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) +int sched::ue_db_access_locked(uint16_t rnti, Func&& f, const char* func_name, bool log_fail) { std::lock_guard lock(sched_mutex); auto it = ue_db.find(rnti); if (it != ue_db.end()) { f(*it->second); } else { - if (func_name != nullptr) { - Error("User rnti=0x%x not found. Failed to call %s.", rnti, func_name); - } else { - Error("User rnti=0x%x not found.", rnti); + if (log_fail) { + if (func_name != nullptr) { + Error("SCHED: User rnti=0x%x not found. Failed to call %s.", rnti, func_name); + } else { + Error("SCHED: User rnti=0x%x not found.", rnti); + } } return SRSRAN_ERROR; } diff --git a/srsenb/src/stack/mac/ue.cc b/srsenb/src/stack/mac/ue.cc index 1da5f75f2..d8d82b82e 100644 --- a/srsenb/src/stack/mac/ue.cc +++ b/srsenb/src/stack/mac/ue.cc @@ -477,7 +477,11 @@ bool ue::process_ce(srsran::sch_subh* subh) rrc->upd_user(rnti, old_rnti); rnti = old_rnti; } else { - logger.error("Updating user C-RNTI: rnti=0x%x already released", old_rnti); + logger.warning("Updating user C-RNTI: rnti=0x%x already released.", old_rnti); + // Disable scheduling for all bearers. The new rnti will be removed on msg3 timer expiry in the RRC + for (uint32_t lcid = 0; lcid < sched_interface::MAX_LC; ++lcid) { + sched->bearer_ue_rem(rnti, lcid); + } } break; case srsran::ul_sch_lcid::TRUNC_BSR: From b94b0e77b3d3ead78c1a71f02d0a37ec4c5a33de Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 10 May 2021 14:16:35 +0100 Subject: [PATCH 02/13] Several fixes in UL scheduler - Limit minimum UL grant size to accommodate both BSR and RLC headers - Limit UL CQI update to PUSCH SNR reports - Avoid TPC commands when target PUSCH and PUCCH SNR are not specified --- srsenb/hdr/stack/mac/sched_ue.h | 12 ++++++++---- srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h | 14 +++++--------- srsenb/src/stack/mac/sched_grid.cc | 6 +++--- srsenb/src/stack/mac/sched_ue.cc | 11 +++++++++-- .../src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc | 14 ++++++++++++-- srsenb/test/mac/sched_tpc_test.cc | 9 ++++----- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/srsenb/hdr/stack/mac/sched_ue.h b/srsenb/hdr/stack/mac/sched_ue.h index 2b90efd92..2d9b0f9c4 100644 --- a/srsenb/hdr/stack/mac/sched_ue.h +++ b/srsenb/hdr/stack/mac/sched_ue.h @@ -90,10 +90,12 @@ public: uint32_t get_required_prb_ul(uint32_t enb_cc_idx, uint32_t req_bytes); - rbg_interval get_required_dl_rbgs(uint32_t enb_cc_idx); - srsran::interval get_requested_dl_bytes(uint32_t enb_cc_idx); - uint32_t get_pending_dl_rlc_data() const; - uint32_t get_expected_dl_bitrate(uint32_t enb_cc_idx, int nof_rbgs = -1) const; + /// Get total pending bytes to be transmitted in DL. + /// The amount of CEs to transmit depends on whether enb_cc_idx is UE's PCell + uint32_t get_pending_dl_bytes(uint32_t enb_cc_idx); + rbg_interval get_required_dl_rbgs(uint32_t enb_cc_idx); + uint32_t get_pending_dl_rlc_data() const; + uint32_t get_expected_dl_bitrate(uint32_t enb_cc_idx, int nof_rbgs = -1) const; uint32_t get_pending_ul_data_total(tti_point tti_tx_ul, int this_enb_cc_idx); uint32_t get_pending_ul_new_data(tti_point tti_tx_ul, int this_enb_cc_idx); @@ -147,6 +149,8 @@ public: bool pusch_enabled(tti_point tti_rx, uint32_t enb_cc_idx, bool needs_pdcch) const; private: + srsran::interval get_requested_dl_bytes(uint32_t enb_cc_idx); + bool is_sr_triggered(); tbs_info allocate_new_dl_mac_pdu(sched_interface::dl_sched_data_t* data, diff --git a/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h b/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h index ad6d0cb40..e191ffefb 100644 --- a/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h +++ b/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h @@ -119,14 +119,14 @@ public: * @remark See TS 36.213 Section 5.1.1 * @return accumulated TPC value {-1, 0, 1, 3} */ - uint8_t encode_pusch_tpc() { return enconde_tpc(PUSCH_CODE); } + uint8_t encode_pusch_tpc() { return encode_tpc(PUSCH_CODE); } /** * Called during DCI format1/2A/A encoding to set PUCCH TPC command * @remark See TS 36.213 Section 5.1.2 * @return accumulated TPC value {-1, 0, 1, 3} */ - uint8_t encode_pucch_tpc() { return enconde_tpc(PUCCH_CODE); } + uint8_t encode_pucch_tpc() { return encode_tpc(PUCCH_CODE); } uint32_t max_ul_prbs() const { return max_prbs_cached; } @@ -147,18 +147,14 @@ private: return 1; } } - uint8_t enconde_tpc(uint32_t cc) + uint8_t encode_tpc(uint32_t cc) { float target_snr_dB = cc == PUSCH_CODE ? target_pusch_snr_dB : target_pucch_snr_dB; auto& ch_snr = snr_estim_list[cc]; assert(ch_snr.pending_delta == 0); // ensure called once per {cc,tti} if (target_snr_dB < 0) { - // undefined target SINR case. Increase Tx power once per PHR, considering the number of allocable PRBs remains - // unchanged - if (not ch_snr.phr_flag) { - ch_snr.pending_delta = (max_prbs_cached == nof_prb) ? 1 : (last_phr < 0 ? -1 : 0); - ch_snr.phr_flag = true; - } + // undefined target sinr case. + ch_snr.pending_delta = 0; } else { // target SINR is finite and there is power headroom float diff = target_snr_dB - ch_snr.snr_avg.value(); diff --git a/srsenb/src/stack/mac/sched_grid.cc b/srsenb/src/stack/mac/sched_grid.cc index d7dff1c74..94c9d200a 100644 --- a/srsenb/src/stack/mac/sched_grid.cc +++ b/srsenb/src/stack/mac/sched_grid.cc @@ -671,7 +671,7 @@ void sf_sched::set_dl_data_sched_result(const sf_cch_allocator::alloc_result_t& continue; } sched_ue* user = ue_it->second.get(); - uint32_t data_before = user->get_requested_dl_bytes(cc_cfg->enb_cc_idx).stop(); + uint32_t data_before = user->get_pending_dl_bytes(cc_cfg->enb_cc_idx); const dl_harq_proc& dl_harq = user->get_dl_harq(data_alloc.pid, cc_cfg->enb_cc_idx); bool is_newtx = dl_harq.is_empty(); @@ -687,7 +687,7 @@ void sf_sched::set_dl_data_sched_result(const sf_cch_allocator::alloc_result_t& data_alloc.pid, data_alloc.user_mask, tbs, - user->get_requested_dl_bytes(cc_cfg->enb_cc_idx).stop()); + user->get_pending_dl_bytes(cc_cfg->enb_cc_idx)); logger.warning("%s", srsran::to_c_str(str_buffer)); continue; } @@ -707,7 +707,7 @@ void sf_sched::set_dl_data_sched_result(const sf_cch_allocator::alloc_result_t& dl_harq.nof_retx(0) + dl_harq.nof_retx(1), tbs, data_before, - user->get_requested_dl_bytes(cc_cfg->enb_cc_idx).stop(), + user->get_pending_dl_bytes(cc_cfg->enb_cc_idx), get_tti_tx_dl()); 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 349a66ea3..193f75249 100644 --- a/srsenb/src/stack/mac/sched_ue.cc +++ b/srsenb/src/stack/mac/sched_ue.cc @@ -311,8 +311,10 @@ void sched_ue::set_ul_snr(tti_point tti_rx, uint32_t enb_cc_idx, float snr, uint { if (cells[enb_cc_idx].cc_state() != cc_st::idle) { cells[enb_cc_idx].tpc_fsm.set_snr(snr, ul_ch_code); - cells[enb_cc_idx].ul_cqi = srsran_cqi_from_snr(snr); - cells[enb_cc_idx].ul_cqi_tti_rx = tti_rx; + if (ul_ch_code == tpc::PUSCH_CODE) { + cells[enb_cc_idx].ul_cqi = srsran_cqi_from_snr(snr); + cells[enb_cc_idx].ul_cqi_tti_rx = tti_rx; + } } else { logger.warning("Received SNR info for invalid cell index %d", enb_cc_idx); } @@ -776,6 +778,11 @@ rbg_interval sched_ue::get_required_dl_rbgs(uint32_t enb_cc_idx) return {min_pending_rbg, max_pending_rbg}; } +uint32_t sched_ue::get_pending_dl_bytes(uint32_t enb_cc_idx) +{ + return get_requested_dl_bytes(enb_cc_idx).stop(); +} + /** * Returns the range (min,max) of possible MAC PDU sizes. * - the lower boundary value is set based on the following conditions: diff --git a/srsenb/src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc b/srsenb/src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc index 3da6d78d1..0a0b1415a 100644 --- a/srsenb/src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc +++ b/srsenb/src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc @@ -307,6 +307,7 @@ int get_required_prb_dl(const sched_ue_cell& cell, uint32_t get_required_prb_ul(const sched_ue_cell& cell, uint32_t req_bytes) { + const static int MIN_ALLOC_BYTES = 10; if (req_bytes == 0) { return 0; } @@ -317,11 +318,20 @@ uint32_t get_required_prb_ul(const sched_ue_cell& cell, uint32_t req_bytes) }; // find nof prbs that lead to a tbs just above req_bytes - int target_tbs = static_cast(req_bytes) + 4; + int target_tbs = std::max(static_cast(req_bytes) + 4, MIN_ALLOC_BYTES); uint32_t max_prbs = std::min(cell.tpc_fsm.max_ul_prbs(), cell.cell_cfg->nof_prb()); std::tuple ret = false_position_method(1U, max_prbs, target_tbs, compute_tbs_approx, [](int y) { return y == SRSRAN_ERROR; }); - uint32_t req_prbs = std::get<2>(ret); + uint32_t req_prbs = std::get<2>(ret); + uint32_t final_tbs = std::get<3>(ret); + while (final_tbs < MIN_ALLOC_BYTES and req_prbs < cell.cell_cfg->nof_prb()) { + // Note: If PHR<0 is limiting the max nof PRBs per UL grant, the UL grant may become too small to fit any + // data other than headers + BSR. Besides, forcing unnecessary segmentation, it may additionally + // forbid the UE from fitting small RRC messages (e.g. RRCReconfComplete) in the UL grants. + // To avoid TBS<10, we force an increase the nof required PRBs. + req_prbs++; + final_tbs = compute_tbs_approx(req_prbs); + } while (!srsran_dft_precoding_valid_prb(req_prbs) && req_prbs < cell.cell_cfg->nof_prb()) { req_prbs++; } diff --git a/srsenb/test/mac/sched_tpc_test.cc b/srsenb/test/mac/sched_tpc_test.cc index 14712e3f2..1ce863351 100644 --- a/srsenb/test/mac/sched_tpc_test.cc +++ b/srsenb/test/mac/sched_tpc_test.cc @@ -109,7 +109,7 @@ int test_undefined_target_snr() TESTASSERT(sum_pusch == 0); TESTASSERT(sum_pucch == 0); - // TEST: If the PHR allows full utilization of available PRBs, the TPC slightly increments UL Tx power + // TEST: Check that high PHR allows full utilization of available PRBs, TPC remains at zero (no target SINR) int phr = 30; tpcfsm.set_phr(phr); TESTASSERT(tpcfsm.max_ul_prbs() == 50); @@ -120,8 +120,7 @@ int test_undefined_target_snr() sum_pusch += decode_tpc(tpcfsm.encode_pusch_tpc()); sum_pucch += decode_tpc(tpcfsm.encode_pucch_tpc()); } - TESTASSERT(sum_pusch > 0 and sum_pusch <= 3); - TESTASSERT(sum_pucch > 0 and sum_pucch <= 3); + TESTASSERT(sum_pusch == 0 and sum_pucch == 0); // TEST: PHR is too low to allow all PRBs to be allocated. This event should not affect TPC commands phr = 5; @@ -144,8 +143,8 @@ int test_undefined_target_snr() sum_pusch += decode_tpc(tpcfsm.encode_pusch_tpc()); sum_pucch += decode_tpc(tpcfsm.encode_pucch_tpc()); } - TESTASSERT(sum_pusch <= 0 and sum_pusch >= -1); - TESTASSERT(sum_pucch <= 0 and sum_pucch >= -1); + TESTASSERT(sum_pusch == 0); + TESTASSERT(sum_pucch == 0); return SRSRAN_SUCCESS; } From 4aa100f7ae989e8498c861678614d389f5af6fdb Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 10 May 2021 14:23:24 +0100 Subject: [PATCH 03/13] GTPU bearer removal fixes - call gtpu rem bearer when erab is removed - fix gtpu bearer removal during handover --- srsenb/src/stack/rrc/rrc_bearer_cfg.cc | 12 +++--------- srsenb/src/stack/upper/gtpu.cc | 18 ++++++++---------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index ff0381d89..f3068bdf8 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -311,6 +311,8 @@ int bearer_cfg_handler::release_erab(uint8_t erab_id) srsran::rem_rrc_obj_id(current_drbs, drb_id); + rem_gtpu_bearer(erab_id); + erabs.erase(it); erab_info_list.erase(erab_id); @@ -319,8 +321,6 @@ int bearer_cfg_handler::release_erab(uint8_t erab_id) void bearer_cfg_handler::release_erabs() { - // TODO: notify GTPU layer for each ERAB - erabs.clear(); while (not erabs.empty()) { release_erab(erabs.begin()->first); } @@ -387,13 +387,7 @@ srsran::expected bearer_cfg_handler::add_gtpu_bearer(uint32_t void bearer_cfg_handler::rem_gtpu_bearer(uint32_t erab_id) { - auto it = erabs.find(erab_id); - if (it != erabs.end()) { - // Map e.g. E-RAB 5 to LCID 3 (==DRB1) - gtpu->rem_bearer(rnti, erab_id - 2); - } else { - logger->error("Removing erab_id=%d to GTPU\n", erab_id); - } + gtpu->rem_bearer(rnti, erab_id - 2); } void bearer_cfg_handler::fill_pending_nas_info(asn1::rrc::rrc_conn_recfg_r8_ies_s* msg) diff --git a/srsenb/src/stack/upper/gtpu.cc b/srsenb/src/stack/upper/gtpu.cc index 91d8d560b..33e6b03e8 100644 --- a/srsenb/src/stack/upper/gtpu.cc +++ b/srsenb/src/stack/upper/gtpu.cc @@ -160,22 +160,20 @@ bool gtpu_tunnel_manager::remove_tunnel(uint32_t teidin) bool gtpu_tunnel_manager::remove_bearer(uint16_t rnti, uint32_t lcid) { - srsran::span to_rem = find_rnti_lcid_tunnels(rnti, lcid); - if (to_rem.empty()) { - return false; - } logger.info("Removing rnti=0x%x,lcid=%d", rnti, lcid); - - for (lcid_tunnel& lcid_tun : to_rem) { - bool ret = tunnels.erase(lcid_tun.teid); + bool removed = false; + for (srsran::span to_rem = find_rnti_lcid_tunnels(rnti, lcid); not to_rem.empty(); + to_rem = find_rnti_lcid_tunnels(rnti, lcid)) { + uint32_t teid = to_rem.front().teid; + bool ret = remove_tunnel(teid); srsran_expect(ret, "Inconsistency detected between internal data structures for rnti=0x%x,lcid=%d," TEID_IN_FMT, rnti, lcid, - lcid_tun.teid); + teid); + removed |= ret; } - ue_teidin_db[rnti].erase(to_rem.begin(), to_rem.end()); - return true; + return removed; } bool gtpu_tunnel_manager::remove_rnti(uint16_t rnti) From 24a7ea9c6ab2d52722bddfffa75b937f61870dfd Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 10 May 2021 14:29:25 +0100 Subject: [PATCH 04/13] Change enb max_nof_ues config parameter to nof_prealloc_ues --- lib/include/srsran/interfaces/enb_mac_interfaces.h | 2 +- srsenb/enb.conf.example | 2 ++ srsenb/src/enb_cfg_parser.cc | 6 +++--- srsenb/src/main.cc | 2 +- srsenb/src/stack/enb_stack_lte.cc | 4 ++-- srsenb/src/stack/mac/mac.cc | 6 +++--- srsenb/test/upper/test_helpers.cc | 12 ++++++------ 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/include/srsran/interfaces/enb_mac_interfaces.h b/lib/include/srsran/interfaces/enb_mac_interfaces.h index 59e6b6d85..c2da852a1 100644 --- a/lib/include/srsran/interfaces/enb_mac_interfaces.h +++ b/lib/include/srsran/interfaces/enb_mac_interfaces.h @@ -31,7 +31,7 @@ struct mac_args_t { uint32_t nof_prb; ///< Needed to dimension MAC softbuffers for all cells sched_interface::sched_args_t sched; int nr_tb_size = -1; - uint32_t max_nof_ues; + uint32_t nof_prealloc_ues; ///< Number of UE resources to pre-allocate at eNB startup uint32_t max_nof_kos; }; diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index d902f6767..19716f130 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -304,6 +304,7 @@ enable = false # tx_amplitude: Transmit amplitude factor (set 0-1 to reduce PAPR) # rrc_inactivity_timer Inactivity timeout used to remove UE context from RRC (in milliseconds). # max_prach_offset_us: Maximum allowed RACH offset (in us) +# nof_prealloc_ues: Number of UE memory resources to preallocate during eNB initialization for faster UE creation (Default 8) # eea_pref_list: Ordered preference list for the selection of encryption algorithm (EEA) (default: EEA0, EEA2, EEA1). # eia_pref_list: Ordered preference list for the selection of integrity algorithm (EIA) (default: EIA2, EIA1, EIA0). # @@ -327,5 +328,6 @@ enable = false #rrc_inactivity_timer = 30000 #max_nof_kos = 100 #max_prach_offset_us = 30 +#nof_prealloc_ues = 8 #eea_pref_list = EEA0, EEA2, EEA1 #eia_pref_list = EIA2, EIA1, EIA0 diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 1f75f64c6..bf1fe4b72 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -914,9 +914,9 @@ int set_derived_args(all_args_t* args_, rrc_cfg_t* rrc_cfg_, phy_cfg_t* phy_cfg_ { // Sanity checks ASSERT_VALID_CFG(not rrc_cfg_->cell_list.empty(), "No cell specified in rr.conf."); - ASSERT_VALID_CFG(args_->stack.mac.max_nof_ues <= SRSENB_MAX_UES and args_->stack.mac.max_nof_ues > 0, - "mac.max_nof_ues=%d must be within [0, %d]", - args_->stack.mac.max_nof_ues, + ASSERT_VALID_CFG(args_->stack.mac.nof_prealloc_ues <= SRSENB_MAX_UES, + "mac.nof_prealloc_ues=%d must be within [0, %d]", + args_->stack.mac.nof_prealloc_ues, SRSENB_MAX_UES); // Check for a forced DL EARFCN or frequency (only valid for a single cell config (Xico's favorite feature)) diff --git a/srsenb/src/main.cc b/srsenb/src/main.cc index f2c77a9b2..d7b841abe 100644 --- a/srsenb/src/main.cc +++ b/srsenb/src/main.cc @@ -223,7 +223,7 @@ void parse_args(all_args_t* args, int argc, char* argv[]) ("expert.print_buffer_state", bpo::value(&args->general.print_buffer_state)->default_value(false), "Prints on the console the buffer state every 10 seconds") ("expert.eea_pref_list", bpo::value(&args->general.eea_pref_list)->default_value("EEA0, EEA2, EEA1"), "Ordered preference list for the selection of encryption algorithm (EEA) (default: EEA0, EEA2, EEA1).") ("expert.eia_pref_list", bpo::value(&args->general.eia_pref_list)->default_value("EIA2, EIA1, EIA0"), "Ordered preference list for the selection of integrity algorithm (EIA) (default: EIA2, EIA1, EIA0).") - ("expert.max_nof_ues", bpo::value(&args->stack.mac.max_nof_ues)->default_value(8), "Maximum number of connected UEs") + ("expert.nof_prealloc_ues", bpo::value(&args->stack.mac.nof_prealloc_ues)->default_value(8), "Number of UE resources to preallocate during eNB initialization") ("expert.max_mac_dl_kos", bpo::value(&args->general.max_mac_dl_kos)->default_value(100), "Maximum number of consecutive KOs in DL before triggering the UE's release") ("expert.max_mac_ul_kos", bpo::value(&args->general.max_mac_ul_kos)->default_value(100), "Maximum number of consecutive KOs in UL before triggering the UE's release") diff --git a/srsenb/src/stack/enb_stack_lte.cc b/srsenb/src/stack/enb_stack_lte.cc index e36f83d8f..5dc28a2fb 100644 --- a/srsenb/src/stack/enb_stack_lte.cc +++ b/srsenb/src/stack/enb_stack_lte.cc @@ -82,9 +82,9 @@ int enb_stack_lte::init(const stack_args_t& args_, const rrc_cfg_t& rrc_cfg_) rrc_cfg = rrc_cfg_; // Init RNTI and bearer memory pools - reserve_rnti_memblocks(args.mac.max_nof_ues); + reserve_rnti_memblocks(args.mac.nof_prealloc_ues); uint32_t min_nof_bearers_per_ue = 4; - reserve_rlc_memblocks(args.mac.max_nof_ues * min_nof_bearers_per_ue); + reserve_rlc_memblocks(args.mac.nof_prealloc_ues * min_nof_bearers_per_ue); // setup logging for each layer mac_logger.set_level(srslog::str_to_basic_level(args.log.mac_level)); diff --git a/srsenb/src/stack/mac/mac.cc b/srsenb/src/stack/mac/mac.cc index 79685d948..c6c449e8e 100644 --- a/srsenb/src/stack/mac/mac.cc +++ b/srsenb/src/stack/mac/mac.cc @@ -93,7 +93,7 @@ bool mac::init(const mac_args_t& args_, }; auto recycle_softbuffers = [](ue_cc_softbuffers& softbuffers) { softbuffers.clear(); }; softbuffer_pool.reset(new srsran::background_obj_pool( - 8, 8, args.max_nof_ues, init_softbuffers, recycle_softbuffers)); + 8, 8, args.nof_prealloc_ues, init_softbuffers, recycle_softbuffers)); // Pre-alloc UE objects for first attaching users prealloc_ue(10); @@ -478,8 +478,8 @@ uint16_t mac::allocate_ue() logger.info("RACH ignored as eNB is being shutdown"); return SRSRAN_INVALID_RNTI; } - if (ue_db.size() >= args.max_nof_ues) { - logger.warning("Maximum number of connected UEs %zd connected to the eNB. Ignoring PRACH", args.max_nof_ues); + if (ue_db.size() >= SRSENB_MAX_UES) { + logger.warning("Maximum number of connected UEs %zd connected to the eNB. Ignoring PRACH", SRSENB_MAX_UES); return SRSRAN_INVALID_RNTI; } auto ret = ue_db.insert(rnti, std::move(ue_ptr)); diff --git a/srsenb/test/upper/test_helpers.cc b/srsenb/test/upper/test_helpers.cc index 3755af047..5b8fcd681 100644 --- a/srsenb/test/upper/test_helpers.cc +++ b/srsenb/test/upper/test_helpers.cc @@ -65,11 +65,11 @@ int parse_default_cfg(rrc_cfg_t* rrc_cfg, srsenb::all_args_t& args) args.enb.n_prb = 50; TESTASSERT(srsran::string_to_mcc("001", &args.stack.s1ap.mcc)); TESTASSERT(srsran::string_to_mnc("01", &args.stack.s1ap.mnc)); - args.enb.transmission_mode = 1; - args.enb.nof_ports = 1; - args.general.eia_pref_list = "EIA2, EIA1, EIA0"; - args.general.eea_pref_list = "EEA0, EEA2, EEA1"; - args.stack.mac.max_nof_ues = 2; + args.enb.transmission_mode = 1; + args.enb.nof_ports = 1; + args.general.eia_pref_list = "EIA2, EIA1, EIA0"; + args.general.eea_pref_list = "EEA0, EEA2, EEA1"; + args.stack.mac.nof_prealloc_ues = 2; args.general.rrc_inactivity_timer = 60000; @@ -193,4 +193,4 @@ bool is_cell_cfg_equal(const meas_cell_cfg_t& cfg, const cells_to_add_mod_s& cel return cfg.pci == cell.pci and cell.cell_individual_offset.to_number() == (int8_t)round(cfg.q_offset); } -} // namespace srsenb \ No newline at end of file +} // namespace srsenb From cdd3932e73ddc290960733f1a548da988051d361 Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 10 May 2021 15:22:02 +0100 Subject: [PATCH 05/13] Fix GTPU RRC reestablishment handling - GTPU bearers rnti is correctly updated during RRC reestablishment - Create rnti_map_t typedef for circular maps of rntis - Failed GTPU rnti creation is now handled - Extend indirect GTPU tunnel timeout to 2000msec - Support EIA0 during S1 Handover --- lib/include/srsran/adt/circular_map.h | 11 +++++++++ srsenb/hdr/common/common_enb.h | 5 ++++ srsenb/hdr/stack/mac/mac.h | 6 ++--- srsenb/hdr/stack/rrc/rrc_bearer_cfg.h | 3 +++ srsenb/hdr/stack/upper/gtpu.h | 4 +-- srsenb/src/stack/rrc/rrc_bearer_cfg.cc | 10 ++++++++ srsenb/src/stack/rrc/rrc_mobility.cc | 4 ++- srsenb/src/stack/rrc/rrc_ue.cc | 17 ++++++++++--- srsenb/src/stack/upper/gtpu.cc | 34 +++++++++++++++++--------- srsenb/test/upper/gtpu_test.cc | 2 +- 10 files changed, 74 insertions(+), 22 deletions(-) diff --git a/lib/include/srsran/adt/circular_map.h b/lib/include/srsran/adt/circular_map.h index e27f80b07..9d9b26aee 100644 --- a/lib/include/srsran/adt/circular_map.h +++ b/lib/include/srsran/adt/circular_map.h @@ -37,9 +37,20 @@ class static_circular_map using obj_t = std::pair; public: + using key_type = K; + using mapped_type = T; + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + class iterator { public: + using iterator_category = std::forward_iterator_tag; + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + iterator() = default; iterator(static_circular_map* map, size_t idx_) : ptr(map), idx(idx_) { diff --git a/srsenb/hdr/common/common_enb.h b/srsenb/hdr/common/common_enb.h index 2a2e78879..e38cec013 100644 --- a/srsenb/hdr/common/common_enb.h +++ b/srsenb/hdr/common/common_enb.h @@ -26,6 +26,7 @@ INCLUDES *******************************************************************************/ +#include "srsran/adt/circular_map.h" #include "srsran/common/common_lte.h" #include @@ -57,6 +58,10 @@ constexpr uint32_t drb_to_lcid(lte_drb drb_id) #define SRSENB_MAX_BUFFER_SIZE_BYTES 12756 #define SRSENB_BUFFER_HEADER_OFFSET 1024 +/// Typedef of circular map container which key corresponding to rnti value and that can be used across layers +template +using rnti_map_t = srsran::static_circular_map; + } // namespace srsenb #endif // SRSENB_COMMON_ENB_H diff --git a/srsenb/hdr/stack/mac/mac.h b/srsenb/hdr/stack/mac/mac.h index 1ab8fc417..2bcc0c449 100644 --- a/srsenb/hdr/stack/mac/mac.h +++ b/srsenb/hdr/stack/mac/mac.h @@ -145,9 +145,9 @@ private: sched_interface::dl_pdu_mch_t mch = {}; /* Map of active UEs */ - srsran::static_circular_map, 64> ue_db; - std::map > ues_to_rem; - uint16_t last_rnti = 70; + rnti_map_t > ue_db; + std::map > ues_to_rem; + uint16_t last_rnti = 70; srsran::static_blocking_queue, 32> ue_pool; ///< Pool of pre-allocated UE objects void prealloc_ue(uint32_t nof_ue); diff --git a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h index b4f78cc99..268fe59db 100644 --- a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h +++ b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h @@ -88,6 +88,9 @@ public: bearer_cfg_handler(uint16_t rnti_, const rrc_cfg_t& cfg_, gtpu_interface_rrc* gtpu_); + /// Called after RRCReestablishmentComplete, to add E-RABs of old rnti + void reestablish_bearers(bearer_cfg_handler&& old_rnti_bearers); + int add_erab(uint8_t erab_id, const asn1::s1ap::erab_level_qos_params_s& qos, const asn1::bounded_bitstring<1, 160, true, true>& addr, diff --git a/srsenb/hdr/stack/upper/gtpu.h b/srsenb/hdr/stack/upper/gtpu.h index b99a76e65..f7f955716 100644 --- a/srsenb/hdr/stack/upper/gtpu.h +++ b/srsenb/hdr/stack/upper/gtpu.h @@ -128,8 +128,8 @@ private: pdcp_interface_gtpu* pdcp = nullptr; srslog::basic_logger& logger; - srsran::static_circular_map ue_teidin_db; - tunnel_list_t tunnels; + rnti_map_t ue_teidin_db; + tunnel_list_t tunnels; }; using gtpu_tunnel_state = gtpu_tunnel_manager::tunnel_state; diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index f3068bdf8..60eff67b8 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -114,6 +114,8 @@ bool security_cfg_handler::set_security_capabilities(const asn1::s1ap::ue_securi case srsran::INTEGRITY_ALGORITHM_ID_EIA0: // Null integrity is not supported logger.info("Skipping EIA0 as RRC integrity algorithm. Null integrity is not supported."); + sec_cfg.integ_algo = srsran::INTEGRITY_ALGORITHM_ID_EIA0; + integ_algo_found = true; break; case srsran::INTEGRITY_ALGORITHM_ID_128_EIA1: // “first bit” – 128-EIA1, @@ -211,6 +213,14 @@ bearer_cfg_handler::bearer_cfg_handler(uint16_t rnti_, const rrc_cfg_t& cfg_, gt rnti(rnti_), cfg(&cfg_), gtpu(gtpu_), logger(&srslog::fetch_basic_logger("RRC")) {} +void bearer_cfg_handler::reestablish_bearers(bearer_cfg_handler&& old_rnti_bearers) +{ + erab_info_list = std::move(old_rnti_bearers.erab_info_list); + erabs = std::move(old_rnti_bearers.erabs); + current_drbs = std::move(old_rnti_bearers.current_drbs); + old_rnti_bearers.current_drbs.clear(); +} + int bearer_cfg_handler::add_erab(uint8_t erab_id, const asn1::s1ap::erab_level_qos_params_s& qos, const asn1::bounded_bitstring<1, 160, true, true>& addr, diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index f0eb2887d..1e52110be 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -96,6 +96,8 @@ uint16_t compute_mac_i(uint16_t crnti, // Compute MAC-I switch (integ_algo) { + case srsran::INTEGRITY_ALGORITHM_ID_EIA0: + return 0; case srsran::INTEGRITY_ALGORITHM_ID_128_EIA1: srsran::security_128_eia1(&k_rrc_int[16], 0xffffffff, // 32-bit all to ones @@ -115,7 +117,7 @@ uint16_t compute_mac_i(uint16_t crnti, mac_key); break; default: - printf("Unsupported integrity algorithm %d.", integ_algo); + srsran::console_stderr("ERROR: Unsupported integrity algorithm %d.\n", integ_algo); } uint16_t short_mac_i = (((uint16_t)mac_key[2] << 8u) | (uint16_t)mac_key[3]); diff --git a/srsenb/src/stack/rrc/rrc_ue.cc b/srsenb/src/stack/rrc/rrc_ue.cc index 3237c09b7..c477c6a06 100644 --- a/srsenb/src/stack/rrc/rrc_ue.cc +++ b/srsenb/src/stack/rrc/rrc_ue.cc @@ -523,10 +523,16 @@ void rrc::ue::handle_rrc_con_reest_req(rrc_conn_reest_request_s* msg) static_cast(rrc_event_type::con_reest_req), static_cast(procedure_result_code::none), rnti); + const rrc_conn_reest_request_r8_ies_s& req_r8 = msg->crit_exts.rrc_conn_reest_request_r8(); + uint16_t old_rnti = req_r8.ue_id.c_rnti.to_number(); + + srsran::console( + "User 0x%x requesting RRC Reestablishment as 0x%x. Cause: %s\n", rnti, old_rnti, req_r8.reest_cause.to_string()); if (not parent->s1ap->is_mme_connected()) { parent->logger.error("MME isn't connected. Sending Connection Reject"); - send_connection_reject(procedure_result_code::error_mme_not_connected); + send_connection_reest_rej(procedure_result_code::error_mme_not_connected); + srsran::console("User 0x%x RRC Reestablishment Request rejected\n", rnti); return; } parent->logger.debug("rnti=0x%x, phyid=0x%x, smac=0x%x, cause=%s", @@ -535,7 +541,6 @@ void rrc::ue::handle_rrc_con_reest_req(rrc_conn_reest_request_s* msg) (uint32_t)msg->crit_exts.rrc_conn_reest_request_r8().ue_id.short_mac_i.to_number(), msg->crit_exts.rrc_conn_reest_request_r8().reest_cause.to_string()); if (is_idle()) { - uint16_t old_rnti = msg->crit_exts.rrc_conn_reest_request_r8().ue_id.c_rnti.to_number(); uint16_t old_pci = msg->crit_exts.rrc_conn_reest_request_r8().ue_id.pci; const enb_cell_common* old_cell = parent->cell_common_list->get_pci(old_pci); auto ue_it = parent->users.find(old_rnti); @@ -590,9 +595,13 @@ void rrc::ue::handle_rrc_con_reest_req(rrc_conn_reest_request_s* msg) } else { parent->logger.error("Received ConnectionReestablishment for rnti=0x%x without context", old_rnti); send_connection_reest_rej(procedure_result_code::error_unknown_rnti); + srsran::console( + "User 0x%x RRC Reestablishment Request rejected. Cause: no rnti=0x%x context available\n", rnti, old_rnti); } } else { parent->logger.error("Received ReestablishmentRequest from an rnti=0x%x not in IDLE", rnti); + send_connection_reest_rej(procedure_result_code::error_unknown_rnti); + srsran::console("ERROR: User 0x%x requesting Reestablishment is not in RRC_IDLE\n", rnti); } } @@ -663,8 +672,8 @@ void rrc::ue::handle_rrc_con_reest_complete(rrc_conn_reest_complete_s* msg, srsr parent->pdcp->enable_integrity(rnti, srb_to_lcid(lte_srb::srb1)); parent->pdcp->enable_encryption(rnti, srb_to_lcid(lte_srb::srb1)); - // Reestablish current DRBs during ConnectionReconfiguration - bearer_list = std::move(parent->users.at(old_reest_rnti)->bearer_list); + // Reestablish E-RABs of old rnti during ConnectionReconfiguration + bearer_list.reestablish_bearers(std::move(parent->users.at(old_reest_rnti)->bearer_list)); // remove old RNTI parent->rem_user_thread(old_reest_rnti); diff --git a/srsenb/src/stack/upper/gtpu.cc b/srsenb/src/stack/upper/gtpu.cc index 33e6b03e8..e2b692005 100644 --- a/srsenb/src/stack/upper/gtpu.cc +++ b/srsenb/src/stack/upper/gtpu.cc @@ -99,7 +99,11 @@ const gtpu_tunnel* gtpu_tunnel_manager::add_tunnel(uint16_t rnti, uint32_t lcid, tun->spgw_addr = spgw_addr; if (not ue_teidin_db.contains(rnti)) { - ue_teidin_db.insert(rnti, ue_lcid_tunnel_list()); + auto ret = ue_teidin_db.insert(rnti, ue_lcid_tunnel_list()); + if (ret.is_error()) { + logger.error("Failed to allocate rnti=0x%x", rnti); + return nullptr; + } } auto& ue_tunnels = ue_teidin_db[rnti]; @@ -130,16 +134,20 @@ bool gtpu_tunnel_manager::update_rnti(uint16_t old_rnti, uint16_t new_rnti) auto* old_rnti_ptr = find_rnti_tunnels(old_rnti); logger.info("Modifying bearer rnti. Old rnti: 0x%x, new rnti: 0x%x", old_rnti, new_rnti); - // Change RNTI bearers map - ue_teidin_db.insert(new_rnti, std::move(*old_rnti_ptr)); - ue_teidin_db.erase(old_rnti); - - // Change TEID in existing tunnels - auto* new_rnti_ptr = find_rnti_tunnels(new_rnti); - for (lcid_tunnel& bearer : *new_rnti_ptr) { + // create new RNTI and update TEIDs of old rnti to reflect new rnti + if (not ue_teidin_db.insert(new_rnti, ue_lcid_tunnel_list())) { + logger.error("Failure to create new rnti=0x%x", new_rnti); + return false; + } + std::swap(ue_teidin_db[new_rnti], *old_rnti_ptr); + auto& new_rnti_obj = ue_teidin_db[new_rnti]; + for (lcid_tunnel& bearer : new_rnti_obj) { tunnels[bearer.teid].rnti = new_rnti; } + // Leave old_rnti as zombie to be removed later + old_rnti_ptr->clear(); + return true; } @@ -246,10 +254,14 @@ void gtpu_tunnel_manager::set_tunnel_priority(uint32_t before_teid, uint32_t aft } }; - // Schedule auto-removal of this indirect tunnel + // Schedule auto-removal of the indirect tunnel in case the End Marker is not received + // TS 36.300 - On detection of the "end marker", the target eNB may also initiate the release of the data forwarding + // resource. However, the release of the data forwarding resource is implementation dependent and could + // also be based on other mechanisms (e.g. timer-based mechanism). before_tun.rx_timer = task_sched.get_unique_timer(); - before_tun.rx_timer.set(500, [this, before_teid](uint32_t tid) { - // This will self-destruct the callback object + before_tun.rx_timer.set(2000, [this, before_teid](uint32_t tid) { + // Note: This will self-destruct the callback object + logger.info("Forwarding tunnel " TEID_IN_FMT "being closed after timeout=2000 msec", before_teid); remove_tunnel(before_teid); }); before_tun.rx_timer.run(); diff --git a/srsenb/test/upper/gtpu_test.cc b/srsenb/test/upper/gtpu_test.cc index 33b117a35..e07d8295b 100644 --- a/srsenb/test/upper/gtpu_test.cc +++ b/srsenb/test/upper/gtpu_test.cc @@ -329,7 +329,7 @@ int test_gtpu_direct_tunneling(tunnel_test_event event) TESTASSERT(tenb_pdcp.last_sdu == nullptr); if (event == tunnel_test_event::wait_end_marker_timeout) { // TEST: EndMarker does not reach TeNB, but there is a timeout that will resume the new GTPU tunnel - for (size_t i = 0; i < 1000; ++i) { + for (size_t i = 0; i < 2001; ++i) { task_sched.tic(); } } else { From 72bff83b6375ce0ad73ce6d3fa79c2dd8f57881b Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 10 May 2021 15:35:28 +0100 Subject: [PATCH 06/13] check if MME-UE-S1AP-ID has been yet assigned before sending UL NAS Transport. If it hasn't log error and abort s1 tx. --- srsenb/src/stack/upper/s1ap.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/srsenb/src/stack/upper/s1ap.cc b/srsenb/src/stack/upper/s1ap.cc index fc9becfd7..cfbc933f8 100644 --- a/srsenb/src/stack/upper/s1ap.cc +++ b/srsenb/src/stack/upper/s1ap.cc @@ -1385,7 +1385,8 @@ bool s1ap::ue::send_initialuemessage(asn1::s1ap::rrc_establishment_cause_e cause bool s1ap::ue::send_ulnastransport(srsran::unique_byte_buffer_t pdu) { - if (not s1ap_ptr->mme_connected) { + if (not ctxt.mme_ue_s1ap_id.has_value()) { + logger.error("Trying to send UL NAS Transport message for rnti=0x%x without MME-S1AP-UE-ID", ctxt.rnti); return false; } From ace8ff2a95a2ea6189dcf8e8adb6f00f8440918a Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 5 May 2021 16:45:46 +0200 Subject: [PATCH 07/13] build: fix linking failure on RPi 32bit this fixes a linking problem with RPi 3 (and probably others) running with Raspbian (new Raspberry Pi OS) that can't use the inline atomic functions but instead require linking against the lib -latomic. The CMake code is based on SoapyRTLSdr file (licensed under MIT) https://github.com/pothosware/SoapyRTLSDR/blob/master/CheckAtomic.cmake --- CMakeLists.txt | 6 ++ COPYRIGHT | 4 ++ cmake/modules/CheckAtomic.cmake | 92 ++++++++++++++++++++++++++++++ debian/copyright | 3 + srsue/src/stack/mac/CMakeLists.txt | 2 +- 5 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 cmake/modules/CheckAtomic.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f46486c47..c12fc5a66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,6 +130,12 @@ if (STOP_ON_WARNING) add_definitions(-DSTOP_ON_WARNING) endif() +# Test for Atomics +include(CheckAtomic) +if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB OR NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) + set(ATOMIC_LIBS "atomic") +endif() + ######################################################################## # Find dependencies ######################################################################## diff --git a/COPYRIGHT b/COPYRIGHT index c1643382d..7b6dc37e7 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -51,6 +51,10 @@ Files: lib/include/srsran/common/backward.hpp Copyright: 2013, Google Inc. License: MIT +Files: cmake/modules/CheckAtomic.cmake +Copyright: 2015, Charles J. Cliffe +License: MIT + License: AGPL-3+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as diff --git a/cmake/modules/CheckAtomic.cmake b/cmake/modules/CheckAtomic.cmake new file mode 100644 index 000000000..c8168d60d --- /dev/null +++ b/cmake/modules/CheckAtomic.cmake @@ -0,0 +1,92 @@ +# +# 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. +# + +# Adopted from https://github.com/pothosware/SoapyRTLSDR +# Copyright: 2015, Charles J. Cliffe +# License: MIT + +# - Try to find if atomics need -latomic linking +# Once done this will define +# HAVE_CXX_ATOMICS_WITHOUT_LIB - Wether atomic types work without -latomic +# HAVE_CXX_ATOMICS64_WITHOUT_LIB - Wether 64 bit atomic types work without -latomic + +INCLUDE(CheckCXXSourceCompiles) +INCLUDE(CheckLibraryExists) + +# Sometimes linking against libatomic is required for atomic ops, if +# the platform doesn't support lock-free atomics. + +function(check_working_cxx_atomics varname) +set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) +set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11") +CHECK_CXX_SOURCE_COMPILES(" +#include +std::atomic x; +int main() { +return std::atomic_is_lock_free(&x); +} +" ${varname}) +set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) +endfunction(check_working_cxx_atomics) + +function(check_working_cxx_atomics64 varname) +set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) +set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") +CHECK_CXX_SOURCE_COMPILES(" +#include +#include +std::atomic x (0); +int main() { +uint64_t i = x.load(std::memory_order_relaxed); +return std::atomic_is_lock_free(&x); +} +" ${varname}) +set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) +endfunction(check_working_cxx_atomics64) + +# Check for atomic operations. +if(MSVC) + # This isn't necessary on MSVC. + set(HAVE_CXX_ATOMICS_WITHOUT_LIB True) +else() + # First check if atomics work without the library. + check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) +endif() + +# If not, check if the library exists, and atomics work with it. +if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) + check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC) + if(NOT HAVE_LIBATOMIC) + message(STATUS "Host compiler appears to require libatomic, but cannot locate it.") + endif() + list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") + check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) + if (NOT HAVE_CXX_ATOMICS_WITH_LIB) + message(FATAL_ERROR "Host compiler must support std::atomic!") + endif() +endif() + +# Check for 64 bit atomic operations. +if(MSVC) + set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True) +else() + check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB) +endif() + +# If not, check if the library exists, and atomics work with it. +if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) + check_library_exists(atomic __atomic_load_8 "" HAVE_LIBATOMIC64) + if(NOT HAVE_LIBATOMIC64) + message(STATUS "Host compiler appears to require libatomic, but cannot locate it.") + endif() + list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") + check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) + if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) + message(FATAL_ERROR "Host compiler must support std::atomic!") + endif() +endif() \ No newline at end of file diff --git a/debian/copyright b/debian/copyright index ad1510717..729597848 100644 --- a/debian/copyright +++ b/debian/copyright @@ -58,6 +58,9 @@ Files: lib/include/srsran/common/backward.hpp Copyright: 2013, Google Inc. License: MIT +Files: cmake/modules/CheckAtomic.cmake +Copyright: 2015, Charles J. Cliffe +License: MIT License: AGPL-3+ This program is free software: you can redistribute it and/or modify diff --git a/srsue/src/stack/mac/CMakeLists.txt b/srsue/src/stack/mac/CMakeLists.txt index 026bba8f2..731f77cbf 100644 --- a/srsue/src/stack/mac/CMakeLists.txt +++ b/srsue/src/stack/mac/CMakeLists.txt @@ -20,4 +20,4 @@ set(SOURCES demux.cc dl_harq.cc mac.cc mux.cc proc_bsr.cc proc_phr.cc proc_ra.cc proc_sr.cc ul_harq.cc) add_library(srsue_mac STATIC ${SOURCES}) -target_link_libraries(srsue_mac srsue_mac_common) \ No newline at end of file +target_link_libraries(srsue_mac srsue_mac_common ${ATOMIC_LIBS}) \ No newline at end of file From d81788e077a97bf1aedbe48eb8989766e50ec216 Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Wed, 5 May 2021 17:21:24 +0200 Subject: [PATCH 08/13] rrc_bearer_cfg: replace fixed E-RAB to DRB mapping also make sure we don't assign LCIDs beyond the possible number. possible fix for https://github.com/srsran/srsRAN/issues/658 Co-authored-by: herlesupreeth Co-authored-by: Francisco --- lib/include/srsran/common/common_lte.h | 5 ++-- lib/src/upper/pdcp.cc | 2 +- srsenb/hdr/stack/rrc/rrc_bearer_cfg.h | 3 ++- srsenb/hdr/stack/upper/gtpu.h | 2 +- srsenb/src/stack/rrc/rrc_bearer_cfg.cc | 32 ++++++++++++++++++++++---- srsenb/src/stack/rrc/rrc_mobility.cc | 2 +- srsenb/src/stack/rrc/rrc_ue.cc | 8 +++---- 7 files changed, 40 insertions(+), 14 deletions(-) diff --git a/lib/include/srsran/common/common_lte.h b/lib/include/srsran/common/common_lte.h index 722b52b8b..8bd041e5d 100644 --- a/lib/include/srsran/common/common_lte.h +++ b/lib/include/srsran/common/common_lte.h @@ -59,11 +59,12 @@ enum class lte_srb { srb0, srb1, srb2, count }; const uint32_t MAX_LTE_SRB_ID = 2; enum class lte_drb { drb1 = 1, drb2, drb3, drb4, drb5, drb6, drb7, drb8, drb9, drb10, drb11, invalid }; const uint32_t MAX_LTE_DRB_ID = 11; -const uint32_t MAX_NOF_BEARERS = 14; +const uint32_t MAX_LTE_LCID = 10; // logicalChannelIdentity 3..10 in TS 36.331 v15.3 +const uint32_t INVALID_LCID = 99; // random invalid LCID constexpr bool is_lte_rb(uint32_t lcid) { - return lcid < MAX_NOF_BEARERS; + return lcid <= MAX_LTE_LCID; } constexpr bool is_lte_srb(uint32_t lcid) diff --git a/lib/src/upper/pdcp.cc b/lib/src/upper/pdcp.cc index c75f5ca4b..18ce0f010 100644 --- a/lib/src/upper/pdcp.cc +++ b/lib/src/upper/pdcp.cc @@ -172,7 +172,7 @@ void pdcp::del_bearer(uint32_t lcid) } if (valid_lcid(lcid)) { pdcp_array.erase(lcid); - logger.warning("Deleted PDCP bearer %s", rrc->get_rb_name(lcid)); + logger.info("Deleted PDCP bearer %s", rrc->get_rb_name(lcid)); } else { logger.warning("Can't delete bearer %s. Bearer doesn't exist.", rrc->get_rb_name(lcid)); } diff --git a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h index 268fe59db..16a9a5743 100644 --- a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h +++ b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h @@ -78,7 +78,8 @@ public: uint32_t teid_in = 0; uint32_t addr = 0; }; - uint8_t id = 0; + uint8_t id = 0; + uint8_t lcid = 0; asn1::s1ap::erab_level_qos_params_s qos_params; asn1::bounded_bitstring<1, 160, true, true> address; uint32_t teid_out = 0; diff --git a/srsenb/hdr/stack/upper/gtpu.h b/srsenb/hdr/stack/upper/gtpu.h index f7f955716..1d1724369 100644 --- a/srsenb/hdr/stack/upper/gtpu.h +++ b/srsenb/hdr/stack/upper/gtpu.h @@ -64,7 +64,7 @@ public: struct tunnel { uint16_t rnti = SRSRAN_INVALID_RNTI; - uint32_t lcid = srsran::MAX_NOF_BEARERS; + uint32_t lcid = srsran::INVALID_LCID; uint32_t teid_in = 0; uint32_t teid_out = 0; uint32_t spgw_addr = 0; diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index 60eff67b8..3b81e2f49 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -233,8 +233,26 @@ int bearer_cfg_handler::add_erab(uint8_t cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::unknown_erab_id; return SRSRAN_ERROR; } - uint8_t lcid = erab_id - 2; // Map e.g. E-RAB 5 to LCID 3 (==DRB1) - uint8_t drbid = erab_id - 4; + + uint8_t lcid = 3; // first E-RAB with DRB1 gets LCID3 + for (const auto& drb : current_drbs) { + if (drb.lc_ch_id == lcid) { + lcid++; + } + } + if (lcid > srsran::MAX_LTE_LCID) { + logger->error("Can't allocate LCID for ERAB id=%d", erab_id); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::radio_res_not_available; + return SRSRAN_ERROR; + } + + // We currently have this static mapping between LCID->DRB ID + uint8_t drbid = lcid - 2; + if (drbid > srsran::MAX_LTE_DRB_ID) { + logger->error("Can't allocate DRB ID for ERAB id=%d", erab_id); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::radio_res_not_available; + return SRSRAN_ERROR; + } auto qci_it = cfg->qci_cfg.find(qos.qci); if (qci_it == cfg->qci_cfg.end() or not qci_it->second.configured) { @@ -250,6 +268,7 @@ int bearer_cfg_handler::add_erab(uint8_t const rrc_cfg_qci_t& qci_cfg = qci_it->second; erabs[erab_id].id = erab_id; + erabs[erab_id].lcid = lcid; erabs[erab_id].qos_params = qos; erabs[erab_id].address = addr; erabs[erab_id].teid_out = teid_out; @@ -385,7 +404,7 @@ srsran::expected bearer_cfg_handler::add_gtpu_bearer(uint32_t erab_t::gtpu_tunnel bearer; bearer.teid_out = teid_out; bearer.addr = addr; - srsran::expected teidin = gtpu->add_bearer(rnti, erab.id - 2, addr, teid_out, props); + srsran::expected teidin = gtpu->add_bearer(rnti, erab.lcid, addr, teid_out, props); if (teidin.is_error()) { logger->error("Adding erab_id=%d to GTPU", erab_id); return srsran::default_error_t(); @@ -397,7 +416,12 @@ srsran::expected bearer_cfg_handler::add_gtpu_bearer(uint32_t void bearer_cfg_handler::rem_gtpu_bearer(uint32_t erab_id) { - gtpu->rem_bearer(rnti, erab_id - 2); + auto it = erabs.find(erab_id); + if (it == erabs.end()) { + logger->error("Removing erab_id=%d from GTPU", erab_id); + return; + } + gtpu->rem_bearer(rnti, it->second.lcid); } void bearer_cfg_handler::fill_pending_nas_info(asn1::rrc::rrc_conn_recfg_r8_ies_s* msg) diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index 1e52110be..89cbf1dab 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -582,7 +582,7 @@ rrc::ue::rrc_mobility::s1_source_ho_st::start_enb_status_transfer(const asn1::s1 for (const auto& erab_pair : rrc_ue->bearer_list.get_erabs()) { s1ap_interface_rrc::bearer_status_info b = {}; - uint8_t lcid = erab_pair.second.id - 2u; + uint8_t lcid = erab_pair.second.lcid; b.erab_id = erab_pair.second.id; srsran::pdcp_lte_state_t pdcp_state = {}; if (not rrc_enb->pdcp->get_bearer_state(rrc_ue->rnti, lcid, &pdcp_state)) { diff --git a/srsenb/src/stack/rrc/rrc_ue.cc b/srsenb/src/stack/rrc/rrc_ue.cc index c477c6a06..9c78d5d44 100644 --- a/srsenb/src/stack/rrc/rrc_ue.cc +++ b/srsenb/src/stack/rrc/rrc_ue.cc @@ -20,10 +20,10 @@ */ #include "srsenb/hdr/stack/rrc/rrc_ue.h" +#include "srsenb/hdr/common/common_enb.h" #include "srsenb/hdr/stack/rrc/mac_controller.h" #include "srsenb/hdr/stack/rrc/rrc_mobility.h" #include "srsenb/hdr/stack/rrc/ue_rr_cfg.h" -#include "srsran/adt/pool/mem_pool.h" #include "srsran/asn1/rrc_utils.h" #include "srsran/common/enb_events.h" #include "srsran/common/int_helpers.h" @@ -1339,7 +1339,7 @@ void rrc::ue::apply_pdcp_srb_updates(const rr_cfg_ded_s& pending_rr_cfg) void rrc::ue::apply_pdcp_drb_updates(const rr_cfg_ded_s& pending_rr_cfg) { for (uint8_t drb_id : pending_rr_cfg.drb_to_release_list) { - parent->pdcp->del_bearer(rnti, drb_id + 2); + parent->pdcp->del_bearer(rnti, drb_to_lcid((lte_drb)drb_id)); } for (const drb_to_add_mod_s& drb : pending_rr_cfg.drb_to_add_mod_list) { // Configure DRB1 in PDCP @@ -1361,7 +1361,7 @@ void rrc::ue::apply_pdcp_drb_updates(const rr_cfg_ded_s& pending_rr_cfg) // If reconf due to reestablishment, recover PDCP state if (state == RRC_STATE_REESTABLISHMENT_COMPLETE) { for (const auto& erab_pair : bearer_list.get_erabs()) { - uint16_t lcid = erab_pair.second.id - 2; + uint16_t lcid = erab_pair.second.lcid; bool is_am = parent->cfg.qci_cfg[erab_pair.second.qos_params.qci].rlc_cfg.type().value == asn1::rrc::rlc_cfg_c::types_opts::am; if (is_am) { @@ -1389,7 +1389,7 @@ void rrc::ue::apply_rlc_rb_updates(const rr_cfg_ded_s& pending_rr_cfg) } if (pending_rr_cfg.drb_to_release_list.size() > 0) { for (uint8_t drb_id : pending_rr_cfg.drb_to_release_list) { - parent->rlc->del_bearer(rnti, drb_id + 2); + parent->rlc->del_bearer(rnti, drb_to_lcid((lte_drb)drb_id)); } } for (const drb_to_add_mod_s& drb : pending_rr_cfg.drb_to_add_mod_list) { From e27ba38f2b61d7bbf68d6ecefa5d167089b14cf6 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 13 May 2021 13:01:56 +0100 Subject: [PATCH 09/13] fix conversions of drb id to erab id in srsenb --- srsenb/hdr/common/common_enb.h | 4 ++++ srsenb/src/stack/rrc/rrc_bearer_cfg.cc | 20 +++++++++++--------- srsenb/src/stack/rrc/rrc_mobility.cc | 10 +++++----- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/srsenb/hdr/common/common_enb.h b/srsenb/hdr/common/common_enb.h index e38cec013..431719dda 100644 --- a/srsenb/hdr/common/common_enb.h +++ b/srsenb/hdr/common/common_enb.h @@ -51,6 +51,10 @@ constexpr uint32_t drb_to_lcid(lte_drb drb_id) { return srb_to_lcid(lte_srb::srb2) + static_cast(drb_id); } +constexpr lte_drb lte_lcid_to_drb(uint32_t lcid) +{ + return srsran::is_lte_drb(lcid) ? static_cast(lcid - srb_to_lcid(lte_srb::srb2)) : lte_drb::invalid; +} // Cat 3 UE - Max number of DL-SCH transport block bits received within a TTI // 3GPP 36.306 Table 4.1.1 diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index 3b81e2f49..d72824480 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -336,9 +336,8 @@ int bearer_cfg_handler::release_erab(uint8_t erab_id) return SRSRAN_ERROR; } - uint8_t drb_id = erab_id - 4; - - srsran::rem_rrc_obj_id(current_drbs, drb_id); + lte_drb drb_id = lte_lcid_to_drb(it->second.lcid); + srsran::rem_rrc_obj_id(current_drbs, (uint8_t)drb_id); rem_gtpu_bearer(erab_id); @@ -438,16 +437,19 @@ void bearer_cfg_handler::fill_pending_nas_info(asn1::rrc::rrc_conn_recfg_r8_ies_ // Add E-RAB info message for the E-RABs if (msg->rr_cfg_ded.drb_to_add_mod_list_present) { for (const drb_to_add_mod_s& drb : msg->rr_cfg_ded.drb_to_add_mod_list) { - uint8_t erab_id = drb.drb_id + 4; - auto it = erab_info_list.find(erab_id); - if (it != erab_info_list.end()) { - const std::vector& erab_info = it->second; + uint32_t lcid = drb_to_lcid((lte_drb)drb.drb_id); + auto erab_it = std::find_if( + erabs.begin(), erabs.end(), [lcid](const std::pair& e) { return e.second.lcid == lcid; }); + uint32_t erab_id = erab_it->second.id; + auto info_it = erab_info_list.find(erab_id); + if (info_it != erab_info_list.end()) { + const std::vector& erab_info = info_it->second; logger->info(&erab_info[0], erab_info.size(), "connection_reconf erab_info -> nas_info rnti 0x%x", rnti); msg->ded_info_nas_list[idx].resize(erab_info.size()); memcpy(msg->ded_info_nas_list[idx].data(), &erab_info[0], erab_info.size()); - erab_info_list.erase(it); + erab_info_list.erase(info_it); } else { - logger->debug("Not adding NAS message to connection reconfiguration. E-RAB id %d", erab_id); + logger->info("Not adding NAS message to connection reconfiguration. E-RAB id %d", erab_id); } idx++; } diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index 89cbf1dab..c6ed18b71 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -997,12 +997,12 @@ void rrc::ue::rrc_mobility::handle_status_transfer(s1_target_ho_st& s, const sta logger.warning("The E-RAB Id=%d is not recognized", erab_item.erab_id); continue; } - const auto& drbs = rrc_ue->bearer_list.get_established_drbs(); - uint8_t drbid = erab_item.erab_id - 4; - auto drb_it = - std::find_if(drbs.begin(), drbs.end(), [drbid](const drb_to_add_mod_s& drb) { return drb.drb_id == drbid; }); + const auto& drbs = rrc_ue->bearer_list.get_established_drbs(); + lte_drb drbid = lte_lcid_to_drb(erab_it->second.lcid); + auto drb_it = std::find_if( + drbs.begin(), drbs.end(), [drbid](const drb_to_add_mod_s& drb) { return (lte_drb)drb.drb_id == drbid; }); if (drb_it == drbs.end()) { - logger.warning("The DRB id=%d does not exist", erab_item.erab_id - 4); + logger.warning("The DRB id=%d does not exist", (int)drbid); } srsran::pdcp_lte_state_t drb_state{}; From 1c6dd8c4adc8419d4a431f382283539f1719582a Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 13 May 2021 16:13:47 +0100 Subject: [PATCH 10/13] delete erab if gtpu tunnel creation failed --- srsenb/src/stack/rrc/rrc_bearer_cfg.cc | 2 +- srsenb/src/stack/rrc/rrc_mobility.cc | 1 + srsenb/src/stack/rrc/rrc_ue.cc | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index d72824480..ea711bae0 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -417,7 +417,7 @@ void bearer_cfg_handler::rem_gtpu_bearer(uint32_t erab_id) { auto it = erabs.find(erab_id); if (it == erabs.end()) { - logger->error("Removing erab_id=%d from GTPU", erab_id); + logger->warning("Removing erab_id=%d from GTPU", erab_id); return; } gtpu->rem_bearer(rnti, it->second.lcid); diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index c6ed18b71..8ac273127 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -925,6 +925,7 @@ bool rrc::ue::rrc_mobility::apply_ho_prep_cfg(const ho_prep_info_r8_ies_s& erabs_failed_to_setup.back().erab_id = erab.erab_id; erabs_failed_to_setup.back().cause.set_transport().value = asn1::s1ap::cause_transport_opts::transport_res_unavailable; + rrc_ue->bearer_list.release_erab(erab.erab_id); continue; } } diff --git a/srsenb/src/stack/rrc/rrc_ue.cc b/srsenb/src/stack/rrc/rrc_ue.cc index 9c78d5d44..4b25573a6 100644 --- a/srsenb/src/stack/rrc/rrc_ue.cc +++ b/srsenb/src/stack/rrc/rrc_ue.cc @@ -1092,6 +1092,7 @@ int rrc::ue::setup_erab(uint16_t erab_ } if (bearer_list.add_gtpu_bearer(erab_id) != SRSRAN_SUCCESS) { cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::radio_res_not_available; + bearer_list.release_erab(erab_id); return SRSRAN_ERROR; } return SRSRAN_SUCCESS; From 89f16eed2b7f2e6c45e675298f5829c230698e1d Mon Sep 17 00:00:00 2001 From: Andre Puschmann Date: Thu, 20 May 2021 10:42:01 +0200 Subject: [PATCH 11/13] update mailing list link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55e6e51a4..3ac88667b 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,4 @@ For license details, see LICENSE file. Support ======= -Mailing list: http://www.srs.io/mailman/listinfo/srslte-users +Mailing list: https://lists.softwareradiosystems.com/mailman/listinfo/srslte-users \ No newline at end of file From f554129d798b3ef065d01c45355562010289cf5e Mon Sep 17 00:00:00 2001 From: Paul Sutton Date: Sun, 3 Oct 2021 09:39:17 +0100 Subject: [PATCH 12/13] Update mailing list --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ac88667b..f8e9792a4 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,4 @@ For license details, see LICENSE file. Support ======= -Mailing list: https://lists.softwareradiosystems.com/mailman/listinfo/srslte-users \ No newline at end of file +Mailing list: https://lists.srsran.com/mailman/listinfo/srsran-users From eccf106f87a55dd67a811827da92a7a7b5dfb004 Mon Sep 17 00:00:00 2001 From: Paul Sutton Date: Sun, 3 Oct 2021 09:56:09 +0100 Subject: [PATCH 13/13] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 976786810..b2cdc030b 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,6 @@ ## Issue Description ##