lib,rlc_am_nr: clean retx_queue of ACK'ed SDUs

This commit is contained in:
Robert Falkenberg 2022-05-11 15:40:38 +02:00
parent 95480297f4
commit 0c24e9c55f
3 changed files with 109 additions and 28 deletions

View File

@ -18,6 +18,7 @@
#include "srsran/adt/intrusive_list.h" #include "srsran/adt/intrusive_list.h"
#include "srsran/common/buffer_pool.h" #include "srsran/common/buffer_pool.h"
#include <array> #include <array>
#include <list>
#include <vector> #include <vector>
namespace srsran { namespace srsran {
@ -449,6 +450,89 @@ private:
size_t rpos = 0; size_t rpos = 0;
}; };
template <class T>
class pdu_retx_queue_list
{
std::list<T> queue;
public:
~pdu_retx_queue_list() = default;
T& push()
{
queue.emplace_back();
return queue.back();
}
void pop()
{
if (not queue.empty()) {
queue.pop_front();
}
}
T& front()
{
assert(not queue.empty());
return queue.front();
}
const std::list<T>& get_inner_queue() const { return queue; }
void clear() { queue.clear(); }
size_t size() const { return queue.size(); }
bool empty() const { return queue.empty(); }
bool has_sn(uint32_t sn) const
{
if (queue.empty()) {
return false;
}
for (auto elem : queue) {
if (elem.sn == sn) {
return true;
}
}
return false;
};
bool has_sn(uint32_t sn, uint32_t so) const
{
if (queue.empty()) {
return false;
}
for (auto elem : queue) {
if (elem.sn == sn) {
if (elem.overlaps(so)) {
return true;
}
}
}
return false;
};
/**
* @brief remove_sn removes SN from queue and returns after first match
* @param sn sequence number to be removed from queue
* @return true if one element was removed, false if no element to remove was found
*/
bool remove_sn(uint32_t sn)
{
if (queue.empty()) {
return false;
}
auto iter = queue.begin();
while (iter != queue.end()) {
if (iter->sn == sn) {
iter = queue.erase(iter);
return true;
} else {
++iter;
}
}
return false;
}
};
} // namespace srsran } // namespace srsran
#endif // SRSRAN_RLC_AM_DATA_STRUCTS_H #endif // SRSRAN_RLC_AM_DATA_STRUCTS_H

View File

@ -154,7 +154,7 @@ private:
std::unique_ptr<rlc_ringbuffer_base<rlc_amd_tx_pdu_nr> > tx_window; std::unique_ptr<rlc_ringbuffer_base<rlc_amd_tx_pdu_nr> > tx_window;
// Queues, buffers and container // Queues, buffers and container
std::unique_ptr<pdu_retx_queue_base<rlc_amd_retx_nr_t> > retx_queue; pdu_retx_queue_list<rlc_amd_retx_nr_t> retx_queue;
uint32_t sdu_under_segmentation_sn = INVALID_RLC_SN; // SN of the SDU currently being segmented. uint32_t sdu_under_segmentation_sn = INVALID_RLC_SN; // SN of the SDU currently being segmented.
pdcp_sn_vector_t notify_info_vec; pdcp_sn_vector_t notify_info_vec;
@ -180,7 +180,7 @@ public:
void set_tx_state(const rlc_am_nr_tx_state_t& st_) { st = st_; } // This should only be used for testing. void set_tx_state(const rlc_am_nr_tx_state_t& st_) { st = st_; } // This should only be used for testing.
rlc_am_nr_tx_state_t get_tx_state() { return st; } // This should only be used for testing. rlc_am_nr_tx_state_t get_tx_state() { return st; } // This should only be used for testing.
uint32_t get_tx_window_utilization() { return tx_window->size(); } // This should only be used for testing. uint32_t get_tx_window_utilization() { return tx_window->size(); } // This should only be used for testing.
size_t get_retx_queue_size() const { return retx_queue->size(); } // This should only be used for testing. size_t get_retx_queue_size() const { return retx_queue.size(); } // This should only be used for testing.
// Debug Helpers // Debug Helpers
void debug_state() const; void debug_state() const;

View File

@ -53,15 +53,11 @@ bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_)
min_hdr_size = 2; min_hdr_size = 2;
tx_window = std::unique_ptr<rlc_ringbuffer_base<rlc_amd_tx_pdu_nr> >( 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)>); new rlc_ringbuffer_t<rlc_amd_tx_pdu_nr, am_window_size(rlc_am_nr_sn_size_t::size12bits)>);
retx_queue = std::unique_ptr<pdu_retx_queue_base<rlc_amd_retx_nr_t> >(
new pdu_retx_queue<rlc_amd_retx_nr_t, am_window_size(rlc_am_nr_sn_size_t::size12bits)>);
break; break;
case rlc_am_nr_sn_size_t::size18bits: case rlc_am_nr_sn_size_t::size18bits:
min_hdr_size = 3; min_hdr_size = 3;
tx_window = std::unique_ptr<rlc_ringbuffer_base<rlc_amd_tx_pdu_nr> >( 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)>); new rlc_ringbuffer_t<rlc_amd_tx_pdu_nr, am_window_size(rlc_am_nr_sn_size_t::size18bits)>);
retx_queue = std::unique_ptr<pdu_retx_queue_base<rlc_amd_retx_nr_t> >(
new pdu_retx_queue<rlc_amd_retx_nr_t, am_window_size(rlc_am_nr_sn_size_t::size18bits)>);
break; break;
default: default:
RlcError("attempt to configure unsupported tx_sn_field_length %s", to_string(cfg.tx_sn_field_length)); RlcError("attempt to configure unsupported tx_sn_field_length %s", to_string(cfg.tx_sn_field_length));
@ -94,8 +90,8 @@ bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_)
bool rlc_am_nr_tx::has_data() bool rlc_am_nr_tx::has_data()
{ {
return do_status() || // if we have a status PDU to transmit 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 tx_sdu_queue.get_n_sdus() != 0 || !retx_queue.empty(); // or if there is a SDU queued up for transmission
} }
/** /**
@ -134,8 +130,8 @@ uint32_t rlc_am_nr_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes)
} }
// Retransmit if required // Retransmit if required
if (not retx_queue->empty()) { if (not retx_queue.empty()) {
RlcInfo("Re-transmission required. Retransmission queue size: %d", retx_queue->size()); RlcInfo("Re-transmission required. Retransmission queue size: %d", retx_queue.size());
return build_retx_pdu(payload, nof_bytes); return build_retx_pdu(payload, nof_bytes);
} }
@ -434,19 +430,19 @@ uint32_t rlc_am_nr_tx::build_continuation_sdu_segment(rlc_amd_tx_pdu_nr& tx_pdu,
uint32_t rlc_am_nr_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes) 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() // Check there is at least 1 element before calling front()
if (retx_queue->empty()) { if (retx_queue.empty()) {
RlcError("in build_retx_pdu(): retx_queue is empty"); RlcError("in build_retx_pdu(): retx_queue is empty");
return 0; return 0;
} }
rlc_amd_retx_nr_t& retx = retx_queue->front(); rlc_amd_retx_nr_t& retx = retx_queue.front();
// Sanity check - drop any retx SNs not present in tx_window // Sanity check - drop any retx SNs not present in tx_window
while (not tx_window->has_sn(retx.sn)) { 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); RlcInfo("SN=%d not in tx window, probably already ACKed. Skip and remove from retx queue", retx.sn);
retx_queue->pop(); retx_queue.pop();
if (!retx_queue->empty()) { if (!retx_queue.empty()) {
retx = retx_queue->front(); retx = retx_queue.front();
} else { } else {
RlcInfo("empty retx queue, cannot provide any retx PDU"); RlcInfo("empty retx queue, cannot provide any retx PDU");
return 0; return 0;
@ -540,7 +536,7 @@ rlc_am_nr_tx::build_retx_pdu_without_segmentation(const rlc_amd_retx_nr_t retx,
// Update RETX queue. This must be done before calculating // Update RETX queue. This must be done before calculating
// the polling bit, to make sure the poll bit is calculated correctly // the polling bit, to make sure the poll bit is calculated correctly
retx_queue->pop(); retx_queue.pop();
// Write header to payload // Write header to payload
rlc_am_nr_pdu_header_t new_header = tx_pdu.header; rlc_am_nr_pdu_header_t new_header = tx_pdu.header;
@ -814,6 +810,7 @@ void rlc_am_nr_tx::handle_control_pdu(uint8_t* payload, uint32_t nof_bytes)
for (uint32_t sn = st.tx_next_ack; tx_mod_base_nr(sn) < tx_mod_base_nr(stop_sn); sn = (sn + 1) % mod_nr) { 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)) { if (tx_window->has_sn(sn)) {
notify_info_vec.push_back((*tx_window)[sn].pdcp_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); tx_window->remove_pdu(sn);
st.tx_next_ack = (sn + 1) % mod_nr; st.tx_next_ack = (sn + 1) % mod_nr;
} else { } else {
@ -892,8 +889,8 @@ void rlc_am_nr_tx::handle_nack(const rlc_status_nack_t& nack, std::set<uint32_t>
bool segment_found = false; bool segment_found = false;
for (const rlc_amd_tx_pdu_nr::pdu_segment& segm : pdu.segment_list) { 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 (segm.so >= nack.so_start && segm.so <= nack.so_end) {
if (not retx_queue->has_sn(nack.nack_sn, segm.so)) { if (not retx_queue.has_sn(nack.nack_sn, segm.so)) {
rlc_amd_retx_nr_t& retx = retx_queue->push(); rlc_amd_retx_nr_t& retx = retx_queue.push();
retx.sn = nack.nack_sn; retx.sn = nack.nack_sn;
retx.is_segment = true; retx.is_segment = true;
retx.so_start = segm.so; retx.so_start = segm.so;
@ -925,10 +922,10 @@ void rlc_am_nr_tx::handle_nack(const rlc_status_nack_t& nack, std::set<uint32_t>
} else { } else {
// NACK'ing full SDU. // NACK'ing full SDU.
// add to retx queue if it's not already there // add to retx queue if it's not already there
if (not retx_queue->has_sn(nack.nack_sn)) { if (not retx_queue.has_sn(nack.nack_sn)) {
// Have we segmented the SDU already? // Have we segmented the SDU already?
if ((*tx_window)[nack.nack_sn].segment_list.empty()) { if ((*tx_window)[nack.nack_sn].segment_list.empty()) {
rlc_amd_retx_nr_t& retx = retx_queue->push(); rlc_amd_retx_nr_t& retx = retx_queue.push();
retx.sn = nack.nack_sn; retx.sn = nack.nack_sn;
retx.is_segment = false; retx.is_segment = false;
retx.so_start = 0; retx.so_start = 0;
@ -940,7 +937,7 @@ void rlc_am_nr_tx::handle_nack(const rlc_status_nack_t& nack, std::set<uint32_t>
RlcInfo("Scheduled RETX of SDU SN=%d", nack.nack_sn); RlcInfo("Scheduled RETX of SDU SN=%d", nack.nack_sn);
retx_sn_set.insert(nack.nack_sn); retx_sn_set.insert(nack.nack_sn);
for (auto segm : (*tx_window)[nack.nack_sn].segment_list) { for (auto segm : (*tx_window)[nack.nack_sn].segment_list) {
rlc_amd_retx_nr_t& retx = retx_queue->push(); rlc_amd_retx_nr_t& retx = retx_queue.push();
retx.sn = nack.nack_sn; retx.sn = nack.nack_sn;
retx.is_segment = true; retx.is_segment = true;
retx.so_start = segm.so; retx.so_start = segm.so;
@ -1016,9 +1013,7 @@ void rlc_am_nr_tx::get_buffer_state(uint32_t& n_bytes_new, uint32_t& n_bytes_pri
} }
// Bytes needed for retx // Bytes needed for retx
size_t n_retx = retx_queue->size(); for (const rlc_amd_retx_nr_t& retx : retx_queue.get_inner_queue()) {
for (size_t i = 0; i < n_retx; i++) {
rlc_amd_retx_nr_t& retx = (*retx_queue)[i];
RlcDebug("buffer state - retx - SN=%d, Segment: %s, %d:%d", RlcDebug("buffer state - retx - SN=%d, Segment: %s, %d:%d",
retx.sn, retx.sn,
retx.is_segment ? "true" : "false", retx.is_segment ? "true" : "false",
@ -1033,6 +1028,8 @@ void rlc_am_nr_tx::get_buffer_state(uint32_t& n_bytes_new, uint32_t& n_bytes_pri
n_bytes_prio += (req_bytes + hdr_req_bytes); n_bytes_prio += (req_bytes + hdr_req_bytes);
RlcDebug("buffer state - retx: %d bytes", n_bytes_prio); RlcDebug("buffer state - retx: %d bytes", n_bytes_prio);
} }
} else {
RlcWarning("buffer state - retx for SN=%d is outside the tx_window", retx.sn);
} }
} }
@ -1119,7 +1116,7 @@ uint8_t rlc_am_nr_tx::get_pdu_poll(uint32_t sn, bool is_retx, uint32_t sdu_bytes
* - if no new RLC SDU can be transmitted after the transmission of the AMD PDU (e.g. due to window stalling); * - 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. * - 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) || if ((tx_sdu_queue.is_empty() && retx_queue.empty() && sdu_under_segmentation_sn == INVALID_RLC_SN) ||
tx_window->full()) { tx_window->full()) {
RlcDebug("Setting poll bit due to empty buffers/inablity to TX. SN=%d, POLL_SN=%d", sn, st.poll_sn); RlcDebug("Setting poll bit due to empty buffers/inablity to TX. SN=%d, POLL_SN=%d", sn, st.poll_sn);
poll = 1; poll = 1;
@ -1200,7 +1197,7 @@ void rlc_am_nr_tx::stop()
tx_window->clear(); tx_window->clear();
// Drop all messages in RETX queue // Drop all messages in RETX queue
retx_queue->clear(); retx_queue.clear();
tx_enabled = false; tx_enabled = false;
} }
@ -1222,7 +1219,7 @@ void rlc_am_nr_tx::timer_expired(uint32_t timeout_id)
* - consider any RLC SDU which has not been positively acknowledged for retransmission. * - 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. * - 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_sdu_queue.is_empty() && retx_queue.empty()) || tx_window->full()) {
if (tx_window->empty()) { 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", 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.poll_sn,
@ -1241,7 +1238,7 @@ void rlc_am_nr_tx::timer_expired(uint32_t timeout_id)
// RETX first RLC SDU that has not been ACKed // RETX first RLC SDU that has not been ACKed
// or first SDU segment of the first RLC SDU // or first SDU segment of the first RLC SDU
// that has not been acked // that has not been acked
rlc_amd_retx_nr_t& retx = retx_queue->push(); rlc_amd_retx_nr_t& retx = retx_queue.push();
retx.sn = st.tx_next_ack; retx.sn = st.tx_next_ack;
if ((*tx_window)[st.tx_next_ack].segment_list.empty()) { if ((*tx_window)[st.tx_next_ack].segment_list.empty()) {
// Full SDU // Full SDU