lib,rlc_am_nr: starting to add test for segmenting retx. Changed sdu under segmentation to only hold the SN. The actual SDU already exists in the pdu stored in the tx_window.

This commit is contained in:
Pedro Alvarez 2022-01-19 12:14:50 +00:00
parent 581a99c616
commit b1a33a07a1
4 changed files with 235 additions and 53 deletions

View File

@ -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<RLC_AM_WINDOW_SIZE> 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;

View File

@ -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<std::mutex> 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;
}
}
}

View File

@ -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;
}

View File

@ -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;