srsLTE/lib/src/rlc/rlc_am_nr.cc

2023 lines
75 KiB
C++

/**
* Copyright 2013-2022 Software Radio Systems Limited
*
* This file is part of srsRAN.
*
* srsRAN is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsRAN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include "srsran/rlc/rlc_am_nr.h"
#include "srsran/common/standard_streams.h"
#include "srsran/common/string_helpers.h"
#include "srsran/interfaces/ue_pdcp_interfaces.h"
#include "srsran/interfaces/ue_rrc_interfaces.h"
#include "srsran/rlc/rlc_am_nr_packing.h"
#include "srsran/srslog/event_trace.h"
#include <iostream>
#include <set>
namespace srsran {
const static uint32_t max_tx_queue_size = 256;
/****************************************************************************
* RLC AM NR entity
***************************************************************************/
/***************************************************************************
* Tx subclass implementation
***************************************************************************/
rlc_am_nr_tx::rlc_am_nr_tx(rlc_am* parent_) :
parent(parent_), rlc_am_base_tx(parent_->logger), poll_retransmit_timer(parent->timers->get_unique_timer())
{}
bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_)
{
cfg = cfg_.am_nr;
rb_name = parent->rb_name;
if (cfg_.tx_queue_length > max_tx_queue_size) {
RlcError("configuring tx queue length of %d PDUs too big. Maximum value is %d.",
cfg_.tx_queue_length,
max_tx_queue_size);
return false;
}
mod_nr = cardinality(cfg.tx_sn_field_length);
switch (cfg.tx_sn_field_length) {
case rlc_am_nr_sn_size_t::size12bits:
min_hdr_size = 2;
tx_window = std::unique_ptr<rlc_ringbuffer_base<rlc_amd_tx_pdu_nr> >(
new rlc_ringbuffer_t<rlc_amd_tx_pdu_nr, am_window_size(rlc_am_nr_sn_size_t::size12bits)>);
break;
case rlc_am_nr_sn_size_t::size18bits:
min_hdr_size = 3;
tx_window = std::unique_ptr<rlc_ringbuffer_base<rlc_amd_tx_pdu_nr> >(
new rlc_ringbuffer_t<rlc_amd_tx_pdu_nr, am_window_size(rlc_am_nr_sn_size_t::size18bits)>);
break;
default:
RlcError("attempt to configure unsupported tx_sn_field_length %s", to_string(cfg.tx_sn_field_length));
return false;
}
max_hdr_size = min_hdr_size + so_size;
// make sure Tx queue is empty before attempting to resize
empty_queue_no_lock();
tx_sdu_queue.resize(cfg_.tx_queue_length);
// Check timers are valid
if (not poll_retransmit_timer.is_valid()) {
RlcError("Configuring TX: timers not configured");
return false;
}
// Configure t_poll_retransmission timer
if (cfg.t_poll_retx > 0) {
poll_retransmit_timer.set(static_cast<uint32_t>(cfg.t_poll_retx),
[this](uint32_t timerid) { timer_expired(timerid); });
}
tx_enabled = true;
RlcDebug("RLC AM NR configured tx entity.");
return true;
}
bool rlc_am_nr_tx::has_data()
{
return do_status() || // if we have a status PDU to transmit
tx_sdu_queue.get_n_sdus() != 0 || !retx_queue.empty(); // or if there is a SDU queued up for transmission
}
/**
* Builds the RLC PDU.
*
* Called by the MAC, trough one of the PHY worker threads.
*
* \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<std::mutex> lock(mutex);
if (not tx_enabled) {
RlcDebug("RLC entity not active. Not generating PDU.");
return 0;
}
RlcDebug("MAC opportunity - bytes=%d, tx_window size=%zu PDUs", nof_bytes, tx_window->size());
// Tx STATUS if requested
if (do_status()) {
unique_byte_buffer_t tx_pdu = srsran::make_byte_buffer();
if (tx_pdu == nullptr) {
RlcError("Couldn't allocate PDU in %s().", __FUNCTION__);
return 0;
}
build_status_pdu(tx_pdu.get(), nof_bytes);
memcpy(payload, tx_pdu->msg, tx_pdu->N_bytes);
RlcDebug("Status PDU built - %d bytes", tx_pdu->N_bytes);
return tx_pdu->N_bytes;
}
// Retransmit if required
if (not retx_queue.empty()) {
RlcInfo("Re-transmission required. Retransmission queue size: %d", retx_queue.size());
return build_retx_pdu(payload, nof_bytes);
}
// Send remaining segment, if it exists
if (sdu_under_segmentation_sn != INVALID_RLC_SN) {
if (not tx_window->has_sn(sdu_under_segmentation_sn)) {
sdu_under_segmentation_sn = INVALID_RLC_SN;
RlcError("SDU currently being segmented does not exist in tx_window. Aborting segmentation SN=%d",
sdu_under_segmentation_sn);
return 0;
}
return build_continuation_sdu_segment((*tx_window)[sdu_under_segmentation_sn], payload, nof_bytes);
}
// Check whether there is something to TX
if (tx_sdu_queue.is_empty()) {
RlcInfo("No data available to be sent");
return 0;
}
return build_new_pdu(payload, nof_bytes);
}
/**
* Builds a new RLC PDU.
*
* 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.
* SDU 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.
*/
uint32_t rlc_am_nr_tx::build_new_pdu(uint8_t* payload, uint32_t nof_bytes)
{
if (nof_bytes <= min_hdr_size) {
RlcInfo("Not enough bytes for payload plus header. nof_bytes=%d", nof_bytes);
return 0;
}
// do not build any more PDU if window is already full
if (tx_window->full()) {
RlcInfo("Cannot build data PDU - Tx window full.");
return 0;
}
// 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());
do {
tx_sdu = tx_sdu_queue.read();
} while (tx_sdu == nullptr && tx_sdu_queue.size() != 0);
if (tx_sdu != nullptr) {
RlcDebug("Read RLC SDU - RLC_SN=%d, PDCP_SN=%d, %d bytes", st.tx_next, tx_sdu->md.pdcp_sn, tx_sdu->N_bytes);
} else {
RlcDebug("No SDUs left in the tx queue.");
return 0;
}
// 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.pdcp_sn = tx_sdu->md.pdcp_sn;
tx_pdu.sdu_buf = srsran::make_byte_buffer();
if (tx_pdu.sdu_buf == nullptr) {
RlcError("Couldn't allocate PDU in %s().", __FUNCTION__);
return 0;
}
// Copy SDU into TX window SDU 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(tx_pdu, payload, nof_bytes);
}
// Prepare header
rlc_am_nr_pdu_header_t hdr = {};
hdr.dc = RLC_DC_FIELD_DATA_PDU;
hdr.p = get_pdu_poll(st.tx_next, false, tx_sdu->N_bytes);
hdr.si = rlc_nr_si_field_t::full_sdu;
hdr.sn_size = cfg.tx_sn_field_length;
hdr.sn = st.tx_next;
tx_pdu.header = hdr;
log_rlc_am_nr_pdu_header_to_string(logger.info, hdr, rb_name);
// Write header
uint32_t len = rlc_am_nr_write_data_pdu_header(hdr, tx_sdu.get());
if (len > nof_bytes) {
RlcError("error writing AMD PDU header");
}
// Update TX Next
st.tx_next = (st.tx_next + 1) % mod_nr;
memcpy(payload, tx_sdu->msg, tx_sdu->N_bytes);
RlcDebug("wrote RLC PDU - %d bytes", tx_sdu->N_bytes);
return tx_sdu->N_bytes;
}
/**
* Builds a new RLC PDU segment, from a RLC SDU.
*
* \param [tx_pdu] is the tx_pdu info contained in the tx_window.
* \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.
*/
uint32_t 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_pdu.sdu_buf->N_bytes, nof_bytes);
// Sanity check: can this SDU be sent this in a single PDU?
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_pdu.sdu_buf->N_bytes,
nof_bytes);
return 0;
}
// Sanity check: can this SDU be sent considering header overhead?
if (nof_bytes <= min_hdr_size) { // Small header as SO is not present
RlcInfo("cannot build new sdu_segment, there are not enough bytes allocated to tx header plus data. nof_bytes=%d, "
"min_hdr_size=%d",
nof_bytes,
min_hdr_size);
return 0;
}
uint32_t segment_payload_len = nof_bytes - min_hdr_size;
// Save SDU currently being segmented
// This needs to be done before calculating the polling bit
// To make sure we check correctly that the buffers are empty.
sdu_under_segmentation_sn = st.tx_next;
// Prepare header
rlc_am_nr_pdu_header_t hdr = {};
hdr.dc = RLC_DC_FIELD_DATA_PDU;
hdr.p = get_pdu_poll(st.tx_next, false, segment_payload_len);
hdr.si = rlc_nr_si_field_t::first_segment;
hdr.sn_size = cfg.tx_sn_field_length;
hdr.sn = st.tx_next;
hdr.so = 0;
tx_pdu.header = hdr;
log_rlc_am_nr_pdu_header_to_string(logger.info, hdr, rb_name);
// Write header
uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(hdr, payload);
if (hdr_len >= nof_bytes || hdr_len != min_hdr_size) {
RlcError("error writing AMD PDU header");
return 0;
}
// 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.sdu_buf->msg, segment_payload_len);
// Store Segment Info
rlc_amd_tx_pdu_nr::pdu_segment segment_info;
segment_info.payload_len = segment_payload_len;
tx_pdu.segment_list.push_back(segment_info);
return hdr_len + segment_payload_len;
}
/**
* Build PDU segment for an RLC SDU that is already on-going segmentation.
*
* \param [tx_pdu] is the tx_pdu info contained in the tx_window.
* \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.
*/
uint32_t 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_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_sn,
tx_pdu.sdu_buf->N_bytes,
nof_bytes);
sdu_under_segmentation_sn = INVALID_RLC_SN;
return 0;
}
// Sanity check: can this SDU be sent considering header overhead?
if (nof_bytes <= max_hdr_size) { // Larger header size, as SO is present
RlcInfo("cannot build new sdu_segment, there are not enough bytes allocated to tx header plus data. nof_bytes=%d, "
"max_header_size=%d",
nof_bytes,
max_hdr_size);
return 0;
}
// Can the rest of the SDU be sent on a single segment PDU?
const rlc_amd_tx_pdu_nr::pdu_segment& seg = tx_pdu.segment_list.back();
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 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 = 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. "
"SDU bytes left %d, nof_bytes %d, ",
segment_payload_full_len,
nof_bytes);
si = rlc_nr_si_field_t::neither_first_nor_last_segment;
segment_payload_len = nof_bytes - max_hdr_size;
segment_payload_full_len = nof_bytes;
} else {
RlcInfo("grant is large enough for full SDU."
"SDU bytes left %d, nof_bytes %d, ",
segment_payload_full_len,
nof_bytes);
si = rlc_nr_si_field_t::last_segment;
sdu_under_segmentation_sn = INVALID_RLC_SN;
}
// Prepare header
rlc_am_nr_pdu_header_t hdr = {};
hdr.dc = RLC_DC_FIELD_DATA_PDU;
hdr.p = get_pdu_poll(st.tx_next, false, segment_payload_len);
hdr.si = si;
hdr.sn_size = cfg.tx_sn_field_length;
hdr.sn = st.tx_next;
hdr.so = last_byte;
tx_pdu.header = hdr;
log_rlc_am_nr_pdu_header_to_string(logger.info, hdr, rb_name);
// Write header
uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(hdr, payload);
if (hdr_len >= nof_bytes || hdr_len != max_hdr_size) {
RlcError("error writing AMD PDU header");
return 0;
}
// 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.sdu_buf->msg[last_byte], segment_payload_len);
// Store PDU segment info into tx_window
rlc_amd_tx_pdu_nr::pdu_segment segment_info = {};
segment_info.so = last_byte;
segment_info.payload_len = segment_payload_len;
tx_pdu.segment_list.push_back(segment_info);
if (si == rlc_nr_si_field_t::neither_first_nor_last_segment) {
RlcInfo("grant is not large enough for full SDU."
"Storing SDU segment info");
} else {
RlcInfo("grant is large enough for full SDU."
"Removing current SDU info");
// SDU is fully TX'ed. Increment TX_NEXT
st.tx_next = (st.tx_next + 1) % mod_nr;
}
return hdr_len + segment_payload_len;
}
/**
* Builds a retx RLC PDU.
*
* This will use the retx_queue to get information about the RLC PDU
* being retx'ed. The retx may have been previously transmitted as
* a full SDU or an SDU segment.
*
* \param [tx_pdu] is the tx_pdu info contained in the tx_window.
* \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.
*/
uint32_t rlc_am_nr_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes)
{
// Check there is at least 1 element before calling front()
if (retx_queue.empty()) {
RlcError("in build_retx_pdu(): retx_queue is empty");
return 0;
}
rlc_amd_retx_nr_t& retx = retx_queue.front();
// Sanity check - drop any retx SNs not present in tx_window
while (not tx_window->has_sn(retx.sn)) {
RlcInfo("SN=%d not in tx window, probably already ACKed. Skip and remove from retx queue", retx.sn);
retx_queue.pop();
if (!retx_queue.empty()) {
retx = retx_queue.front();
} else {
RlcInfo("empty retx queue, cannot provide any retx PDU");
return 0;
}
}
RlcDebug("RETX - SN=%d, is_segment=%s, current_so=%d, so_start=%d, segment_length=%d",
retx.sn,
retx.is_segment ? "true" : "false",
retx.current_so,
retx.so_start,
retx.segment_length);
// Is segmentation/re-segmentation required?
bool segmentation_required = is_retx_segmentation_required(retx, nof_bytes);
if (segmentation_required) {
return build_retx_pdu_with_segmentation(retx, payload, nof_bytes);
}
return build_retx_pdu_without_segmentation(retx, payload, nof_bytes);
}
/**
* Builds a retx RLC PDU, without requiring (re-)segmentation.
*
* The RETX PDU may be transporting a full SDU or an SDU segment.
*
* \param [retx] is the retx info contained in the retx_queue. This is passed by copy, to avoid
* issues when using retx after pop'ing it from the queue.
* \param [payload] is a pointer to the MAC buffer that will hold the PDU segment.
* \param [nof_bytes] is the number of bytes the RLC is allowed to fill.
*
* \returns the number of bytes written to the payload buffer.
* \remark this function will not update the SI. This means that if the retx is of the last
* SDU segment, the SI should already be of the `last_segment` type.
*/
uint32_t
rlc_am_nr_tx::build_retx_pdu_without_segmentation(const rlc_amd_retx_nr_t retx, uint8_t* payload, uint32_t nof_bytes)
{
srsran_assert(tx_window->has_sn(retx.sn), "Called %s without checking retx SN", __FUNCTION__);
srsran_assert(not is_retx_segmentation_required(retx, nof_bytes),
"Called %s without checking if segmentation was required",
__FUNCTION__);
// Get tx_pdu info from tx_window
rlc_amd_tx_pdu_nr& tx_pdu = (*tx_window)[retx.sn];
// Get expected header and payload len
uint32_t expected_hdr_len = get_retx_expected_hdr_len(retx);
uint32_t retx_payload_len = retx.is_segment ? (retx.so_start + retx.segment_length - retx.current_so)
: (*tx_window)[retx.sn].sdu_buf->N_bytes;
srsran_assert(nof_bytes >= (expected_hdr_len + retx_payload_len),
"Called %s but segmentation is required. nof_bytes=%d, expeced_hdr_len=%d, retx_payload_len=%d",
__FUNCTION__,
nof_bytes,
expected_hdr_len,
retx_payload_len);
// Log RETX info
RlcDebug("SDU%scan be fully re-transmitted. SN=%d, nof_bytes=%d, expected_hdr_len=%d, "
"current_so=%d, so_start=%d, segment_length=%d",
retx.is_segment ? " segment " : " ",
retx.sn,
nof_bytes,
expected_hdr_len,
retx.current_so,
retx.so_start,
retx.segment_length);
// Get RETX SN, current SO and SI
rlc_nr_si_field_t si = rlc_nr_si_field_t::full_sdu;
if (retx.is_segment) {
if (retx.current_so == 0) {
si = rlc_nr_si_field_t::first_segment;
} else if ((retx.current_so + retx_payload_len) < tx_pdu.sdu_buf->N_bytes) {
si = rlc_nr_si_field_t::neither_first_nor_last_segment;
} else {
si = rlc_nr_si_field_t::last_segment;
}
}
// Get RETX PDU payload size
uint32_t retx_pdu_payload_size = 0;
if (not retx.is_segment) {
// RETX full SDU
retx_pdu_payload_size = (*tx_window)[retx.sn].sdu_buf->N_bytes;
} else {
// RETX SDU segment
retx_pdu_payload_size = (retx.so_start + retx.segment_length - retx.current_so);
}
// Update RETX queue. This must be done before calculating
// the polling bit, to make sure the poll bit is calculated correctly
retx_queue.pop();
// Write header to payload
rlc_am_nr_pdu_header_t new_header = tx_pdu.header;
new_header.si = si;
new_header.so = retx.current_so;
new_header.p = get_pdu_poll(retx.sn, true, 0);
uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(new_header, payload);
// Write SDU/SDU segment to payload
uint32_t pdu_bytes = hdr_len + retx_pdu_payload_size;
srsran_assert(pdu_bytes <= nof_bytes, "Error calculating hdr_len and pdu_payload_len");
memcpy(&payload[hdr_len], &tx_pdu.sdu_buf->msg[retx.current_so], retx_pdu_payload_size);
// Log RETX
RlcHexInfo((*tx_window)[retx.sn].sdu_buf->msg,
(*tx_window)[retx.sn].sdu_buf->N_bytes,
"Original SDU SN=%d (%d B) (attempt %d/%d)",
retx.sn,
(*tx_window)[retx.sn].sdu_buf->N_bytes,
(*tx_window)[retx.sn].retx_count + 1,
cfg.max_retx_thresh);
RlcHexInfo(payload, pdu_bytes, "RETX PDU SN=%d (%d B)", retx.sn, pdu_bytes);
log_rlc_am_nr_pdu_header_to_string(logger.debug, new_header, rb_name);
debug_state();
return pdu_bytes;
}
/**
* Builds a retx RLC PDU that requires (re-)segmentation.
*
* \param [tx_pdu] is the tx_pdu info contained in the tx_window.
* \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.
*/
uint32_t rlc_am_nr_tx::build_retx_pdu_with_segmentation(rlc_amd_retx_nr_t& retx, uint8_t* payload, uint32_t nof_bytes)
{
// Get tx_pdu info from tx_window
srsran_assert(tx_window->has_sn(retx.sn), "Called %s without checking retx SN", __FUNCTION__);
srsran_assert(is_retx_segmentation_required(retx, nof_bytes),
"Called %s without checking if segmentation was not required",
__FUNCTION__);
rlc_amd_tx_pdu_nr& tx_pdu = (*tx_window)[retx.sn];
// Is this an SDU segment or a full SDU?
if (not retx.is_segment) {
RlcDebug("Creating SDU segment from full SDU. SN=%d Tx SDU (%d B), nof_bytes=%d B ",
retx.sn,
tx_pdu.sdu_buf->N_bytes,
nof_bytes);
} else {
RlcDebug("Creating SDU segment from SDU segment. SN=%d, current_so=%d, so_start=%d, segment_length=%d",
retx.sn,
retx.current_so,
retx.so_start,
retx.segment_length);
}
uint32_t expected_hdr_len = min_hdr_size;
rlc_nr_si_field_t si = rlc_nr_si_field_t::first_segment;
if (retx.current_so != 0) {
si = rlc_nr_si_field_t::neither_first_nor_last_segment;
expected_hdr_len = max_hdr_size;
}
// Sanity check: are there enough bytes for header plus data?
if (nof_bytes <= expected_hdr_len) {
RlcInfo("Not enough bytes for RETX payload plus header. SN=%d, nof_bytes=%d, hdr_len=%d",
retx.sn,
nof_bytes,
expected_hdr_len);
return 0;
}
// Sanity check: could this have been transmitted without segmentation?
if (nof_bytes > (tx_pdu.sdu_buf->N_bytes + expected_hdr_len)) {
RlcError("called %s, but there are enough bytes to avoid segmentation. SN=%d", __FUNCTION__, retx.sn);
return 0;
}
// Can the RETX PDU be transmitted in a single PDU?
uint32_t retx_pdu_payload_size = nof_bytes - expected_hdr_len;
// Write header
rlc_am_nr_pdu_header_t hdr = tx_pdu.header;
hdr.p = get_pdu_poll(retx.sn, true, 0);
hdr.so = retx.current_so;
hdr.si = si;
uint32_t hdr_len = rlc_am_nr_write_data_pdu_header(hdr, payload);
if (hdr_len >= nof_bytes || hdr_len != expected_hdr_len) {
log_rlc_am_nr_pdu_header_to_string(logger.error, hdr, rb_name);
RlcError("Error writing AMD PDU header. nof_bytes=%d, hdr_len=%d", nof_bytes, hdr_len);
return 0;
}
log_rlc_am_nr_pdu_header_to_string(logger.info, hdr, rb_name);
// Copy SDU segment into payload
srsran_assert((hdr_len + retx_pdu_payload_size) <= nof_bytes, "Error calculating hdr_len and segment_payload_len");
memcpy(&payload[hdr_len], &tx_pdu.sdu_buf->msg[retx.current_so], retx_pdu_payload_size);
// Store PDU segment info into tx_window
RlcDebug("Updating RETX segment info. SN=%d, is_segment=%s", retx.sn, retx.is_segment ? "true" : "false");
if (!retx.is_segment) {
// Retx is not a segment yet
rlc_amd_tx_pdu_nr::pdu_segment seg1 = {};
seg1.so = retx.current_so;
seg1.payload_len = retx_pdu_payload_size;
rlc_amd_tx_pdu_nr::pdu_segment seg2 = {};
seg2.so = retx.current_so + retx_pdu_payload_size;
seg2.payload_len = retx.segment_length - retx_pdu_payload_size;
tx_pdu.segment_list.push_back(seg1);
tx_pdu.segment_list.push_back(seg2);
RlcDebug("New segment: SN=%d, SO=%d len=%d", retx.sn, seg1.so, seg1.payload_len);
RlcDebug("New segment: SN=%d, SO=%d len=%d", retx.sn, seg2.so, seg2.payload_len);
} else {
// Retx is already a segment
// Find current segment in segment list.
std::list<rlc_amd_tx_pdu_nr::pdu_segment>::iterator it;
for (it = tx_pdu.segment_list.begin(); it != tx_pdu.segment_list.end(); ++it) {
if (it->so == retx.current_so) {
break;
}
}
if (it != tx_pdu.segment_list.end()) {
rlc_amd_tx_pdu_nr::pdu_segment seg1 = {};
seg1.so = it->so;
seg1.payload_len = retx_pdu_payload_size;
rlc_amd_tx_pdu_nr::pdu_segment seg2 = {};
seg2.so = it->so + retx_pdu_payload_size;
seg2.payload_len = it->payload_len - retx_pdu_payload_size;
std::list<rlc_amd_tx_pdu_nr::pdu_segment>::iterator begin_it = tx_pdu.segment_list.erase(it);
std::list<rlc_amd_tx_pdu_nr::pdu_segment>::iterator insert_it = tx_pdu.segment_list.insert(begin_it, seg2);
std::list<rlc_amd_tx_pdu_nr::pdu_segment>::iterator insert_it2 = tx_pdu.segment_list.insert(insert_it, seg1);
RlcDebug("Old segment SN=%d, SO=%d len=%d", retx.sn, retx.current_so, retx.segment_length);
RlcDebug("New segment SN=%d, SO=%d len=%d", retx.sn, seg1.so, seg1.payload_len);
RlcDebug("New segment SN=%d, SO=%d len=%d", retx.sn, seg2.so, seg2.payload_len);
} else {
RlcDebug("Could not find segment. SN=%d, SO=%d length=%d", retx.sn, retx.current_so, retx.segment_length);
}
}
// Update retx queue
retx.is_segment = true;
retx.current_so = retx.current_so + retx_pdu_payload_size;
RlcDebug("Updated RETX info. is_segment=%s, current_so=%d, so_start=%d, segment_length=%d",
retx.is_segment ? "true" : "false",
retx.current_so,
retx.so_start,
retx.segment_length);
if (retx.current_so >= tx_pdu.sdu_buf->N_bytes) {
RlcError("Current SO larger or equal to SDU size when creating SDU segment. SN=%d, current SO=%d, SO_start=%d, "
"segment_length=%d",
retx.sn,
retx.current_so,
retx.so_start,
retx.segment_length);
return 0;
}
if (retx.current_so >= retx.so_start + retx.segment_length) {
RlcError("Current SO larger than SO_start + segment_length. SN=%d, current SO=%d, SO_start=%d, segment_length=%s",
retx.sn,
retx.current_so,
retx.so_start,
retx.segment_length);
return 0;
}
return hdr_len + retx_pdu_payload_size;
}
bool rlc_am_nr_tx::is_retx_segmentation_required(const rlc_amd_retx_nr_t& retx, uint32_t nof_bytes)
{
bool segmentation_required = false;
if (retx.is_segment) {
uint32_t expected_hdr_size = retx.current_so == 0 ? min_hdr_size : max_hdr_size;
if (nof_bytes < ((retx.so_start + retx.segment_length - retx.current_so) + expected_hdr_size)) {
RlcInfo("Re-segmentation required for RETX. SN=%d", retx.sn);
segmentation_required = true;
}
} else {
if (nof_bytes < ((*tx_window)[retx.sn].sdu_buf->N_bytes + min_hdr_size)) {
RlcInfo("Segmentation required for RETX. SN=%d", retx.sn);
segmentation_required = true;
}
}
return segmentation_required;
}
uint32_t rlc_am_nr_tx::get_retx_expected_hdr_len(const rlc_amd_retx_nr_t& retx)
{
uint32_t expected_hdr_len = min_hdr_size;
if (retx.is_segment && retx.current_so != 0) {
expected_hdr_len = max_hdr_size;
}
return expected_hdr_len;
}
uint32_t rlc_am_nr_tx::build_status_pdu(byte_buffer_t* payload, uint32_t nof_bytes)
{
RlcInfo("generating status PDU. Bytes available:%d", nof_bytes);
rlc_am_nr_status_pdu_t status(cfg.rx_sn_field_length); // carries status of RX entity, hence use SN length of RX
int pdu_len = rx->get_status_pdu(&status, nof_bytes);
if (pdu_len == SRSRAN_ERROR) {
RlcDebug("deferred status PDU. Cause: Failed to acquire rx lock");
pdu_len = 0;
} else if (pdu_len > 0 && nof_bytes >= static_cast<uint32_t>(pdu_len)) {
RlcDebug("generated status PDU. Bytes:%d", pdu_len);
log_rlc_am_nr_status_pdu_to_string(logger.info, "TX status PDU - %s", &status, rb_name);
pdu_len = rlc_am_nr_write_status_pdu(status, cfg.tx_sn_field_length, payload);
} else {
RlcInfo("cannot tx status PDU - %d bytes available, %d bytes required", nof_bytes, pdu_len);
pdu_len = 0;
}
return payload->N_bytes;
}
void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes)
{
if (not tx_enabled) {
return;
}
std::lock_guard<std::mutex> lock(mutex);
rlc_am_nr_status_pdu_t status(cfg.tx_sn_field_length);
RlcHexDebug(payload, nof_bytes, "%s Rx control PDU", parent->rb_name);
rlc_am_nr_read_status_pdu(payload, nof_bytes, cfg.tx_sn_field_length, &status);
log_rlc_am_nr_status_pdu_to_string(logger.info, "RX status PDU: %s", &status, parent->rb_name);
/*
* Sanity check the received status report.
* Checking if the ACK_SN is inside the valid ACK_SN window (the TX window "off-by-one")
* makes sure we discard out of order status reports.
* Checking if ACK_SN > Tx_Next + 1 makes sure we do not receive a ACK/NACK for something we did not TX
* ACK_SN may be equal to TX_NEXT + 1, if not all SDU segments with SN=TX_NEXT have been transmitted.
*/
if (not valid_ack_sn(status.ack_sn)) {
RlcInfo("Received ACK with SN outside of TX_WINDOW, ignoring status report. ACK_SN=%d, TX_NEXT_ACK=%d.",
status.ack_sn,
st.tx_next_ack);
info_state();
return;
}
if (tx_mod_base_nr(status.ack_sn) > tx_mod_base_nr(st.tx_next + 1)) {
RlcWarning("Received ACK with SN larger than TX_NEXT, ignoring status report. SN=%d, TX_NEXT_ACK=%d, TX_NEXT=%d",
status.ack_sn,
st.tx_next_ack,
st.tx_next);
info_state();
return;
}
/**
* Section 5.3.3.3: Reception of a STATUS report
* - if the STATUS report comprises a positive or negative acknowledgement for the RLC SDU with sequence
* number equal to POLL_SN:
* - if t-PollRetransmit is running:
* - stop and reset t-PollRetransmit.
*/
if (tx_mod_base_nr(st.poll_sn) < tx_mod_base_nr(status.ack_sn)) {
if (poll_retransmit_timer.is_running()) {
RlcDebug("Received ACK or NACK for POLL_SN=%d. Stopping t-PollRetransmit", st.poll_sn);
poll_retransmit_timer.stop();
} else {
RlcDebug("Received ACK or NACK for POLL_SN=%d. t-PollRetransmit already stopped", st.poll_sn);
}
} else {
RlcDebug("POLL_SN=%d > ACK_SN=%d. Not stopping t-PollRetransmit ", st.poll_sn, status.ack_sn);
}
/*
* - if the SN of the corresponding RLC SDU falls within the range
* TX_Next_Ack <= SN < = the highest SN of the AMD PDU among the AMD PDUs submitted to lower layer:
* - consider the RLC SDU or the RLC SDU segment for which a negative acknowledgement was received for
* retransmission.
*/
// Process ACKs
uint32_t stop_sn = status.nacks.size() == 0
? status.ack_sn
: status.nacks[0].nack_sn; // Stop processing ACKs at the first NACK, if it exists.
for (uint32_t sn = st.tx_next_ack; tx_mod_base_nr(sn) < tx_mod_base_nr(stop_sn); sn = (sn + 1) % mod_nr) {
if (tx_window->has_sn(sn)) {
notify_info_vec.push_back((*tx_window)[sn].pdcp_sn);
retx_queue.remove_sn(sn); // remove any pending retx for that SN
tx_window->remove_pdu(sn);
st.tx_next_ack = (sn + 1) % mod_nr;
} else {
RlcError("Missing ACKed SN from TX window");
break;
}
}
RlcDebug("Processed status report ACKs. ACK_SN=%d. Tx_Next_Ack=%d", status.ack_sn, st.tx_next_ack);
// Process N_nacks
std::set<uint32_t> retx_sn_set; // Set of PDU SNs added for retransmission (no duplicates)
for (uint32_t nack_idx = 0; nack_idx < status.nacks.size(); nack_idx++) {
if (status.nacks[nack_idx].has_nack_range) {
for (uint32_t range_sn = status.nacks[nack_idx].nack_sn;
range_sn < status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range;
range_sn++) {
rlc_status_nack_t nack = {};
nack.nack_sn = range_sn;
if (status.nacks[nack_idx].has_so) {
// Apply so_start to first range item
if (range_sn == status.nacks[nack_idx].nack_sn) {
nack.so_start = status.nacks[nack_idx].so_start;
}
// Apply so_end to last range item
if (range_sn == (status.nacks[nack_idx].nack_sn + status.nacks[nack_idx].nack_range - 1)) {
nack.so_end = status.nacks[nack_idx].so_end;
}
// Enable has_so only if the offsets do not span the whole SDU
nack.has_so = (nack.so_start != 0) || (nack.so_end != rlc_status_nack_t::so_end_of_sdu);
}
handle_nack(nack, retx_sn_set);
}
} else {
handle_nack(status.nacks[nack_idx], retx_sn_set);
}
}
// Process retx_count and inform upper layers if needed
for (uint32_t retx_sn : retx_sn_set) {
auto& pdu = (*tx_window)[retx_sn];
// Increment retx_count
if (pdu.retx_count == RETX_COUNT_NOT_STARTED) {
// Set retx_count = 0 on first RE-transmission of associated SDU (38.322 Sec. 5.3.2)
pdu.retx_count = 0;
} else {
// Increment otherwise
pdu.retx_count++;
}
// Inform upper layers if needed
check_sn_reached_max_retx(retx_sn);
}
// Notify PDCP
if (not notify_info_vec.empty()) {
parent->pdcp->notify_delivery(parent->lcid, notify_info_vec);
}
notify_info_vec.clear();
}
void rlc_am_nr_tx::handle_nack(const rlc_status_nack_t& nack, std::set<uint32_t>& retx_sn_set)
{
if (tx_mod_base_nr(st.tx_next_ack) <= tx_mod_base_nr(nack.nack_sn) &&
tx_mod_base_nr(nack.nack_sn) <= tx_mod_base_nr(st.tx_next)) {
RlcDebug("Handling NACK for SN=%d", nack.nack_sn);
if (tx_window->has_sn(nack.nack_sn)) {
auto& pdu = (*tx_window)[nack.nack_sn];
if (nack.has_so) {
// NACK'ing missing bytes in SDU segment.
// Retransmit all SDU segments within those missing bytes.
if (pdu.segment_list.empty()) {
RlcError("Received NACK with SO, but there is no segment information. SN=%d", nack.nack_sn);
}
bool segment_found = false;
for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) {
if (segm.so >= nack.so_start && segm.so <= nack.so_end) {
if (not retx_queue.has_sn(nack.nack_sn, segm.so)) {
rlc_amd_retx_nr_t& retx = retx_queue.push();
retx.sn = nack.nack_sn;
retx.is_segment = true;
retx.so_start = segm.so;
retx.current_so = segm.so;
retx.segment_length = segm.payload_len;
retx_sn_set.insert(nack.nack_sn);
RlcInfo("Scheduled RETX of SDU segment SN=%d, so_start=%d, segment_length=%d",
retx.sn,
retx.so_start,
retx.segment_length);
} else {
RlcInfo("Skip already scheduled RETX of SDU segment SN=%d, so_start=%d, segment_length=%d",
nack.nack_sn,
segm.so,
segm.payload_len);
}
segment_found = true;
}
}
if (!segment_found) {
RlcWarning("Could not find segment for NACK_SN=%d. SO_start=%d, SO_end=%d",
nack.nack_sn,
nack.so_start,
nack.so_end);
for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) {
RlcDebug("Segments for SN=%d. SO=%d, SO_end=%d", nack.nack_sn, segm.so, segm.payload_len);
}
}
} else {
// NACK'ing full SDU.
// add to retx queue if it's not already there
if (not retx_queue.has_sn(nack.nack_sn)) {
// Have we segmented the SDU already?
if ((*tx_window)[nack.nack_sn].segment_list.empty()) {
rlc_amd_retx_nr_t& retx = retx_queue.push();
retx.sn = nack.nack_sn;
retx.is_segment = false;
retx.so_start = 0;
retx.current_so = 0;
retx.segment_length = pdu.sdu_buf->N_bytes;
retx_sn_set.insert(nack.nack_sn);
RlcInfo("Scheduled RETX of SDU SN=%d", retx.sn);
} else {
RlcInfo("Scheduled RETX of SDU SN=%d", nack.nack_sn);
retx_sn_set.insert(nack.nack_sn);
for (auto segm : (*tx_window)[nack.nack_sn].segment_list) {
rlc_amd_retx_nr_t& retx = retx_queue.push();
retx.sn = nack.nack_sn;
retx.is_segment = true;
retx.so_start = segm.so;
retx.current_so = segm.so;
retx.segment_length = segm.payload_len;
RlcInfo("Scheduled RETX of SDU Segment. SN=%d, SO=%d, len=%d", retx.sn, segm.so, segm.payload_len);
}
}
} else {
RlcInfo("RETX queue already has NACK_SN. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d",
nack.nack_sn,
st.tx_next_ack,
st.tx_next);
}
}
} else {
RlcInfo("TX window does not contain NACK_SN. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d",
nack.nack_sn,
st.tx_next_ack,
st.tx_next);
} // TX window containts NACK SN
} else {
RlcInfo(
"RETX not in expected range. SDU SN=%d, Tx_Next_Ack=%d, Tx_Next=%d", nack.nack_sn, st.tx_next_ack, st.tx_next);
} // NACK SN within expected range
}
/**
* 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<std::mutex> lock(parent->metrics_mutex);
parent->metrics.num_lost_pdus++;
}
}
uint32_t rlc_am_nr_tx::get_buffer_state()
{
uint32_t tx_queue = 0;
uint32_t prio_tx_queue = 0;
get_buffer_state(tx_queue, prio_tx_queue);
return tx_queue + prio_tx_queue;
}
void rlc_am_nr_tx::get_buffer_state(uint32_t& n_bytes_new, uint32_t& n_bytes_prio)
{
std::lock_guard<std::mutex> lock(mutex);
RlcDebug("buffer state - do_status=%s", do_status() ? "yes" : "no");
if (!tx_enabled) {
RlcError("get_buffer_state() failed: TX is not enabled.");
return;
}
// Bytes needed for status report
if (do_status()) {
n_bytes_prio += rx->get_status_pdu_length();
RlcDebug("buffer state - total status report: %d bytes", n_bytes_prio);
}
// Bytes needed for retx
for (const rlc_amd_retx_nr_t& retx : retx_queue.get_inner_queue()) {
RlcDebug("buffer state - retx - SN=%d, Segment: %s, %d:%d",
retx.sn,
retx.is_segment ? "true" : "false",
retx.so_start,
retx.so_start + retx.segment_length - 1);
if (tx_window->has_sn(retx.sn)) {
int req_bytes = retx.segment_length;
int hdr_req_bytes = (retx.is_segment && retx.current_so != 0) ? max_hdr_size : min_hdr_size;
if (req_bytes <= 0) {
RlcError("buffer state - retx - invalid length=%d for SN=%d", req_bytes, retx.sn);
} else {
n_bytes_prio += (req_bytes + hdr_req_bytes);
RlcDebug("buffer state - retx: %d bytes", n_bytes_prio);
}
} else {
RlcWarning("buffer state - retx for SN=%d is outside the tx_window", retx.sn);
}
}
// Bytes needed for tx of the rest of the SDU that is currently under segmentation (if any)
if (sdu_under_segmentation_sn != INVALID_RLC_SN) {
if (tx_window->has_sn(sdu_under_segmentation_sn)) {
rlc_amd_tx_pdu_nr& seg_pdu = (*tx_window)[sdu_under_segmentation_sn];
if (not seg_pdu.segment_list.empty()) {
// obtain amount of already transmitted Bytes
const rlc_amd_tx_pdu_nr::pdu_segment& seg = seg_pdu.segment_list.back();
uint32_t last_byte = seg.so + seg.payload_len;
if (last_byte <= seg_pdu.sdu_buf->N_bytes) {
// compute remaining bytes pending for transmission
uint32_t remaining_bytes = seg_pdu.sdu_buf->N_bytes - last_byte;
n_bytes_new += remaining_bytes + max_hdr_size;
} else {
RlcError(
"buffer state - last segment of SDU under segmentation exceeds SDU len. SDU len=%d B, last_byte=%d B",
seg_pdu.sdu_buf->N_bytes,
last_byte);
}
} else {
RlcError("buffer state - SDU under segmentation has empty segment list. Ignoring SN=%d",
sdu_under_segmentation_sn);
}
} else {
sdu_under_segmentation_sn = INVALID_RLC_SN;
RlcError("buffer state - SDU under segmentation does not exist in tx_window. Aborting segmentation SN=%d",
sdu_under_segmentation_sn);
}
}
// Bytes needed for tx SDUs in queue
uint32_t n_sdus = tx_sdu_queue.get_n_sdus();
n_bytes_new += tx_sdu_queue.size_bytes();
// Room needed for fixed header of data PDUs
n_bytes_new += min_hdr_size * n_sdus;
RlcDebug("total buffer state - %d SDUs (%d B)", n_sdus, n_bytes_new + n_bytes_prio);
if (bsr_callback) {
RlcDebug("calling BSR callback - %d new_tx, %d priority bytes", n_bytes_new, n_bytes_prio);
bsr_callback(parent->lcid, n_bytes_new, n_bytes_prio);
}
}
/*
* Check whether the polling bit needs to be set, as specified in
* TS 38.322, section 5.3.3.2
*/
uint8_t rlc_am_nr_tx::get_pdu_poll(uint32_t sn, bool is_retx, uint32_t sdu_bytes)
{
RlcDebug("Checking poll bit requirements for PDU. SN=%d, retx=%s, sdu_bytes=%d, POLL_SN=%d",
sn,
is_retx ? "true" : "false",
sdu_bytes,
st.poll_sn);
/* For each AMD PDU or AMD PDU segment that has not been previoulsy tranmitted:
* - increment PDU_WITHOUT_POLL by one;
* - increment BYTE_WITHOUT_POLL by every new byte of Data field element that it maps to the Data field of the AMD
* PDU;
* - if PDU_WITHOUT_POLL >= pollPDU; or
* - if BYTE_WITHOUT_POLL >= pollByte:
* - include a poll in the AMD PDU as described below.
*/
uint8_t poll = 0;
if (!is_retx) {
st.pdu_without_poll++;
st.byte_without_poll += sdu_bytes;
if (cfg.poll_pdu > 0 && st.pdu_without_poll >= (uint32_t)cfg.poll_pdu) {
poll = 1;
RlcDebug("Setting poll bit due to PollPDU. SN=%d, POLL_SN=%d", sn, st.poll_sn);
}
if (cfg.poll_byte > 0 && st.byte_without_poll >= (uint32_t)cfg.poll_byte) {
poll = 1;
RlcDebug("Setting poll bit due to PollBYTE. SN=%d, POLL_SN=%d", sn, st.poll_sn);
}
}
/*
* - if both the transmission buffer and the retransmission buffer becomes empty
* (excluding transmitted RLC SDUs or RLC SDU segments awaiting acknowledgements)
* after the transmission of the AMD PDU; or
* - if no new RLC SDU can be transmitted after the transmission of the AMD PDU (e.g. due to window stalling);
* - include a poll in the AMD PDU as described below.
*/
if ((tx_sdu_queue.is_empty() && retx_queue.empty() && sdu_under_segmentation_sn == INVALID_RLC_SN) ||
tx_window->full()) {
RlcDebug("Setting poll bit due to empty buffers/inablity to TX. SN=%d, POLL_SN=%d", sn, st.poll_sn);
poll = 1;
}
/*
* - If poll bit is included:
* - set PDU_WITHOUT_POLL to 0;
* - set BYTE_WITHOUT_POLL to 0.
*/
if (poll == 1) {
st.pdu_without_poll = 0;
st.byte_without_poll = 0;
/*
* - set POLL_SN to the highest SN of the AMD PDU among the AMD PDUs submitted to lower layer;
* - if t-PollRetransmit is not running:
* - start t-PollRetransmit.
* - else:
* - restart t-PollRetransmit.
*/
if (!is_retx) {
// This is not an RETX, but a new transmission
// As such it should be the highest SN submitted to the lower layers
st.poll_sn = sn;
RlcDebug("Setting new POLL_SN. POLL_SN=%d", sn);
}
if (cfg.t_poll_retx > 0) {
if (not poll_retransmit_timer.is_running()) {
poll_retransmit_timer.run();
} else {
poll_retransmit_timer.stop();
poll_retransmit_timer.run();
}
RlcInfo("Started t-PollRetransmit. POLL_SN=%d", st.poll_sn);
}
}
return poll;
}
bool rlc_am_nr_tx::do_status()
{
return rx->get_do_status();
}
void rlc_am_nr_tx::reestablish()
{
stop();
}
void rlc_am_nr_tx::empty_queue()
{
std::lock_guard<std::mutex> lock(mutex);
empty_queue_no_lock();
}
void rlc_am_nr_tx::empty_queue_no_lock()
{
// deallocate all SDUs in transmit queue
while (tx_sdu_queue.size() > 0) {
unique_byte_buffer_t buf = tx_sdu_queue.read();
}
}
void rlc_am_nr_tx::stop()
{
std::lock_guard<std::mutex> lock(mutex);
empty_queue_no_lock();
if (parent->timers != nullptr && poll_retransmit_timer.is_valid()) {
poll_retransmit_timer.stop();
}
st = {};
sdu_under_segmentation_sn = INVALID_RLC_SN;
// Drop all messages in TX window
tx_window->clear();
// Drop all messages in RETX queue
retx_queue.clear();
tx_enabled = false;
}
void rlc_am_nr_tx::timer_expired(uint32_t timeout_id)
{
std::unique_lock<std::mutex> lock(mutex);
// t-PollRetransmit
if (poll_retransmit_timer.is_valid() && poll_retransmit_timer.id() == timeout_id) {
RlcDebug("Poll retransmission timer expired after %dms", poll_retransmit_timer.duration());
debug_state();
/*
* - if both the transmission buffer and the retransmission buffer are empty
* (excluding transmitted RLC SDU or RLC SDU segment awaiting acknowledgements); or
* - if no new RLC SDU or RLC SDU segment can be transmitted (e.g. due to window stalling):
* - consider the RLC SDU with the highest SN among the RLC SDUs submitted to lower layer for
* retransmission; or
* - consider any RLC SDU which has not been positively acknowledged for retransmission.
* - include a poll in an AMD PDU as described in section 5.3.3.2.
*/
if ((tx_sdu_queue.is_empty() && retx_queue.empty()) || tx_window->full()) {
if (tx_window->empty()) {
RlcError("t-PollRetransmit expired, but the tx_window is empty. POLL_SN=%d, Tx_Next_Ack=%d, tx_window_size=%d",
st.poll_sn,
st.tx_next_ack,
tx_window->size());
return;
}
if (not tx_window->has_sn(st.tx_next_ack)) {
RlcError("t-PollRetransmit expired, but Tx_Next_Ack is not in the tx_widow. POLL_SN=%d, Tx_Next_Ack=%d, "
"tx_window_size=%d",
st.poll_sn,
st.tx_next_ack,
tx_window->size());
return;
}
// RETX first RLC SDU that has not been ACKed
// or first SDU segment of the first RLC SDU
// that has not been acked
rlc_amd_retx_nr_t& retx = retx_queue.push();
retx.sn = st.tx_next_ack;
if ((*tx_window)[st.tx_next_ack].segment_list.empty()) {
// Full SDU
retx.is_segment = false;
retx.so_start = 0;
retx.segment_length = (*tx_window)[st.tx_next_ack].sdu_buf->N_bytes;
retx.current_so = 0;
} else {
// To make sure we do not mess up the segment list
// We RETX an SDU segment instead of the full SDU
// if the SDU has been segmented before.
// As we cannot know which segments have been ACKed before
// we simply RETX the first one.
retx.is_segment = true;
retx.so_start = 0;
retx.current_so = 0;
retx.segment_length = (*tx_window)[st.tx_next_ack].segment_list.begin()->payload_len;
}
RlcDebug("Retransmission because of t-PollRetransmit. RETX SN=%d, is_segment=%s, so_start=%d, segment_length=%d",
retx.sn,
retx.is_segment ? "true" : "false",
retx.so_start,
retx.segment_length);
}
return;
}
}
/*
* Window helpers
*/
uint32_t rlc_am_nr_tx::tx_mod_base_nr(uint32_t sn) const
{
return (sn - st.tx_next_ack) % mod_nr;
}
uint32_t rlc_am_nr_tx::tx_window_size() const
{
return am_window_size(cfg.tx_sn_field_length);
}
bool rlc_am_nr_tx::inside_tx_window(uint32_t sn) const
{
// TX_Next_Ack <= SN < TX_Next_Ack + AM_Window_Size
return tx_mod_base_nr(sn) < tx_window_size();
}
/*
* This function is used to check if a received status report
* as a valid ACK_SN.
*
* ACK_SN may be equal to TX_NEXT + AM_Window_Size if the PDU
* with SN=TX_NEXT+AM_Window_Size has been received by the RX
* An ACK_SN == Tx_Next_Ack doesn't ACK or NACKs any PDUs, as
* such, such a status report can be discarded.
*/
bool rlc_am_nr_tx::valid_ack_sn(uint32_t sn) const
{
// Tx_Next_Ack < SN <= TX_Next + AM_Window_Size
return (0 < tx_mod_base_nr(sn)) && (tx_mod_base_nr(sn) <= tx_window_size());
}
/*
* Debug Helpers
*/
void rlc_am_nr_tx::debug_state() const
{
RlcDebug("TX entity state: Tx_Next_Ack=%d, Tx_Next=%d, POLL_SN=%d, PDU_WITHOUT_POLL=%d, BYTE_WITHOUT_POLL=%d",
st.tx_next_ack,
st.tx_next,
st.poll_sn,
st.pdu_without_poll,
st.byte_without_poll);
}
void rlc_am_nr_tx::info_state() const
{
RlcInfo("TX window state: SDUs %d", tx_window->size());
RlcInfo("TX entity state: Tx_Next_Ack=%d, Tx_Next=%d, POLL_SN=%d, PDU_WITHOUT_POLL=%d, BYTE_WITHOUT_POLL=%d",
st.tx_next_ack,
st.tx_next,
st.poll_sn,
st.pdu_without_poll,
st.byte_without_poll);
}
void rlc_am_nr_tx::debug_window() const
{
RlcDebug("TX window state: Tx_Next_Ack=%d, Tx_Next=%d, SDUs=%d", st.tx_next_ack, st.tx_next, tx_window->size());
}
/****************************************************************************
* Rx subclass implementation
***************************************************************************/
rlc_am_nr_rx::rlc_am_nr_rx(rlc_am* parent_) :
parent(parent_),
pool(byte_buffer_pool::get_instance()),
status_prohibit_timer(parent->timers->get_unique_timer()),
reassembly_timer(parent->timers->get_unique_timer()),
rlc_am_base_rx(parent_, parent_->logger)
{}
bool rlc_am_nr_rx::configure(const rlc_config_t& cfg_)
{
cfg = cfg_.am_nr;
rb_name = parent->rb_name;
// Configure status prohibit timer
if (cfg.t_status_prohibit > 0) {
status_prohibit_timer.set(static_cast<uint32_t>(cfg.t_status_prohibit),
[this](uint32_t timerid) { timer_expired(timerid); });
}
// Configure t_reassembly timer
if (cfg.t_reassembly > 0) {
reassembly_timer.set(static_cast<uint32_t>(cfg.t_reassembly), [this](uint32_t timerid) { timer_expired(timerid); });
}
mod_nr = cardinality(cfg.rx_sn_field_length);
switch (cfg.rx_sn_field_length) {
case rlc_am_nr_sn_size_t::size12bits:
rx_window = std::unique_ptr<rlc_ringbuffer_base<rlc_amd_rx_sdu_nr_t> >(
new rlc_ringbuffer_t<rlc_amd_rx_sdu_nr_t, am_window_size(rlc_am_nr_sn_size_t::size12bits)>);
break;
case rlc_am_nr_sn_size_t::size18bits:
rx_window = std::unique_ptr<rlc_ringbuffer_base<rlc_amd_rx_sdu_nr_t> >(
new rlc_ringbuffer_t<rlc_amd_rx_sdu_nr_t, am_window_size(rlc_am_nr_sn_size_t::size18bits)>);
break;
default:
RlcError("attempt to configure unsupported rx_sn_field_length %s", to_string(cfg.rx_sn_field_length));
return false;
}
RlcDebug("RLC AM NR configured rx entity.");
return true;
}
void rlc_am_nr_rx::stop()
{
std::lock_guard<std::mutex> lock(mutex);
if (parent->timers != nullptr && reassembly_timer.is_valid()) {
reassembly_timer.stop();
}
if (parent->timers != nullptr && status_prohibit_timer.is_valid()) {
status_prohibit_timer.stop();
}
st = {};
do_status = false;
// Drop all messages in RX window
rx_window->clear();
}
void rlc_am_nr_rx::reestablish()
{
stop();
}
void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
{
std::lock_guard<std::mutex> lock(mutex);
// Get AMD PDU Header
rlc_am_nr_pdu_header_t header = {};
uint32_t hdr_len = rlc_am_nr_read_data_pdu_header(payload, nof_bytes, cfg.rx_sn_field_length, &header);
RlcHexInfo(payload, nof_bytes, "Rx data PDU SN=%d (%d B)", header.sn, nof_bytes);
log_rlc_am_nr_pdu_header_to_string(logger.debug, header, rb_name);
// Trigger polling if poll bit is set.
// We do this before checking if the PDU is inside the RX window,
// as the RX window may have advanced without the TX having received the ACKs
// This can cause a data stall, whereby the TX keeps retransmiting
// a PDU outside of the Rx window.
// Also, we do this before discarding duplicate SDUs/SDU segments
// Because t-PollRetransmit may transmit a PDU that was already
// received.
if (header.p != 0U) {
RlcInfo("status packet requested through polling bit");
do_status = true;
}
// Check whether SDU is within Rx Window
if (!inside_rx_window(header.sn)) {
RlcInfo("SN=%d outside rx window [%d:%d] - discarding", header.sn, st.rx_next, st.rx_next + rx_window_size());
return;
}
// Section 5.2.3.2.2, discard duplicate PDUs
if (rx_window->has_sn(header.sn) && (*rx_window)[header.sn].fully_received) {
RlcInfo("discarding duplicate SN=%d", header.sn);
return;
}
// Section 5.2.3.2.2, discard segments with overlapping bytes
if (rx_window->has_sn(header.sn) && header.si != rlc_nr_si_field_t::full_sdu) {
for (const auto& segm : (*rx_window)[header.sn].segments) {
uint32_t segm_last_byte = segm.header.so + segm.buf->N_bytes - 1;
uint32_t pdu_last_byte = header.so + nof_bytes - hdr_len - 1;
if ((header.so >= segm.header.so && header.so <= segm_last_byte) ||
(pdu_last_byte >= segm.header.so && pdu_last_byte <= segm_last_byte)) {
RlcInfo("Got SDU segment with duplicate bytes. Discarding.");
RlcInfo("Discarded SDU segment. SN=%d, SO=%d, last_byte=%d, payload=%d",
header.sn,
header.so,
header.so + (nof_bytes - hdr_len),
(nof_bytes - hdr_len));
RlcInfo("Overlaping with SDU segment with SN=%d, SO=%d, last_byte=%d, payload=%d",
header.sn,
segm.header.so,
segm_last_byte,
segm.buf->N_bytes);
return;
}
}
}
// Write to rx window either full SDU or SDU segment
if (header.si == rlc_nr_si_field_t::full_sdu) {
int err = handle_full_data_sdu(header, payload, nof_bytes);
if (err != SRSRAN_SUCCESS) {
return;
}
} else {
int err = handle_segment_data_sdu(header, payload, nof_bytes);
if (err != SRSRAN_SUCCESS) {
return;
}
}
debug_state();
// 5.2.3.2.3 Actions when an AMD PDU is placed in the reception buffer
/*
* - if x >= RX_Next_Highest
* - update RX_Next_Highest to x+ 1.
*/
if (rx_mod_base_nr(header.sn) >= rx_mod_base_nr(st.rx_next_highest)) {
st.rx_next_highest = (header.sn + 1) % mod_nr;
}
/*
* - if all bytes of the RLC SDU with SN = x are received:
*/
if (rx_window->has_sn(header.sn) && (*rx_window)[header.sn].fully_received) {
/*
* - reassemble the RLC SDU from AMD PDU(s) with SN = x, remove RLC headers when doing so and deliver
* the reassembled RLC SDU to upper layer;
*/
write_to_upper_layers(parent->lcid, std::move((*rx_window)[header.sn].buf));
/*
* - if x = RX_Highest_Status,
* - update RX_Highest_Status to the SN of the first RLC SDU with SN > current RX_Highest_Status for which not
* all bytes have been received.
*/
if (rx_mod_base_nr(header.sn) == rx_mod_base_nr(st.rx_highest_status)) {
uint32_t sn_upd = 0;
for (sn_upd = (st.rx_highest_status + 1) % mod_nr; rx_mod_base_nr(sn_upd) < rx_mod_base_nr(st.rx_next_highest);
sn_upd = (sn_upd + 1) % mod_nr) {
if (rx_window->has_sn(sn_upd)) {
if (not(*rx_window)[sn_upd].fully_received) {
break; // first SDU not fully received
}
} else {
break; // first SDU not fully received
}
}
// Update to the SN of the first SDU with missing bytes.
// If it not exists, update to the end of the rx_window.
st.rx_highest_status = sn_upd;
}
/*
* - if x = RX_Next:
* - update RX_Next to the SN of the first RLC SDU with SN > current RX_Next for which not all bytes
* have been received.
*/
if (rx_mod_base_nr(header.sn) == rx_mod_base_nr(st.rx_next)) {
uint32_t sn_upd = 0;
// move rx_next forward and remove all fully received SDUs from rx_window
for (sn_upd = (st.rx_next) % mod_nr; rx_mod_base_nr(sn_upd) < rx_mod_base_nr(st.rx_next_highest);
sn_upd = (sn_upd + 1) % mod_nr) {
if (rx_window->has_sn(sn_upd)) {
if (not(*rx_window)[sn_upd].fully_received) {
break; // first SDU not fully received
}
// RX_Next serves as the lower edge of the receiving window
// As such, we remove any SDU from the window if we update this value
rx_window->remove_pdu(sn_upd);
} else {
break; // first SDU not fully received
}
}
// Update to the SN of the first SDU with missing bytes.
// If it not exists, update to the end of the rx_window.
st.rx_next = sn_upd;
}
}
if (reassembly_timer.is_running()) {
// if t-Reassembly is running:
/*
* - if RX_Next_Status_Trigger = RX_Next; or
* - if RX_Next_Status_Trigger = RX_Next + 1 and there is no missing byte segment of the SDU associated with
* SN = RX_Next before the last byte of all received segments of this SDU; or
* - if RX_Next_Status_Trigger falls outside of the receiving window and RX_Next_Status_Trigger is not equal
* to RX_Next + AM_Window_Size:
* - stop and reset t-Reassembly.
*/
bool stop_reassembly_timer = false;
if (st.rx_next_status_trigger == st.rx_next) {
stop_reassembly_timer = true;
}
if (rx_mod_base_nr(st.rx_next_status_trigger) == rx_mod_base_nr(st.rx_next + 1)) {
if (not(*rx_window)[st.rx_next].has_gap) {
stop_reassembly_timer = true;
}
}
if (not inside_rx_window(st.rx_next_status_trigger)) {
stop_reassembly_timer = true;
}
if (stop_reassembly_timer) {
reassembly_timer.stop();
}
}
if (not reassembly_timer.is_running()) {
// if t-Reassembly is not running (includes the case t-Reassembly is stopped due to actions above):
/*
* - if RX_Next_Highest> RX_Next +1; or
* - if RX_Next_Highest = RX_Next + 1 and there is at least one missing byte segment of the SDU associated
* with SN = RX_Next before the last byte of all received segments of this SDU:
* - start t-Reassembly;
* - set RX_Next_Status_Trigger to RX_Next_Highest.
*/
bool restart_reassembly_timer = false;
if (rx_mod_base_nr(st.rx_next_highest) > rx_mod_base_nr(st.rx_next + 1)) {
restart_reassembly_timer = true;
}
if (rx_mod_base_nr(st.rx_next_highest) == rx_mod_base_nr(st.rx_next + 1)) {
if (rx_window->has_sn(st.rx_next) && (*rx_window)[st.rx_next].has_gap) {
restart_reassembly_timer = true;
}
}
if (restart_reassembly_timer) {
reassembly_timer.run();
st.rx_next_status_trigger = st.rx_next_highest;
}
}
}
/*
* SDU handling helpers
*/
int rlc_am_nr_rx::handle_full_data_sdu(const rlc_am_nr_pdu_header_t& header, const uint8_t* payload, uint32_t nof_bytes)
{
uint32_t hdr_len = rlc_am_nr_packed_length(header);
// Full SDU received. Add SDU to Rx Window and copy full PDU into SDU buffer.
rlc_amd_rx_sdu_nr_t& rx_sdu = rx_window->add_pdu(header.sn);
rx_sdu.buf = srsran::make_byte_buffer();
if (rx_sdu.buf == nullptr) {
RlcError("fatal error. Couldn't allocate PDU in %s.", __FUNCTION__);
rx_window->remove_pdu(header.sn);
return SRSRAN_ERROR;
}
rx_sdu.buf->set_timestamp();
// check available space for payload
if (nof_bytes > rx_sdu.buf->get_tailroom()) {
RlcError("discarding SN=%d of size %d B (available space %d B)", header.sn, nof_bytes, rx_sdu.buf->get_tailroom());
rx_window->remove_pdu(header.sn);
return SRSRAN_ERROR;
}
memcpy(rx_sdu.buf->msg, payload + hdr_len, nof_bytes - hdr_len); // Don't copy header
rx_sdu.buf->N_bytes = nof_bytes - hdr_len;
rx_sdu.fully_received = true;
rx_sdu.has_gap = false;
return SRSRAN_SUCCESS;
}
int rlc_am_nr_rx::handle_segment_data_sdu(const rlc_am_nr_pdu_header_t& header,
const uint8_t* payload,
uint32_t nof_bytes)
{
if (header.si == rlc_nr_si_field_t::full_sdu) {
RlcError("called %s but the SI implies a full SDU. SN=%d", __FUNCTION__, header.sn);
return SRSRAN_ERROR;
}
uint32_t hdr_len = rlc_am_nr_packed_length(header);
// Log SDU segment reception
if (header.si == rlc_nr_si_field_t::first_segment) { // Check whether it's a full SDU
RlcDebug("Initial segment PDU. SN=%d.", header.sn);
} else if (header.si == rlc_nr_si_field_t::neither_first_nor_last_segment) {
RlcDebug("Middle segment PDU. SN=%d.", header.sn);
} else if (header.si == rlc_nr_si_field_t::last_segment) {
RlcDebug("Final segment PDU. SN=%d.", header.sn);
}
// Add a new SDU to the RX window if necessary
rlc_amd_rx_sdu_nr_t& rx_sdu = rx_window->has_sn(header.sn) ? (*rx_window)[header.sn] : rx_window->add_pdu(header.sn);
// Create PDU segment info, to be stored later
rlc_amd_rx_pdu_nr pdu_segment = {};
pdu_segment.header = header;
pdu_segment.buf = srsran::make_byte_buffer();
if (pdu_segment.buf == nullptr) {
RlcError("fatal error. Couldn't allocate PDU in %s.", __FUNCTION__);
return SRSRAN_ERROR;
}
memcpy(pdu_segment.buf->msg, payload + hdr_len, nof_bytes - hdr_len); // Don't copy header
pdu_segment.buf->N_bytes = nof_bytes - hdr_len;
// Store SDU segment. Sort by SO and check for duplicate bytes.
insert_received_segment(std::move(pdu_segment), rx_sdu.segments);
// Check weather all segments have been received
update_segment_inventory(rx_sdu);
if (rx_sdu.fully_received) {
RlcInfo("Fully received segmented SDU. SN=%d.", header.sn);
rx_sdu.buf = srsran::make_byte_buffer();
if (rx_sdu.buf == nullptr) {
RlcError("fatal error. Couldn't allocate PDU in %s.", __FUNCTION__);
rx_window->remove_pdu(header.sn);
return SRSRAN_ERROR;
}
// Assemble SDU from segments
for (const auto& it : rx_sdu.segments) {
memcpy(&rx_sdu.buf->msg[rx_sdu.buf->N_bytes], it.buf->msg, it.buf->N_bytes);
rx_sdu.buf->N_bytes += it.buf->N_bytes;
}
}
return SRSRAN_SUCCESS;
}
/*
* Status PDU
*/
uint32_t rlc_am_nr_rx::get_status_pdu(rlc_am_nr_status_pdu_t* status, uint32_t max_len)
{
std::unique_lock<std::mutex> lock(mutex, std::try_to_lock);
if (not lock.owns_lock()) {
return SRSRAN_ERROR;
}
status->reset();
/*
* - for the RLC SDUs with SN such that RX_Next <= SN < RX_Highest_Status that has not been completely
* received yet, in increasing SN order of RLC SDUs and increasing byte segment order within RLC SDUs,
* starting with SN = RX_Next up to the point where the resulting STATUS PDU still fits to the total size of RLC
* PDU(s) indicated by lower layer:
*/
RlcDebug("Generating status PDU");
for (uint32_t i = st.rx_next; rx_mod_base_nr(i) < rx_mod_base_nr(st.rx_highest_status); i = (i + 1) % mod_nr) {
if ((rx_window->has_sn(i) && (*rx_window)[i].fully_received)) {
RlcDebug("SDU SN=%d is fully received", i);
} else {
if (not rx_window->has_sn(i)) {
// No segment received, NACK the whole SDU
RlcDebug("Adding NACK for full SDU. NACK SN=%d", i);
rlc_status_nack_t nack;
nack.nack_sn = i;
nack.has_so = false;
status->push_nack(nack);
} else if (not(*rx_window)[i].fully_received) {
// Some segments were received, but not all.
// NACK non consecutive missing bytes
RlcDebug("Adding NACKs for segmented SDU. NACK SN=%d", i);
uint32_t last_so = 0;
bool last_segment_rx = false;
for (auto segm = (*rx_window)[i].segments.begin(); segm != (*rx_window)[i].segments.end(); segm++) {
if (segm->header.so != last_so) {
// Some bytes were not received
rlc_status_nack_t nack;
nack.nack_sn = i;
nack.has_so = true;
nack.so_start = last_so;
nack.so_end = segm->header.so - 1; // set to last missing byte
status->push_nack(nack);
if (nack.so_start > nack.so_end) {
// Print segment list
for (auto segm_it = (*rx_window)[i].segments.begin(); segm_it != (*rx_window)[i].segments.end();
segm_it++) {
RlcError("Segment: segm.header.so=%d, segm.buf.N_bytes=%d", segm_it->header.so, segm_it->buf->N_bytes);
}
RlcError("Error: SO_start=%d > SO_end=%d. NACK_SN=%d. SO_start=%d, SO_end=%d, seg.so=%d",
nack.so_start,
nack.so_end,
nack.nack_sn,
nack.so_start,
nack.so_end,
segm->header.so);
srsran_assert(nack.so_start <= nack.so_end,
"Error: SO_start=%d > SO_end=%d. NACK_SN=%d",
nack.so_start,
nack.so_end,
nack.nack_sn);
} else {
RlcDebug("First/middle segment missing. NACK_SN=%d. SO_start=%d, SO_end=%d",
nack.nack_sn,
nack.so_start,
nack.so_end);
}
}
if (segm->header.si == rlc_nr_si_field_t::last_segment) {
last_segment_rx = true;
}
last_so = segm->header.so + segm->buf->N_bytes;
} // Segment loop
if (not last_segment_rx) {
rlc_status_nack_t nack;
nack.nack_sn = i;
nack.has_so = true;
nack.so_start = last_so;
nack.so_end = rlc_status_nack_t::so_end_of_sdu;
status->push_nack(nack);
RlcDebug(
"Final segment missing. NACK_SN=%d. SO_start=%d, SO_end=%d", nack.nack_sn, nack.so_start, nack.so_end);
srsran_assert(nack.so_start <= nack.so_end, "Error: SO_start > SO_end. NACK_SN=%d", nack.nack_sn);
}
}
}
} // NACK loop
/*
* - set the ACK_SN to the SN of the next not received RLC SDU which is not
* indicated as missing in the resulting STATUS PDU.
*/
status->ack_sn = st.rx_highest_status;
// trim PDU if necessary
if (status->packed_size > max_len) {
RlcInfo("Trimming status PDU with %d NACKs and packed_size=%d into max_len=%d",
status->nacks.size(),
status->packed_size,
max_len);
log_rlc_am_nr_status_pdu_to_string(logger.debug, "Untrimmed status PDU - %s", status, rb_name);
if (not status->trim(max_len)) {
RlcError("Failed to trim status PDU into provided space: max_len=%d", max_len);
}
}
if (max_len != UINT32_MAX) {
// UINT32_MAX is used just to query the status PDU length
if (status_prohibit_timer.is_valid()) {
status_prohibit_timer.run();
}
do_status = false;
}
return status->packed_size;
}
uint32_t rlc_am_nr_rx::get_status_pdu_length()
{
rlc_am_nr_status_pdu_t tmp_status(cfg.rx_sn_field_length);
get_status_pdu(&tmp_status, UINT32_MAX);
return tmp_status.get_packed_size();
}
bool rlc_am_nr_rx::get_do_status()
{
return do_status.load(std::memory_order_relaxed) && not status_prohibit_timer.is_running();
}
void rlc_am_nr_rx::timer_expired(uint32_t timeout_id)
{
std::unique_lock<std::mutex> lock(mutex);
// Status Prohibit
if (status_prohibit_timer.is_valid() && status_prohibit_timer.id() == timeout_id) {
RlcDebug("Status prohibit timer expired after %dms", status_prohibit_timer.duration());
return;
}
// Reassembly
if (reassembly_timer.is_valid() && reassembly_timer.id() == timeout_id) {
RlcDebug("Reassembly timer expired after %dms", reassembly_timer.duration());
/*
* 5.2.3.2.4 Actions when t-Reassembly expires:
* - update RX_Highest_Status to the SN of the first RLC SDU with SN >= RX_Next_Status_Trigger for which not
* all bytes have been received;
* - if RX_Next_Highest> RX_Highest_Status +1: or
* - if RX_Next_Highest = RX_Highest_Status + 1 and there is at least one missing byte segment of the SDU
* associated with SN = RX_Highest_Status before the last byte of all received segments of this SDU:
* - start t-Reassembly;
* - set RX_Next_Status_Trigger to RX_Next_Highest.
*/
uint32_t sn_upd = {};
for (sn_upd = st.rx_next_status_trigger; rx_mod_base_nr(sn_upd) < rx_mod_base_nr(st.rx_next_highest);
sn_upd = (sn_upd + 1) % mod_nr) {
if (not rx_window->has_sn(sn_upd) || (rx_window->has_sn(sn_upd) && not(*rx_window)[sn_upd].fully_received)) {
break;
}
}
st.rx_highest_status = sn_upd;
if (not valid_ack_sn(st.rx_highest_status)) {
RlcError("Rx_Highest_Status not inside RX window");
debug_state();
}
srsran_assert(valid_ack_sn(st.rx_highest_status), "Error: rx_highest_status assigned outside rx window");
bool restart_reassembly_timer = false;
if (rx_mod_base_nr(st.rx_next_highest) > rx_mod_base_nr(st.rx_highest_status + 1)) {
restart_reassembly_timer = true;
}
if (rx_mod_base_nr(st.rx_next_highest) == rx_mod_base_nr(st.rx_highest_status + 1)) {
if (rx_window->has_sn(st.rx_highest_status) && (*rx_window)[st.rx_highest_status].has_gap) {
restart_reassembly_timer = true;
}
}
if (restart_reassembly_timer) {
reassembly_timer.run();
st.rx_next_status_trigger = st.rx_next_highest;
}
/* 5.3.4 Status reporting:
* - The receiving side of an AM RLC entity shall trigger a STATUS report when t-Reassembly expires.
* NOTE 2: The expiry of t-Reassembly triggers both RX_Highest_Status to be updated and a STATUS report to be
* triggered, but the STATUS report shall be triggered after RX_Highest_Status is updated.
*/
do_status = true;
debug_state();
debug_window();
return;
}
}
void rlc_am_nr_rx::write_to_upper_layers(uint32_t lcid, unique_byte_buffer_t sdu)
{
uint32_t nof_bytes = sdu->N_bytes;
parent->pdcp->write_pdu(lcid, std::move(sdu));
std::lock_guard<std::mutex> lock(parent->metrics_mutex);
parent->metrics.num_rx_sdus++;
parent->metrics.num_rx_sdu_bytes += nof_bytes;
}
/*
* Segment Helpers
*/
void rlc_am_nr_rx::insert_received_segment(rlc_amd_rx_pdu_nr segment,
std::set<rlc_amd_rx_pdu_nr, rlc_amd_rx_pdu_nr_cmp>& segment_list) const
{
segment_list.insert(std::move(segment));
}
void rlc_am_nr_rx::update_segment_inventory(rlc_amd_rx_sdu_nr_t& rx_sdu) const
{
if (rx_sdu.segments.empty()) {
rx_sdu.fully_received = false;
rx_sdu.has_gap = false;
return;
}
// Check for gaps and if all segments have been received
uint32_t next_byte = 0;
for (const auto& it : rx_sdu.segments) {
if (it.header.so != next_byte) {
// Found gap: set flags and return
rx_sdu.has_gap = true;
rx_sdu.fully_received = false;
return;
}
if (it.header.si == rlc_nr_si_field_t::last_segment) {
// Reached last segment without any gaps: set flags and return
rx_sdu.has_gap = false;
rx_sdu.fully_received = true;
return;
}
next_byte += it.buf->N_bytes;
}
// No gaps, but last segment not yet received
rx_sdu.has_gap = false;
rx_sdu.fully_received = false;
}
/*
* Window Helpers
*/
uint32_t rlc_am_nr_rx::rx_mod_base_nr(uint32_t sn) const
{
return (sn - st.rx_next) % mod_nr;
}
uint32_t rlc_am_nr_rx::rx_window_size() const
{
return am_window_size(cfg.rx_sn_field_length);
}
bool rlc_am_nr_rx::inside_rx_window(uint32_t sn) const
{
// RX_Next <= SN < RX_Next + AM_Window_Size
return rx_mod_base_nr(sn) < rx_window_size();
}
/*
* This function is used to check if the Rx_Highest_Status is
* valid when t-Reasseambly expires.
*
* ACK_SN may be equal to RX_NEXT + AM_Window_Size if the PDU
* with SN=RX_NEXT+AM_Window_Size has been received by the RX.
* An ACK_SN == Rx_Next should not update Rx_Highest_Status,
* it should be updated when Rx_Next is updated.
*/
bool rlc_am_nr_rx::valid_ack_sn(uint32_t sn) const
{
// RX_Next < SN <= RX_Next + AM_Window_Size
return (0 < rx_mod_base_nr(sn)) && (rx_mod_base_nr(sn) <= rx_window_size());
}
/*
* Debug Helpers
*/
void rlc_am_nr_rx::debug_state() const
{
RlcDebug("RX entity state: Rx_Next=%d, Rx_Next_Status_Trigger=%d, Rx_Highest_Status=%d, Rx_Next_Highest=%d",
st.rx_next,
st.rx_next_status_trigger,
st.rx_highest_status,
st.rx_next_highest);
}
void rlc_am_nr_rx::debug_window() const
{
RlcDebug(
"RX window state: Rx_Next=%d, Rx_Next_Highest=%d, SDUs %d", st.rx_next, st.rx_next_highest, rx_window->size());
}
/*
* Metrics
*/
uint32_t rlc_am_nr_rx::get_sdu_rx_latency_ms()
{
return 0;
}
uint32_t rlc_am_nr_rx::get_rx_buffered_bytes()
{
return 0;
}
} // namespace srsran