diff --git a/lib/include/srsran/rlc/rlc_am_nr.h b/lib/include/srsran/rlc/rlc_am_nr.h index a5f052d85..95e08c6f3 100644 --- a/lib/include/srsran/rlc/rlc_am_nr.h +++ b/lib/include/srsran/rlc/rlc_am_nr.h @@ -74,7 +74,7 @@ struct rlc_amd_tx_pdu_nr { const uint32_t rlc_sn = INVALID_RLC_SN; const uint32_t pdcp_sn = INVALID_RLC_SN; rlc_am_nr_pdu_header_t header = {}; - unique_byte_buffer_t buf = nullptr; + unique_byte_buffer_t sdu_buf = nullptr; uint32_t retx_count = 0; struct pdu_segment { uint32_t so = 0; @@ -104,10 +104,8 @@ public: void empty_queue() final; // Data PDU helpers - int build_new_sdu_segment(unique_byte_buffer_t tx_sdu, - rlc_amd_tx_pdu_nr& tx_pdu, - uint8_t* payload, - uint32_t nof_bytes); + int build_new_pdu(uint8_t* payload, uint32_t nof_bytes); + int build_new_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint8_t* payload, uint32_t nof_bytes); int build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint8_t* payload, uint32_t nof_bytes); int build_retx_pdu(unique_byte_buffer_t& tx_pdu, uint32_t nof_bytes); @@ -149,7 +147,7 @@ private: // Queues and buffers pdu_retx_queue retx_queue; - rlc_amd_tx_sdu_nr_t sdu_under_segmentation; + uint32_t sdu_under_segmentation_sn = INVALID_RLC_SN; // SN of the SDU currently being segmented. // Helper constants uint32_t min_hdr_size = 2; diff --git a/lib/src/rlc/rlc_am_nr.cc b/lib/src/rlc/rlc_am_nr.cc index ba1e30328..3ee441e4e 100644 --- a/lib/src/rlc/rlc_am_nr.cc +++ b/lib/src/rlc/rlc_am_nr.cc @@ -67,6 +67,18 @@ bool rlc_am_nr_tx::has_data() tx_sdu_queue.get_n_sdus() != 1; // or if there is a SDU queued up for transmission } +/** + * Builds the RLC PDU. + * + * Called by the MAC, trough the STACK thread. + * + * \param [payload] is a pointer to the buffer that will hold the PDU. + * \param [nof_bytes] is the number of bytes the RLC is allowed to fill. + * + * \returns the number of bytes written to the payload buffer. + * \remark: This will be called multiple times from the MAC, + * while there is something to TX and enough space in the TB. + */ uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) { std::lock_guard lock(mutex); @@ -106,14 +118,14 @@ uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) } // Send remaining segment, if it exists - if (sdu_under_segmentation.rlc_sn != INVALID_RLC_SN) { - if (not tx_window.has_sn(sdu_under_segmentation.rlc_sn)) { - sdu_under_segmentation.rlc_sn = INVALID_RLC_SN; + if (sdu_under_segmentation_sn != INVALID_RLC_SN) { + if (not tx_window.has_sn(sdu_under_segmentation_sn)) { + sdu_under_segmentation_sn = INVALID_RLC_SN; RlcError("SDU currently being segmented does not exist in tx_window. Aborting segmentation SN=%d", - sdu_under_segmentation.rlc_sn); + sdu_under_segmentation_sn); return 0; } - return build_continuation_sdu_segment(tx_window[sdu_under_segmentation.rlc_sn], payload, nof_bytes); + return build_continuation_sdu_segment(tx_window[sdu_under_segmentation_sn], payload, nof_bytes); } // Check whether there is something to TX @@ -122,6 +134,26 @@ uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) return 0; } + return build_new_pdu(payload, nof_bytes); +} + +/** + * Builds a new RLC PDU, which contains the full SDU. + * + * Called by the MAC, trough the STACK thread. + * This will be called after checking whether control, retransmission, + * or segment PDUs needed to be transmitted first. + * + * This will read an SDU from the SDU queue, build a new PDU, and add it to the tx_window. + * Segmentation will be done if necessary. + * + * \param [payload] is a pointer to the buffer that will hold the PDU. + * \param [nof_bytes] is the number of bytes the RLC is allowed to fill. + * + * \returns the number of bytes written to the payload buffer. + */ +int rlc_am_nr_tx::build_new_pdu(uint8_t* payload, uint32_t nof_bytes) +{ // Read new SDU from TX queue unique_byte_buffer_t tx_sdu; RlcDebug("reading from RLC SDU queue. Queue size %d", tx_sdu_queue.size()); @@ -139,21 +171,22 @@ uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) // insert newly assigned SN into window and use reference for in-place operations // NOTE: from now on, we can't return from this function anymore before increasing tx_next rlc_amd_tx_pdu_nr& tx_pdu = tx_window.add_pdu(st.tx_next); - tx_pdu.buf = srsran::make_byte_buffer(); - if (tx_pdu.buf == nullptr) { + tx_pdu.sdu_buf = srsran::make_byte_buffer(); + if (tx_pdu.sdu_buf == nullptr) { RlcError("couldn't allocate PDU in %s().", __FUNCTION__); return 0; } + // Copy SDU into PDU info + memcpy(tx_pdu.sdu_buf->msg, tx_sdu->msg, tx_sdu->N_bytes); + tx_pdu.sdu_buf->N_bytes = tx_sdu->N_bytes; + // Segment new SDU if necessary if (tx_sdu->N_bytes + min_hdr_size > nof_bytes) { RlcInfo("trying to build PDU segment from SDU."); - return build_new_sdu_segment(std::move(tx_sdu), tx_pdu, payload, nof_bytes); + return build_new_sdu_segment(tx_pdu, payload, nof_bytes); } - memcpy(tx_pdu.buf->msg, tx_sdu->msg, tx_sdu->N_bytes); - tx_pdu.buf->N_bytes = tx_sdu->N_bytes; - // Prepare header rlc_am_nr_pdu_header_t hdr = {}; hdr.dc = RLC_DC_FIELD_DATA_PDU; @@ -179,18 +212,27 @@ uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) return tx_sdu->N_bytes; } -int rlc_am_nr_tx::build_new_sdu_segment(unique_byte_buffer_t tx_sdu, - rlc_amd_tx_pdu_nr& tx_pdu, - uint8_t* payload, - uint32_t nof_bytes) +/** + * Builds a new RLC PDU segment. + * + * Called by the MAC, trough the STACK thread. + * + * \param [tx_pdu] is a pointer to the buffer that will hold the PDU. + * \param [payload] is a pointer to the MAC buffer that will hold the PDU segment. + * \param [nof_bytes] is the number of bytes the RLC is allowed to fill. + * + * \returns the number of bytes written to the payload buffer. + * \remark: This functions assumes that the SDU has already been copied to tx_pdu.sdu_buf. + */ +int rlc_am_nr_tx::build_new_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint8_t* payload, uint32_t nof_bytes) { - RlcInfo("creating new SDU segment. Tx SDU (%d B), nof_bytes=%d B ", tx_sdu->N_bytes, nof_bytes); + RlcInfo("creating new SDU segment. Tx SDU (%d B), nof_bytes=%d B ", tx_pdu.sdu_buf->N_bytes, nof_bytes); // Sanity check: can this SDU be sent this in a single PDU? - if ((tx_sdu->N_bytes + min_hdr_size) < nof_bytes) { + if ((tx_pdu.sdu_buf->N_bytes + min_hdr_size) < nof_bytes) { RlcError("calling build_new_sdu_segment(), but there are enough bytes to tx in a single PDU. Tx SDU (%d B), " "nof_bytes=%d B ", - tx_sdu->N_bytes, + tx_pdu.sdu_buf->N_bytes, nof_bytes); return 0; } @@ -223,11 +265,10 @@ int rlc_am_nr_tx::build_new_sdu_segment(unique_byte_buffer_t tx_sdu, // Copy PDU to payload uint32_t segment_payload_len = nof_bytes - hdr_len; srsran_assert((hdr_len + segment_payload_len) <= nof_bytes, "Error calculating hdr_len and segment_payload_len"); - memcpy(&payload[hdr_len], tx_pdu.buf->msg, segment_payload_len); + memcpy(&payload[hdr_len], tx_pdu.sdu_buf->msg, segment_payload_len); // Save SDU currently being segmented - sdu_under_segmentation.rlc_sn = st.tx_next; - sdu_under_segmentation.buf = std::move(tx_sdu); + sdu_under_segmentation_sn = st.tx_next; // Store Segment Info rlc_amd_tx_pdu_nr::pdu_segment segment_info; @@ -239,19 +280,18 @@ int rlc_am_nr_tx::build_new_sdu_segment(unique_byte_buffer_t tx_sdu, int rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint8_t* payload, uint32_t nof_bytes) { RlcInfo("continuing SDU segment. SN=%d, Tx SDU (%d B), nof_bytes=%d B ", - sdu_under_segmentation.rlc_sn, - sdu_under_segmentation.buf->N_bytes, + sdu_under_segmentation_sn, + tx_pdu.sdu_buf->N_bytes, nof_bytes); // Sanity check: is there an initial SDU segment? if (tx_pdu.segment_list.empty()) { RlcError("build_continuation_sdu_segment was called, but there was no initial segment. SN=%d, Tx SDU (%d B), " "nof_bytes=%d B ", - sdu_under_segmentation.rlc_sn, - sdu_under_segmentation.buf->N_bytes, + sdu_under_segmentation_sn, + tx_pdu.sdu_buf->N_bytes, nof_bytes); - sdu_under_segmentation.rlc_sn = INVALID_RLC_SN; - sdu_under_segmentation.buf = nullptr; + sdu_under_segmentation_sn = INVALID_RLC_SN; return 0; } @@ -267,15 +307,16 @@ int rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint uint32_t last_byte = seg.so + seg.payload_len; RlcDebug("continuing SDU segment. SN=%d, last byte transmitted %d", tx_pdu.rlc_sn, last_byte); - // Sanity check: last byte must be smaller than SDU - if (sdu_under_segmentation.buf->N_bytes < last_byte) { - RlcError("last byte transmitted larger than SDU len. SDU len=%d B, last_byte=%d B", tx_pdu.buf->N_bytes, last_byte); + // Sanity check: last byte must be smaller than SDU size + if (last_byte > tx_pdu.sdu_buf->N_bytes) { + RlcError( + "last byte transmitted larger than SDU len. SDU len=%d B, last_byte=%d B", tx_pdu.sdu_buf->N_bytes, last_byte); return 0; } - uint32_t segment_payload_full_len = sdu_under_segmentation.buf->N_bytes - last_byte + max_hdr_size; // SO is included - uint32_t segment_payload_len = sdu_under_segmentation.buf->N_bytes - last_byte; - rlc_nr_si_field_t si = {}; + uint32_t segment_payload_full_len = tx_pdu.sdu_buf->N_bytes - last_byte + max_hdr_size; // SO is included + uint32_t segment_payload_len = tx_pdu.sdu_buf->N_bytes - last_byte; + rlc_nr_si_field_t si = {}; if (segment_payload_full_len > nof_bytes) { RlcInfo("grant is not large enough for full SDU. " @@ -313,7 +354,7 @@ int rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint // Copy PDU to payload srsran_assert((hdr_len + segment_payload_len) <= nof_bytes, "Error calculating hdr_len and segment_payload_len"); - memcpy(&payload[hdr_len], &tx_pdu.buf->msg[last_byte], segment_payload_len); + memcpy(&payload[hdr_len], &tx_pdu.sdu_buf->msg[last_byte], segment_payload_len); // Store PDU segment info into tx_window rlc_amd_tx_pdu_nr::pdu_segment segment_info = {}; @@ -327,8 +368,7 @@ int rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu, uint } else { RlcInfo("grant is large enough for full SDU." "Removing current SDU info"); - sdu_under_segmentation.rlc_sn = INVALID_RLC_SN; - sdu_under_segmentation.buf = nullptr; + sdu_under_segmentation_sn = INVALID_RLC_SN; } return hdr_len + segment_payload_len; @@ -361,22 +401,21 @@ int rlc_am_nr_tx::build_retx_pdu(unique_byte_buffer_t& tx_pdu, uint32_t nof_byte uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(new_header, tx_pdu.get()); // Check if we exceed allocated number of bytes - if (hdr_len + tx_window[retx.sn].buf->N_bytes > nof_bytes) { - RlcWarning("segmentation not supported yet. Cannot provide retx PDU"); + if (hdr_len + tx_window[retx.sn].sdu_buf->N_bytes > nof_bytes) { + RlcWarning("Trying to segment retx PDU"); return SRSRAN_ERROR; } - // TODO Consider re-segmentation - memcpy(&tx_pdu->msg[hdr_len], tx_window[retx.sn].buf->msg, tx_window[retx.sn].buf->N_bytes); - tx_pdu->N_bytes += tx_window[retx.sn].buf->N_bytes; + memcpy(&tx_pdu->msg[hdr_len], tx_window[retx.sn].sdu_buf->msg, tx_window[retx.sn].sdu_buf->N_bytes); + tx_pdu->N_bytes += tx_window[retx.sn].sdu_buf->N_bytes; retx_queue.pop(); - RlcHexInfo(tx_window[retx.sn].buf->msg, - tx_window[retx.sn].buf->N_bytes, + RlcHexInfo(tx_window[retx.sn].sdu_buf->msg, + tx_window[retx.sn].sdu_buf->N_bytes, "Original SDU SN=%d (%d B) (attempt %d/%d)", retx.sn, - tx_window[retx.sn].buf->N_bytes, + tx_window[retx.sn].sdu_buf->N_bytes, tx_window[retx.sn].retx_count + 1, cfg.max_retx_thresh); RlcHexInfo(tx_pdu->msg, tx_pdu->N_bytes, "retx PDU SN=%d (%d B)", retx.sn, tx_pdu->N_bytes); @@ -463,7 +502,7 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes) retx.sn = nack_sn; retx.is_segment = false; retx.so_start = 0; - retx.so_end = pdu.buf->N_bytes; + retx.so_end = pdu.sdu_buf->N_bytes; } } } diff --git a/lib/test/rlc/rlc_am_nr_test.cc b/lib/test/rlc/rlc_am_nr_test.cc index 673887b32..1136c8830 100644 --- a/lib/test/rlc/rlc_am_nr_test.cc +++ b/lib/test/rlc/rlc_am_nr_test.cc @@ -410,7 +410,151 @@ int basic_segmentation_test() return SRSRAN_SUCCESS; } -int main(int argc, char** argv) +int segment_retx_test() +{ + rlc_am_tester tester; + timer_handler timers(8); + byte_buffer_t pdu_bufs[NBUFS]; + + auto& test_logger = srslog::fetch_basic_logger("TESTER "); + rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers); + rlc_am rlc2(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers); + test_delimit_logger delimiter("segment retx PDU"); + + // before configuring entity + TESTASSERT(0 == rlc1.get_buffer_state()); + + if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) { + return -1; + } + + if (not rlc2.configure(rlc_config_t::default_rlc_am_nr_config())) { + return -1; + } + + // Push 5 SDUs into RLC1 + unique_byte_buffer_t sdu_bufs[NBUFS]; + for (int i = 0; i < NBUFS; i++) { + sdu_bufs[i] = srsran::make_byte_buffer(); + sdu_bufs[i]->msg[0] = i; // Write the index into the buffer + sdu_bufs[i]->N_bytes = 3; // Give each buffer a size of 3 bytes + sdu_bufs[i]->md.pdcp_sn = i; // PDCP SN for notifications + rlc1.write_sdu(std::move(sdu_bufs[i])); + } + + TESTASSERT(25 == rlc1.get_buffer_state()); // 2 Bytes * NBUFFS (header size) + NBUFFS * 3 (data) = 25 + + // Read 5 PDUs from RLC1 (1 byte each) + for (int i = 0; i < NBUFS; i++) { + uint32_t len = rlc1.read_pdu(pdu_bufs[i].msg, 5); // 2 bytes for header + 3 byte payload + pdu_bufs[i].N_bytes = len; + TESTASSERT_EQ(5, len); + } + + TESTASSERT(0 == rlc1.get_buffer_state()); + + // Write 5 PDUs into RLC2 + for (int i = 0; i < NBUFS; i++) { + if (i != 3) { + rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes); // Don't write RLC_SN=3. + } + } + + // Only after t-reassembly has expired, will the status report include NACKs. + TESTASSERT(3 == rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, 5); + status_buf.N_bytes = len; + + TESTASSERT(0 == rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check = {}; + rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); + TESTASSERT(status_check.ack_sn == 3); // 3 is the next expected SN (i.e. the lost packet.) + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + } + + // Step timers until reassambly timeout expires + for (int cnt = 0; cnt < 35; cnt++) { + timers.step_all(); + } + + // t-reassembly has expired. There should be a NACK in the status report. + TESTASSERT(5 == rlc2.get_buffer_state()); + { + // Read status PDU from RLC2 + byte_buffer_t status_buf; + int len = rlc2.read_pdu(status_buf.msg, 5); + status_buf.N_bytes = len; + + TESTASSERT(0 == rlc2.get_buffer_state()); + + // Assert status is correct + rlc_am_nr_status_pdu_t status_check = {}; + rlc_am_nr_read_status_pdu(&status_buf, rlc_am_nr_sn_size_t::size12bits, &status_check); + TESTASSERT(status_check.ack_sn == 5); // 5 is the next expected SN. + TESTASSERT(status_check.N_nack == 1); // We lost one PDU. + TESTASSERT(status_check.nacks[0].nack_sn == 3); // Lost PDU SN=3. + + // Write status PDU to RLC1 + rlc1.write_pdu(status_buf.msg, status_buf.N_bytes); + + // Check there is an Retx of SN=3 + TESTASSERT(5 == rlc1.get_buffer_state()); + } + + { + // Re-transmit PDU in 3 segments + for (int i = 0; i < 3; i++) { + byte_buffer_t retx_buf; + int len = rlc1.read_pdu(retx_buf.msg, 3); + retx_buf.N_bytes = len; + TESTASSERT(3 == len); + + rlc2.write_pdu(retx_buf.msg, retx_buf.N_bytes); + } + TESTASSERT(0 == rlc1.get_buffer_state()); + } + + // Check statistics + rlc_bearer_metrics_t metrics1 = rlc1.get_metrics(); + rlc_bearer_metrics_t metrics2 = rlc2.get_metrics(); + + // SDU metrics + TESTASSERT_EQ(5, metrics1.num_tx_sdus); + TESTASSERT_EQ(0, metrics1.num_rx_sdus); + TESTASSERT_EQ(15, metrics1.num_tx_sdu_bytes); + TESTASSERT_EQ(0, metrics1.num_rx_sdu_bytes); + TESTASSERT_EQ(0, metrics1.num_lost_sdus); + // PDU metrics + TESTASSERT_EQ(5 + 3, metrics1.num_tx_pdus); // 3 re-transmissions + TESTASSERT_EQ(2, metrics1.num_rx_pdus); // Two status PDU + TESTASSERT_EQ(18, + metrics1.num_tx_pdu_bytes); // 2 Bytes * NBUFFS (header size) + NBUFFS * 3 (data) + 3 rext (3 * 3) = 34 + TESTASSERT_EQ(3 + 5, metrics1.num_rx_pdu_bytes); // Two status PDU (one with a NACK) + TESTASSERT_EQ(0, metrics1.num_lost_sdus); // No lost SDUs + + // PDU metrics + TESTASSERT_EQ(0, metrics2.num_tx_sdus); + TESTASSERT_EQ(5, metrics2.num_rx_sdus); + TESTASSERT_EQ(0, metrics2.num_tx_sdu_bytes); + TESTASSERT_EQ(5, metrics2.num_rx_sdu_bytes); + TESTASSERT_EQ(0, metrics2.num_lost_sdus); + // SDU metrics + TESTASSERT_EQ(2, metrics2.num_tx_pdus); // Two status PDUs + TESTASSERT_EQ(5, metrics2.num_rx_pdus); // 5 PDUs (6 tx'ed, but one was lost) + TESTASSERT_EQ(5 + 3, metrics2.num_tx_pdu_bytes); // Two status PDU (one with a NACK) + TESTASSERT_EQ(15, metrics2.num_rx_pdu_bytes); // 2 Bytes * NBUFFS (header size) + NBUFFS (data) = 15 + TESTASSERT_EQ(0, metrics2.num_lost_sdus); // No lost SDUs + return SRSRAN_SUCCESS; +} + +int main() { // Setup the log message spy to intercept error and warning log entries from RLC if (!srslog::install_custom_sink(srsran::log_sink_message_spy::name(), @@ -439,6 +583,7 @@ int main(int argc, char** argv) TESTASSERT(basic_test() == SRSRAN_SUCCESS); TESTASSERT(lost_pdu_test() == SRSRAN_SUCCESS); TESTASSERT(basic_segmentation_test() == SRSRAN_SUCCESS); + TESTASSERT(segment_retx_test() == SRSRAN_SUCCESS); return SRSRAN_SUCCESS; } diff --git a/srsgnb/hdr/stack/rrc/rrc_nr.h b/srsgnb/hdr/stack/rrc/rrc_nr.h index 85508d457..a82aafdc7 100644 --- a/srsgnb/hdr/stack/rrc/rrc_nr.h +++ b/srsgnb/hdr/stack/rrc/rrc_nr.h @@ -71,7 +71,7 @@ public: int read_pdu_bcch_bch(const uint32_t tti, srsran::byte_buffer_t& buffer) final; int read_pdu_bcch_dlsch(uint32_t sib_index, srsran::byte_buffer_t& buffer) final; - /// User manegement + /// User management int add_user(uint16_t rnti, uint32_t pcell_cc_idx) final; void rem_user(uint16_t rnti); int update_user(uint16_t new_rnti, uint16_t old_rnti) final;