mirror of https://github.com/pvnis/srsRAN_4G.git
refactor RLC UM and add NR receiver
parent
a9a33256e9
commit
b20f7ba541
@ -1,281 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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/.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SRSLTE_RLC_UM_H
|
|
||||||
#define SRSLTE_RLC_UM_H
|
|
||||||
|
|
||||||
#include "srslte/common/buffer_pool.h"
|
|
||||||
#include "srslte/common/common.h"
|
|
||||||
#include "srslte/common/log.h"
|
|
||||||
#include "srslte/interfaces/ue_interfaces.h"
|
|
||||||
#include "srslte/upper/rlc_common.h"
|
|
||||||
#include "srslte/upper/rlc_tx_queue.h"
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
namespace srslte {
|
|
||||||
|
|
||||||
struct rlc_umd_pdu_t{
|
|
||||||
rlc_umd_pdu_header_t header;
|
|
||||||
unique_byte_buffer_t buf;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
rlc_um_nr_pdu_header_t header;
|
|
||||||
unique_byte_buffer_t buf;
|
|
||||||
} rlc_umd_pdu_nr_t;
|
|
||||||
|
|
||||||
class rlc_um
|
|
||||||
:public rlc_common
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
rlc_um(srslte::log* log_,
|
|
||||||
uint32_t lcid_,
|
|
||||||
srsue::pdcp_interface_rlc* pdcp_,
|
|
||||||
srsue::rrc_interface_rlc* rrc_,
|
|
||||||
srslte::timer_handler* timers_);
|
|
||||||
~rlc_um();
|
|
||||||
bool configure(rlc_config_t cnfg);
|
|
||||||
void reestablish();
|
|
||||||
void stop();
|
|
||||||
void empty_queue();
|
|
||||||
bool is_mrb();
|
|
||||||
|
|
||||||
rlc_mode_t get_mode();
|
|
||||||
uint32_t get_bearer();
|
|
||||||
|
|
||||||
// PDCP interface
|
|
||||||
void write_sdu(unique_byte_buffer_t sdu, bool blocking = true);
|
|
||||||
|
|
||||||
// MAC interface
|
|
||||||
bool has_data();
|
|
||||||
uint32_t get_buffer_state();
|
|
||||||
int read_pdu(uint8_t *payload, uint32_t nof_bytes);
|
|
||||||
void write_pdu(uint8_t *payload, uint32_t nof_bytes);
|
|
||||||
int get_increment_sequence_num();
|
|
||||||
|
|
||||||
uint32_t get_num_tx_bytes();
|
|
||||||
uint32_t get_num_rx_bytes();
|
|
||||||
void reset_metrics();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Transmitter sub-class base
|
|
||||||
class rlc_um_tx_base
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
rlc_um_tx_base(srslte::log* log_);
|
|
||||||
virtual ~rlc_um_tx_base();
|
|
||||||
virtual bool configure(rlc_config_t cfg, std::string rb_name) = 0;
|
|
||||||
int build_data_pdu(uint8_t *payload, uint32_t nof_bytes);
|
|
||||||
void stop();
|
|
||||||
void reestablish();
|
|
||||||
void empty_queue();
|
|
||||||
void write_sdu(unique_byte_buffer_t sdu);
|
|
||||||
void try_write_sdu(unique_byte_buffer_t sdu);
|
|
||||||
uint32_t get_num_tx_bytes();
|
|
||||||
void reset_metrics();
|
|
||||||
bool has_data();
|
|
||||||
uint32_t get_buffer_state();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
byte_buffer_pool* pool = nullptr;
|
|
||||||
srslte::log* log = nullptr;
|
|
||||||
std::string rb_name;
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Configurable parameters
|
|
||||||
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
|
|
||||||
***************************************************************************/
|
|
||||||
rlc_config_t cfg = {};
|
|
||||||
|
|
||||||
// TX SDU buffers
|
|
||||||
rlc_tx_queue tx_sdu_queue;
|
|
||||||
unique_byte_buffer_t tx_sdu;
|
|
||||||
|
|
||||||
// Mutexes
|
|
||||||
std::mutex mutex;
|
|
||||||
|
|
||||||
bool tx_enabled = false;
|
|
||||||
|
|
||||||
uint32_t num_tx_bytes = 0;
|
|
||||||
|
|
||||||
virtual int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) = 0;
|
|
||||||
|
|
||||||
// helper functions
|
|
||||||
virtual void debug_state() = 0;
|
|
||||||
const char* get_rb_name();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transmitter sub-class for LTE
|
|
||||||
class rlc_um_tx : public rlc_um_tx_base
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
rlc_um_tx(srslte::log* log_);
|
|
||||||
|
|
||||||
bool configure(rlc_config_t cfg, std::string rb_name);
|
|
||||||
int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/****************************************************************************
|
|
||||||
* State variables and counters
|
|
||||||
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
|
|
||||||
***************************************************************************/
|
|
||||||
uint32_t vt_us = 0; // Send state. SN to be assigned for next PDU.
|
|
||||||
|
|
||||||
void debug_state();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transmitter sub-class for NR
|
|
||||||
class rlc_um_tx_nr : public rlc_um_tx_base
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
rlc_um_tx_nr(srslte::log* log_);
|
|
||||||
|
|
||||||
bool configure(rlc_config_t cfg, std::string rb_name);
|
|
||||||
int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint32_t TX_Next = 0; // send state as defined in TS 38.322 v15.3 Section 7
|
|
||||||
|
|
||||||
void debug_state();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Receiver sub-class
|
|
||||||
class rlc_um_rx : public timer_callback {
|
|
||||||
public:
|
|
||||||
rlc_um_rx(srslte::log* log_,
|
|
||||||
uint32_t lcid_,
|
|
||||||
srsue::pdcp_interface_rlc* pdcp_,
|
|
||||||
srsue::rrc_interface_rlc* rrc_,
|
|
||||||
srslte::timer_handler* timers_);
|
|
||||||
~rlc_um_rx();
|
|
||||||
void stop();
|
|
||||||
void reestablish();
|
|
||||||
bool configure(rlc_config_t cfg, std::string rb_name);
|
|
||||||
void handle_data_pdu(uint8_t *payload, uint32_t nof_bytes);
|
|
||||||
void reassemble_rx_sdus();
|
|
||||||
bool pdu_belongs_to_rx_sdu();
|
|
||||||
bool inside_reordering_window(uint16_t sn);
|
|
||||||
uint32_t get_num_rx_bytes();
|
|
||||||
void reset_metrics();
|
|
||||||
|
|
||||||
// Timeout callback interface
|
|
||||||
void timer_expired(uint32_t timeout_id);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
byte_buffer_pool* pool = nullptr;
|
|
||||||
srslte::log* log = nullptr;
|
|
||||||
srslte::timer_handler* timers = nullptr;
|
|
||||||
std::string rb_name;
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Configurable parameters
|
|
||||||
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
|
|
||||||
***************************************************************************/
|
|
||||||
rlc_config_t cfg = {};
|
|
||||||
|
|
||||||
// Rx window
|
|
||||||
std::map<uint32_t, rlc_umd_pdu_t> rx_window;
|
|
||||||
|
|
||||||
// RX SDU buffers
|
|
||||||
unique_byte_buffer_t rx_sdu;
|
|
||||||
uint32_t vr_ur_in_rx_sdu = 0;
|
|
||||||
|
|
||||||
// Rx state variables and counter
|
|
||||||
uint32_t vr_ur = 0; // Receive state. SN of earliest PDU still considered for reordering.
|
|
||||||
uint32_t vr_ux = 0; // t_reordering state. SN following PDU which triggered t_reordering.
|
|
||||||
uint32_t vr_uh = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs.
|
|
||||||
bool pdu_lost = false;
|
|
||||||
|
|
||||||
uint32_t num_rx_bytes = 0;
|
|
||||||
|
|
||||||
// Upper layer handles and variables
|
|
||||||
srsue::pdcp_interface_rlc* pdcp = nullptr;
|
|
||||||
srsue::rrc_interface_rlc* rrc = nullptr;
|
|
||||||
uint32_t lcid = 0;
|
|
||||||
|
|
||||||
// Mutexes
|
|
||||||
std::mutex mutex;
|
|
||||||
|
|
||||||
bool rx_enabled = false;
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Timers
|
|
||||||
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
|
|
||||||
***************************************************************************/
|
|
||||||
srslte::timer_handler::unique_timer reordering_timer;
|
|
||||||
|
|
||||||
// helper functions
|
|
||||||
void debug_state();
|
|
||||||
const char* get_rb_name();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Common variables needed by parent class
|
|
||||||
srsue::rrc_interface_rlc* rrc = nullptr;
|
|
||||||
srslte::log* log = nullptr;
|
|
||||||
uint32_t lcid = 0;
|
|
||||||
rlc_config_t cfg = {};
|
|
||||||
std::string rb_name;
|
|
||||||
byte_buffer_pool* pool = nullptr;
|
|
||||||
|
|
||||||
std::string get_rb_name(srsue::rrc_interface_rlc *rrc, uint32_t lcid, bool is_mrb);
|
|
||||||
|
|
||||||
// Rx and Tx objects
|
|
||||||
std::unique_ptr<rlc_um_tx_base> tx;
|
|
||||||
rlc_um_rx rx;
|
|
||||||
};
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* 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);
|
|
||||||
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);
|
|
||||||
void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu);
|
|
||||||
|
|
||||||
uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t *header);
|
|
||||||
bool rlc_um_start_aligned(uint8_t fi);
|
|
||||||
bool rlc_um_end_aligned(uint8_t fi);
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Header pack/unpack helper functions for NR
|
|
||||||
* Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3
|
|
||||||
***************************************************************************/
|
|
||||||
uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu,
|
|
||||||
const rlc_um_nr_sn_size_t sn_size,
|
|
||||||
rlc_um_nr_pdu_header_t* header);
|
|
||||||
|
|
||||||
uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload,
|
|
||||||
const uint32_t nof_bytes,
|
|
||||||
const rlc_um_nr_sn_size_t sn_size,
|
|
||||||
rlc_um_nr_pdu_header_t* header);
|
|
||||||
|
|
||||||
uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu);
|
|
||||||
|
|
||||||
uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header);
|
|
||||||
|
|
||||||
} // namespace srslte
|
|
||||||
|
|
||||||
#endif // SRSLTE_RLC_UM_H
|
|
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SRSLTE_RLC_UM_BASE_H
|
||||||
|
#define SRSLTE_RLC_UM_BASE_H
|
||||||
|
|
||||||
|
#include "srslte/common/buffer_pool.h"
|
||||||
|
#include "srslte/common/common.h"
|
||||||
|
#include "srslte/common/log.h"
|
||||||
|
#include "srslte/interfaces/ue_interfaces.h"
|
||||||
|
#include "srslte/upper/rlc_common.h"
|
||||||
|
#include "srslte/upper/rlc_tx_queue.h"
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace srslte {
|
||||||
|
|
||||||
|
class rlc_um_base : public rlc_common
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rlc_um_base(srslte::log* log_,
|
||||||
|
uint32_t lcid_,
|
||||||
|
srsue::pdcp_interface_rlc* pdcp_,
|
||||||
|
srsue::rrc_interface_rlc* rrc_,
|
||||||
|
srslte::timer_handler* timers_);
|
||||||
|
virtual ~rlc_um_base();
|
||||||
|
void reestablish();
|
||||||
|
void stop();
|
||||||
|
void empty_queue();
|
||||||
|
bool is_mrb();
|
||||||
|
|
||||||
|
rlc_mode_t get_mode();
|
||||||
|
uint32_t get_bearer();
|
||||||
|
|
||||||
|
// PDCP interface
|
||||||
|
void write_sdu(unique_byte_buffer_t sdu, bool blocking = true);
|
||||||
|
|
||||||
|
// MAC interface
|
||||||
|
bool has_data();
|
||||||
|
uint32_t get_buffer_state();
|
||||||
|
int read_pdu(uint8_t* payload, uint32_t nof_bytes);
|
||||||
|
void write_pdu(uint8_t* payload, uint32_t nof_bytes);
|
||||||
|
int get_increment_sequence_num();
|
||||||
|
|
||||||
|
rlc_bearer_metrics_t get_metrics();
|
||||||
|
void reset_metrics();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Transmitter sub-class base
|
||||||
|
class rlc_um_base_tx
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rlc_um_base_tx(rlc_um_base* parent_);
|
||||||
|
virtual ~rlc_um_base_tx();
|
||||||
|
virtual bool configure(rlc_config_t cfg, std::string rb_name) = 0;
|
||||||
|
int build_data_pdu(uint8_t* payload, uint32_t nof_bytes);
|
||||||
|
void stop();
|
||||||
|
void reestablish();
|
||||||
|
void empty_queue();
|
||||||
|
void write_sdu(unique_byte_buffer_t sdu);
|
||||||
|
void try_write_sdu(unique_byte_buffer_t sdu);
|
||||||
|
void reset_metrics();
|
||||||
|
bool has_data();
|
||||||
|
virtual uint32_t get_buffer_state() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
byte_buffer_pool* pool = nullptr;
|
||||||
|
srslte::log* log = nullptr;
|
||||||
|
std::string rb_name;
|
||||||
|
|
||||||
|
rlc_config_t cfg = {};
|
||||||
|
|
||||||
|
// TX SDU buffers
|
||||||
|
rlc_tx_queue tx_sdu_queue;
|
||||||
|
unique_byte_buffer_t tx_sdu;
|
||||||
|
|
||||||
|
// Mutexes
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
virtual int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes) = 0;
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
virtual void debug_state() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Receiver sub-class base
|
||||||
|
class rlc_um_base_rx : public timer_callback
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
rlc_um_base_rx(rlc_um_base* parent_);
|
||||||
|
virtual ~rlc_um_base_rx();
|
||||||
|
virtual bool configure() = 0;
|
||||||
|
|
||||||
|
virtual void stop() = 0;
|
||||||
|
virtual void reestablish() = 0;
|
||||||
|
|
||||||
|
virtual void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
byte_buffer_pool* pool = nullptr;
|
||||||
|
srslte::log* log = nullptr;
|
||||||
|
srslte::timer_handler* timers = nullptr;
|
||||||
|
srsue::pdcp_interface_rlc* pdcp = nullptr;
|
||||||
|
srsue::rrc_interface_rlc* rrc = nullptr;
|
||||||
|
|
||||||
|
rlc_bearer_metrics_t& metrics;
|
||||||
|
|
||||||
|
std::string rb_name;
|
||||||
|
rlc_config_t cfg = {};
|
||||||
|
|
||||||
|
unique_byte_buffer_t rx_sdu;
|
||||||
|
|
||||||
|
uint32_t& lcid;
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
virtual void debug_state() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Common variables needed by parent class
|
||||||
|
srsue::rrc_interface_rlc* rrc = nullptr;
|
||||||
|
srsue::pdcp_interface_rlc* pdcp = nullptr;
|
||||||
|
srslte::log* log = nullptr;
|
||||||
|
srslte::timer_handler* timers = nullptr;
|
||||||
|
uint32_t lcid = 0;
|
||||||
|
rlc_config_t cfg = {};
|
||||||
|
std::string rb_name;
|
||||||
|
byte_buffer_pool* pool = nullptr;
|
||||||
|
std::string get_rb_name(srsue::rrc_interface_rlc* rrc, uint32_t lcid, bool is_mrb);
|
||||||
|
|
||||||
|
// Rx and Tx objects
|
||||||
|
std::unique_ptr<rlc_um_base_tx> tx;
|
||||||
|
std::unique_ptr<rlc_um_base_rx> rx;
|
||||||
|
|
||||||
|
bool tx_enabled = false;
|
||||||
|
bool rx_enabled = false;
|
||||||
|
|
||||||
|
rlc_bearer_metrics_t metrics = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace srslte
|
||||||
|
|
||||||
|
#endif // SRSLTE_RLC_UM_BASE_H
|
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SRSLTE_RLC_UM_LTE_H
|
||||||
|
#define SRSLTE_RLC_UM_LTE_H
|
||||||
|
|
||||||
|
#include "srslte/common/buffer_pool.h"
|
||||||
|
#include "srslte/common/common.h"
|
||||||
|
#include "srslte/common/log.h"
|
||||||
|
#include "srslte/interfaces/ue_interfaces.h"
|
||||||
|
#include "srslte/upper/rlc_tx_queue.h"
|
||||||
|
#include "srslte/upper/rlc_um_base.h"
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace srslte {
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
rlc_umd_pdu_header_t header;
|
||||||
|
unique_byte_buffer_t buf;
|
||||||
|
} rlc_umd_pdu_t;
|
||||||
|
|
||||||
|
class rlc_um_lte : public rlc_um_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rlc_um_lte(srslte::log* log_,
|
||||||
|
uint32_t lcid_,
|
||||||
|
srsue::pdcp_interface_rlc* pdcp_,
|
||||||
|
srsue::rrc_interface_rlc* rrc_,
|
||||||
|
srslte::timer_handler* timers_);
|
||||||
|
~rlc_um_lte();
|
||||||
|
bool configure(rlc_config_t cnfg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Transmitter sub-class for LTE
|
||||||
|
class rlc_um_lte_tx : public rlc_um_base_tx
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rlc_um_lte_tx(rlc_um_base* parent_);
|
||||||
|
|
||||||
|
bool configure(rlc_config_t cfg, std::string rb_name);
|
||||||
|
int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes);
|
||||||
|
uint32_t get_buffer_state();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/****************************************************************************
|
||||||
|
* State variables and counters
|
||||||
|
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
|
||||||
|
***************************************************************************/
|
||||||
|
uint32_t vt_us = 0; // Send state. SN to be assigned for next PDU.
|
||||||
|
|
||||||
|
void debug_state();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Receiver sub-class for LTE
|
||||||
|
class rlc_um_lte_rx : public rlc_um_base_rx
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rlc_um_lte_rx(rlc_um_base* parent_);
|
||||||
|
~rlc_um_lte_rx();
|
||||||
|
void stop();
|
||||||
|
void reestablish();
|
||||||
|
bool configure();
|
||||||
|
void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes);
|
||||||
|
void reassemble_rx_sdus();
|
||||||
|
bool pdu_belongs_to_rx_sdu();
|
||||||
|
bool inside_reordering_window(uint16_t sn);
|
||||||
|
|
||||||
|
// Timeout callback interface
|
||||||
|
void timer_expired(uint32_t timeout_id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
// Rx window
|
||||||
|
std::map<uint32_t, rlc_umd_pdu_t> rx_window;
|
||||||
|
|
||||||
|
// RX SDU buffers
|
||||||
|
uint32_t vr_ur_in_rx_sdu = 0;
|
||||||
|
|
||||||
|
// Rx state variables and counter
|
||||||
|
uint32_t vr_ur = 0; // Receive state. SN of earliest PDU still considered for reordering.
|
||||||
|
uint32_t vr_ux = 0; // t_reordering state. SN following PDU which triggered t_reordering.
|
||||||
|
uint32_t vr_uh = 0; // Highest rx state. SN following PDU with highest SN among rxed PDUs.
|
||||||
|
bool pdu_lost = false;
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Timers
|
||||||
|
* Ref: 3GPP TS 36.322 v10.0.0 Section 7
|
||||||
|
***************************************************************************/
|
||||||
|
srslte::timer_handler::unique_timer reordering_timer;
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
void debug_state();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 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);
|
||||||
|
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);
|
||||||
|
void rlc_um_write_data_pdu_header(rlc_umd_pdu_header_t* header, byte_buffer_t* pdu);
|
||||||
|
|
||||||
|
uint32_t rlc_um_packed_length(rlc_umd_pdu_header_t* header);
|
||||||
|
bool rlc_um_start_aligned(uint8_t fi);
|
||||||
|
bool rlc_um_end_aligned(uint8_t fi);
|
||||||
|
|
||||||
|
} // namespace srslte
|
||||||
|
|
||||||
|
#endif // SRSLTE_RLC_UM_LTE_H
|
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SRSLTE_RLC_UM_NR_H
|
||||||
|
#define SRSLTE_RLC_UM_NR_H
|
||||||
|
|
||||||
|
#include "srslte/common/buffer_pool.h"
|
||||||
|
#include "srslte/common/common.h"
|
||||||
|
#include "srslte/common/log.h"
|
||||||
|
#include "srslte/interfaces/ue_interfaces.h"
|
||||||
|
#include "srslte/upper/rlc_tx_queue.h"
|
||||||
|
#include "srslte/upper/rlc_um_base.h"
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace srslte {
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
rlc_um_nr_pdu_header_t header;
|
||||||
|
unique_byte_buffer_t buf;
|
||||||
|
} rlc_umd_pdu_nr_t;
|
||||||
|
|
||||||
|
class rlc_um_nr : public rlc_um_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rlc_um_nr(srslte::log* log_,
|
||||||
|
uint32_t lcid_,
|
||||||
|
srsue::pdcp_interface_rlc* pdcp_,
|
||||||
|
srsue::rrc_interface_rlc* rrc_,
|
||||||
|
srslte::timer_handler* timers_);
|
||||||
|
~rlc_um_nr();
|
||||||
|
bool configure(rlc_config_t cnfg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Transmitter sub-class for NR
|
||||||
|
class rlc_um_nr_tx : public rlc_um_base_tx
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rlc_um_nr_tx(rlc_um_base* parent_);
|
||||||
|
|
||||||
|
bool configure(rlc_config_t cfg, std::string rb_name);
|
||||||
|
int build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes);
|
||||||
|
uint32_t get_buffer_state();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t TX_Next = 0; // send state as defined in TS 38.322 v15.3 Section 7
|
||||||
|
// It holds the value of the SN to be assigned for the next newly generated UMD PDU with
|
||||||
|
// segment. It is initially set to 0, and is updated after the UM RLC entity submits a UMD PDU
|
||||||
|
// including the last segment of an RLC SDU to lower layers.
|
||||||
|
|
||||||
|
uint32_t next_so = 0; // The segment offset for the next generated PDU
|
||||||
|
|
||||||
|
void debug_state();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Receiver sub-class for NR
|
||||||
|
class rlc_um_nr_rx : public rlc_um_base_rx
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rlc_um_nr_rx(rlc_um_base* parent_);
|
||||||
|
|
||||||
|
bool configure();
|
||||||
|
void handle_data_pdu(uint8_t* payload, uint32_t nof_bytes);
|
||||||
|
|
||||||
|
void reestablish();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void timer_expired(uint32_t timeout_id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool sn_in_reassembly_window(const uint32_t sn);
|
||||||
|
bool sn_invalid_for_rx_buffer(const uint32_t sn);
|
||||||
|
bool has_missing_byte_segment(const uint32_t sn);
|
||||||
|
unique_byte_buffer_t
|
||||||
|
rlc_um_nr_strip_pdu_header(const rlc_um_nr_pdu_header_t& header, const uint8_t* payload, const uint32_t nof_bytes);
|
||||||
|
|
||||||
|
void handle_rx_buffer_update(const uint32_t sn);
|
||||||
|
|
||||||
|
uint32_t RX_Next_Reassembly = 0; // the earliest SN that is still considered for reassembly
|
||||||
|
uint32_t RX_Timer_Trigger = 0; // the SN following the SN which triggered t-Reassembly
|
||||||
|
uint32_t RX_Next_Highest = 0; // the SN following the SN of the UMD PDU with the highest SN among
|
||||||
|
// received UMD PDUs. It serves as the higher edge of the reassembly window.
|
||||||
|
|
||||||
|
// Rx window
|
||||||
|
typedef struct {
|
||||||
|
std::map<uint32_t, rlc_umd_pdu_nr_t> segments; // Map of segments with SO as key
|
||||||
|
unique_byte_buffer_t sdu;
|
||||||
|
uint32_t next_expected_so;
|
||||||
|
uint32_t total_sdu_length;
|
||||||
|
} rlc_umd_pdu_segments_nr_t;
|
||||||
|
std::map<uint32_t, rlc_umd_pdu_segments_nr_t> rx_window;
|
||||||
|
|
||||||
|
void update_total_sdu_length(rlc_umd_pdu_segments_nr_t& pdu_segments, const rlc_umd_pdu_nr_t& rx_pdu);
|
||||||
|
|
||||||
|
// TS 38.322 Sec. 7.3
|
||||||
|
srslte::timer_handler::unique_timer reassembly_timer; // to detect loss of RLC PDUs at lower layers
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
void reset();
|
||||||
|
void debug_state();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Header pack/unpack helper functions for NR
|
||||||
|
* Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3
|
||||||
|
***************************************************************************/
|
||||||
|
uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu,
|
||||||
|
const rlc_um_nr_sn_size_t sn_size,
|
||||||
|
rlc_um_nr_pdu_header_t* header);
|
||||||
|
|
||||||
|
uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload,
|
||||||
|
const uint32_t nof_bytes,
|
||||||
|
const rlc_um_nr_sn_size_t sn_size,
|
||||||
|
rlc_um_nr_pdu_header_t* header);
|
||||||
|
|
||||||
|
uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu);
|
||||||
|
|
||||||
|
uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header);
|
||||||
|
|
||||||
|
} // namespace srslte
|
||||||
|
|
||||||
|
#endif // SRSLTE_RLC_UM_NR_H
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
* 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_base.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using namespace asn1::rrc;
|
||||||
|
|
||||||
|
namespace srslte {
|
||||||
|
|
||||||
|
rlc_um_base::rlc_um_base(srslte::log* log_,
|
||||||
|
uint32_t lcid_,
|
||||||
|
srsue::pdcp_interface_rlc* pdcp_,
|
||||||
|
srsue::rrc_interface_rlc* rrc_,
|
||||||
|
srslte::timer_handler* timers_) :
|
||||||
|
log(log_), lcid(lcid_), pdcp(pdcp_), rrc(rrc_), timers(timers_), pool(byte_buffer_pool::get_instance())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
rlc_um_base::~rlc_um_base() {}
|
||||||
|
|
||||||
|
void rlc_um_base::stop()
|
||||||
|
{
|
||||||
|
if (tx) {
|
||||||
|
tx->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx) {
|
||||||
|
rx->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rlc_mode_t rlc_um_base::get_mode()
|
||||||
|
{
|
||||||
|
return rlc_mode_t::um;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rlc_um_base::get_bearer()
|
||||||
|
{
|
||||||
|
return lcid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_base::is_mrb()
|
||||||
|
{
|
||||||
|
return cfg.um.is_mrb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_base::reestablish()
|
||||||
|
{
|
||||||
|
tx_enabled = false;
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
tx->reestablish(); // calls stop and enables tx again
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx) {
|
||||||
|
rx->reestablish(); // nothing else needed
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_base::empty_queue()
|
||||||
|
{
|
||||||
|
// Drop all messages in TX SDU queue
|
||||||
|
if (tx) {
|
||||||
|
tx->empty_queue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* PDCP interface
|
||||||
|
***************************************************************************/
|
||||||
|
void rlc_um_base::write_sdu(unique_byte_buffer_t sdu, bool blocking)
|
||||||
|
{
|
||||||
|
if (not tx_enabled || not tx) {
|
||||||
|
log->debug("%s is currently deactivated. Dropping SDU (%d B)\n", rb_name.c_str(), sdu->N_bytes);
|
||||||
|
metrics.num_dropped_sdus++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocking) {
|
||||||
|
tx->write_sdu(std::move(sdu));
|
||||||
|
} else {
|
||||||
|
tx->try_write_sdu(std::move(sdu));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* MAC interface
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
bool rlc_um_base::has_data()
|
||||||
|
{
|
||||||
|
if (tx) {
|
||||||
|
return tx->has_data();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rlc_um_base::get_buffer_state()
|
||||||
|
{
|
||||||
|
if (tx) {
|
||||||
|
return tx->get_buffer_state();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rlc_um_base::read_pdu(uint8_t* payload, uint32_t nof_bytes)
|
||||||
|
{
|
||||||
|
if (tx && tx_enabled) {
|
||||||
|
uint32_t len = tx->build_data_pdu(payload, nof_bytes);
|
||||||
|
metrics.num_tx_bytes += len;
|
||||||
|
metrics.num_tx_pdus++;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_base::write_pdu(uint8_t* payload, uint32_t nof_bytes)
|
||||||
|
{
|
||||||
|
if (rx && rx_enabled) {
|
||||||
|
metrics.num_rx_pdus++;
|
||||||
|
metrics.num_rx_bytes += nof_bytes;
|
||||||
|
rx->handle_data_pdu(payload, nof_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rlc_bearer_metrics_t rlc_um_base::get_metrics()
|
||||||
|
{
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_base::reset_metrics()
|
||||||
|
{
|
||||||
|
metrics = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Helper functions
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
std::string rlc_um_base::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Rx subclass implementation (base)
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
rlc_um_base::rlc_um_base_rx::rlc_um_base_rx(rlc_um_base* parent_) :
|
||||||
|
pool(parent_->pool),
|
||||||
|
log(parent_->log),
|
||||||
|
timers(parent_->timers),
|
||||||
|
pdcp(parent_->pdcp),
|
||||||
|
rrc(parent_->rrc),
|
||||||
|
cfg(parent_->cfg),
|
||||||
|
metrics(parent_->metrics),
|
||||||
|
lcid(parent_->lcid)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
rlc_um_base::rlc_um_base_rx::~rlc_um_base_rx() {}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Tx subclass implementation (base)
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
rlc_um_base::rlc_um_base_tx::rlc_um_base_tx(rlc_um_base* parent_) : log(parent_->log), pool(parent_->pool) {}
|
||||||
|
|
||||||
|
rlc_um_base::rlc_um_base_tx::~rlc_um_base_tx() {}
|
||||||
|
|
||||||
|
void rlc_um_base::rlc_um_base_tx::stop()
|
||||||
|
{
|
||||||
|
empty_queue();
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_base::rlc_um_base_tx::reestablish()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
// bearer is enabled in base class
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_base::rlc_um_base_tx::empty_queue()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
|
||||||
|
// deallocate all SDUs in transmit queue
|
||||||
|
while (not tx_sdu_queue.is_empty()) {
|
||||||
|
unique_byte_buffer_t buf = tx_sdu_queue.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
// deallocate SDU that is currently processed
|
||||||
|
tx_sdu.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_base::rlc_um_base_tx::has_data()
|
||||||
|
{
|
||||||
|
return (tx_sdu != nullptr || !tx_sdu_queue.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_base::rlc_um_base_tx::write_sdu(unique_byte_buffer_t sdu)
|
||||||
|
{
|
||||||
|
if (sdu) {
|
||||||
|
log->info_hex(sdu->msg,
|
||||||
|
sdu->N_bytes,
|
||||||
|
"%s Tx SDU (%d B, tx_sdu_queue_len=%d)",
|
||||||
|
rb_name.c_str(),
|
||||||
|
sdu->N_bytes,
|
||||||
|
tx_sdu_queue.size());
|
||||||
|
tx_sdu_queue.write(std::move(sdu));
|
||||||
|
} else {
|
||||||
|
log->warning("NULL SDU pointer in write_sdu()\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_base::rlc_um_base_tx::try_write_sdu(unique_byte_buffer_t sdu)
|
||||||
|
{
|
||||||
|
if (sdu) {
|
||||||
|
uint8_t* msg_ptr = sdu->msg;
|
||||||
|
uint32_t nof_bytes = sdu->N_bytes;
|
||||||
|
std::pair<bool, unique_byte_buffer_t> ret = tx_sdu_queue.try_write(std::move(sdu));
|
||||||
|
if (ret.first) {
|
||||||
|
log->info_hex(
|
||||||
|
msg_ptr, nof_bytes, "%s Tx SDU (%d B, tx_sdu_queue_len=%d)", rb_name.c_str(), nof_bytes, tx_sdu_queue.size());
|
||||||
|
} else {
|
||||||
|
log->info_hex(ret.second->msg,
|
||||||
|
ret.second->N_bytes,
|
||||||
|
"[Dropped SDU] %s Tx SDU (%d B, tx_sdu_queue_len=%d)",
|
||||||
|
rb_name.c_str(),
|
||||||
|
ret.second->N_bytes,
|
||||||
|
tx_sdu_queue.size());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log->warning("NULL SDU pointer in write_sdu()\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rlc_um_base::rlc_um_base_tx::build_data_pdu(uint8_t* payload, uint32_t nof_bytes)
|
||||||
|
{
|
||||||
|
unique_byte_buffer_t pdu;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
log->debug("MAC opportunity - %d bytes\n", nof_bytes);
|
||||||
|
|
||||||
|
if (!tx_sdu && tx_sdu_queue.size() == 0) {
|
||||||
|
log->info("No data available to be sent\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pdu = allocate_unique_buffer(*pool);
|
||||||
|
if (!pdu || pdu->N_bytes != 0) {
|
||||||
|
log->error("Failed to allocate PDU buffer\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return build_data_pdu(std::move(pdu), payload, nof_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace srslte
|
@ -0,0 +1,815 @@
|
|||||||
|
/*
|
||||||
|
* 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_lte.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#define RX_MOD_BASE(x) (((x)-vr_uh - cfg.um.rx_window_size) % cfg.um.rx_mod)
|
||||||
|
|
||||||
|
using namespace asn1::rrc;
|
||||||
|
|
||||||
|
namespace srslte {
|
||||||
|
|
||||||
|
rlc_um_lte::rlc_um_lte(srslte::log* log_,
|
||||||
|
uint32_t lcid_,
|
||||||
|
srsue::pdcp_interface_rlc* pdcp_,
|
||||||
|
srsue::rrc_interface_rlc* rrc_,
|
||||||
|
srslte::timer_handler* timers_) :
|
||||||
|
rlc_um_base(log_, lcid_, pdcp_, rrc_, timers_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning: must call stop() to properly deallocate all buffers
|
||||||
|
rlc_um_lte::~rlc_um_lte()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_lte::configure(rlc_config_t cnfg_)
|
||||||
|
{
|
||||||
|
// determine bearer name and configure Rx/Tx objects
|
||||||
|
rb_name = get_rb_name(rrc, lcid, cnfg_.um.is_mrb);
|
||||||
|
|
||||||
|
// store config
|
||||||
|
cfg = cnfg_;
|
||||||
|
|
||||||
|
rx.reset(new rlc_um_lte_rx(this));
|
||||||
|
if (not rx->configure()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.reset(new rlc_um_lte_tx(this));
|
||||||
|
if (not tx->configure(cfg, rb_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log->info("%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(),
|
||||||
|
srslte::to_string(cnfg_.rlc_mode).c_str(),
|
||||||
|
cfg.um.t_reordering,
|
||||||
|
srslte::to_number(cfg.um.rx_sn_field_length),
|
||||||
|
srslte::to_number(cfg.um.tx_sn_field_length));
|
||||||
|
|
||||||
|
rx_enabled = true;
|
||||||
|
tx_enabled = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Tx Subclass implementation for LTE
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
rlc_um_lte::rlc_um_lte_tx::rlc_um_lte_tx(rlc_um_base* parent_) : rlc_um_base_tx(parent_) {}
|
||||||
|
|
||||||
|
uint32_t rlc_um_lte::rlc_um_lte_tx::get_buffer_state()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
|
||||||
|
// 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.um.is_mrb) ? 2 : 3;
|
||||||
|
|
||||||
|
return n_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_lte::rlc_um_lte_tx::configure(rlc_config_t cnfg_, std::string rb_name_)
|
||||||
|
{
|
||||||
|
cfg = cnfg_;
|
||||||
|
|
||||||
|
if (cfg.um.tx_mod == 0) {
|
||||||
|
log->error("Error configuring %s RLC UM: tx_mod==0\n", rb_name.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_sdu_queue.resize(cnfg_.tx_queue_length);
|
||||||
|
|
||||||
|
rb_name = rb_name_;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rlc_um_lte::rlc_um_lte_tx::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
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.um.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) {
|
||||||
|
log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n",
|
||||||
|
rb_name.c_str(),
|
||||||
|
nof_bytes,
|
||||||
|
head_len);
|
||||||
|
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", rb_name.c_str(), 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", rb_name.c_str(), tx_sdu->get_latency_us());
|
||||||
|
|
||||||
|
tx_sdu.reset();
|
||||||
|
}
|
||||||
|
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 = tx_sdu_queue.read();
|
||||||
|
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", rb_name.c_str(), 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", rb_name.c_str(), tx_sdu->get_latency_us());
|
||||||
|
|
||||||
|
tx_sdu.reset();
|
||||||
|
}
|
||||||
|
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.um.tx_mod;
|
||||||
|
|
||||||
|
// Add header and TX
|
||||||
|
rlc_um_write_data_pdu_header(&header, pdu.get());
|
||||||
|
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", rb_name.c_str(), header.sn, pdu->N_bytes);
|
||||||
|
|
||||||
|
debug_state();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_lte::rlc_um_lte_tx::debug_state()
|
||||||
|
{
|
||||||
|
log->debug("%s vt_us = %d\n", rb_name.c_str(), vt_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Rx subclass implementation
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
rlc_um_lte::rlc_um_lte_rx::rlc_um_lte_rx(rlc_um_base* parent_) : rlc_um_base_rx(parent_), reordering_timer(timers->get_unique_timer())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
rlc_um_lte::rlc_um_lte_rx::~rlc_um_lte_rx()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_lte::rlc_um_lte_rx::configure()
|
||||||
|
{
|
||||||
|
if (cfg.um.rx_mod == 0) {
|
||||||
|
log->error("Error configuring %s RLC UM: rx_mod==0\n", rb_name.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check timer
|
||||||
|
if (not reordering_timer.is_valid()) {
|
||||||
|
log->error("Configuring RLC UM RX: timers not configured\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure timer
|
||||||
|
if (cfg.um.t_reordering > 0) {
|
||||||
|
reordering_timer.set(static_cast<uint32_t>(cfg.um.t_reordering), [this](uint32_t tid) { timer_expired(tid); });
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_lte::rlc_um_lte_rx::reestablish()
|
||||||
|
{
|
||||||
|
// try to reassemble any SDUs if possible
|
||||||
|
if (reordering_timer.is_valid() && reordering_timer.is_running()) {
|
||||||
|
reordering_timer.stop();
|
||||||
|
timer_expired(reordering_timer.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_lte::rlc_um_lte_rx::stop()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
reset();
|
||||||
|
|
||||||
|
reordering_timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_lte::rlc_um_lte_rx::reset()
|
||||||
|
{
|
||||||
|
vr_ur = 0;
|
||||||
|
vr_ux = 0;
|
||||||
|
vr_uh = 0;
|
||||||
|
pdu_lost = false;
|
||||||
|
|
||||||
|
rx_sdu.reset();
|
||||||
|
|
||||||
|
// Drop all messages in RX window
|
||||||
|
rx_window.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_lte::rlc_um_lte_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
|
||||||
|
rlc_umd_pdu_header_t header;
|
||||||
|
rlc_um_read_data_pdu_header(payload, nof_bytes, cfg.um.rx_sn_field_length, &header);
|
||||||
|
log->info_hex(payload, nof_bytes, "RX %s Rx data PDU SN: %d (%d B)", rb_name.c_str(), header.sn, nof_bytes);
|
||||||
|
|
||||||
|
if (RX_MOD_BASE(header.sn) >= RX_MOD_BASE(vr_uh - cfg.um.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", rb_name.c_str(), header.sn, vr_ur, vr_uh);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<uint32_t, rlc_umd_pdu_t>::iterator it = rx_window.find(header.sn);
|
||||||
|
if (rx_window.end() != it) {
|
||||||
|
log->info("%s Discarding duplicate SN: %d\n", rb_name.c_str(), header.sn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to rx window
|
||||||
|
rlc_umd_pdu_t pdu = {};
|
||||||
|
pdu.buf = allocate_unique_buffer(*pool);
|
||||||
|
if (!pdu.buf) {
|
||||||
|
log->error("Discarting packet: no space in buffer pool\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(pdu.buf->msg, payload, nof_bytes);
|
||||||
|
pdu.buf->N_bytes = nof_bytes;
|
||||||
|
// Strip header from PDU
|
||||||
|
int 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] = std::move(pdu);
|
||||||
|
|
||||||
|
// Update vr_uh
|
||||||
|
if (!inside_reordering_window(header.sn)) {
|
||||||
|
vr_uh = (header.sn + 1) % cfg.um.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.run();
|
||||||
|
vr_ux = vr_uh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
// No locking required as only called from within handle_data_pdu and timer_expired which lock
|
||||||
|
void rlc_um_lte::rlc_um_lte_rx::reassemble_rx_sdus()
|
||||||
|
{
|
||||||
|
if (!rx_sdu) {
|
||||||
|
rx_sdu = allocate_unique_buffer(*pool);
|
||||||
|
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->clear();
|
||||||
|
} else {
|
||||||
|
// Handle any SDU segments
|
||||||
|
for (uint32_t i = 0; i < rx_window[vr_ur].header.N_li; i++) {
|
||||||
|
int len = rx_window[vr_ur].header.li[i];
|
||||||
|
log->debug_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->clear();
|
||||||
|
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.um.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->clear();
|
||||||
|
} else {
|
||||||
|
log->info_hex(rx_sdu->msg,
|
||||||
|
rx_sdu->N_bytes,
|
||||||
|
"%s Rx SDU vr_ur=%d, i=%d (lower edge middle segments)",
|
||||||
|
rb_name.c_str(),
|
||||||
|
vr_ur,
|
||||||
|
i);
|
||||||
|
rx_sdu->set_timestamp();
|
||||||
|
if (cfg.um.is_mrb) {
|
||||||
|
pdcp->write_pdu_mch(lcid, std::move(rx_sdu));
|
||||||
|
} else {
|
||||||
|
pdcp->write_pdu(lcid, std::move(rx_sdu));
|
||||||
|
}
|
||||||
|
rx_sdu = allocate_unique_buffer(*pool);
|
||||||
|
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->clear();
|
||||||
|
} else {
|
||||||
|
log->info_hex(
|
||||||
|
rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", rb_name.c_str(), vr_ur);
|
||||||
|
rx_sdu->set_timestamp();
|
||||||
|
if (cfg.um.is_mrb) {
|
||||||
|
pdcp->write_pdu_mch(lcid, std::move(rx_sdu));
|
||||||
|
} else {
|
||||||
|
pdcp->write_pdu(lcid, std::move(rx_sdu));
|
||||||
|
}
|
||||||
|
rx_sdu = allocate_unique_buffer(*pool);
|
||||||
|
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
|
||||||
|
rx_window.erase(vr_ur);
|
||||||
|
}
|
||||||
|
|
||||||
|
vr_ur = (vr_ur + 1) % cfg.um.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->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle any SDU segments
|
||||||
|
for (uint32_t i = 0; i < rx_window[vr_ur].header.N_li; i++) {
|
||||||
|
uint16_t len = rx_window[vr_ur].header.li[i];
|
||||||
|
log->debug("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->clear();
|
||||||
|
|
||||||
|
// 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->clear();
|
||||||
|
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.um.rx_mod,
|
||||||
|
(vr_ur_in_rx_sdu + 1) % cfg.um.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)",
|
||||||
|
rb_name.c_str(),
|
||||||
|
vr_ur,
|
||||||
|
i);
|
||||||
|
rx_sdu->set_timestamp();
|
||||||
|
if (cfg.um.is_mrb) {
|
||||||
|
pdcp->write_pdu_mch(lcid, std::move(rx_sdu));
|
||||||
|
} else {
|
||||||
|
pdcp->write_pdu(lcid, std::move(rx_sdu));
|
||||||
|
}
|
||||||
|
rx_sdu = allocate_unique_buffer(*pool);
|
||||||
|
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->clear();
|
||||||
|
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->clear();
|
||||||
|
} else {
|
||||||
|
log->info_hex(
|
||||||
|
rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (update vr_ur last segments)", rb_name.c_str(), vr_ur);
|
||||||
|
rx_sdu->set_timestamp();
|
||||||
|
if (cfg.um.is_mrb) {
|
||||||
|
pdcp->write_pdu_mch(lcid, std::move(rx_sdu));
|
||||||
|
} else {
|
||||||
|
pdcp->write_pdu(lcid, std::move(rx_sdu));
|
||||||
|
}
|
||||||
|
rx_sdu = allocate_unique_buffer(*pool);
|
||||||
|
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
|
||||||
|
rx_window.erase(vr_ur);
|
||||||
|
|
||||||
|
vr_ur = (vr_ur + 1) % cfg.um.rx_mod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only called when lock is hold
|
||||||
|
bool rlc_um_lte::rlc_um_lte_rx::pdu_belongs_to_rx_sdu()
|
||||||
|
{
|
||||||
|
// return true if the currently received SDU
|
||||||
|
if (((vr_ur_in_rx_sdu + 1) % cfg.um.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_lte::rlc_um_lte_rx::inside_reordering_window(uint16_t sn)
|
||||||
|
{
|
||||||
|
if (cfg.um.rx_window_size == 0 || rx_window.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (RX_MOD_BASE(vr_uh - cfg.um.rx_window_size) <= RX_MOD_BASE(sn) && RX_MOD_BASE(sn) < RX_MOD_BASE(vr_uh)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Timeout callback interface
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
void rlc_um_lte::rlc_um_lte_rx::timer_expired(uint32_t timeout_id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
if (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", rb_name.c_str());
|
||||||
|
|
||||||
|
log->warning("Lost PDU SN: %d\n", vr_ur);
|
||||||
|
|
||||||
|
pdu_lost = true;
|
||||||
|
if (rx_sdu != NULL) {
|
||||||
|
rx_sdu->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (RX_MOD_BASE(vr_ur) < RX_MOD_BASE(vr_ux)) {
|
||||||
|
vr_ur = (vr_ur + 1) % cfg.um.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.run();
|
||||||
|
vr_ux = vr_uh;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Helper functions
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
void rlc_um_lte::rlc_um_lte_rx::debug_state()
|
||||||
|
{
|
||||||
|
log->debug("%s vr_ur = %d, vr_ux = %d, vr_uh = %d\n", rb_name.c_str(), vr_ur, vr_ux, vr_uh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 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 (sn_size == rlc_umd_sn_size_t::size5bits) {
|
||||||
|
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 (header->sn_size == rlc_umd_sn_size_t::size5bits) {
|
||||||
|
*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 (header->sn_size == rlc_umd_sn_size_t::size5bits) {
|
||||||
|
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 srslte
|
@ -0,0 +1,688 @@
|
|||||||
|
/*
|
||||||
|
* 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_nr.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#define RX_MOD_NR_BASE(x) (((x)-RX_Next_Highest - cfg.um_nr.UM_Window_Size) % cfg.um_nr.mod)
|
||||||
|
|
||||||
|
using namespace asn1::rrc;
|
||||||
|
|
||||||
|
namespace srslte {
|
||||||
|
|
||||||
|
rlc_um_nr::rlc_um_nr(srslte::log* log_,
|
||||||
|
uint32_t lcid_,
|
||||||
|
srsue::pdcp_interface_rlc* pdcp_,
|
||||||
|
srsue::rrc_interface_rlc* rrc_,
|
||||||
|
srslte::timer_handler* timers_) :
|
||||||
|
rlc_um_base(log_, lcid_, pdcp_, rrc_, timers_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
rlc_um_nr::~rlc_um_nr()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_nr::configure(rlc_config_t cnfg_)
|
||||||
|
{
|
||||||
|
// determine bearer name and configure Rx/Tx objects
|
||||||
|
rb_name = get_rb_name(rrc, lcid, cnfg_.um.is_mrb);
|
||||||
|
|
||||||
|
// store config
|
||||||
|
cfg = cnfg_;
|
||||||
|
|
||||||
|
rx.reset(new rlc_um_nr_rx(this));
|
||||||
|
if (not rx->configure()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.reset(new rlc_um_nr_tx(this));
|
||||||
|
if (not tx->configure(cfg, rb_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log->info("%s configured in %s: sn_field_length=%u bits\n",
|
||||||
|
rb_name.c_str(),
|
||||||
|
srslte::to_string(cnfg_.rlc_mode).c_str(),
|
||||||
|
srslte::to_number(cfg.um_nr.sn_field_length));
|
||||||
|
|
||||||
|
rx_enabled = true;
|
||||||
|
tx_enabled = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Tx Subclass implementation
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
rlc_um_nr::rlc_um_nr_tx::rlc_um_nr_tx(rlc_um_base* parent_) : rlc_um_base_tx(parent_) {}
|
||||||
|
|
||||||
|
uint32_t rlc_um_nr::rlc_um_nr_tx::get_buffer_state()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
|
||||||
|
// 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.um.is_mrb) ? 2 : 3;
|
||||||
|
|
||||||
|
return n_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_nr::rlc_um_nr_tx::configure(rlc_config_t cnfg_, std::string rb_name_)
|
||||||
|
{
|
||||||
|
cfg = cnfg_;
|
||||||
|
|
||||||
|
if (cfg.um_nr.mod == 0) {
|
||||||
|
log->error("Error configuring %s RLC UM: tx_mod==0\n", rb_name.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_sdu_queue.resize(cnfg_.tx_queue_length);
|
||||||
|
|
||||||
|
rb_name = rb_name_;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rlc_um_nr::rlc_um_nr_tx::build_data_pdu(unique_byte_buffer_t pdu, uint8_t* payload, uint32_t nof_bytes)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
rlc_um_nr_pdu_header_t header = {};
|
||||||
|
header.si = rlc_nr_si_field_t::full_sdu;
|
||||||
|
header.sn = TX_Next;
|
||||||
|
header.sn_size = cfg.um_nr.sn_field_length;
|
||||||
|
|
||||||
|
uint32_t to_move = 0;
|
||||||
|
uint8_t* pdu_ptr = pdu->msg;
|
||||||
|
|
||||||
|
int head_len = rlc_um_nr_packed_length(header);
|
||||||
|
int pdu_space = SRSLTE_MIN(nof_bytes, pdu->get_tailroom());
|
||||||
|
|
||||||
|
if (pdu_space <= head_len + 1) {
|
||||||
|
log->warning("%s Cannot build a PDU - %d bytes available, %d bytes required for header\n",
|
||||||
|
rb_name.c_str(),
|
||||||
|
nof_bytes,
|
||||||
|
head_len);
|
||||||
|
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", rb_name.c_str(), to_move, tx_sdu->N_bytes);
|
||||||
|
memcpy(pdu_ptr, tx_sdu->msg, 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", rb_name.c_str(), tx_sdu->get_latency_us());
|
||||||
|
tx_sdu.reset();
|
||||||
|
header.si = rlc_nr_si_field_t::last_segment;
|
||||||
|
} else {
|
||||||
|
header.si = rlc_nr_si_field_t::neither_first_nor_last_segment;
|
||||||
|
}
|
||||||
|
pdu_space -= SRSLTE_MIN(to_move, pdu->get_tailroom());
|
||||||
|
header.so = next_so;
|
||||||
|
} else {
|
||||||
|
// Pull SDU from queue
|
||||||
|
log->debug("pdu_space=%d, head_len=%d\n", pdu_space, head_len);
|
||||||
|
|
||||||
|
head_len = rlc_um_nr_packed_length(header);
|
||||||
|
tx_sdu = tx_sdu_queue.read();
|
||||||
|
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 - %d bytes of %d remaining\n", rb_name.c_str(), to_move, tx_sdu->N_bytes);
|
||||||
|
memcpy(pdu_ptr, tx_sdu->msg, 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", rb_name.c_str(), tx_sdu->get_latency_us());
|
||||||
|
tx_sdu.reset();
|
||||||
|
header.si = rlc_nr_si_field_t::full_sdu;
|
||||||
|
} else {
|
||||||
|
header.si = rlc_nr_si_field_t::first_segment;
|
||||||
|
}
|
||||||
|
pdu_space -= to_move;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance SO offset
|
||||||
|
next_so += to_move;
|
||||||
|
|
||||||
|
// Update SN if needed
|
||||||
|
if (header.si == rlc_nr_si_field_t::last_segment) {
|
||||||
|
TX_Next = (TX_Next + 1) % cfg.um_nr.mod;
|
||||||
|
next_so = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add header and TX
|
||||||
|
rlc_um_nr_write_data_pdu_header(header, pdu.get());
|
||||||
|
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", rb_name.c_str(), header.sn, pdu->N_bytes);
|
||||||
|
|
||||||
|
debug_state();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_nr::rlc_um_nr_tx::debug_state()
|
||||||
|
{
|
||||||
|
log->debug("%s TX_Next = %d\n", rb_name.c_str(), TX_Next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Rx Subclass implementation
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
rlc_um_nr::rlc_um_nr_rx::rlc_um_nr_rx(rlc_um_base* parent_) :
|
||||||
|
rlc_um_base_rx(parent_), reassembly_timer(timers->get_unique_timer())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_nr::rlc_um_nr_rx::configure()
|
||||||
|
{
|
||||||
|
if (cfg.um_nr.mod == 0) {
|
||||||
|
log->error("Error configuring %s RLC UM: rx_mod==0\n", rb_name.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check timer
|
||||||
|
if (not reassembly_timer.is_valid()) {
|
||||||
|
log->error("Configuring RLC UM NR RX: timers not configured\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure timer
|
||||||
|
if (cfg.um_nr.t_reassembly_ms > 0) {
|
||||||
|
reassembly_timer.set(static_cast<uint32_t>(cfg.um_nr.t_reassembly_ms),
|
||||||
|
[this](uint32_t tid) { timer_expired(tid); });
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_nr::rlc_um_nr_rx::stop()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
reset();
|
||||||
|
reassembly_timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_nr::rlc_um_nr_rx::reset()
|
||||||
|
{
|
||||||
|
RX_Next_Reassembly = 0;
|
||||||
|
RX_Timer_Trigger = 0;
|
||||||
|
RX_Next_Highest = 0;
|
||||||
|
|
||||||
|
rx_sdu.reset();
|
||||||
|
|
||||||
|
// Drop all messages in RX window
|
||||||
|
rx_window.clear();
|
||||||
|
|
||||||
|
// stop timer
|
||||||
|
if (reassembly_timer.is_valid()) {
|
||||||
|
reassembly_timer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TS 38.322 Sec. 5.1.2
|
||||||
|
void rlc_um_nr::rlc_um_nr_rx::reestablish()
|
||||||
|
{
|
||||||
|
// drop all SDUs, SDU segments, PDUs and reset timers
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TS 38.322 v15.003 Section 5.2.2.2.4
|
||||||
|
void rlc_um_nr::rlc_um_nr_rx::timer_expired(uint32_t timeout_id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
if (reassembly_timer.id() == timeout_id) {
|
||||||
|
log->info("%s reassembly timeout expiry - updating RX_Next_Reassembly and reassembling\n", rb_name.c_str());
|
||||||
|
|
||||||
|
log->warning("Lost PDU SN: %d\n", RX_Next_Reassembly);
|
||||||
|
metrics.num_lost_pdus++;
|
||||||
|
|
||||||
|
if (rx_sdu != nullptr) {
|
||||||
|
rx_sdu->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update RX_Next_Reassembly to the next SN that has not been reassembled yet
|
||||||
|
RX_Next_Reassembly = RX_Timer_Trigger;
|
||||||
|
while (RX_MOD_NR_BASE(RX_Next_Reassembly) < RX_MOD_NR_BASE(RX_Next_Highest)) {
|
||||||
|
RX_Next_Reassembly = (RX_Next_Reassembly + 1) % cfg.um_nr.mod;
|
||||||
|
debug_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
// discard all segments with SN < updated RX_Next_Reassembly
|
||||||
|
for (auto it = rx_window.begin(); it != rx_window.end();) {
|
||||||
|
if (it->first < RX_Next_Reassembly) {
|
||||||
|
it = rx_window.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check start of t_reassembly
|
||||||
|
if (RX_MOD_NR_BASE(RX_Next_Highest) > RX_MOD_NR_BASE(RX_Next_Reassembly + 1) ||
|
||||||
|
(RX_MOD_NR_BASE(RX_Next_Highest) == RX_MOD_NR_BASE(RX_Next_Reassembly + 1) &&
|
||||||
|
has_missing_byte_segment(RX_Next_Reassembly))) {
|
||||||
|
reassembly_timer.run();
|
||||||
|
RX_Timer_Trigger = RX_Next_Highest;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sec 5.2.2.2.1
|
||||||
|
bool rlc_um_nr::rlc_um_nr_rx::sn_in_reassembly_window(const uint32_t sn)
|
||||||
|
{
|
||||||
|
return (RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size) <= RX_MOD_NR_BASE(sn) &&
|
||||||
|
RX_MOD_NR_BASE(sn) < RX_MOD_NR_BASE(RX_Next_Highest));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sec 5.2.2.2.2
|
||||||
|
bool rlc_um_nr::rlc_um_nr_rx::sn_invalid_for_rx_buffer(const uint32_t sn)
|
||||||
|
{
|
||||||
|
return (RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size) <= RX_MOD_NR_BASE(sn) &&
|
||||||
|
RX_MOD_NR_BASE(sn) < RX_MOD_NR_BASE(RX_Next_Reassembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_byte_buffer_t rlc_um_nr::rlc_um_nr_rx::rlc_um_nr_strip_pdu_header(const rlc_um_nr_pdu_header_t& header,
|
||||||
|
const uint8_t* payload,
|
||||||
|
const uint32_t nof_bytes)
|
||||||
|
{
|
||||||
|
unique_byte_buffer_t sdu = allocate_unique_buffer(*pool);
|
||||||
|
if (!sdu) {
|
||||||
|
log->error("Discarting packet: no space in buffer pool\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
memcpy(sdu->msg, payload, nof_bytes);
|
||||||
|
sdu->N_bytes = nof_bytes;
|
||||||
|
|
||||||
|
// strip RLC header
|
||||||
|
int header_len = rlc_um_nr_packed_length(header);
|
||||||
|
sdu->msg += header_len;
|
||||||
|
sdu->N_bytes -= header_len;
|
||||||
|
return sdu;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rlc_um_nr::rlc_um_nr_rx::has_missing_byte_segment(const uint32_t sn)
|
||||||
|
{
|
||||||
|
// is at least one missing byte segment of the RLC SDU associated with SN = RX_Next_Reassembly before the last byte of
|
||||||
|
// all received segments of this RLC SDU
|
||||||
|
// FIXME: check assumption
|
||||||
|
// if SN can be found in rx_window, it means that at least one segment is missing
|
||||||
|
return (rx_window.find(sn) != rx_window.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sect 5.2.2.2.3
|
||||||
|
void rlc_um_nr::rlc_um_nr_rx::handle_rx_buffer_update(const uint32_t sn)
|
||||||
|
{
|
||||||
|
if (rx_window.find(sn) != rx_window.end()) {
|
||||||
|
// iterate over received segments and try to assemble full SDU
|
||||||
|
auto& pdu = rx_window.at(sn);
|
||||||
|
for (auto it = pdu.segments.begin(); it != pdu.segments.end();) {
|
||||||
|
log->debug("Have %s segment with SO=%d for SN=%d\n",
|
||||||
|
to_string_short(it->second.header.si).c_str(),
|
||||||
|
it->second.header.so,
|
||||||
|
it->second.header.sn);
|
||||||
|
if (it->second.header.so == pdu.next_expected_so) {
|
||||||
|
if (pdu.next_expected_so == 0) {
|
||||||
|
if (pdu.sdu == nullptr) {
|
||||||
|
// reuse buffer of first segment for final SDU
|
||||||
|
pdu.sdu = std::move(it->second.buf);
|
||||||
|
pdu.next_expected_so = pdu.sdu->N_bytes;
|
||||||
|
log->debug("Reusing first segment of SN=%d for final SDU\n", it->second.header.sn);
|
||||||
|
it = pdu.segments.erase(it);
|
||||||
|
} else {
|
||||||
|
log->debug("SDU buffer already allocated. Possible retransmission of first segment.\n");
|
||||||
|
if (it->second.header.so != pdu.next_expected_so) {
|
||||||
|
log->error("Invalid PDU. SO doesn't match. Discarting all segments of SN=%d.\n", sn);
|
||||||
|
rx_window.erase(sn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (it->second.buf->N_bytes > pdu.sdu->get_tailroom()) {
|
||||||
|
log->error("Cannot fit RLC PDU in SDU buffer (tailroom=%d, len=%d), dropping both. Erasing SN=%d.\n",
|
||||||
|
rx_sdu->get_tailroom(),
|
||||||
|
it->second.buf->N_bytes,
|
||||||
|
it->second.header.sn);
|
||||||
|
rx_window.erase(sn);
|
||||||
|
metrics.num_lost_pdus++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add this segment to the end of the SDU buffer
|
||||||
|
memcpy(pdu.sdu->msg + pdu.sdu->N_bytes, it->second.buf->msg, it->second.buf->N_bytes);
|
||||||
|
pdu.sdu->N_bytes += it->second.buf->N_bytes;
|
||||||
|
pdu.next_expected_so += it->second.buf->N_bytes;
|
||||||
|
log->debug("Appended SO=%d of SN=%d\n", it->second.header.so, it->second.header.sn);
|
||||||
|
it = pdu.segments.erase(it);
|
||||||
|
|
||||||
|
if (pdu.next_expected_so == pdu.total_sdu_length) {
|
||||||
|
// deliver full SDU to upper layers
|
||||||
|
log->info("Delivering %s SDU SN=%d (%d B)", rb_name.c_str(), sn, pdu.sdu->N_bytes);
|
||||||
|
pdcp->write_pdu(lcid, std::move(pdu.sdu));
|
||||||
|
|
||||||
|
// find next SN in rx buffer
|
||||||
|
if (sn == RX_Next_Reassembly) {
|
||||||
|
RX_Next_Reassembly = ((RX_Next_Reassembly + 1) % cfg.um_nr.mod);
|
||||||
|
while (RX_MOD_NR_BASE(RX_Next_Reassembly) < RX_MOD_NR_BASE(RX_Next_Highest)) {
|
||||||
|
RX_Next_Reassembly = (RX_Next_Reassembly + 1) % cfg.um_nr.mod;
|
||||||
|
}
|
||||||
|
log->debug("Updating RX_Next_Reassembly=%d\n", RX_Next_Reassembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete PDU from rx_window
|
||||||
|
rx_window.erase(sn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// handle next segment
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for SN outside of rx window
|
||||||
|
if (not sn_in_reassembly_window(sn)) {
|
||||||
|
// update RX_Next_highest
|
||||||
|
RX_Next_Highest = sn + 1;
|
||||||
|
log->debug("Updating RX_Next_Highest=%d\n", RX_Next_Highest);
|
||||||
|
|
||||||
|
// drop all SNs outside of new rx window
|
||||||
|
for (auto it = rx_window.begin(); it != rx_window.end();) {
|
||||||
|
if (not sn_in_reassembly_window(it->first)) {
|
||||||
|
log->info("%s SN: %d outside rx window [%d:%d] - discarding\n",
|
||||||
|
rb_name.c_str(),
|
||||||
|
it->first,
|
||||||
|
RX_Next_Highest - cfg.um_nr.UM_Window_Size,
|
||||||
|
RX_Next_Highest);
|
||||||
|
it = rx_window.erase(it);
|
||||||
|
metrics.num_lost_pdus++;
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not sn_in_reassembly_window(RX_Next_Reassembly)) {
|
||||||
|
// update RX_Next_Reassembly to first SN that has not been reassembled and delivered
|
||||||
|
for (const auto& rx_pdu : rx_window) {
|
||||||
|
if (rx_pdu.first >= RX_MOD_NR_BASE(RX_Next_Highest - cfg.um_nr.UM_Window_Size)) {
|
||||||
|
RX_Next_Reassembly = rx_pdu.first;
|
||||||
|
log->debug("Updating RX_Next_Reassembly=%d\n", RX_Next_Reassembly);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reassembly_timer.is_running()) {
|
||||||
|
if (RX_Timer_Trigger <= RX_Next_Reassembly ||
|
||||||
|
(not sn_in_reassembly_window(RX_Timer_Trigger) and RX_Timer_Trigger != RX_Next_Highest) ||
|
||||||
|
((RX_Next_Highest == RX_Next_Reassembly + 1) && not has_missing_byte_segment(sn))) {
|
||||||
|
reassembly_timer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not reassembly_timer.is_running() && has_missing_byte_segment(sn)) {
|
||||||
|
if (RX_Next_Highest > RX_Next_Reassembly + 1) {
|
||||||
|
reassembly_timer.run();
|
||||||
|
RX_Timer_Trigger = RX_Next_Highest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log->error("SN=%d does not exist in Rx buffer\n", sn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void rlc_um_nr::rlc_um_nr_rx::update_total_sdu_length(rlc_umd_pdu_segments_nr_t& pdu_segments,
|
||||||
|
const rlc_umd_pdu_nr_t& rx_pdu)
|
||||||
|
{
|
||||||
|
if (rx_pdu.header.si == rlc_nr_si_field_t::last_segment) {
|
||||||
|
pdu_segments.total_sdu_length = rx_pdu.header.so + rx_pdu.buf->N_bytes;
|
||||||
|
log->info("%s updating total SDU length for SN=%d to %d B\n",
|
||||||
|
rb_name.c_str(),
|
||||||
|
rx_pdu.header.sn,
|
||||||
|
pdu_segments.total_sdu_length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Section 5.2.2.2.2
|
||||||
|
void rlc_um_nr::rlc_um_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
|
||||||
|
rlc_um_nr_pdu_header_t header = {};
|
||||||
|
rlc_um_nr_read_data_pdu_header(payload, nof_bytes, cfg.um_nr.sn_field_length, &header);
|
||||||
|
log->debug_hex(payload, nof_bytes, "RX %s Rx data PDU (%d B)", rb_name.c_str(), nof_bytes);
|
||||||
|
|
||||||
|
// check if PDU contains a SN
|
||||||
|
if (header.si == rlc_nr_si_field_t::full_sdu) {
|
||||||
|
// copy full PDU into buffer
|
||||||
|
unique_byte_buffer_t sdu = rlc_um_nr_strip_pdu_header(header, payload, nof_bytes);
|
||||||
|
|
||||||
|
// deliver to PDCP
|
||||||
|
log->info("Delivering %s SDU (%d B)", rb_name.c_str(), sdu->N_bytes);
|
||||||
|
pdcp->write_pdu(lcid, std::move(sdu));
|
||||||
|
} else if (sn_invalid_for_rx_buffer(header.sn)) {
|
||||||
|
log->info("%s Discarding SN=%d\n", rb_name.c_str(), header.sn);
|
||||||
|
// Nothing else to do here ..
|
||||||
|
} else {
|
||||||
|
// place PDU in receive buffer
|
||||||
|
rlc_umd_pdu_nr_t rx_pdu = {};
|
||||||
|
rx_pdu.header = header;
|
||||||
|
rx_pdu.buf = rlc_um_nr_strip_pdu_header(header, payload, nof_bytes);
|
||||||
|
|
||||||
|
// check if this SN is already present in rx buffer
|
||||||
|
if (rx_window.find(header.sn) == rx_window.end()) {
|
||||||
|
// first received segment of this SN, add to rx buffer
|
||||||
|
log->info("%s placing %s segment of SN=%d in Rx buffer\n",
|
||||||
|
rb_name.c_str(),
|
||||||
|
to_string_short(header.si).c_str(),
|
||||||
|
header.sn);
|
||||||
|
rlc_umd_pdu_segments_nr_t pdu_segments = {};
|
||||||
|
update_total_sdu_length(pdu_segments, rx_pdu);
|
||||||
|
pdu_segments.segments.emplace(header.so, std::move(rx_pdu));
|
||||||
|
rx_window[header.sn] = std::move(pdu_segments);
|
||||||
|
} else {
|
||||||
|
// other segment for this SN already present, update received data
|
||||||
|
log->info("%s updating SN=%d at SO=%d with %d B\n",
|
||||||
|
rb_name.c_str(),
|
||||||
|
rx_pdu.header.sn,
|
||||||
|
rx_pdu.header.so,
|
||||||
|
rx_pdu.buf->N_bytes);
|
||||||
|
|
||||||
|
auto& pdu_segments = rx_window.at(header.sn);
|
||||||
|
|
||||||
|
// calculate total SDU length
|
||||||
|
update_total_sdu_length(pdu_segments, rx_pdu);
|
||||||
|
|
||||||
|
// append to list of segments
|
||||||
|
pdu_segments.segments.emplace(header.so, std::move(rx_pdu));
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle received segments
|
||||||
|
handle_rx_buffer_update(header.sn);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
void rlc_um_nr::rlc_um_nr_rx::debug_state()
|
||||||
|
{
|
||||||
|
log->debug("%s RX_Next_Reassembly=%d, RX_Timer_Trigger=%d, RX_Next_Highest=%d, t_Reassembly=%s\n",
|
||||||
|
rb_name.c_str(),
|
||||||
|
RX_Next_Reassembly,
|
||||||
|
RX_Timer_Trigger,
|
||||||
|
RX_Next_Highest,
|
||||||
|
reassembly_timer.is_running() ? "running" : "stopped");
|
||||||
|
}
|
||||||
|
/****************************************************************************
|
||||||
|
* Header pack/unpack helper functions
|
||||||
|
* Ref: 3GPP TS 38.322 v15.3.0 Section 6.2.2.3
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
uint32_t rlc_um_nr_read_data_pdu_header(const byte_buffer_t* pdu,
|
||||||
|
const rlc_um_nr_sn_size_t sn_size,
|
||||||
|
rlc_um_nr_pdu_header_t* header)
|
||||||
|
{
|
||||||
|
return rlc_um_nr_read_data_pdu_header(pdu->msg, pdu->N_bytes, sn_size, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rlc_um_nr_read_data_pdu_header(const uint8_t* payload,
|
||||||
|
const uint32_t nof_bytes,
|
||||||
|
const rlc_um_nr_sn_size_t sn_size,
|
||||||
|
rlc_um_nr_pdu_header_t* header)
|
||||||
|
{
|
||||||
|
uint8_t* ptr = const_cast<uint8_t*>(payload);
|
||||||
|
|
||||||
|
header->sn_size = sn_size;
|
||||||
|
|
||||||
|
// Fixed part
|
||||||
|
if (sn_size == rlc_um_nr_sn_size_t::size6bits) {
|
||||||
|
header->si = (rlc_nr_si_field_t)((*ptr >> 6) & 0x03); // 2 bits SI
|
||||||
|
header->sn = *ptr & 0x3F; // 6 bits SN
|
||||||
|
// sanity check
|
||||||
|
if (header->si == rlc_nr_si_field_t::full_sdu and !(header->sn == 0)) {
|
||||||
|
fprintf(stderr, "Malformed PDU, reserved bits are set.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
} else if (sn_size == rlc_um_nr_sn_size_t::size12bits) {
|
||||||
|
header->si = (rlc_nr_si_field_t)((*ptr >> 6) & 0x03); // 2 bits SI
|
||||||
|
header->sn = (*ptr & 0x0F) << 4; // 4 bits SN
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
if (header->si == rlc_nr_si_field_t::first_segment) {
|
||||||
|
// make sure two reserved bits are not set
|
||||||
|
if (((*ptr >> 4) & 0x03) != 0) {
|
||||||
|
fprintf(stderr, "Malformed PDU, reserved bits are set.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue unpacking remaining SN
|
||||||
|
ptr++;
|
||||||
|
header->sn |= (*ptr & 0xFF); // 8 bits SN
|
||||||
|
ptr++;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Unsupported SN length\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read optional part
|
||||||
|
if (header->si == rlc_nr_si_field_t::last_segment ||
|
||||||
|
header->si == rlc_nr_si_field_t::neither_first_nor_last_segment) {
|
||||||
|
// read SO
|
||||||
|
header->so = (*ptr & 0xFF) << 8;
|
||||||
|
ptr++;
|
||||||
|
header->so |= (*ptr & 0xFF);
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return consumed bytes
|
||||||
|
return (ptr - payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rlc_um_nr_packed_length(const rlc_um_nr_pdu_header_t& header)
|
||||||
|
{
|
||||||
|
uint32_t len = 0;
|
||||||
|
if (header.si == rlc_nr_si_field_t::full_sdu || header.si == rlc_nr_si_field_t::first_segment) {
|
||||||
|
len = 1;
|
||||||
|
if (header.sn_size == rlc_um_nr_sn_size_t::size12bits) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (header.sn_size == rlc_um_nr_sn_size_t::size6bits) {
|
||||||
|
len = 3;
|
||||||
|
} else {
|
||||||
|
len = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rlc_um_nr_write_data_pdu_header(const rlc_um_nr_pdu_header_t& header, byte_buffer_t* pdu)
|
||||||
|
{
|
||||||
|
// Make room for the header
|
||||||
|
uint32_t len = rlc_um_nr_packed_length(header);
|
||||||
|
pdu->msg -= len;
|
||||||
|
uint8_t* ptr = pdu->msg;
|
||||||
|
|
||||||
|
// write SI field
|
||||||
|
*ptr = (header.si & 0x03) << 6; // 2 bits SI
|
||||||
|
|
||||||
|
if (header.si == rlc_nr_si_field_t::full_sdu) {
|
||||||
|
// that's all ..
|
||||||
|
ptr++;
|
||||||
|
} else {
|
||||||
|
if (header.sn_size == rlc_um_nr_sn_size_t::size6bits) {
|
||||||
|
// write SN
|
||||||
|
*ptr |= (header.sn & 0x3f); // 6 bit SN
|
||||||
|
ptr++;
|
||||||
|
} else {
|
||||||
|
// 12bit SN
|
||||||
|
*ptr |= (header.sn & 0xf); // 4 bit SN
|
||||||
|
ptr++;
|
||||||
|
*ptr = (header.sn & 0xFF); // remaining 8 bit SN
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
if (header.so) {
|
||||||
|
// write SO
|
||||||
|
*ptr = (header.so) >> 8; // first part of SO
|
||||||
|
ptr++;
|
||||||
|
*ptr = (header.so & 0xFF); // second part of SO
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdu->N_bytes += ptr - pdu->msg;
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace srslte
|
Loading…
Reference in New Issue