/* * Copyright 2013-2019 Software Radio Systems Limited * * This file is part of srsLTE. * * srsLTE 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. * * srsLTE 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 "srslte/upper/rlc_um.h" #include #include #include #define RX_MOD_BASE(x) (((x)-vr_uh-cfg.rx_window_size)%cfg.rx_mod) using namespace asn1::rrc; namespace srslte { rlc_um::rlc_um() : lcid(0), tx(), pool(byte_buffer_pool::get_instance()), rrc(NULL), log(NULL) { bzero(&cfg, sizeof(srslte_rlc_um_config_t)); } // Warning: must call stop() to properly deallocate all buffers rlc_um::~rlc_um() { stop(); } void rlc_um::init(srslte::log *log_, uint32_t lcid_, srsue::pdcp_interface_rlc *pdcp_, srsue::rrc_interface_rlc *rrc_, srslte::mac_interface_timers *mac_timers_) { tx.init(log_); rx.init(log_, lcid_, pdcp_, rrc_, mac_timers_); lcid = lcid_; rrc = rrc_; // needed to determine bearer name during configuration log = log_; } bool rlc_um::configure(srslte_rlc_config_t cnfg_) { // determine bearer name and configure Rx/Tx objects rb_name = get_rb_name(rrc, lcid, cnfg_.um.is_mrb); if (not rx.configure(cnfg_, rb_name)) { return false; } if (not tx.configure(cnfg_, rb_name)) { return false; } // store config cfg = cnfg_.um; log->warning("%s configured in %s: t_reordering=%d ms, rx_sn_field_length=%u bits, tx_sn_field_length=%u bits\n", rb_name.c_str(), rlc_mode_text[cnfg_.rlc_mode], cfg.t_reordering, rlc_umd_sn_size_num[cfg.rx_sn_field_length], rlc_umd_sn_size_num[cfg.rx_sn_field_length]); return true; } bool rlc_um::rlc_um_rx::configure(srslte_rlc_config_t cnfg_, std::string rb_name_) { cfg = cnfg_.um; if (cfg.rx_mod == 0) { log->error("Error configuring %s RLC UM: rx_mod==0\n", get_rb_name()); return false; } // set reordering timer if (reordering_timer != NULL) { reordering_timer->set(this, cfg.t_reordering); } rb_name = rb_name_; rx_enabled = true; return true; } void rlc_um::empty_queue() { // Drop all messages in TX SDU queue tx.empty_queue(); } bool rlc_um::is_mrb() { return cfg.is_mrb; } void rlc_um::reestablish() { tx.reestablish(); // calls stop and enables tx again rx.reestablish(); // nothing else needed } void rlc_um::stop() { tx.stop(); rx.stop(); } rlc_mode_t rlc_um::get_mode() { return RLC_MODE_UM; } uint32_t rlc_um::get_bearer() { return lcid; } /**************************************************************************** * PDCP interface ***************************************************************************/ void rlc_um::write_sdu(byte_buffer_t *sdu, bool blocking) { if (blocking) { tx.write_sdu(sdu); } else { tx.try_write_sdu(sdu); } } /**************************************************************************** * MAC interface ***************************************************************************/ bool rlc_um::has_data() { return tx.has_data(); } uint32_t rlc_um::get_buffer_state() { return tx.get_buffer_state(); } int rlc_um::read_pdu(uint8_t *payload, uint32_t nof_bytes) { return tx.build_data_pdu(payload, nof_bytes); } void rlc_um::write_pdu(uint8_t *payload, uint32_t nof_bytes) { rx.handle_data_pdu(payload, nof_bytes); } uint32_t rlc_um::get_num_tx_bytes() { return tx.get_num_tx_bytes(); } uint32_t rlc_um::get_num_rx_bytes() { return rx.get_num_rx_bytes(); } void rlc_um::reset_metrics() { tx.reset_metrics(); rx.reset_metrics(); } /**************************************************************************** * Helper functions ***************************************************************************/ std::string rlc_um::get_rb_name(srsue::rrc_interface_rlc *rrc, uint32_t lcid, bool is_mrb) { if (is_mrb) { std::stringstream ss; ss << "MRB" << lcid; return ss.str(); } else { return rrc->get_rb_name(lcid); } } /**************************************************************************** * Tx subclass implementation ***************************************************************************/ rlc_um::rlc_um_tx::rlc_um_tx() : pool(byte_buffer_pool::get_instance()), log(NULL), tx_sdu(NULL), vt_us(0), tx_enabled(false), num_tx_bytes(0) { pthread_mutex_init(&mutex, NULL); } rlc_um::rlc_um_tx::~rlc_um_tx() { pthread_mutex_destroy(&mutex); } void rlc_um::rlc_um_tx::init(srslte::log *log_) { log = log_; } bool rlc_um::rlc_um_tx::configure(srslte_rlc_config_t cnfg_, std::string rb_name_) { cfg = cnfg_.um; if (cfg.tx_mod == 0) { log->error("Error configuring %s RLC UM: tx_mod==0\n", get_rb_name()); return false; } tx_sdu_queue.resize(cnfg_.tx_queue_length); rb_name = rb_name_; tx_enabled = true; return true; } void rlc_um::rlc_um_tx::stop() { tx_enabled = false; empty_queue(); } void rlc_um::rlc_um_tx::reestablish() { stop(); tx_enabled = true; } void rlc_um::rlc_um_tx::empty_queue() { pthread_mutex_lock(&mutex); // deallocate all SDUs in transmit queue while(tx_sdu_queue.size() > 0) { byte_buffer_t *buf; tx_sdu_queue.read(&buf); pool->deallocate(buf); } // deallocate SDU that is currently processed if(tx_sdu) { pool->deallocate(tx_sdu); tx_sdu = NULL; } pthread_mutex_unlock(&mutex); } uint32_t rlc_um::rlc_um_tx::get_num_tx_bytes() { return num_tx_bytes; } void rlc_um::rlc_um_tx::reset_metrics() { pthread_mutex_lock(&mutex); num_tx_bytes = 0; pthread_mutex_unlock(&mutex); } bool rlc_um::rlc_um_tx::has_data() { return (tx_sdu != NULL || !tx_sdu_queue.is_empty()); } uint32_t rlc_um::rlc_um_tx::get_buffer_state() { // Bytes needed for tx SDUs uint32_t n_sdus = tx_sdu_queue.size(); uint32_t n_bytes = tx_sdu_queue.size_bytes(); if(tx_sdu) { n_sdus++; n_bytes += tx_sdu->N_bytes; } // Room needed for header extensions? (integer rounding) if(n_sdus > 1) { n_bytes += ((n_sdus-1)*1.5)+0.5; } // Room needed for fixed header? if(n_bytes > 0) n_bytes += (cfg.is_mrb)?2:3; return n_bytes; } void rlc_um::rlc_um_tx::write_sdu(byte_buffer_t *sdu) { if (!tx_enabled) { byte_buffer_pool::get_instance()->deallocate(sdu); return; } if (sdu) { tx_sdu_queue.write(sdu); log->info_hex(sdu->msg, sdu->N_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", get_rb_name(), sdu->N_bytes, tx_sdu_queue.size()); } else { log->warning("NULL SDU pointer in write_sdu()\n"); } } void rlc_um::rlc_um_tx::try_write_sdu(byte_buffer_t *sdu) { if (!tx_enabled) { byte_buffer_pool::get_instance()->deallocate(sdu); return; } if (sdu) { if (tx_sdu_queue.try_write(sdu)) { log->info_hex(sdu->msg, sdu->N_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", get_rb_name(), sdu->N_bytes, tx_sdu_queue.size()); } else { log->info_hex(sdu->msg, sdu->N_bytes, "[Dropped SDU] %s Tx SDU (%d B, tx_sdu_queue_len=%d)", get_rb_name(), sdu->N_bytes, tx_sdu_queue.size()); pool->deallocate(sdu); } } else { log->warning("NULL SDU pointer in write_sdu()\n"); } } int rlc_um::rlc_um_tx::build_data_pdu(uint8_t *payload, uint32_t nof_bytes) { pthread_mutex_lock(&mutex); log->debug("MAC opportunity - %d bytes\n", nof_bytes); if (not tx_enabled) { pthread_mutex_unlock(&mutex); return 0; } if(!tx_sdu && tx_sdu_queue.size() == 0) { log->info("No data available to be sent\n"); pthread_mutex_unlock(&mutex); return 0; } byte_buffer_t *pdu = pool_allocate; if(!pdu || pdu->N_bytes != 0) { log->error("Failed to allocate PDU buffer\n"); pthread_mutex_unlock(&mutex); return 0; } rlc_umd_pdu_header_t header; header.fi = RLC_FI_FIELD_START_AND_END_ALIGNED; header.sn = vt_us; header.N_li = 0; header.sn_size = cfg.tx_sn_field_length; uint32_t to_move = 0; uint32_t last_li = 0; uint8_t *pdu_ptr = pdu->msg; int head_len = rlc_um_packed_length(&header); int pdu_space = SRSLTE_MIN(nof_bytes, pdu->get_tailroom());; if(pdu_space <= head_len + 1) { pool->deallocate(pdu); log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n", get_rb_name(), nof_bytes, head_len); pthread_mutex_unlock(&mutex); return 0; } // Check for SDU segment if(tx_sdu) { uint32_t space = pdu_space-head_len; to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; log->debug("%s adding remainder of SDU segment - %d bytes of %d remaining\n", get_rb_name(), to_move, tx_sdu->N_bytes); memcpy(pdu_ptr, tx_sdu->msg, to_move); last_li = to_move; pdu_ptr += to_move; pdu->N_bytes += to_move; tx_sdu->N_bytes -= to_move; tx_sdu->msg += to_move; if(tx_sdu->N_bytes == 0) { log->debug("%s Complete SDU scheduled for tx. Stack latency: %ld us\n", get_rb_name(), tx_sdu->get_latency_us()); pool->deallocate(tx_sdu); tx_sdu = NULL; } pdu_space -= SRSLTE_MIN(to_move, pdu->get_tailroom()); header.fi |= RLC_FI_FIELD_NOT_START_ALIGNED; // First byte does not correspond to first byte of SDU } // Pull SDUs from queue while(pdu_space > head_len + 1 && tx_sdu_queue.size() > 0) { log->debug("pdu_space=%d, head_len=%d\n", pdu_space, head_len); if(last_li > 0) header.li[header.N_li++] = last_li; head_len = rlc_um_packed_length(&header); tx_sdu_queue.read(&tx_sdu); uint32_t space = pdu_space-head_len; to_move = space >= tx_sdu->N_bytes ? tx_sdu->N_bytes : space; log->debug("%s adding new SDU segment - %d bytes of %d remaining\n", get_rb_name(), to_move, tx_sdu->N_bytes); memcpy(pdu_ptr, tx_sdu->msg, to_move); last_li = to_move; pdu_ptr += to_move; pdu->N_bytes += to_move; tx_sdu->N_bytes -= to_move; tx_sdu->msg += to_move; if(tx_sdu->N_bytes == 0) { log->debug("%s Complete SDU scheduled for tx. Stack latency: %ld us\n", get_rb_name(), tx_sdu->get_latency_us()); pool->deallocate(tx_sdu); tx_sdu = NULL; } pdu_space -= to_move; } if(tx_sdu) { header.fi |= RLC_FI_FIELD_NOT_END_ALIGNED; // Last byte does not correspond to last byte of SDU } // Set SN header.sn = vt_us; vt_us = (vt_us + 1)%cfg.tx_mod; // Add header and TX rlc_um_write_data_pdu_header(&header, pdu); memcpy(payload, pdu->msg, pdu->N_bytes); uint32_t ret = pdu->N_bytes; log->info_hex(payload, ret, "%s Tx PDU SN=%d (%d B)\n", get_rb_name(), header.sn, pdu->N_bytes); pool->deallocate(pdu); debug_state(); num_tx_bytes += ret; pthread_mutex_unlock(&mutex); return ret; } const char* rlc_um::rlc_um_tx::get_rb_name() { return rb_name.c_str(); } void rlc_um::rlc_um_tx::debug_state() { log->debug("%s vt_us = %d\n", get_rb_name(), vt_us); } /**************************************************************************** * Rx subclass implementation ***************************************************************************/ rlc_um::rlc_um_rx::rlc_um_rx() :reordering_timer(NULL) ,reordering_timer_id(0) ,pool(byte_buffer_pool::get_instance()) ,log(NULL) ,pdcp(NULL) ,rrc(NULL) ,rx_sdu(NULL) ,vr_ur(0) ,vr_ux (0) ,vr_uh(0) ,vr_ur_in_rx_sdu(0) ,pdu_lost(false) ,mac_timers(NULL) ,lcid(0) ,num_rx_bytes(0) ,rx_enabled(false) { pthread_mutex_init(&mutex, NULL); } rlc_um::rlc_um_rx::~rlc_um_rx() { pthread_mutex_destroy(&mutex); } void rlc_um::rlc_um_rx::init(srslte::log *log_, uint32_t lcid_, srsue::pdcp_interface_rlc *pdcp_, srsue::rrc_interface_rlc *rrc_, srslte::mac_interface_timers *mac_timers_) { log = log_; lcid = lcid_; pdcp = pdcp_; rrc = rrc_; mac_timers = mac_timers_; reordering_timer_id = mac_timers->timer_get_unique_id(); reordering_timer = mac_timers->timer_get(reordering_timer_id); } void rlc_um::rlc_um_rx::reestablish() { // try to reassemble any SDUs if possible if (reordering_timer != NULL) { if (reordering_timer->is_running()) { reordering_timer->stop(); timer_expired(reordering_timer_id); } } pthread_mutex_lock(&mutex); reset(); rx_enabled = true; pthread_mutex_unlock(&mutex); } void rlc_um::rlc_um_rx::stop() { pthread_mutex_lock(&mutex); reset(); if (mac_timers != NULL && reordering_timer != NULL) { reordering_timer->stop(); mac_timers->timer_release_id(reordering_timer_id); reordering_timer = NULL; } pthread_mutex_unlock(&mutex); } void rlc_um::rlc_um_rx::reset() { vr_ur = 0; vr_ux = 0; vr_uh = 0; pdu_lost = false; rx_enabled = false; if (rx_sdu) { pool->deallocate(rx_sdu); rx_sdu = NULL; } // Drop all messages in RX window std::map::iterator it; for(it = rx_window.begin(); it != rx_window.end(); it++) { pool->deallocate(it->second.buf); } rx_window.clear(); } void rlc_um::rlc_um_rx::handle_data_pdu(uint8_t *payload, uint32_t nof_bytes) { pthread_mutex_lock(&mutex); rlc_umd_pdu_t pdu; int header_len = 0; std::map::iterator it; rlc_umd_pdu_header_t header; if (!rx_enabled) { goto unlock_and_exit; } num_rx_bytes += nof_bytes; rlc_um_read_data_pdu_header(payload, nof_bytes, cfg.rx_sn_field_length, &header); log->info_hex(payload, nof_bytes, "RX %s Rx data PDU SN: %d (%d B)", get_rb_name(), header.sn, nof_bytes); if(RX_MOD_BASE(header.sn) >= RX_MOD_BASE(vr_uh-cfg.rx_window_size) && RX_MOD_BASE(header.sn) < RX_MOD_BASE(vr_ur)) { log->info("%s SN: %d outside rx window [%d:%d] - discarding\n", get_rb_name(), header.sn, vr_ur, vr_uh); goto unlock_and_exit; } it = rx_window.find(header.sn); if(rx_window.end() != it) { log->info("%s Discarding duplicate SN: %d\n", get_rb_name(), header.sn); goto unlock_and_exit; } // Write to rx window pdu.buf = pool_allocate; if (!pdu.buf) { log->error("Discarting packet: no space in buffer pool\n"); goto unlock_and_exit; } memcpy(pdu.buf->msg, payload, nof_bytes); pdu.buf->N_bytes = nof_bytes; //Strip header from PDU header_len = rlc_um_packed_length(&header); pdu.buf->msg += header_len; pdu.buf->N_bytes -= header_len; pdu.header = header; rx_window[header.sn] = pdu; // Update vr_uh if(!inside_reordering_window(header.sn)) { vr_uh = (header.sn + 1)%cfg.rx_mod; } // Reassemble and deliver SDUs, while updating vr_ur log->debug("Entering Reassemble from received PDU\n"); reassemble_rx_sdus(); log->debug("Finished reassemble from received PDU\n"); // Update reordering variables and timers if(reordering_timer->is_running()) { if(RX_MOD_BASE(vr_ux) <= RX_MOD_BASE(vr_ur) || (!inside_reordering_window(vr_ux) && vr_ux != vr_uh)) { reordering_timer->stop(); } } if(!reordering_timer->is_running()) { if(RX_MOD_BASE(vr_uh) > RX_MOD_BASE(vr_ur)) { reordering_timer->reset(); reordering_timer->run(); vr_ux = vr_uh; } } debug_state(); unlock_and_exit: pthread_mutex_unlock(&mutex); } // No locking required as only called from within handle_data_pdu and timer_expired which lock void rlc_um::rlc_um_rx::reassemble_rx_sdus() { if(!rx_sdu) { rx_sdu = pool_allocate; if (!rx_sdu) { log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); return; } } // First catch up with lower edge of reordering window while(!inside_reordering_window(vr_ur)) { log->debug("SN=%d is not inside reordering windows\n", vr_ur); if(rx_window.end() == rx_window.find(vr_ur)) { log->debug("SN=%d not in rx_window. Reset received SDU\n", vr_ur); rx_sdu->reset(); }else{ // Handle any SDU segments for(uint32_t i=0; idebug_hex(rx_window[vr_ur].buf->msg, len, "Handling segment %d/%d of length %d B of SN=%d\n", i+1, rx_window[vr_ur].header.N_li, len, vr_ur); // Check if we received a middle or end segment if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { log->warning("Dropping PDU %d due to lost start segment\n", vr_ur); // Advance data pointers and continue with next segment rx_window[vr_ur].buf->msg += len; rx_window[vr_ur].buf->N_bytes -= len; rx_sdu->reset(); break; } memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len); rx_sdu->N_bytes += len; rx_window[vr_ur].buf->msg += len; rx_window[vr_ur].buf->N_bytes -= len; if((pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) || (vr_ur != ((vr_ur_in_rx_sdu+1)%cfg.rx_mod))) { log->warning("Dropping remainder of lost PDU (lower edge middle segments, vr_ur=%d, vr_ur_in_rx_sdu=%d)\n", vr_ur, vr_ur_in_rx_sdu); rx_sdu->reset(); } else { log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d, i=%d (lower edge middle segments)", get_rb_name(), vr_ur, i); rx_sdu->set_timestamp(); if(cfg.is_mrb){ pdcp->write_pdu_mch(lcid, rx_sdu); } else { pdcp->write_pdu(lcid, rx_sdu); } rx_sdu = pool_allocate; if (!rx_sdu) { log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); return; } } pdu_lost = false; } // Handle last segment if (rx_sdu->N_bytes > 0 || rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { log->info("Writing last segment in SDU buffer. Lower edge vr_ur=%d, Buffer size=%d, segment size=%d\n", vr_ur, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes); memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes); rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes; vr_ur_in_rx_sdu = vr_ur; if(rlc_um_end_aligned(rx_window[vr_ur].header.fi)) { if(pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { log->warning("Dropping remainder of lost PDU (lower edge last segments)\n"); rx_sdu->reset(); } else { log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", get_rb_name(), vr_ur); rx_sdu->set_timestamp(); if(cfg.is_mrb){ pdcp->write_pdu_mch(lcid, rx_sdu); } else { pdcp->write_pdu(lcid, rx_sdu); } rx_sdu = pool_allocate; if (!rx_sdu) { log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); return; } } pdu_lost = false; } } // Clean up rx_window pool->deallocate(rx_window[vr_ur].buf); rx_window.erase(vr_ur); } vr_ur = (vr_ur + 1)%cfg.rx_mod; } // Now update vr_ur until we reach an SN we haven't yet received while(rx_window.end() != rx_window.find(vr_ur)) { log->debug("Reassemble loop for vr_ur=%d\n", vr_ur); if (not pdu_belongs_to_rx_sdu()) { log->warning("PDU SN=%d lost, stop reassambling SDU (vr_ur_in_rx_sdu=%d)\n", vr_ur_in_rx_sdu+1, vr_ur_in_rx_sdu); pdu_lost = false; // Reset flag to not prevent reassembling of further segments rx_sdu->reset(); } // Handle any SDU segments for(uint32_t i=0; idebug("Handling SDU segment i=%d with len=%d of vr_ur=%d N_li=%d [%s]\n", i, len, vr_ur, rx_window[vr_ur].header.N_li, rlc_fi_field_text[rx_window[vr_ur].header.fi]); // Check if the first part of the PDU is a middle or end segment if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { log->warning_hex(rx_window[vr_ur].buf->msg, len, "Dropping first %d B of SN %d due to lost start segment\n", len, vr_ur); if (rx_window[vr_ur].buf->N_bytes < len) { log->error("Dropping remaining remainder of SN %d too (N_bytes=%u < len=%d)\n", vr_ur, rx_window[vr_ur].buf->N_bytes, len); goto clean_up_rx_window; } // Advance data pointers and continue with next segment rx_window[vr_ur].buf->msg += len; rx_window[vr_ur].buf->N_bytes -= len; rx_sdu->reset(); // Reset flag, it is safe to process all remaining segments of this PDU pdu_lost = false; continue; } // Check available space in SDU if ((uint32_t)len > rx_sdu->get_tailroom()) { log->error("Dropping PDU %d due to buffer mis-alignment (current segment len %d B, received %d B)\n", vr_ur, rx_sdu->N_bytes, len); rx_sdu->reset(); goto clean_up_rx_window; } if (not pdu_belongs_to_rx_sdu()) { log->info_hex(rx_window[vr_ur].buf->msg, len, "Copying first %d bytes of new SDU\n", len); log->info("Updating vr_ur_in_rx_sdu. old=%d, new=%d\n", vr_ur_in_rx_sdu, vr_ur); vr_ur_in_rx_sdu = vr_ur; } else { log->info_hex(rx_window[vr_ur].buf->msg, len, "Concatenating %d bytes in to current length %d. rx_window remaining bytes=%d, vr_ur_in_rx_sdu=%d, vr_ur=%d, rx_mod=%d, last_mod=%d\n", len, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes, vr_ur_in_rx_sdu, vr_ur, cfg.rx_mod, (vr_ur_in_rx_sdu+1)%cfg.rx_mod); } memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len); rx_sdu->N_bytes += len; rx_window[vr_ur].buf->msg += len; rx_window[vr_ur].buf->N_bytes -= len; vr_ur_in_rx_sdu = vr_ur; if (pdu_belongs_to_rx_sdu()) { log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d, i=%d, (update vr_ur middle segments)", get_rb_name(), vr_ur, i); rx_sdu->set_timestamp(); if(cfg.is_mrb){ pdcp->write_pdu_mch(lcid, rx_sdu); } else { pdcp->write_pdu(lcid, rx_sdu); } rx_sdu = pool_allocate; if (!rx_sdu) { log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); return; } } else { log->warning("Dropping remainder of lost PDU (update vr_ur middle segments, vr_ur=%d, vr_ur_in_rx_sdu=%d)\n", vr_ur, vr_ur_in_rx_sdu); // Advance data pointers and continue with next segment rx_window[vr_ur].buf->msg += len; rx_window[vr_ur].buf->N_bytes -= len; } pdu_lost = false; } // Handle last segment if (rx_sdu->N_bytes == 0 && rx_window[vr_ur].header.N_li == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { log->warning("Dropping PDU %d due to lost start segment\n", vr_ur); rx_sdu->reset(); goto clean_up_rx_window; } if (rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && rx_window[vr_ur].buf->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && rx_window[vr_ur].buf->N_bytes + rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) { log->info_hex(rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes, "Writing last segment in SDU buffer. Updating vr_ur=%d, vr_ur_in_rx_sdu=%d, Buffer size=%d, segment size=%d\n", vr_ur, vr_ur_in_rx_sdu, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes); memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes); rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes; } else { log->error("Out of bounds while reassembling SDU buffer in UM: sdu_len=%d, window_buffer_len=%d, vr_ur=%d\n", rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes, vr_ur); } vr_ur_in_rx_sdu = vr_ur; if(rlc_um_end_aligned(rx_window[vr_ur].header.fi)) { if(pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { log->warning("Dropping remainder of lost PDU (update vr_ur last segments)\n"); rx_sdu->reset(); } else { log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (update vr_ur last segments)", get_rb_name(), vr_ur); rx_sdu->set_timestamp(); if(cfg.is_mrb){ pdcp->write_pdu_mch(lcid, rx_sdu); } else { pdcp->write_pdu(lcid, rx_sdu); } rx_sdu = pool_allocate; if (!rx_sdu) { log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); return; } } pdu_lost = false; } clean_up_rx_window: // Clean up rx_window pool->deallocate(rx_window[vr_ur].buf); rx_window.erase(vr_ur); vr_ur = (vr_ur + 1)%cfg.rx_mod; } } // Only called when lock is hold bool rlc_um::rlc_um_rx::pdu_belongs_to_rx_sdu() { // return true if the currently received SDU if (((vr_ur_in_rx_sdu + 1)%cfg.rx_mod == vr_ur) || (vr_ur == vr_ur_in_rx_sdu)) { return true; } return false; } // Only called when lock is hold // 36.322 Section 5.1.2.2.1 bool rlc_um::rlc_um_rx::inside_reordering_window(uint16_t sn) { if (cfg.rx_window_size == 0 || rx_window.empty()) { return true; } if (RX_MOD_BASE(vr_uh-cfg.rx_window_size) <= RX_MOD_BASE(sn) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_uh)) { return true; } else { return false; } } uint32_t rlc_um::rlc_um_rx::get_num_rx_bytes() { return num_rx_bytes; } void rlc_um::rlc_um_rx::reset_metrics() { pthread_mutex_lock(&mutex); num_rx_bytes = 0; pthread_mutex_unlock(&mutex); } /**************************************************************************** * Timeout callback interface ***************************************************************************/ void rlc_um::rlc_um_rx::timer_expired(uint32_t timeout_id) { pthread_mutex_lock(&mutex); if (reordering_timer != NULL && reordering_timer_id == timeout_id) { // 36.322 v10 Section 5.1.2.2.4 log->info("%s reordering timeout expiry - updating vr_ur and reassembling\n", get_rb_name()); log->warning("Lost PDU SN: %d\n", vr_ur); pdu_lost = true; if (rx_sdu != NULL) { rx_sdu->reset(); } while(RX_MOD_BASE(vr_ur) < RX_MOD_BASE(vr_ux)) { vr_ur = (vr_ur + 1)%cfg.rx_mod; log->debug("Entering Reassemble from timeout id=%d\n", timeout_id); reassemble_rx_sdus(); log->debug("Finished reassemble from timeout id=%d\n", timeout_id); } if (RX_MOD_BASE(vr_uh) > RX_MOD_BASE(vr_ur)) { reordering_timer->reset(); reordering_timer->run(); vr_ux = vr_uh; } debug_state(); } pthread_mutex_unlock(&mutex); } /**************************************************************************** * Helper functions ***************************************************************************/ void rlc_um::rlc_um_rx::debug_state() { log->debug("%s vr_ur = %d, vr_ux = %d, vr_uh = %d\n", get_rb_name(), vr_ur, vr_ux, vr_uh); } const char* rlc_um::rlc_um_rx::get_rb_name() { return rb_name.c_str(); } /**************************************************************************** * Header pack/unpack helper functions * Ref: 3GPP TS 36.322 v10.0.0 Section 6.2.1 ***************************************************************************/ void rlc_um_read_data_pdu_header(byte_buffer_t *pdu, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t *header) { rlc_um_read_data_pdu_header(pdu->msg, pdu->N_bytes, sn_size, header); } void rlc_um_read_data_pdu_header(uint8_t *payload, uint32_t nof_bytes, rlc_umd_sn_size_t sn_size, rlc_umd_pdu_header_t *header) { uint8_t ext; uint8_t *ptr = payload; // Fixed part if(RLC_UMD_SN_SIZE_5_BITS == sn_size) { header->fi = (rlc_fi_field_t)((*ptr >> 6) & 0x03); // 2 bits FI ext = ((*ptr >> 5) & 0x01); // 1 bit EXT header->sn = *ptr & 0x1F; // 5 bits SN ptr++; }else{ header->fi = (rlc_fi_field_t)((*ptr >> 3) & 0x03); // 2 bits FI ext = ((*ptr >> 2) & 0x01); // 1 bit EXT header->sn = (*ptr & 0x03) << 8; // 2 bits SN ptr++; header->sn |= (*ptr & 0xFF); // 8 bits SN ptr++; } header->sn_size = sn_size; // Extension part header->N_li = 0; while(ext) { if(header->N_li%2 == 0) { ext = ((*ptr >> 7) & 0x01); header->li[header->N_li] = (*ptr & 0x7F) << 4; // 7 bits of LI ptr++; header->li[header->N_li] |= (*ptr & 0xF0) >> 4; // 4 bits of LI header->N_li++; } else { ext = (*ptr >> 3) & 0x01; header->li[header->N_li] = (*ptr & 0x07) << 8; // 3 bits of LI ptr++; header->li[header->N_li] |= (*ptr & 0xFF); // 8 bits of LI header->N_li++; ptr++; } } } void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t *header, byte_buffer_t *pdu) { uint32_t i; uint8_t ext = (header->N_li > 0) ? 1 : 0; // Make room for the header uint32_t len = rlc_um_packed_length(header); pdu->msg -= len; uint8_t *ptr = pdu->msg; // Fixed part if(RLC_UMD_SN_SIZE_5_BITS == header->sn_size) { *ptr = (header->fi & 0x03) << 6; // 2 bits FI *ptr |= (ext & 0x01) << 5; // 1 bit EXT *ptr |= header->sn & 0x1F; // 5 bits SN ptr++; }else{ *ptr = (header->fi & 0x03) << 3; // 3 Reserved bits | 2 bits FI *ptr |= (ext & 0x01) << 2; // 1 bit EXT *ptr |= (header->sn & 0x300) >> 8; // 2 bits SN ptr++; *ptr = (header->sn & 0xFF); // 8 bits SN ptr++; } // Extension part i = 0; while(i < header->N_li) { ext = ((i+1) == header->N_li) ? 0 : 1; *ptr = (ext & 0x01) << 7; // 1 bit header *ptr |= (header->li[i] & 0x7F0) >> 4; // 7 bits of LI ptr++; *ptr = (header->li[i] & 0x00F) << 4; // 4 bits of LI i++; if(i < header->N_li) { ext = ((i+1) == header->N_li) ? 0 : 1; *ptr |= (ext & 0x01) << 3; // 1 bit header *ptr |= (header->li[i] & 0x700) >> 8; // 3 bits of LI ptr++; *ptr = (header->li[i] & 0x0FF); // 8 bits of LI ptr++; i++; } } // Pad if N_li is odd if(header->N_li%2 == 1) ptr++; pdu->N_bytes += ptr-pdu->msg; } uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t *header) { uint32_t len = 0; if(RLC_UMD_SN_SIZE_5_BITS == header->sn_size) { len += 1; // Fixed part is 1 byte }else{ len += 2; // Fixed part is 2 bytes } len += header->N_li * 1.5 + 0.5; // Extension part - integer rounding up return len; } bool rlc_um_start_aligned(uint8_t fi) { return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_END_ALIGNED); } bool rlc_um_end_aligned(uint8_t fi) { return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_START_ALIGNED); } } // namespace srsue