From 034aa6a6d45d66c01aaab8e57306d8f9ddeec9b9 Mon Sep 17 00:00:00 2001 From: Robert Falkenberg Date: Thu, 24 Feb 2022 11:50:42 +0100 Subject: [PATCH] rlc, nr: inform upper layer when max retransmissions is exceeded --- lib/include/srsran/rlc/rlc_am_nr.h | 3 +- lib/include/srsran/rlc/rlc_am_nr_packing.h | 1 + lib/src/rlc/rlc_am_nr.cc | 39 +++++++++++- lib/test/rlc/rlc_am_nr_test.cc | 69 ++++++++++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index b7ec41552..0763a445d 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -76,7 +76,7 @@ struct rlc_amd_tx_pdu_nr { uint32_t pdcp_sn = INVALID_RLC_SN; rlc_am_nr_pdu_header_t header = {}; unique_byte_buffer_t sdu_buf = nullptr; - uint32_t retx_count = 0; + uint32_t retx_count = RETX_COUNT_NOT_STARTED; struct pdu_segment { uint32_t so = 0; uint32_t retx_count = 0; @@ -135,6 +135,7 @@ private: uint32_t mod_nr = 4096; inline uint32_t tx_mod_base_nr(uint32_t sn) const; + void check_sn_reached_max_retx(uint32_t sn); /**************************************************************************** * Configurable parameters diff --git a/lib/include/srsran/rlc/rlc_am_nr_packing.h b/lib/include/srsran/rlc/rlc_am_nr_packing.h index 1297880ce..234b4ea22 100644 --- a/lib/include/srsran/rlc/rlc_am_nr_packing.h +++ b/lib/include/srsran/rlc/rlc_am_nr_packing.h @@ -22,6 +22,7 @@ namespace srsran { const uint32_t RLC_AM_NR_WINDOW_SIZE_12BIT = 4096; const uint32_t RLC_AM_NR_WINDOW_SIZE_18BIT = 262144; const uint32_t INVALID_RLC_SN = 0xFFFFFFFF; +const uint32_t RETX_COUNT_NOT_STARTED = 0xFFFFFFFF; ///< AM NR PDU header struct rlc_am_nr_pdu_header_t { diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index 9d9b65301..3636ed5f6 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -747,14 +747,23 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) retx.so_end = segm->so + segm->payload_len; } } + + // TODO: Handle retx_count for segments + } else { // NACK'ing full SDU. // add to retx queue if it's not already there if (not retx_queue.has_sn(nack_sn)) { - // increment Retx counter and inform upper layers if needed - pdu.retx_count++; + // Increment retx_count and inform upper layers if needed + if (pdu.retx_count == RETX_COUNT_NOT_STARTED) { + // Set retx_count = 0 on first RE-transmission (38.322 Sec. 5.3.2) + pdu.retx_count = 0; + } else { + // Increment otherwise + pdu.retx_count++; + } - // check_sn_reached_max_retx(nack_sn); + check_sn_reached_max_retx(nack_sn); rlc_amd_retx_t& retx = retx_queue.push(); retx.sn = nack_sn; retx.is_segment = false; @@ -776,6 +785,30 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) */ } +/** + * Helper to check if a SN has reached the max reTx threshold + * + * Caller _must_ hold the mutex when calling the function. + * If the retx has been reached for a SN the upper layers (i.e. RRC/PDCP) will be informed. + * The SN is _not_ removed from the Tx window, so retransmissions of that SN can still occur. + * + * + * @param sn The SN of the PDU to check + */ +void rlc_am_nr_tx::check_sn_reached_max_retx(uint32_t sn) +{ + if (tx_window[sn].retx_count == cfg.max_retx_thresh) { + RlcWarning("Signaling max number of reTx=%d for SN=%d", tx_window[sn].retx_count, sn); + parent->rrc->max_retx_attempted(); + srsran::pdcp_sn_vector_t pdcp_sns; + pdcp_sns.push_back(tx_window[sn].pdcp_sn); + parent->pdcp->notify_failure(parent->lcid, pdcp_sns); + + std::lock_guard lock(parent->metrics_mutex); + parent->metrics.num_lost_pdus++; + } +} + uint32_t rlc_am_nr_tx::get_buffer_state() { uint32_t tx_queue = 0; diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index 99b203f83..79f92ab1f 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -883,6 +883,74 @@ int retx_segment_test() return SRSRAN_SUCCESS; } +// This test checks whether RLC informs upper layer when max retransmission has been reached +int max_retx_test() +{ + rlc_am_tester tester; + timer_handler timers(8); + int len = 0; + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100); + srslog::fetch_basic_logger("RLC").set_hex_dump_max_size(100); + + const rlc_config_t rlc_cfg = rlc_config_t::default_rlc_am_nr_config(); + if (not rlc1.configure(rlc_cfg)) { + return SRSRAN_ERROR; + } + + // Push 2 SDUs into RLC1 + const uint32_t n_sdus = 2; + unique_byte_buffer_t sdu_bufs[n_sdus]; + for (uint32_t i = 0; i < n_sdus; i++) { + sdu_bufs[i] = srsran::make_byte_buffer(); + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = 1; // Give each buffer a size of 1 byte + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + // Read 2 PDUs from RLC1 (1 byte each) + const uint32_t n_pdus = 2; + byte_buffer_t pdu_bufs[n_pdus]; + for (uint32_t i = 0; i < n_pdus; i++) { + len = rlc1.read_pdu(pdu_bufs[i].msg, 3); // 2 byte header + 1 byte payload + pdu_bufs[i].N_bytes = len; + } + + TESTASSERT(0 == rlc1.get_buffer_state()); + + // Fake status PDU that ack SN=1 and nack SN=0 + rlc_am_nr_status_pdu_t fake_status = {}; + fake_status.ack_sn = 2; // delivered up to SN=1 + fake_status.N_nack = 1; // one SN was lost + fake_status.nacks[0].nack_sn = 0; // it was SN=0 that was lost + + // pack into PDU + byte_buffer_t status_pdu; + rlc_am_nr_write_status_pdu(fake_status, rlc_cfg.am_nr.tx_sn_field_length, &status_pdu); + + // Exceed the number of tolerated retransmissions by one additional retransmission + // to trigger notification of the higher protocol layers. Note that the initial transmission + // (before starting retransmissions) does not count. See TS 38.322 Sec. 5.3.2 + for (uint32_t retx_count = 0; retx_count < rlc_cfg.am_nr.max_retx_thresh + 1; ++retx_count) { + // we've not yet reached max attempts + TESTASSERT(tester.max_retx_triggered == false); + + // Write status PDU to RLC1 + rlc1.write_pdu(status_pdu.msg, status_pdu.N_bytes); + + byte_buffer_t pdu_buf; + len = rlc1.read_pdu(pdu_buf.msg, 3); // 2 byte header + 1 byte payload + } + + // Now maxRetx should have been triggered + TESTASSERT(tester.max_retx_triggered == true); + + return SRSRAN_SUCCESS; +} + // This test checks the correct functioning of RLC discard functionality int discard_test() { @@ -1016,6 +1084,7 @@ int main() TESTASSERT(basic_segmentation_test() == SRSRAN_SUCCESS); TESTASSERT(segment_retx_test() == SRSRAN_SUCCESS); TESTASSERT(retx_segment_test() == SRSRAN_SUCCESS); + TESTASSERT(max_retx_test() == SRSRAN_SUCCESS); TESTASSERT(discard_test() == SRSRAN_SUCCESS); return SRSRAN_SUCCESS; }