Merge branch 'next' of github.com:softwareradiosystems/srsLTE into next

master
Ismael Gomez 7 years ago
commit 443d2b7fa0

@ -34,6 +34,7 @@
#define MAC_LTE_DLT 147 #define MAC_LTE_DLT 147
#define NAS_LTE_DLT 148 #define NAS_LTE_DLT 148
#define RLC_LTE_DLT 149 // UDP needs to be selected as protocol
/* This structure gets written to the start of the file */ /* This structure gets written to the start of the file */
@ -104,6 +105,67 @@ typedef struct NAS_Context_Info_s {
} NAS_Context_Info_t; } NAS_Context_Info_t;
/* RLC-LTE disector */
/* rlcMode */
#define RLC_TM_MODE 1
#define RLC_UM_MODE 2
#define RLC_AM_MODE 4
#define RLC_PREDEF 8
/* priority ? */
/* channelType */
#define CHANNEL_TYPE_CCCH 1
#define CHANNEL_TYPE_BCCH_BCH 2
#define CHANNEL_TYPE_PCCH 3
#define CHANNEL_TYPE_SRB 4
#define CHANNEL_TYPE_DRB 5
#define CHANNEL_TYPE_BCCH_DL_SCH 6
#define CHANNEL_TYPE_MCCH 7
#define CHANNEL_TYPE_MTCH 8
/* sequenceNumberLength */
#define UM_SN_LENGTH_5_BITS 5
#define UM_SN_LENGTH_10_BITS 10
#define AM_SN_LENGTH_10_BITS 10
#define AM_SN_LENGTH_16_BITS 16
/* Narrow band mode */
typedef enum {
rlc_no_nb_mode = 0,
rlc_nb_mode = 1
} rlc_lte_nb_mode;
/* Context information for every RLC PDU that will be logged */
typedef struct {
unsigned char rlcMode;
unsigned char direction;
unsigned char priority;
unsigned char sequenceNumberLength;
unsigned short ueid;
unsigned short channelType;
unsigned short channelId; /* for SRB: 1=SRB1, 2=SRB2, 3=SRB1bis; for DRB: DRB ID */
unsigned short pduLength;
bool extendedLiField;
rlc_lte_nb_mode nbMode;
} RLC_Context_Info_t;
// See Wireshark's packet-rlc-lte.h for details
#define RLC_LTE_START_STRING "rlc-lte"
#define RLC_LTE_SN_LENGTH_TAG 0x02
#define RLC_LTE_DIRECTION_TAG 0x03
#define RLC_LTE_PRIORITY_TAG 0x04
#define RLC_LTE_UEID_TAG 0x05
#define RLC_LTE_CHANNEL_TYPE_TAG 0x06
#define RLC_LTE_CHANNEL_ID_TAG 0x07
#define RLC_LTE_EXT_LI_FIELD_TAG 0x08
#define RLC_LTE_NB_MODE_TAG 0x09
#define RLC_LTE_PAYLOAD_TAG 0x01
/************************************************************************** /**************************************************************************
* API functions for opening/closing LTE PCAP files * * API functions for opening/closing LTE PCAP files *
**************************************************************************/ **************************************************************************/
@ -247,4 +309,93 @@ inline int LTE_PCAP_NAS_WritePDU(FILE *fd, NAS_Context_Info_t *context,
return 1; return 1;
} }
/**************************************************************************
* API functions for writing RLC-LTE PCAP files *
**************************************************************************/
/* Write an individual RLC PDU (PCAP packet header + UDP header + rlc-context + rlc-pdu) */
inline int LTE_PCAP_RLC_WritePDU(FILE *fd, RLC_Context_Info_t *context,
const unsigned char *PDU, unsigned int length)
{
pcaprec_hdr_t packet_header;
char context_header[256];
int offset = 0;
uint16_t tmp16;
/* Can't write if file wasn't successfully opened */
if (fd == NULL) {
printf("Error: Can't write to empty file handle\n");
return 0;
}
/*****************************************************************/
// Add dummy UDP header, start with src and dest port
context_header[offset++] = 0xde;
context_header[offset++] = 0xad;
context_header[offset++] = 0xbe;
context_header[offset++] = 0xef;
// length
tmp16 = length + 12;
memcpy(context_header+offset, &tmp16, 2);
offset += 2;
// dummy CRC
context_header[offset++] = 0xde;
context_header[offset++] = 0xad;
// Start magic string
memcpy(&context_header[offset], RLC_LTE_START_STRING, strlen(RLC_LTE_START_STRING));
offset += strlen(RLC_LTE_START_STRING);
// Fixed field RLC mode
context_header[offset++] = context->rlcMode;
// Conditional fields
if (context->rlcMode == RLC_UM_MODE) {
context_header[offset++] = RLC_LTE_SN_LENGTH_TAG;
context_header[offset++] = context->sequenceNumberLength;
}
// Optional fields
context_header[offset++] = RLC_LTE_DIRECTION_TAG;
context_header[offset++] = context->direction;
context_header[offset++] = RLC_LTE_PRIORITY_TAG;
context_header[offset++] = context->priority;
context_header[offset++] = RLC_LTE_UEID_TAG;
tmp16 = htons(context->ueid);
memcpy(context_header+offset, &tmp16, 2);
offset += 2;
context_header[offset++] = RLC_LTE_CHANNEL_TYPE_TAG;
tmp16 = htons(context->channelType);
memcpy(context_header+offset, &tmp16, 2);
offset += 2;
context_header[offset++] = RLC_LTE_CHANNEL_ID_TAG;
tmp16 = htons(context->channelId);
memcpy(context_header+offset, &tmp16, 2);
offset += 2;
// Now the actual PDU
context_header[offset++] = RLC_LTE_PAYLOAD_TAG;
// PCAP header
struct timeval t;
gettimeofday(&t, NULL);
packet_header.ts_sec = t.tv_sec;
packet_header.ts_usec = t.tv_usec;
packet_header.incl_len = offset + length;
packet_header.orig_len = offset + length;
// Write everything to file
fwrite(&packet_header, sizeof(pcaprec_hdr_t), 1, fd);
fwrite(context_header, 1, offset, fd);
fwrite(PDU, 1, length, fd);
return 1;
}
#endif // SRSLTE_PCAP_H #endif // SRSLTE_PCAP_H

@ -0,0 +1,65 @@
/**
*
* \section COPYRIGHT
*
* Copyright 2013-2015 Software Radio Systems Limited
*
* \section LICENSE
*
* This file is part of the srsUE library.
*
* srsUE 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.
*
* srsUE 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 RLCPCAP_H
#define RLCPCAP_H
#include <stdint.h>
#include "srslte/common/pcap.h"
namespace srslte {
class rlc_pcap
{
public:
rlc_pcap() {enable_write=false; ue_id=0; pcap_file = NULL; };
void enable(bool en);
void open(const char *filename, uint32_t ue_id = 0);
void close();
void set_ue_id(uint16_t ue_id);
void write_dl_am_ccch(uint8_t* pdu, uint32_t pdu_len_bytes);
void write_ul_am_ccch(uint8_t* pdu, uint32_t pdu_len_bytes);
private:
bool enable_write;
FILE *pcap_file;
uint32_t ue_id;
void pack_and_write(uint8_t* pdu,
uint32_t pdu_len_bytes,
uint8_t mode,
uint8_t direction,
uint8_t priority,
uint8_t seqnumberlength,
uint16_t ueid,
uint16_t channel_type,
uint16_t channel_id);
};
} // namespace srsue
#endif // RLCPCAP_H

@ -217,8 +217,10 @@ bool rlc_am_is_control_pdu(byte_buffer_t *pdu);
bool rlc_am_is_control_pdu(uint8_t *payload); bool rlc_am_is_control_pdu(uint8_t *payload);
bool rlc_am_is_pdu_segment(uint8_t *payload); bool rlc_am_is_pdu_segment(uint8_t *payload);
std::string rlc_am_to_string(rlc_status_pdu_t *status); std::string rlc_am_to_string(rlc_status_pdu_t *status);
bool rlc_am_start_aligned(uint8_t fi); bool rlc_am_start_aligned(const uint8_t fi);
bool rlc_am_end_aligned(uint8_t fi); bool rlc_am_end_aligned(const uint8_t fi);
bool rlc_am_is_unaligned(const uint8_t fi);
bool rlc_am_not_start_aligned(const uint8_t fi);
} // namespace srslte } // namespace srslte

@ -83,6 +83,9 @@ public:
srslte_rlc_am_config_t am; srslte_rlc_am_config_t am;
srslte_rlc_um_config_t um; srslte_rlc_um_config_t um;
// Default ctor
srslte_rlc_config_t(): rlc_mode(LIBLTE_RRC_RLC_MODE_AM), am(), um() {};
// Constructor based on liblte's RLC config // Constructor based on liblte's RLC config
srslte_rlc_config_t(LIBLTE_RRC_RLC_CONFIG_STRUCT *cnfg) : rlc_mode(cnfg->rlc_mode), am(), um() srslte_rlc_config_t(LIBLTE_RRC_RLC_CONFIG_STRUCT *cnfg) : rlc_mode(cnfg->rlc_mode), am(), um()
{ {

@ -0,0 +1,90 @@
/**
*
* \section COPYRIGHT
*
* Copyright 2013-2015 Software Radio Systems Limited
*
* \section LICENSE
*
* This file is part of the srsUE library.
*
* srsUE 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.
*
* srsUE 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 <stdint.h>
#include "srslte/srslte.h"
#include "srslte/common/pcap.h"
#include "srslte/common/rlc_pcap.h"
namespace srslte {
void rlc_pcap::enable(bool en)
{
enable_write = true;
}
void rlc_pcap::open(const char* filename, uint32_t ue_id)
{
fprintf(stdout, "Opening RLC PCAP with DLT=%d\n", RLC_LTE_DLT);
pcap_file = LTE_PCAP_Open(RLC_LTE_DLT, filename);
this->ue_id = ue_id;
enable_write = true;
}
void rlc_pcap::close()
{
fprintf(stdout, "Saving RLC PCAP file\n");
LTE_PCAP_Close(pcap_file);
}
void rlc_pcap::set_ue_id(uint16_t ue_id) {
this->ue_id = ue_id;
}
void rlc_pcap::pack_and_write(uint8_t* pdu, uint32_t pdu_len_bytes, uint8_t mode, uint8_t direction, uint8_t priority, uint8_t seqnumberlength, uint16_t ueid, uint16_t channel_type, uint16_t channel_id)
{
if (enable_write) {
RLC_Context_Info_t context;
context.rlcMode = mode;
context.direction = direction;
context.priority = priority;
context.sequenceNumberLength = seqnumberlength;
context.ueid = ueid;
context.channelType = channel_type;
context.channelId = channel_id;
context.pduLength = pdu_len_bytes;
if (pdu) {
LTE_PCAP_RLC_WritePDU(pcap_file, &context, pdu, pdu_len_bytes);
}
}
}
void rlc_pcap::write_dl_am_ccch(uint8_t* pdu, uint32_t pdu_len_bytes)
{
uint8_t priority = 0;
uint8_t seqnumberlength = 0; // normal length of 10bit
uint8_t channel_id = 0;
pack_and_write(pdu, pdu_len_bytes, RLC_AM_MODE, DIRECTION_DOWNLINK, priority, seqnumberlength, ue_id, CHANNEL_TYPE_CCCH, channel_id);
}
void rlc_pcap::write_ul_am_ccch(uint8_t* pdu, uint32_t pdu_len_bytes)
{
uint8_t priority = 0;
uint8_t seqnumberlength = 0; // normal length of 10bit
uint8_t channel_id = 0;
pack_and_write(pdu, pdu_len_bytes, RLC_AM_MODE, DIRECTION_UPLINK, priority, seqnumberlength, ue_id, CHANNEL_TYPE_CCCH, channel_id);
}
}

@ -224,7 +224,76 @@ add_test(pmch_file_test pmch_file_test -i ${CMAKE_CURRENT_SOURCE_DIR}/pmch_100p
add_executable(pusch_test pusch_test.c) add_executable(pusch_test pusch_test.c)
target_link_libraries(pusch_test srslte_phy) target_link_libraries(pusch_test srslte_phy)
add_test(pusch_test pusch_test) if (NOT DEFINED TEST_EXTENSION)
set(TEST_EXTENSION Normal)
endif(NOT DEFINED TEST_EXTENSION)
if (TEST_EXTENSION STREQUAL Paranoid)
# All valid number of PRBs for PUSCH
set(cell_n_prb_valid 1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 40 45 48 50 54 60 64 72 75 80 81 90 96 100)
set(pusch_min_mcs 0)
set(pusch_max_mcs 28)
set(pusch_step_mcs 1)
set(pusch_acks -1 0 1)
set(pusch_cqi none wideband)
else (TEST_EXTENSION STREQUAL Paranoid)
set(cell_n_prb_valid 6 15 25 50 100)
set(pusch_min_mcs 0)
set(pusch_max_mcs 28)
set(pusch_step_mcs 10)
set(pusch_acks -1 0)
set(pusch_cqi none wideband)
endif (TEST_EXTENSION STREQUAL Paranoid)
foreach (cell_n_prb 6 15 25 50 75 100)
set(pusch_cell_n_prb)
foreach (n_prb ${cell_n_prb_valid})
if (NOT (${n_prb} GREATER ${cell_n_prb}))
set(pusch_cell_n_prb ${pusch_cell_n_prb} ${n_prb})
endif (NOT (${n_prb} GREATER ${cell_n_prb}))
endforeach (n_prb)
foreach (n_prb ${pusch_cell_n_prb})
foreach (mcs RANGE ${pusch_min_mcs} ${pusch_max_mcs} ${pusch_step_mcs})
foreach (ack ${pusch_acks})
foreach (cqi ${pusch_cqi})
set(pusch_test_args "")
set(pusch_test_args ${pusch_test_args} -n ${cell_n_prb})
set(pusch_test_args ${pusch_test_args} -L ${n_prb})
if (NOT (${ack} EQUAL -1))
set(pusch_test_args ${pusch_test_args} -p uci_ack ${ack})
if (mcs EQUAL 28)
set(mcs 27)
endif (mcs EQUAL 28)
endif (NOT (${ack} EQUAL -1))
if (NOT (${cqi} STREQUAL none))
set(pusch_test_args ${pusch_test_args} -p uci_cqi ${cqi})
#if (mcs EQUAL 28)
# set(mcs 27)
#endif (mcs EQUAL 28)
endif (NOT (${cqi} STREQUAL none))
set(pusch_test_args ${pusch_test_args} -m ${mcs})
string(REGEX REPLACE "\ " "" test_name_args ${pusch_test_args})
add_test(pusch_test${test_name_args} pusch_test ${pusch_test_args})
endforeach (cqi)
endforeach (ack)
endforeach (mcs)
endforeach (n_prb)
endforeach (cell_n_prb)
######################################################################## ########################################################################
# PUCCH TEST # PUCCH TEST

@ -34,12 +34,32 @@
#include "srslte/srslte.h" #include "srslte/srslte.h"
srslte_cell_t cell = { srslte_cell_t cell = {
6, // nof_prb .nof_prb = 6, // nof_prb
1, // nof_ports .nof_ports = 1, // nof_ports
0, // cell_id .id = 0, // cell_id
SRSLTE_CP_NORM, // cyclic prefix .cp = SRSLTE_CP_NORM, // cyclic prefix
SRSLTE_PHICH_R_1_6, // PHICH resources .phich_length = SRSLTE_PHICH_NORM, // PHICH length
SRSLTE_PHICH_NORM // PHICH length .phich_resources = SRSLTE_PHICH_R_1_6 // PHICH resources
};
srslte_uci_cfg_t uci_cfg = {
.I_offset_cqi = 6,
.I_offset_ri = 2,
.I_offset_ack = 9,
};
srslte_uci_data_t uci_data_tx = {
.uci_cqi = {0},
.uci_cqi_len = 0,
.uci_ri = 0,
.uci_ri_len = 0,
.uci_ack = 0,
.uci_ack_2 = 0,
.uci_ack_len = 0,
.ri_periodic_report = false,
.scheduling_request = false,
.channel_selection = false,
.cqi_ack = false
}; };
uint32_t cfi = 2; uint32_t cfi = 2;
@ -52,25 +72,110 @@ uint32_t n_prb = 0;
int freq_hop = -1; int freq_hop = -1;
int riv = -1; int riv = -1;
uint32_t mcs_idx = 0; uint32_t mcs_idx = 0;
srslte_cqi_value_t cqi_value;
void usage(char *prog) { void usage(char *prog) {
printf("Usage: %s [csrnfvmtLNF] -m MCS \n", prog); printf("Usage: %s [csrnfvmtLNF] \n", prog);
printf("\t-m MCS index [Default %d]\n", mcs_idx); printf("\n\tCell specific parameters:\n");
printf("\t-c cell id [Default %d]\n", cell.id); printf("\t\t-n number of PRB [Default %d]\n", cell.nof_prb);
printf("\t-s subframe [Default %d]\n", subframe); printf("\t\t-c cell id [Default %d]\n", cell.id);
printf("\t-L L_prb [Default %d]\n", L_prb);
printf("\t-N n_prb [Default %d]\n", n_prb); printf("\n\tGrant parameters:\n");
printf("\t-F frequency hopping [Default %d]\n", freq_hop); printf("\t\t-m MCS index (0-28) [Default %d]\n", mcs_idx);
printf("\t-R RIV [Default %d]\n", riv); printf("\t\t-L L_prb [Default %d]\n", L_prb);
printf("\t-r rv_idx [Default %d]\n", rv_idx); printf("\t\t-N n_prb [Default %d]\n", n_prb);
printf("\t-f cfi [Default %d]\n", cfi); printf("\t\t-F frequency hopping [Default %d]\n", freq_hop);
printf("\t-n cell.nof_prb [Default %d]\n", cell.nof_prb); printf("\t\t-R RIV [Default %d]\n", riv);
printf("\t\t-r rv_idx (0-3) [Default %d]\n", rv_idx);
printf("\t\t-f cfi [Default %d]\n", cfi);
printf("\n\tCQI/RI/ACK Reporting indexes parameters:\n");
printf("\t\t-p I_offset_cqi (0-15) [Default %d]\n", uci_cfg.I_offset_cqi);
printf("\t\t-p I_offset_ri (0-15) [Default %d]\n", uci_cfg.I_offset_ri);
printf("\t\t-p I_offset_ack (0-15) [Default %d]\n", uci_cfg.I_offset_ack);
printf("\n\tCQI/RI/ACK Reporting contents:\n");
printf("\t\t-p uci_cqi (zeros, ones, random) [Default zeros]\n");
printf("\t\t-p uci_cqi_len (0-64) [Default %d]\n", uci_data_tx.uci_cqi_len);
printf("\t\t-p uci_ri (0-1) (zeros, ones, random) [Default none]\n");
printf("\t\t-p uci_ack (0-1) [Default none]\n");
printf("\t\t-p uci_ack_2 (0-1) [Default none]\n");
printf("\n\tOther parameters:\n");
printf("\t\t-s subframe [Default %d]\n", subframe);
printf("\t-v [set srslte_verbose to debug, default none]\n"); printf("\t-v [set srslte_verbose to debug, default none]\n");
} }
void parse_extensive_param (char *param, char *arg) {
int ext_code = SRSLTE_SUCCESS;
if (!strcmp(param, "I_offset_cqi")) {
uci_cfg.I_offset_cqi = (uint32_t) atoi(arg);
if (uci_cfg.I_offset_cqi > 15) {
ext_code = SRSLTE_ERROR;
}
} else if (!strcmp(param, "I_offset_ri")) {
uci_cfg.I_offset_ri = (uint32_t) atoi(arg);
if (uci_cfg.I_offset_ri > 15) {
ext_code = SRSLTE_ERROR;
}
} else if (!strcmp(param, "I_offset_ack")) {
uci_cfg.I_offset_ack = (uint32_t) atoi(arg);
if (uci_cfg.I_offset_ack > 15) {
ext_code = SRSLTE_ERROR;
}
} else if (!strcmp(param, "uci_cqi")) {
if (!strcmp(arg, "wideband")) {
cqi_value.type = SRSLTE_CQI_TYPE_WIDEBAND;
cqi_value.wideband.wideband_cqi = (uint8_t) (rand() & 0x03);
uci_data_tx.uci_cqi_len = (uint32_t) srslte_cqi_value_unpack(uci_data_tx.uci_cqi, &cqi_value);
} else {
ext_code = SRSLTE_ERROR;
}
} else if (!strcmp(param, "uci_cqi_len")) {
uci_data_tx.uci_cqi_len = (uint32_t) atol(arg);
if (uci_data_tx.uci_cqi_len >= SRSLTE_CQI_MAX_BITS) {
ext_code = SRSLTE_ERROR;
}
} else if (!strcmp(param, "uci_ri")) {
uci_data_tx.uci_ri = (uint8_t) atol(arg);
if (uci_data_tx.uci_ri > 1) {
ext_code = SRSLTE_ERROR;
} else {
uci_data_tx.uci_ri_len = 1;
}
} else if (!strcmp(param, "uci_ack")) {
uci_data_tx.uci_ack = (uint8_t) atol(arg);
if (uci_data_tx.uci_ack > 1) {
ext_code = SRSLTE_ERROR;
} else {
uci_data_tx.uci_ack_len++;
if (uci_data_tx.uci_ack_len > 2) {
uci_data_tx.uci_ack_len = 2;
}
}
} else if (!strcmp(param, "uci_ack_2")) {
uci_data_tx.uci_ack_2 = (uint8_t) atol(arg);
if (uci_data_tx.uci_ack_2 > 1) {
ext_code = SRSLTE_ERROR;
} else {
uci_data_tx.uci_ack_len++;
if (uci_data_tx.uci_ack_len > 2) {
uci_data_tx.uci_ack_len = 2;
}
}
} else {
ext_code = SRSLTE_ERROR;
}
if (ext_code) {
fprintf(stderr, "Error parsing parameter '%s' and argument '%s'\n", param, arg);
exit(ext_code);
}
}
void parse_args(int argc, char **argv) { void parse_args(int argc, char **argv) {
int opt; int opt;
while ((opt = getopt(argc, argv, "cnfvmtsrLNFR")) != -1) { while ((opt = getopt(argc, argv, "msLNRFrncpv")) != -1) {
switch(opt) { switch(opt) {
case 'm': case 'm':
mcs_idx = atoi(argv[optind]); mcs_idx = atoi(argv[optind]);
@ -99,6 +204,10 @@ void parse_args(int argc, char **argv) {
case 'c': case 'c':
cell.id = atoi(argv[optind]); cell.id = atoi(argv[optind]);
break; break;
case 'p':
parse_extensive_param(argv[optind], argv[optind + 1]);
optind++;
break;
case 'v': case 'v':
srslte_verbose++; srslte_verbose++;
break; break;
@ -125,6 +234,8 @@ int main(int argc, char **argv) {
bzero(&cfg, sizeof(srslte_pusch_cfg_t)); bzero(&cfg, sizeof(srslte_pusch_cfg_t));
srslte_dft_load();
srslte_ra_ul_dci_t dci; srslte_ra_ul_dci_t dci;
dci.freq_hop_fl = freq_hop; dci.freq_hop_fl = freq_hop;
if (riv < 0) { if (riv < 0) {
@ -165,13 +276,6 @@ int main(int argc, char **argv) {
/* Configure PUSCH */ /* Configure PUSCH */
printf("Encoding rv_idx=%d\n",rv_idx);
srslte_uci_cfg_t uci_cfg;
uci_cfg.I_offset_cqi = 6;
uci_cfg.I_offset_ri = 2;
uci_cfg.I_offset_ack = 4;
if (srslte_pusch_cfg(&pusch_tx, &cfg, &grant, &uci_cfg, &ul_hopping, NULL, subframe, 0, 0)) { if (srslte_pusch_cfg(&pusch_tx, &cfg, &grant, &uci_cfg, &ul_hopping, NULL, subframe, 0, 0)) {
fprintf(stderr, "Error configuring PDSCH\n"); fprintf(stderr, "Error configuring PDSCH\n");
exit(-1); exit(-1);
@ -185,21 +289,9 @@ int main(int argc, char **argv) {
srslte_pusch_set_rnti(&pusch_tx, rnti); srslte_pusch_set_rnti(&pusch_tx, rnti);
srslte_pusch_set_rnti(&pusch_rx, rnti); srslte_pusch_set_rnti(&pusch_rx, rnti);
srslte_uci_data_t uci_data_tx; srslte_uci_data_t uci_data_rx;
srslte_uci_data_t uci_data_rx;
bzero(&uci_data_tx, sizeof(srslte_uci_data_t));
uci_data_tx.uci_cqi_len = 4;
uci_data_tx.uci_ri_len = 0;
uci_data_tx.uci_ack_len = 1;
memcpy(&uci_data_rx, &uci_data_tx, sizeof(srslte_uci_data_t)); memcpy(&uci_data_rx, &uci_data_tx, sizeof(srslte_uci_data_t));
for (uint32_t i=0;i<uci_data_tx.uci_cqi_len;i++) {
uci_data_tx.uci_cqi [i] = 1;
}
uci_data_tx.uci_ri = 1;
uci_data_tx.uci_ack = 1;
uci_data_tx.uci_ack_2 = 1;
uint32_t nof_re = SRSLTE_NRE*cell.nof_prb*2*SRSLTE_CP_NSYMB(cell.cp); uint32_t nof_re = SRSLTE_NRE*cell.nof_prb*2*SRSLTE_CP_NSYMB(cell.cp);
sf_symbols = srslte_vec_malloc(sizeof(cf_t) * nof_re); sf_symbols = srslte_vec_malloc(sizeof(cf_t) * nof_re);
if (!sf_symbols) { if (!sf_symbols) {
@ -252,7 +344,16 @@ int main(int argc, char **argv) {
} }
gettimeofday(&t[1], NULL); gettimeofday(&t[1], NULL);
int r = srslte_pusch_decode(&pusch_rx, &cfg, &softbuffer_rx, sf_symbols, ce, 0, rnti, data, NULL, &uci_data_rx); int r = srslte_pusch_decode(&pusch_rx,
&cfg,
&softbuffer_rx,
sf_symbols,
ce,
0,
rnti,
data,
(uci_data_tx.uci_cqi_len) ? &cqi_value : NULL,
&uci_data_rx);
gettimeofday(&t[2], NULL); gettimeofday(&t[2], NULL);
get_time_interval(t); get_time_interval(t);
if (r) { if (r) {
@ -285,10 +386,13 @@ int main(int argc, char **argv) {
} }
} }
if (uci_data_tx.uci_cqi_len) { if (uci_data_tx.uci_cqi_len) {
printf("cqi_tx="); if (memcmp(uci_data_tx.uci_cqi, uci_data_rx.uci_cqi, uci_data_tx.uci_cqi_len)) {
srslte_vec_fprint_b(stdout, uci_data_tx.uci_cqi, uci_data_tx.uci_cqi_len); printf("cqi_tx=");
printf("cqi_rx="); srslte_vec_fprint_b(stdout, uci_data_tx.uci_cqi, uci_data_tx.uci_cqi_len);
srslte_vec_fprint_b(stdout, uci_data_rx.uci_cqi, uci_data_rx.uci_cqi_len); printf("cqi_rx=");
srslte_vec_fprint_b(stdout, uci_data_rx.uci_cqi, uci_data_rx.uci_cqi_len);
ret = SRSLTE_ERROR;
}
} }
quit: quit:

@ -670,6 +670,13 @@ int rlc_am::build_segment(uint8_t *payload, uint32_t nof_bytes, rlc_amd_retx_t r
lower += old_header.li[i]; lower += old_header.li[i];
} }
// Make sure LI is not deleted in case the SDU boundary is crossed
// FIXME: fix if N_li > 1
if (new_header.N_li == 1 && retx.so_start + new_header.li[0] < retx.so_end && retx.so_end <= retx.so_start + pdu_space) {
// This segment crosses a SDU boundary
new_header.N_li++;
}
// Update retx_queue // Update retx_queue
if(tx_window[retx.sn].buf->N_bytes == retx.so_end) { if(tx_window[retx.sn].buf->N_bytes == retx.so_end) {
retx_queue.pop_front(); retx_queue.pop_front();
@ -913,6 +920,13 @@ void rlc_am::handle_data_pdu(uint8_t *payload, uint32_t nof_bytes, rlc_amd_pdu_h
#endif #endif
} }
// check available space for payload
if (nof_bytes > pdu.buf->get_tailroom()) {
log->error("%s Discarding SN: %d of size %d B (available space %d B)\n",
rrc->get_rb_name(lcid).c_str(), header.sn, nof_bytes, pdu.buf->get_tailroom());
pool->deallocate(pdu.buf);
return;
}
memcpy(pdu.buf->msg, payload, nof_bytes); memcpy(pdu.buf->msg, payload, nof_bytes);
pdu.buf->N_bytes = nof_bytes; pdu.buf->N_bytes = nof_bytes;
memcpy(&pdu.header, &header, sizeof(rlc_amd_pdu_header_t)); memcpy(&pdu.header, &header, sizeof(rlc_amd_pdu_header_t));
@ -1173,6 +1187,11 @@ void rlc_am::reassemble_rx_sdus()
for(uint32_t i=0; i<rx_window[vr_r].header.N_li; i++) for(uint32_t i=0; i<rx_window[vr_r].header.N_li; i++)
{ {
uint32_t len = rx_window[vr_r].header.li[i]; uint32_t len = rx_window[vr_r].header.li[i];
// sanity check to avoid zero-size SDUs
if (len == 0) {
break;
}
if (rx_sdu->get_tailroom() >= len) { if (rx_sdu->get_tailroom() >= len) {
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_r].buf->msg, len); memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_r].buf->msg, len);
rx_sdu->N_bytes += len; rx_sdu->N_bytes += len;
@ -1345,7 +1364,13 @@ bool rlc_am::add_segment_and_check(rlc_amd_rx_pdu_segments_t *pdu, rlc_amd_rx_pd
count += it->header.li[i]; count += it->header.li[i];
} }
} }
carryover = it->buf->N_bytes - count;
// accumulate segment sizes until end aligned PDU is received
if (rlc_am_not_start_aligned(it->header.fi)) {
carryover += it->buf->N_bytes - count;
} else {
carryover = it->buf->N_bytes - count;
}
tmpit = it; tmpit = it;
if(rlc_am_end_aligned(it->header.fi) && ++tmpit != pdu->segments.end()) { if(rlc_am_end_aligned(it->header.fi) && ++tmpit != pdu->segments.end()) {
header.li[header.N_li++] = carryover; header.li[header.N_li++] = carryover;
@ -1741,14 +1766,24 @@ std::string rlc_am_to_string(rlc_status_pdu_t *status)
return ss.str(); return ss.str();
} }
bool rlc_am_start_aligned(uint8_t fi) bool rlc_am_start_aligned(const uint8_t fi)
{ {
return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_END_ALIGNED); return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_END_ALIGNED);
} }
bool rlc_am_end_aligned(uint8_t fi) bool rlc_am_end_aligned(const uint8_t fi)
{ {
return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_START_ALIGNED); return (fi == RLC_FI_FIELD_START_AND_END_ALIGNED || fi == RLC_FI_FIELD_NOT_START_ALIGNED);
} }
bool rlc_am_is_unaligned(const uint8_t fi)
{
return (fi == RLC_FI_FIELD_NOT_START_OR_END_ALIGNED);
}
bool rlc_am_not_start_aligned(const uint8_t fi)
{
return (fi == RLC_FI_FIELD_NOT_START_ALIGNED || fi == RLC_FI_FIELD_NOT_START_OR_END_ALIGNED);
}
} // namespace srsue } // namespace srsue

@ -52,7 +52,7 @@ void rlc_tm::init(srslte::log *log_,
void rlc_tm::configure(srslte_rlc_config_t cnfg) void rlc_tm::configure(srslte_rlc_config_t cnfg)
{ {
log->error("Attempted to configure TM RLC entity"); log->error("Attempted to configure TM RLC entity\n");
} }
void rlc_tm::empty_queue() void rlc_tm::empty_queue()

@ -31,7 +31,7 @@
namespace srslte { namespace srslte {
rlc_um::rlc_um() : tx_sdu_queue(16) rlc_um::rlc_um() : tx_sdu_queue(32)
{ {
log = NULL; log = NULL;
pdcp = NULL; pdcp = NULL;
@ -467,6 +467,17 @@ void rlc_um::reassemble_rx_sdus()
for(uint32_t i=0; i<rx_window[vr_ur].header.N_li; i++) for(uint32_t i=0; i<rx_window[vr_ur].header.N_li; i++)
{ {
int len = rx_window[vr_ur].header.li[i]; int len = rx_window[vr_ur].header.li[i];
// Check if we received a middle or end segment
if (rx_sdu->N_bytes == 0 && i == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->warning("Dropping PDU %d due to lost start segment\n", vr_ur);
// Advance data pointers and continue with next segment
rx_window[vr_ur].buf->msg += len;
rx_window[vr_ur].buf->N_bytes -= len;
rx_sdu->reset();
break;
}
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len); memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len);
rx_sdu->N_bytes += len; rx_sdu->N_bytes += len;
rx_window[vr_ur].buf->msg += len; rx_window[vr_ur].buf->msg += len;
@ -488,27 +499,30 @@ void rlc_um::reassemble_rx_sdus()
} }
// Handle last segment // Handle last segment
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes); if (rx_sdu->N_bytes > 0 || rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes; log->debug("Writing last segment in SDU buffer. Lower edge vr_ur=%d, Buffer size=%d, segment size=%d\n",
log->debug("Writting 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);
vr_ur, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes);
vr_ur_in_rx_sdu = vr_ur; memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, rx_window[vr_ur].buf->N_bytes);
if(rlc_um_end_aligned(rx_window[vr_ur].header.fi)) rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes;
{ vr_ur_in_rx_sdu = vr_ur;
if(pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) { if(rlc_um_end_aligned(rx_window[vr_ur].header.fi))
log->warning("Dropping remainder of lost PDU (lower edge last segments)\n"); {
rx_sdu->reset(); if(pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
} else { log->warning("Dropping remainder of lost PDU (lower edge last segments)\n");
log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", rrc->get_rb_name(lcid).c_str(), vr_ur); rx_sdu->reset();
rx_sdu->set_timestamp(); } else {
pdcp->write_pdu(lcid, rx_sdu); log->info_hex(rx_sdu->msg, rx_sdu->N_bytes, "%s Rx SDU vr_ur=%d (lower edge last segments)", rrc->get_rb_name(lcid).c_str(), vr_ur);
rx_sdu = pool_allocate; rx_sdu->set_timestamp();
if (!rx_sdu) { pdcp->write_pdu(lcid, rx_sdu);
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n"); rx_sdu = pool_allocate;
return; if (!rx_sdu) {
log->error("Fatal Error: Couldn't allocate buffer in rlc_um::reassemble_rx_sdus().\n");
return;
}
} }
pdu_lost = false;
} }
pdu_lost = false;
} }
// Clean up rx_window // Clean up rx_window
@ -527,10 +541,21 @@ void rlc_um::reassemble_rx_sdus()
for(uint32_t i=0; i<rx_window[vr_ur].header.N_li; i++) for(uint32_t i=0; i<rx_window[vr_ur].header.N_li; i++)
{ {
int len = rx_window[vr_ur].header.li[i]; int len = rx_window[vr_ur].header.li[i];
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, len);
// 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("Dropping PDU %d due to lost start segment\n", vr_ur);
// Advance data pointers and continue with next segment
rx_window[vr_ur].buf->msg += len;
rx_window[vr_ur].buf->N_bytes -= len;
rx_sdu->reset();
break;
}
log->debug("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", log->debug("Concatenating %d bytes in to current length %d. rx_window remaining bytes=%d, vr_ur_in_rx_sdu=%d, vr_ur=%d, rx_mod=%d, last_mod=%d\n",
len, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes, vr_ur_in_rx_sdu, vr_ur, cfg.rx_mod, (vr_ur_in_rx_sdu+1)%cfg.rx_mod); len, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes, vr_ur_in_rx_sdu, vr_ur, cfg.rx_mod, (vr_ur_in_rx_sdu+1)%cfg.rx_mod);
rx_sdu->N_bytes += len; 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->msg += len;
rx_window[vr_ur].buf->N_bytes -= len; rx_window[vr_ur].buf->N_bytes -= len;
if((pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) || (vr_ur != ((vr_ur_in_rx_sdu+1)%cfg.rx_mod))) { if((pdu_lost && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) || (vr_ur != ((vr_ur_in_rx_sdu+1)%cfg.rx_mod))) {
@ -548,8 +573,14 @@ void rlc_um::reassemble_rx_sdus()
} }
pdu_lost = false; pdu_lost = false;
} }
// Handle last segment // Handle last segment
if (rx_sdu->N_bytes == 0 && rx_window[vr_ur].header.N_li == 0 && !rlc_um_start_aligned(rx_window[vr_ur].header.fi)) {
log->warning("Dropping PDU %d due to lost start segment\n", vr_ur);
rx_sdu->reset();
goto clean_up_rx_window;
}
if (rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES && 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 < SRSLTE_MAX_BUFFER_SIZE_BYTES &&
rx_window[vr_ur].buf->N_bytes + rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES) rx_window[vr_ur].buf->N_bytes + rx_sdu->N_bytes < SRSLTE_MAX_BUFFER_SIZE_BYTES)
@ -557,7 +588,7 @@ void rlc_um::reassemble_rx_sdus()
memcpy(&rx_sdu->msg[rx_sdu->N_bytes], rx_window[vr_ur].buf->msg, 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; rx_sdu->N_bytes += rx_window[vr_ur].buf->N_bytes;
log->debug("Writting last segment in SDU buffer. Updating vr_ur=%d, Buffer size=%d, segment size=%d\n", log->debug("Writing last segment in SDU buffer. Updating vr_ur=%d, Buffer size=%d, segment size=%d\n",
vr_ur, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes); vr_ur, rx_sdu->N_bytes, rx_window[vr_ur].buf->N_bytes);
} else { } else {
log->error("Out of bounds while reassembling SDU buffer in UM: sdu_len=%d, window_buffer_len=%d, vr_ur=%d\n", log->error("Out of bounds while reassembling SDU buffer in UM: sdu_len=%d, window_buffer_len=%d, vr_ur=%d\n",
@ -582,6 +613,8 @@ void rlc_um::reassemble_rx_sdus()
pdu_lost = false; pdu_lost = false;
} }
clean_up_rx_window:
// Clean up rx_window // Clean up rx_window
pool->deallocate(rx_window[vr_ur].buf); pool->deallocate(rx_window[vr_ur].buf);
rx_window.erase(vr_ur); rx_window.erase(vr_ur);

@ -30,9 +30,11 @@ add_executable(rlc_am_test rlc_am_test.cc)
target_link_libraries(rlc_am_test srslte_upper srslte_phy srslte_common) target_link_libraries(rlc_am_test srslte_upper srslte_phy srslte_common)
add_test(rlc_am_test rlc_am_test) add_test(rlc_am_test rlc_am_test)
add_executable(rlc_am_stress_test rlc_am_stress_test.cc) add_executable(rlc_stress_test rlc_stress_test.cc)
target_link_libraries(rlc_am_stress_test srslte_upper srslte_phy srslte_common ${Boost_LIBRARIES}) target_link_libraries(rlc_stress_test srslte_upper srslte_phy srslte_common ${Boost_LIBRARIES})
add_test(rlc_am_stress_test rlc_am_stress_test --duration 10) add_test(rlc_am_stress_test rlc_stress_test --mode=AM)
add_test(rlc_um_stress_test rlc_stress_test --mode=UM)
add_test(rlc_tm_stress_test rlc_stress_test --mode=TM --opp_sdu_ratio=1.0)
add_executable(rlc_um_data_test rlc_um_data_test.cc) add_executable(rlc_um_data_test rlc_um_data_test.cc)
target_link_libraries(rlc_um_data_test srslte_upper srslte_phy srslte_common) target_link_libraries(rlc_um_data_test srslte_upper srslte_phy srslte_common)

@ -40,6 +40,20 @@ uint32_t PDU2_LEN = 5;
uint8_t pdu3[] = {0x8C, 0x00, 0xDD, 0xCD, 0xDC, 0x5D, 0xC0}; uint8_t pdu3[] = {0x8C, 0x00, 0xDD, 0xCD, 0xDC, 0x5D, 0xC0};
uint32_t PDU3_LEN = 7; uint32_t PDU3_LEN = 7;
// D/C = 1 = Data PDU
// RF = 0 = AMD PDU
// P = 0 = Status PDU is not requested
// FI = 11 = First byte of the Data field does not corresponds to the first byte of a RLC SDU,
// Last byte of the Data field does not corresponds to the last byte of a RLC SDU
// E = 1 = A set of E field and LI field follows from the octet following the fixed part of the header
// SN = 0000000010 -> SN 2
// E = 1
// LI1 = 1010011 1110 (1342 Dec)
// E = 0
// LI2 = 10111011100 (1500 Dec)
uint8_t pdu4[] = {0x9C, 0x02, 0xD3, 0xE5, 0xDC };
uint32_t PDU4_LEN = 5;
using namespace srslte; using namespace srslte;
int main(int argc, char **argv) { int main(int argc, char **argv) {
@ -106,4 +120,26 @@ int main(int argc, char **argv) {
assert(b2.N_bytes == PDU3_LEN); assert(b2.N_bytes == PDU3_LEN);
for(uint32_t i=0;i<b2.N_bytes;i++) for(uint32_t i=0;i<b2.N_bytes;i++)
assert(b2.msg[i] == b1.msg[i]); assert(b2.msg[i] == b1.msg[i]);
b1.reset();
b2.reset();
memset(&h, 0, sizeof(srslte::rlc_amd_pdu_header_t));
memcpy(b1.msg, &pdu4[0], PDU4_LEN);
b1.N_bytes = PDU4_LEN;
rlc_am_read_data_pdu_header(&b1, &h);
assert(RLC_DC_FIELD_DATA_PDU == h.dc);
assert(0x03 == h.fi);
assert(2 == h.N_li);
assert(1342 == h.li[0]);
assert(1500 == h.li[1]);
assert(0 == h.lsf);
assert(0 == h.p);
assert(0 == h.rf);
assert(0 == h.so);
assert(2 == h.sn);
rlc_am_write_data_pdu_header(&h, &b2);
assert(b2.N_bytes == PDU4_LEN);
for(uint32_t i=0;i<b2.N_bytes;i++)
assert(b2.msg[i] == b1.msg[i]);
} }

@ -28,8 +28,10 @@
#include "srslte/common/log_filter.h" #include "srslte/common/log_filter.h"
#include "srslte/common/logger_stdout.h" #include "srslte/common/logger_stdout.h"
#include "srslte/upper/rlc_am.h" #include "srslte/upper/rlc_am.h"
#include "srslte/common/rlc_pcap.h"
#include <assert.h> #include <assert.h>
#define NBUFS 5 #define NBUFS 5
#define HAVE_PCAP 0
using namespace srsue; using namespace srsue;
using namespace srslte; using namespace srslte;
@ -54,9 +56,11 @@ class rlc_am_tester
,public rrc_interface_rlc ,public rrc_interface_rlc
{ {
public: public:
rlc_am_tester(){ rlc_am_tester(rlc_pcap *pcap_ = NULL)
{
bzero(sdus, sizeof(sdus)); bzero(sdus, sizeof(sdus));
n_sdus = 0; n_sdus = 0;
pcap = pcap_;
} }
~rlc_am_tester(){ ~rlc_am_tester(){
@ -83,6 +87,7 @@ public:
byte_buffer_t *sdus[10]; byte_buffer_t *sdus[10];
int n_sdus; int n_sdus;
rlc_pcap *pcap;
}; };
void basic_test() void basic_test()
@ -1056,6 +1061,311 @@ void resegment_test_6()
} }
} }
// Retransmission of PDU segments of the same size
void resegment_test_7()
{
// SDUs: | 30 | 30 |
// PDUs: | 13 | 13 | 11 | 13 | 10 |
// Rxed PDUs | 13 | 13 | | 13 | 10 |
// Retx PDU segments: | 4 | 7 |
// Retx PDU segments: |3|3]3|2|
const uint32_t N_SDU_BUFS = 2;
const uint32_t N_PDU_BUFS = 5;
const uint32_t sdu_size = 30;
srslte::log_filter log1("RLC_AM_1");
srslte::log_filter log2("RLC_AM_2");
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(100);
log2.set_hex_limit(100);
#if HAVE_PCAP
rlc_pcap pcap;
pcap.open("rlc_am_test7.pcap", 0);
rlc_am_tester tester(&pcap);
#else
rlc_am_tester tester(NULL);
#endif
mac_dummy_timers timers;
rlc_am rlc1;
rlc_am rlc2;
int len;
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
rlc1.init(&log1, 1, &tester, &tester, &timers);
rlc2.init(&log2, 1, &tester, &tester, &timers);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_AM;
cnfg.dl_am_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_am_rlc.t_status_prohibit = LIBLTE_RRC_T_STATUS_PROHIBIT_MS5;
cnfg.ul_am_rlc.max_retx_thresh = LIBLTE_RRC_MAX_RETX_THRESHOLD_T4;
cnfg.ul_am_rlc.poll_byte = LIBLTE_RRC_POLL_BYTE_KB25;
cnfg.ul_am_rlc.poll_pdu = LIBLTE_RRC_POLL_PDU_P4;
cnfg.ul_am_rlc.t_poll_retx = LIBLTE_RRC_T_POLL_RETRANSMIT_MS5;
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
// Push 2 SDUs into RLC1
byte_buffer_t sdu_bufs[N_SDU_BUFS];
for(uint32_t i=0;i<N_SDU_BUFS;i++)
{
for(uint32_t j=0;j<sdu_size;j++) {
sdu_bufs[i].msg[j] = i;
}
sdu_bufs[i].N_bytes = sdu_size; // Give each buffer a size of 15 bytes
rlc1.write_sdu(&sdu_bufs[i]);
}
assert(65 == rlc1.get_buffer_state());
// Read PDUs from RLC1 (15 bytes each)
byte_buffer_t pdu_bufs[N_PDU_BUFS];
for(uint32_t i=0;i<N_PDU_BUFS;i++)
{
pdu_bufs[i].N_bytes = rlc1.read_pdu(pdu_bufs[i].msg, 15); // 2 bytes for header + 12 B payload
assert(pdu_bufs[i].N_bytes);
}
assert(0 == rlc1.get_buffer_state());
// Skip PDU with SN 2
for(uint32_t i=0;i<N_PDU_BUFS;i++) {
if (i!=2) {
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
#endif
}
}
// Sleep to let reordering timeout expire
usleep(10000);
assert(12 == rlc1.get_buffer_state());
// first round of retx, forcing resegmentation
byte_buffer_t retx[4];
for (uint32_t i = 0; i < 4; i++) {
assert(rlc1.get_buffer_state());
retx[i].N_bytes = rlc1.read_pdu(retx[i].msg, 7);
assert(retx[i].N_bytes);
// Write the last two segments to RLC2
if (i > 1) {
rlc2.write_pdu(retx[i].msg, retx[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(retx[i].msg, retx[i].N_bytes);
#endif
}
}
usleep(10000);
// Read status PDU from RLC2
assert(rlc2.get_buffer_state());
byte_buffer_t status_buf;
status_buf.N_bytes = rlc2.read_pdu(status_buf.msg, 10); // 10 bytes is enough to hold the status
// Write status PDU to RLC1
rlc1.write_pdu(status_buf.msg, status_buf.N_bytes);
#if HAVE_PCAP
pcap.write_ul_am_ccch(status_buf.msg, status_buf.N_bytes);
#endif
assert(15 == rlc1.get_buffer_state());
// second round of retx, forcing resegmentation
byte_buffer_t retx2[4];
for (uint32_t i = 0; i < 4; i++) {
assert(rlc1.get_buffer_state() != 0);
retx2[i].N_bytes = rlc1.read_pdu(retx2[i].msg, 7);
assert(retx2[i].N_bytes != 0);
rlc2.write_pdu(retx2[i].msg, retx2[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(retx[i].msg, retx[i].N_bytes);
#endif
}
// check buffer states
assert(0 == rlc1.get_buffer_state());
assert(0 == rlc2.get_buffer_state());
// Check number of SDUs and their content
assert(tester.n_sdus == N_SDU_BUFS);
for(int i=0; i<tester.n_sdus; i++)
{
assert(tester.sdus[i]->N_bytes == sdu_size);
for(uint32_t j=0;j<N_SDU_BUFS;j++) {
assert(tester.sdus[i]->msg[j] == i);
}
}
#if HAVE_PCAP
pcap.close();
#endif
}
// Retransmission of PDU segments with different size
void resegment_test_8()
{
// SDUs: | 30 | 30 |
// PDUs: | 15 | 15 | 15 | 15 | 15 |
// Rxed PDUs | 15 | | 15 | 15 |
// Retx PDU segments: | 7 | 7 | 7 | 7 |
// Retx PDU segments: | 6 | 6 ] 6 | 6 | 6 | 6 | 6 | 6 |
const uint32_t N_SDU_BUFS = 2;
const uint32_t N_PDU_BUFS = 5;
const uint32_t sdu_size = 30;
srslte::log_filter log1("RLC_AM_1");
srslte::log_filter log2("RLC_AM_2");
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(100);
log2.set_hex_limit(100);
#if HAVE_PCAP
rlc_pcap pcap;
pcap.open("rlc_am_test8.pcap", 0);
rlc_am_tester tester(&pcap);
#else
rlc_am_tester tester(NULL);
#endif
mac_dummy_timers timers;
rlc_am rlc1;
rlc_am rlc2;
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
rlc1.init(&log1, 1, &tester, &tester, &timers);
rlc2.init(&log2, 1, &tester, &tester, &timers);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_AM;
cnfg.dl_am_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_am_rlc.t_status_prohibit = LIBLTE_RRC_T_STATUS_PROHIBIT_MS5;
cnfg.ul_am_rlc.max_retx_thresh = LIBLTE_RRC_MAX_RETX_THRESHOLD_T4;
cnfg.ul_am_rlc.poll_byte = LIBLTE_RRC_POLL_BYTE_KB25;
cnfg.ul_am_rlc.poll_pdu = LIBLTE_RRC_POLL_PDU_P4;
cnfg.ul_am_rlc.t_poll_retx = LIBLTE_RRC_T_POLL_RETRANSMIT_MS5;
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
// Push 2 SDUs into RLC1
byte_buffer_t sdu_bufs[N_SDU_BUFS];
for(uint32_t i=0;i<N_SDU_BUFS;i++)
{
for(uint32_t j=0;j<sdu_size;j++) {
sdu_bufs[i].msg[j] = i;
}
sdu_bufs[i].N_bytes = sdu_size; // Give each buffer a size of 15 bytes
rlc1.write_sdu(&sdu_bufs[i]);
}
assert(65 == rlc1.get_buffer_state());
// Read PDUs from RLC1 (15 bytes each)
byte_buffer_t pdu_bufs[N_PDU_BUFS];
for(uint32_t i=0;i<N_PDU_BUFS;i++)
{
pdu_bufs[i].N_bytes = rlc1.read_pdu(pdu_bufs[i].msg, 15); // 12 bytes for header + payload
assert(pdu_bufs[i].N_bytes);
}
assert(0 == rlc1.get_buffer_state());
// Skip PDU one and two
for(uint32_t i=0;i<N_PDU_BUFS;i++) {
if (i < 1 || i > 2) {
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
#endif
}
}
// Sleep to let reordering timeout expire
usleep(10000);
assert(12 == rlc1.get_buffer_state());
// first round of retx, forcing resegmentation
byte_buffer_t retx[4];
for (uint32_t i = 0; i < 3; i++) {
assert(rlc1.get_buffer_state());
retx[i].N_bytes = rlc1.read_pdu(retx[i].msg, 8);
assert(retx[i].N_bytes);
// Write the last two segments to RLC2
if (i > 1) {
rlc2.write_pdu(retx[i].msg, retx[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(retx[i].msg, retx[i].N_bytes);
#endif
}
}
usleep(20000);
// Read status PDU from RLC2
assert(rlc2.get_buffer_state());
byte_buffer_t status_buf;
status_buf.N_bytes = rlc2.read_pdu(status_buf.msg, 10); // 10 bytes is enough to hold the status
// Write status PDU to RLC1
rlc1.write_pdu(status_buf.msg, status_buf.N_bytes);
#if HAVE_PCAP
pcap.write_ul_am_ccch(status_buf.msg, status_buf.N_bytes);
#endif
assert(15 == rlc1.get_buffer_state());
// second round of retx, reduce grant size to force different segment sizes
byte_buffer_t retx2[20];
for (uint32_t i = 0; i < 9; i++) {
assert(rlc1.get_buffer_state() != 0);
retx2[i].N_bytes = rlc1.read_pdu(retx2[i].msg, 7);
assert(retx2[i].N_bytes != 0);
rlc2.write_pdu(retx2[i].msg, retx2[i].N_bytes);
#if HAVE_PCAP
pcap.write_dl_am_ccch(retx[i].msg, retx[i].N_bytes);
#endif
}
// check buffer states
assert(0 == rlc1.get_buffer_state());
assert(0 == rlc2.get_buffer_state());
// Check number of SDUs and their content
assert(tester.n_sdus == N_SDU_BUFS);
for(int i=0; i<tester.n_sdus; i++)
{
assert(tester.sdus[i]->N_bytes == sdu_size);
for(uint32_t j=0;j<N_SDU_BUFS;j++) {
assert(tester.sdus[i]->msg[j] == i);
}
}
#if HAVE_PCAP
pcap.close();
#endif
}
void reset_test() void reset_test()
{ {
srslte::log_filter log1("RLC_AM_1"); srslte::log_filter log1("RLC_AM_1");
@ -1141,6 +1451,12 @@ int main(int argc, char **argv) {
resegment_test_6(); resegment_test_6();
byte_buffer_pool::get_instance()->cleanup(); byte_buffer_pool::get_instance()->cleanup();
resegment_test_7();
byte_buffer_pool::get_instance()->cleanup();
resegment_test_8();
byte_buffer_pool::get_instance()->cleanup();
reset_test(); reset_test();
byte_buffer_pool::get_instance()->cleanup(); byte_buffer_pool::get_instance()->cleanup();
} }

@ -30,10 +30,14 @@
#include "srslte/common/log_filter.h" #include "srslte/common/log_filter.h"
#include "srslte/common/logger_stdout.h" #include "srslte/common/logger_stdout.h"
#include "srslte/common/threads.h" #include "srslte/common/threads.h"
#include "srslte/common/rlc_pcap.h"
#include "srslte/upper/rlc.h" #include "srslte/upper/rlc.h"
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/program_options/parsers.hpp> #include <boost/program_options/parsers.hpp>
#include <assert.h> #include <assert.h>
#include <srslte/upper/rlc_interface.h>
#define SDU_SIZE 1500
using namespace std; using namespace std;
using namespace srsue; using namespace srsue;
@ -41,12 +45,16 @@ using namespace srslte;
namespace bpo = boost::program_options; namespace bpo = boost::program_options;
typedef struct { typedef struct {
uint32_t test_duration_sec; std::string mode;
float error_rate; uint32_t test_duration_sec;
uint32_t sdu_gen_delay_usec; float error_rate;
uint32_t pdu_tx_delay_usec; uint32_t sdu_gen_delay_usec;
bool reestablish; uint32_t pdu_tx_delay_usec;
uint32_t log_level; bool reestablish;
uint32_t log_level;
bool single_tx;
bool write_pcap;
float opp_sdu_ratio;
} stress_test_args_t; } stress_test_args_t;
void parse_args(stress_test_args_t *args, int argc, char *argv[]) { void parse_args(stress_test_args_t *args, int argc, char *argv[]) {
@ -61,12 +69,16 @@ void parse_args(stress_test_args_t *args, int argc, char *argv[]) {
// Command line or config file options // Command line or config file options
bpo::options_description common("Configuration options"); bpo::options_description common("Configuration options");
common.add_options() common.add_options()
("duration", bpo::value<uint32_t>(&args->test_duration_sec)->default_value(10), "Duration (sec)") ("mode", bpo::value<std::string>(&args->mode)->default_value("AM"), "Whether to test RLC acknowledged or unacknowledged mode (AM/UM)")
("sdu_gen_delay", bpo::value<uint32_t>(&args->sdu_gen_delay_usec)->default_value(10), "SDU generation delay (usec)") ("duration", bpo::value<uint32_t>(&args->test_duration_sec)->default_value(5), "Duration (sec)")
("pdu_tx_delay", bpo::value<uint32_t>(&args->pdu_tx_delay_usec)->default_value(10), "Delay in MAC for transfering PDU from tx'ing RLC to rx'ing RLC (usec)") ("sdu_gen_delay", bpo::value<uint32_t>(&args->sdu_gen_delay_usec)->default_value(0), "SDU generation delay (usec)")
("pdu_tx_delay", bpo::value<uint32_t>(&args->pdu_tx_delay_usec)->default_value(0), "Delay in MAC for transfering PDU from tx'ing RLC to rx'ing RLC (usec)")
("error_rate", bpo::value<float>(&args->error_rate)->default_value(0.1), "Rate at which RLC PDUs are dropped") ("error_rate", bpo::value<float>(&args->error_rate)->default_value(0.1), "Rate at which RLC PDUs are dropped")
("opp_sdu_ratio", bpo::value<float>(&args->opp_sdu_ratio)->default_value(0.0), "Ratio between MAC opportunity and SDU size (0==random)")
("reestablish", bpo::value<bool>(&args->reestablish)->default_value(false), "Mimic RLC reestablish during execution") ("reestablish", bpo::value<bool>(&args->reestablish)->default_value(false), "Mimic RLC reestablish during execution")
("loglevel", bpo::value<uint32_t>(&args->log_level)->default_value(srslte::LOG_LEVEL_DEBUG), "Log level (1=Error,2=Warning,3=Info,4=Debug"); ("loglevel", bpo::value<uint32_t>(&args->log_level)->default_value(srslte::LOG_LEVEL_DEBUG), "Log level (1=Error,2=Warning,3=Info,4=Debug)")
("singletx", bpo::value<bool>(&args->single_tx)->default_value(false), "If set to true, only one node is generating data")
("pcap", bpo::value<bool>(&args->write_pcap)->default_value(false), "Whether to write all RLC PDU to PCAP file");
// these options are allowed on the command line // these options are allowed on the command line
bpo::options_description cmdline_options; bpo::options_description cmdline_options;
@ -94,14 +106,18 @@ class mac_reader
:public thread :public thread
{ {
public: public:
mac_reader(rlc_interface_mac *rlc1_, rlc_interface_mac *rlc2_, float fail_rate_, uint32_t pdu_tx_delay_usec_) mac_reader(rlc_interface_mac *rlc1_, rlc_interface_mac *rlc2_, float fail_rate_, float opp_sdu_ratio_, uint32_t pdu_tx_delay_usec_, rlc_pcap *pcap_, uint32_t lcid_, bool is_dl_ = true)
{ {
rlc1 = rlc1_; rlc1 = rlc1_;
rlc2 = rlc2_; rlc2 = rlc2_;
fail_rate = fail_rate_; fail_rate = fail_rate_;
opp_sdu_ratio = opp_sdu_ratio_;
run_enable = true; run_enable = true;
running = false; running = false;
pdu_tx_delay_usec = pdu_tx_delay_usec_; pdu_tx_delay_usec = pdu_tx_delay_usec_;
pcap = pcap_;
is_dl = is_dl_;
lcid = lcid_;
} }
void stop() void stop()
@ -129,14 +145,23 @@ private:
} }
while(run_enable) { while(run_enable) {
float r = (float)rand()/RAND_MAX; // generate MAC opportunities of random size or with fixed ratio
int opp_size = r*1500; float r = opp_sdu_ratio ? opp_sdu_ratio : (float)rand()/RAND_MAX;
rlc1->get_buffer_state(1); int opp_size = r*SDU_SIZE;
int read = rlc1->read_pdu(1, pdu->msg, opp_size); uint32_t buf_state = rlc1->get_buffer_state(lcid);
if(((float)rand()/RAND_MAX > fail_rate) && read>0) { if (buf_state) {
rlc2->write_pdu(1, pdu->msg, opp_size); int read = rlc1->read_pdu(lcid, pdu->msg, opp_size);
if (pdu_tx_delay_usec) usleep(pdu_tx_delay_usec);
if(((float)rand()/RAND_MAX > fail_rate) && read>0) {
pdu->N_bytes = read;
rlc2->write_pdu(lcid, pdu->msg, pdu->N_bytes);
if (is_dl) {
pcap->write_dl_am_ccch(pdu->msg, pdu->N_bytes);
} else {
pcap->write_ul_am_ccch(pdu->msg, pdu->N_bytes);
}
}
} }
usleep(pdu_tx_delay_usec);
} }
running = false; running = false;
byte_buffer_pool::get_instance()->deallocate(pdu); byte_buffer_pool::get_instance()->deallocate(pdu);
@ -145,7 +170,11 @@ private:
rlc_interface_mac *rlc1; rlc_interface_mac *rlc1;
rlc_interface_mac *rlc2; rlc_interface_mac *rlc2;
float fail_rate; float fail_rate;
float opp_sdu_ratio;
uint32_t pdu_tx_delay_usec; uint32_t pdu_tx_delay_usec;
rlc_pcap *pcap;
uint32_t lcid;
bool is_dl;
bool run_enable; bool run_enable;
bool running; bool running;
@ -155,9 +184,9 @@ class mac_dummy
:public srslte::mac_interface_timers :public srslte::mac_interface_timers
{ {
public: public:
mac_dummy(rlc_interface_mac *rlc1_, rlc_interface_mac *rlc2_, float fail_rate_, uint32_t pdu_tx_delay) mac_dummy(rlc_interface_mac *rlc1_, rlc_interface_mac *rlc2_, float fail_rate_, float opp_sdu_ratio_, int32_t pdu_tx_delay, uint32_t lcid, rlc_pcap* pcap = NULL)
:r1(rlc1_, rlc2_, fail_rate_, pdu_tx_delay) :r1(rlc1_, rlc2_, fail_rate_, opp_sdu_ratio_, pdu_tx_delay, pcap, lcid, true)
,r2(rlc2_, rlc1_, fail_rate_, pdu_tx_delay) ,r2(rlc2_, rlc1_, fail_rate_, opp_sdu_ratio_, pdu_tx_delay, pcap, lcid, false)
{ {
} }
@ -189,19 +218,20 @@ private:
class rlc_am_tester class rlc_tester
:public pdcp_interface_rlc :public pdcp_interface_rlc
,public rrc_interface_rlc ,public rrc_interface_rlc
,public thread ,public thread
{ {
public: public:
rlc_am_tester(rlc_interface_pdcp *rlc_, std::string name_, uint32_t sdu_gen_delay_usec_){ rlc_tester(rlc_interface_pdcp *rlc_, std::string name_, uint32_t sdu_gen_delay_usec_, uint32_t lcid_){
rlc = rlc_; rlc = rlc_;
run_enable = true; run_enable = true;
running = false; running = false;
rx_pdus = 0; rx_pdus = 0;
name = name_; name = name_;
sdu_gen_delay_usec = sdu_gen_delay_usec_; sdu_gen_delay_usec = sdu_gen_delay_usec_;
lcid = lcid_;
} }
void stop() void stop()
@ -219,11 +249,16 @@ public:
} }
// PDCP interface // PDCP interface
void write_pdu(uint32_t lcid, byte_buffer_t *sdu) void write_pdu(uint32_t rx_lcid, byte_buffer_t *sdu)
{ {
assert(lcid == 1); assert(rx_lcid == lcid);
if (sdu->N_bytes != SDU_SIZE) {
printf("Received PDU with size %d, expected %d. Exiting.\n", sdu->N_bytes, SDU_SIZE);
exit(-1);
}
byte_buffer_pool::get_instance()->deallocate(sdu); byte_buffer_pool::get_instance()->deallocate(sdu);
std::cout << "rlc_am_tester " << name << " received " << rx_pdus++ << " PDUs" << std::endl; rx_pdus++;
} }
void write_pdu_bcch_bch(byte_buffer_t *sdu) {} void write_pdu_bcch_bch(byte_buffer_t *sdu) {}
void write_pdu_bcch_dlsch(byte_buffer_t *sdu) {} void write_pdu_bcch_dlsch(byte_buffer_t *sdu) {}
@ -231,7 +266,9 @@ public:
// RRC interface // RRC interface
void max_retx_attempted(){} void max_retx_attempted(){}
std::string get_rb_name(uint32_t lcid) { return std::string(""); } std::string get_rb_name(uint32_t rx_lcid) { return std::string(""); }
int get_nof_rx_pdus() { return rx_pdus; }
private: private:
void run_thread() void run_thread()
@ -239,15 +276,18 @@ private:
uint8_t sn = 0; uint8_t sn = 0;
running = true; running = true;
while(run_enable) { while(run_enable) {
byte_buffer_t *pdu = byte_buffer_pool::get_instance()->allocate("rlc_am_tester::run_thread"); byte_buffer_t *pdu = byte_buffer_pool::get_instance()->allocate("rlc_tester::run_thread");
if (!pdu) { if (!pdu) {
printf("Fatal Error: Could not allocate PDU in rlc_am_tester::run_thread\n"); printf("Fatal Error: Could not allocate PDU in rlc_tester::run_thread\n");
exit(-1); exit(-1);
} }
pdu->N_bytes = 1500; for (uint32_t i = 0; i < SDU_SIZE; i++) {
pdu->msg[0] = sn++; pdu->msg[i] = sn;
rlc->write_sdu(1, pdu); }
usleep(sdu_gen_delay_usec); sn++;
pdu->N_bytes = SDU_SIZE;
rlc->write_sdu(lcid, pdu);
if (sdu_gen_delay_usec) usleep(sdu_gen_delay_usec);
} }
running = false; running = false;
} }
@ -255,6 +295,7 @@ private:
bool run_enable; bool run_enable;
bool running; bool running;
long rx_pdus; long rx_pdus;
uint32_t lcid;
std::string name; std::string name;
@ -265,40 +306,67 @@ private:
void stress_test(stress_test_args_t args) void stress_test(stress_test_args_t args)
{ {
srslte::log_filter log1("RLC_AM_1"); srslte::log_filter log1("RLC_1");
srslte::log_filter log2("RLC_AM_2"); srslte::log_filter log2("RLC_2");
log1.set_level((LOG_LEVEL_ENUM)args.log_level); log1.set_level((LOG_LEVEL_ENUM)args.log_level);
log2.set_level((LOG_LEVEL_ENUM)args.log_level); log2.set_level((LOG_LEVEL_ENUM)args.log_level);
log1.set_hex_limit(-1); log1.set_hex_limit(-1);
log2.set_hex_limit(-1); log2.set_hex_limit(-1);
rlc_pcap pcap;
uint32_t lcid = 1;
if (args.write_pcap) {
pcap.open("rlc_stress_test.pcap", 0);
}
srslte_rlc_config_t cnfg_;
if (args.mode == "AM") {
// config RLC AM bearer
cnfg_.rlc_mode = LIBLTE_RRC_RLC_MODE_AM;
cnfg_.am.max_retx_thresh = 4;
cnfg_.am.poll_byte = 25*1000;
cnfg_.am.poll_pdu = 4;
cnfg_.am.t_poll_retx = 5;
cnfg_.am.t_reordering = 5;
cnfg_.am.t_status_prohibit = 5;
} else if (args.mode == "UM") {
// config UM bearer
cnfg_.rlc_mode = LIBLTE_RRC_RLC_MODE_UM_BI;
cnfg_.um.t_reordering = 5;
cnfg_.um.rx_mod = 32;
cnfg_.um.rx_sn_field_length = RLC_UMD_SN_SIZE_5_BITS;
cnfg_.um.rx_window_size = 16;
cnfg_.um.tx_sn_field_length = RLC_UMD_SN_SIZE_5_BITS;
cnfg_.um.tx_mod = 32;
} else if (args.mode == "TM") {
// use default LCID in TM
lcid = 0;
} else {
cout << "Unsupported RLC mode " << args.mode << ", exiting." << endl;
exit(-1);
}
rlc rlc1; rlc rlc1;
rlc rlc2; rlc rlc2;
rlc_am_tester tester1(&rlc1, "tester1", args.sdu_gen_delay_usec); rlc_tester tester1(&rlc1, "tester1", args.sdu_gen_delay_usec, lcid);
rlc_am_tester tester2(&rlc2, "tester2", args.sdu_gen_delay_usec); rlc_tester tester2(&rlc2, "tester2", args.sdu_gen_delay_usec, lcid);
mac_dummy mac(&rlc1, &rlc2, args.error_rate, args.pdu_tx_delay_usec); mac_dummy mac(&rlc1, &rlc2, args.error_rate, args.opp_sdu_ratio, args.pdu_tx_delay_usec, lcid, &pcap);
ue_interface ue; ue_interface ue;
rlc1.init(&tester1, &tester1, &ue, &log1, &mac, 0); rlc1.init(&tester1, &tester1, &ue, &log1, &mac, 0);
rlc2.init(&tester2, &tester2, &ue, &log2, &mac, 0); rlc2.init(&tester2, &tester2, &ue, &log2, &mac, 0);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg; // only add AM and UM bearers
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_AM; if (args.mode != "TM") {
cnfg.dl_am_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5; rlc1.add_bearer(lcid, cnfg_);
cnfg.dl_am_rlc.t_status_prohibit = LIBLTE_RRC_T_STATUS_PROHIBIT_MS5; rlc2.add_bearer(lcid, cnfg_);
cnfg.ul_am_rlc.max_retx_thresh = LIBLTE_RRC_MAX_RETX_THRESHOLD_T4; }
cnfg.ul_am_rlc.poll_byte = LIBLTE_RRC_POLL_BYTE_KB25;
cnfg.ul_am_rlc.poll_pdu = LIBLTE_RRC_POLL_PDU_P4;
cnfg.ul_am_rlc.t_poll_retx = LIBLTE_RRC_T_POLL_RETRANSMIT_MS5;
srslte_rlc_config_t cnfg_(&cnfg);
rlc1.add_bearer(1, cnfg_);
rlc2.add_bearer(1, cnfg_);
tester1.start(7); tester1.start(7);
tester2.start(7); if (!args.single_tx) {
tester2.start(7);
}
mac.start(); mac.start();
for (uint32_t i = 0; i < args.test_duration_sec; i++) { for (uint32_t i = 0; i < args.test_duration_sec; i++) {
@ -313,6 +381,19 @@ void stress_test(stress_test_args_t args)
tester1.stop(); tester1.stop();
tester2.stop(); tester2.stop();
mac.stop(); mac.stop();
if (args.write_pcap) {
pcap.close();
}
printf("RLC1 received %d SDUs in %ds (%.2f PDU/s)\n",
tester1.get_nof_rx_pdus(),
args.test_duration_sec,
(float)tester1.get_nof_rx_pdus()/args.test_duration_sec);
printf("RLC2 received %d SDUs in %ds (%.2f PDU/s)\n",
tester2.get_nof_rx_pdus(),
args.test_duration_sec,
(float)tester2.get_nof_rx_pdus()/args.test_duration_sec);
} }
@ -322,4 +403,6 @@ int main(int argc, char **argv) {
stress_test(args); stress_test(args);
byte_buffer_pool::get_instance()->cleanup(); byte_buffer_pool::get_instance()->cleanup();
exit(0);
} }

@ -29,6 +29,7 @@
#include "srslte/upper/rlc_um.h" #include "srslte/upper/rlc_um.h"
#include <assert.h> #include <assert.h>
#define MAX_NBUFS 100
#define NBUFS 5 #define NBUFS 5
using namespace srslte; using namespace srslte;
@ -60,6 +61,7 @@ public:
rlc_um_tester(){ rlc_um_tester(){
bzero(sdus, sizeof(sdus)); bzero(sdus, sizeof(sdus));
n_sdus = 0; n_sdus = 0;
expected_sdu_len = 0;
} }
~rlc_um_tester(){ ~rlc_um_tester(){
@ -74,6 +76,10 @@ public:
void write_pdu(uint32_t lcid, byte_buffer_t *sdu) void write_pdu(uint32_t lcid, byte_buffer_t *sdu)
{ {
assert(lcid == 3); assert(lcid == 3);
if (sdu->N_bytes != expected_sdu_len) {
printf("Received PDU with size %d, expected %d. Exiting.\n", sdu->N_bytes, expected_sdu_len);
exit(-1);
}
sdus[n_sdus++] = sdu; sdus[n_sdus++] = sdu;
} }
void write_pdu_bcch_bch(byte_buffer_t *sdu) {} void write_pdu_bcch_bch(byte_buffer_t *sdu) {}
@ -83,9 +89,11 @@ public:
// RRC interface // RRC interface
void max_retx_attempted(){} void max_retx_attempted(){}
std::string get_rb_name(uint32_t lcid) { return std::string(""); } std::string get_rb_name(uint32_t lcid) { return std::string(""); }
void set_expected_sdu_len(uint32_t len) { expected_sdu_len = len; }
byte_buffer_t *sdus[5]; byte_buffer_t *sdus[MAX_NBUFS];
int n_sdus; int n_sdus;
uint32_t expected_sdu_len;
}; };
void basic_test() void basic_test()
@ -119,6 +127,8 @@ void basic_test()
rlc1.configure(&cnfg); rlc1.configure(&cnfg);
rlc2.configure(&cnfg); rlc2.configure(&cnfg);
tester.set_expected_sdu_len(1);
// Push 5 SDUs into RLC1 // Push 5 SDUs into RLC1
byte_buffer_t sdu_bufs[NBUFS]; byte_buffer_t sdu_bufs[NBUFS];
for(int i=0;i<NBUFS;i++) for(int i=0;i<NBUFS;i++)
@ -187,6 +197,8 @@ void loss_test()
rlc1.configure(&cnfg); rlc1.configure(&cnfg);
rlc2.configure(&cnfg); rlc2.configure(&cnfg);
tester.set_expected_sdu_len(1);
// Push 5 SDUs into RLC1 // Push 5 SDUs into RLC1
byte_buffer_t sdu_bufs[NBUFS]; byte_buffer_t sdu_bufs[NBUFS];
for(int i=0;i<NBUFS;i++) for(int i=0;i<NBUFS;i++)
@ -222,9 +234,243 @@ void loss_test()
assert(NBUFS-1 == tester.n_sdus); assert(NBUFS-1 == tester.n_sdus);
} }
// This test checks the reassembly routines when a PDU
// is lost that contains the beginning of SDU segment.
// The PDU that contains the end of this SDU _also_ contains
// a segment of another SDU.
// On reassembly of the SDUs, the missing start segment
// should be detected and the complete SDU be discarded
// Therefore, one SDU less should be received than was tx'ed.
// This test sends PDU in two batches so it's not the reordering
// timeout that detects the missing PDU but the fact more
// PDUs than rx_mod are received.
void reassmble_test()
{
srslte::log_filter log1("RLC_UM_1");
srslte::log_filter log2("RLC_UM_2");
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(-1);
log2.set_hex_limit(-1);
rlc_um_tester tester;
mac_dummy_timers timers;
rlc_um rlc1;
rlc_um rlc2;
int len;
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
rlc1.init(&log1, 3, &tester, &tester, &timers);
rlc2.init(&log2, 3, &tester, &tester, &timers);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_UM_BI;
cnfg.dl_um_bi_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_um_bi_rlc.sn_field_len = LIBLTE_RRC_SN_FIELD_LENGTH_SIZE5;
cnfg.ul_um_bi_rlc.sn_field_len = LIBLTE_RRC_SN_FIELD_LENGTH_SIZE5;
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
// Push SDUs into RLC1
const int n_sdus = 25;
const int sdu_len = 100;
tester.set_expected_sdu_len(sdu_len);
byte_buffer_t sdu_bufs[n_sdus];
const int n_sdu_first_batch = 17;
for(int i=0;i<n_sdu_first_batch;i++) {
for (int k = 0; k < sdu_len; ++k) {
sdu_bufs[i].msg[k] = i;
}
sdu_bufs[i].N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(&sdu_bufs[i]);
}
// Read PDUs from RLC1 (use smaller grant for first PDU and large for the rest)
const int max_n_pdus = 100;
int n_pdus = 0;
byte_buffer_t pdu_bufs[max_n_pdus];
for(int i=0;i<max_n_pdus;i++)
{
len = rlc1.read_pdu(pdu_bufs[i].msg, (i == 0) ? sdu_len*3/4 : sdu_len*1.25);
pdu_bufs[i].N_bytes = len;
if (len) {
n_pdus++;
} else {
break;
}
}
printf("Generated %d PDUs in first batch\n", n_pdus);
assert(0 == rlc1.get_buffer_state());
// push second batch of SDUs
for (int i = n_sdu_first_batch; i < n_sdus; ++i) {
for (int k = 0; k < sdu_len; ++k) {
sdu_bufs[i].msg[k] = i;
}
sdu_bufs[i].N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(&sdu_bufs[i]);
}
// Read second batch of PDUs (use large grants)
for(int i=n_pdus;i<max_n_pdus;i++)
{
len = rlc1.read_pdu(pdu_bufs[i].msg, sdu_len*1.25);
pdu_bufs[i].N_bytes = len;
if (len) {
n_pdus++;
} else {
// stop reading PDUs after first zero length PDU
break;
}
}
printf("Generated %d PDUs in total\n", n_pdus);
// Write all PDUs into RLC2 except first one
for(int i=0;i<n_pdus;i++)
{
if (i!=0) {
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
}
}
// We should have received one SDU less than we tx'ed
assert(tester.n_sdus == n_sdus - 1);
for (int i = 0; i < tester.n_sdus; ++i) {
assert(tester.sdus[i]->N_bytes == sdu_len);
}
}
// This reassmble test checks the reassembly routines when a PDU
// is lost that _only_ contains the beginning of SDU segment,
// while the next PDU contains the middle part of this SDU (and
// yet another PDU the end part).
// On reassembly of the SDUs, the missing start segment
// should be detected and the complete SDU be discarded
// Therefore, one SDU less should be received than was tx'ed.
void reassmble_test2()
{
srslte::log_filter log1("RLC_UM_1");
srslte::log_filter log2("RLC_UM_2");
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
log1.set_hex_limit(-1);
log2.set_hex_limit(-1);
rlc_um_tester tester;
mac_dummy_timers timers;
rlc_um rlc1;
rlc_um rlc2;
int len;
log1.set_level(srslte::LOG_LEVEL_DEBUG);
log2.set_level(srslte::LOG_LEVEL_DEBUG);
rlc1.init(&log1, 3, &tester, &tester, &timers);
rlc2.init(&log2, 3, &tester, &tester, &timers);
LIBLTE_RRC_RLC_CONFIG_STRUCT cnfg;
cnfg.rlc_mode = LIBLTE_RRC_RLC_MODE_UM_BI;
cnfg.dl_um_bi_rlc.t_reordering = LIBLTE_RRC_T_REORDERING_MS5;
cnfg.dl_um_bi_rlc.sn_field_len = LIBLTE_RRC_SN_FIELD_LENGTH_SIZE5;
cnfg.ul_um_bi_rlc.sn_field_len = LIBLTE_RRC_SN_FIELD_LENGTH_SIZE5;
rlc1.configure(&cnfg);
rlc2.configure(&cnfg);
// Push SDUs into RLC1
const int n_sdus = 25;
const int sdu_len = 100;
tester.set_expected_sdu_len(sdu_len);
byte_buffer_t sdu_bufs[n_sdus];
const int n_sdu_first_batch = 17;
for(int i=0;i<n_sdu_first_batch;i++) {
for (int k = 0; k < sdu_len; ++k) {
sdu_bufs[i].msg[k] = i;
}
sdu_bufs[i].N_bytes = sdu_len;
rlc1.write_sdu(&sdu_bufs[i]);
}
const int max_n_pdus = 100;
int n_pdus = 0;
byte_buffer_t pdu_bufs[max_n_pdus];
for(int i=0;i<max_n_pdus;i++)
{
len = rlc1.read_pdu(pdu_bufs[i].msg, (i == 0) ? sdu_len*.75 : sdu_len*.25);
pdu_bufs[i].N_bytes = len;
if (len) {
n_pdus++;
} else {
break;
}
}
printf("Generated %d PDUs in first batch\n", n_pdus);
assert(0 == rlc1.get_buffer_state());
// push second batch of SDUs
for (int i = n_sdu_first_batch; i < n_sdus; ++i) {
for (int k = 0; k < sdu_len; ++k) {
sdu_bufs[i].msg[k] = i;
}
sdu_bufs[i].N_bytes = sdu_len; // Give each buffer a size of 1 byte
rlc1.write_sdu(&sdu_bufs[i]);
}
// Read second batch of PDUs
for(int i=n_pdus;i<max_n_pdus;i++)
{
len = rlc1.read_pdu(pdu_bufs[i].msg, sdu_len*1.25);
pdu_bufs[i].N_bytes = len;
if (len) {
n_pdus++;
} else {
break;
}
}
printf("Generated %d PDUs in total\n", n_pdus);
// Write all PDUs into RLC2 except first one
for(int i=0;i<n_pdus;i++) {
if (i!=0) {
rlc2.write_pdu(pdu_bufs[i].msg, pdu_bufs[i].N_bytes);
}
}
// We should have received one SDU less than we tx'ed
assert(tester.n_sdus == n_sdus - 1);
for (int i = 0; i < tester.n_sdus; ++i) {
assert(tester.sdus[i]->N_bytes == sdu_len);
}
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
basic_test(); basic_test();
byte_buffer_pool::get_instance()->cleanup(); byte_buffer_pool::get_instance()->cleanup();
loss_test(); loss_test();
byte_buffer_pool::get_instance()->cleanup(); byte_buffer_pool::get_instance()->cleanup();
reassmble_test();
byte_buffer_pool::get_instance()->cleanup();
reassmble_test2();
byte_buffer_pool::get_instance()->cleanup();
} }

@ -366,7 +366,6 @@ void phch_recv::run_thread()
bool is_end_of_burst = false; bool is_end_of_burst = false;
cf_t *dummy_buffer[SRSLTE_MAX_PORTS]; cf_t *dummy_buffer[SRSLTE_MAX_PORTS];
for (int i=0;i<SRSLTE_MAX_PORTS;i++) { for (int i=0;i<SRSLTE_MAX_PORTS;i++) {
dummy_buffer[i] = (cf_t*) malloc(sizeof(cf_t)*SRSLTE_SF_LEN_PRB(100)); dummy_buffer[i] = (cf_t*) malloc(sizeof(cf_t)*SRSLTE_SF_LEN_PRB(100));
} }
@ -556,6 +555,12 @@ void phch_recv::run_thread()
// Increase TTI counter // Increase TTI counter
tti = (tti+1) % 10240; tti = (tti+1) % 10240;
} }
for (int i=0;i<SRSLTE_MAX_PORTS;i++) {
if (dummy_buffer[i]) {
free(dummy_buffer[i]);
}
}
} }

@ -126,7 +126,7 @@ void gw::write_pdu(uint32_t lcid, srslte::byte_buffer_t *pdu)
int n = write(tun_fd, pdu->msg, pdu->N_bytes); int n = write(tun_fd, pdu->msg, pdu->N_bytes);
if(n > 0 && (pdu->N_bytes != (uint32_t)n)) if(n > 0 && (pdu->N_bytes != (uint32_t)n))
{ {
gw_log->warning("DL TUN/TAP write failure\n"); gw_log->warning("DL TUN/TAP write failure. Wanted to write %d B but only wrote %d B.\n", pdu->N_bytes, n);
} }
} }
pool->deallocate(pdu); pool->deallocate(pdu);

Loading…
Cancel
Save