mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
769 lines
22 KiB
C
769 lines
22 KiB
C
/*
|
|
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
|
*/
|
|
|
|
#include "nr_rlc_entity_um.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "nr_rlc_pdu.h"
|
|
|
|
#include "LOG/log.h"
|
|
#include "common/utils/time_stat.h"
|
|
|
|
/* for a given SDU/SDU segment, computes the corresponding PDU header size */
|
|
static int compute_pdu_header_size(nr_rlc_entity_um_t *entity,
|
|
nr_rlc_sdu_segment_t *sdu)
|
|
{
|
|
int header_size = 1;
|
|
|
|
/* if SN to be included then one more byte if SN field length is 12 */
|
|
if (!(sdu->is_first && sdu->is_last) && entity->sn_field_length == 12)
|
|
header_size++;
|
|
/* two more bytes for SO if SDU segment is not the first */
|
|
if (!sdu->is_first) header_size += 2;
|
|
return header_size;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* PDU RX functions */
|
|
/*************************************************************************/
|
|
|
|
static int modulus_rx(nr_rlc_entity_um_t *entity, int a)
|
|
{
|
|
/* as per 38.322 7.1, modulus base is rx_next_highest - window_size */
|
|
int r = a - (entity->rx_next_highest - entity->window_size);
|
|
if (r < 0) r += entity->sn_modulus;
|
|
return r % entity->sn_modulus;
|
|
}
|
|
|
|
static int sn_compare_rx(void *_entity, int a, int b)
|
|
{
|
|
nr_rlc_entity_um_t *entity = _entity;
|
|
return modulus_rx(entity, a) - modulus_rx(entity, b);
|
|
}
|
|
|
|
/* checks that all the bytes of the SDU sn have been received (but SDU
|
|
* has not been already processed)
|
|
*/
|
|
static int sdu_full(nr_rlc_entity_um_t *entity, int sn)
|
|
{
|
|
nr_rlc_pdu_t *l = entity->rx_list;
|
|
int last_byte;
|
|
int new_last_byte;
|
|
|
|
last_byte = -1;
|
|
while (l != NULL) {
|
|
if (l->sn == sn)
|
|
break;
|
|
l = l->next;
|
|
}
|
|
|
|
/* check if the data has already been processed */
|
|
if (l != NULL && l->data == NULL)
|
|
return 0;
|
|
|
|
while (l != NULL && l->sn == sn) {
|
|
if (l->so > last_byte + 1)
|
|
return 0;
|
|
if (l->is_last)
|
|
return 1;
|
|
new_last_byte = l->so + l->size - 1;
|
|
if (new_last_byte > last_byte)
|
|
last_byte = new_last_byte;
|
|
l = l->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* checks that an SDU has already been delivered */
|
|
static int sdu_delivered(nr_rlc_entity_um_t *entity, int sn)
|
|
{
|
|
nr_rlc_pdu_t *l = entity->rx_list;
|
|
|
|
while (l != NULL) {
|
|
if (l->sn == sn)
|
|
break;
|
|
l = l->next;
|
|
}
|
|
|
|
return l != NULL && l->data == NULL;
|
|
}
|
|
|
|
/* check if there is some missing bytes before the last received of SDU sn */
|
|
/* todo: be sure that when no byte was received or the SDU has already been
|
|
* processed then the SDU has no missing byte
|
|
*/
|
|
static int sdu_has_missing_bytes(nr_rlc_entity_um_t *entity, int sn)
|
|
{
|
|
nr_rlc_pdu_t *l = entity->rx_list;
|
|
int last_byte;
|
|
int new_last_byte;
|
|
|
|
last_byte = -1;
|
|
while (l != NULL) {
|
|
if (l->sn == sn)
|
|
break;
|
|
l = l->next;
|
|
}
|
|
|
|
/* check if the data has already been processed */
|
|
if (l != NULL && l->data == NULL)
|
|
return 0; /* data already processed: no missing byte */
|
|
|
|
while (l != NULL && l->sn == sn) {
|
|
if (l->so > last_byte + 1)
|
|
return 1;
|
|
new_last_byte = l->so + l->size - 1;
|
|
if (new_last_byte > last_byte)
|
|
last_byte = new_last_byte;
|
|
l = l->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void reassemble_and_deliver(nr_rlc_entity_um_t *entity, int sn)
|
|
{
|
|
nr_rlc_pdu_t *pdu;
|
|
char sdu[NR_SDU_MAX];
|
|
int so = 0;
|
|
int bad_sdu = 0;
|
|
|
|
/* go to first segment of sn */
|
|
pdu = entity->rx_list;
|
|
while (pdu->sn != sn)
|
|
pdu = pdu->next;
|
|
|
|
/* reassemble - free 'data' of each segment after processing */
|
|
while (pdu != NULL && pdu->sn == sn) {
|
|
if (pdu->so > so && !bad_sdu) {
|
|
/* pdu->so > so is possible when the other end sends bogus data */
|
|
LOG_E(RLC, "%s:%d:%s: inconsistent SDU, discarding\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
bad_sdu = 1;
|
|
}
|
|
int len = pdu->size - (so - pdu->so);
|
|
if (so + len > NR_SDU_MAX && !bad_sdu) {
|
|
LOG_E(RLC, "%s:%d:%s: bad SDU, too big, discarding\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
bad_sdu = 1;
|
|
}
|
|
if (!bad_sdu && len > 0) {
|
|
memcpy(sdu + so, pdu->data + so - pdu->so, len);
|
|
so += len;
|
|
}
|
|
free(pdu->data);
|
|
pdu->data = NULL;
|
|
entity->rx_size -= pdu->size;
|
|
pdu->size = 0;
|
|
pdu = pdu->next;
|
|
}
|
|
|
|
if (bad_sdu)
|
|
return;
|
|
|
|
/* deliver */
|
|
entity->common.deliver_sdu(entity->common.deliver_sdu_data,
|
|
(nr_rlc_entity_t *)entity,
|
|
sdu, so);
|
|
|
|
entity->common.stats.txsdu_pkts++;
|
|
entity->common.stats.txsdu_bytes += so;
|
|
}
|
|
|
|
static void reception_actions(nr_rlc_entity_um_t *entity, nr_rlc_pdu_t *pdu)
|
|
{
|
|
int x = pdu->sn;
|
|
|
|
if (sdu_full(entity, x)) {
|
|
/* SDU full */
|
|
reassemble_and_deliver(entity, x);
|
|
|
|
if (x == entity->rx_next_reassembly) {
|
|
int rx_next_reassembly = entity->rx_next_reassembly;
|
|
while (sdu_delivered(entity, rx_next_reassembly))
|
|
rx_next_reassembly = (rx_next_reassembly + 1) % entity->sn_modulus;
|
|
entity->rx_next_reassembly = rx_next_reassembly;
|
|
}
|
|
} else {
|
|
/* SDU not full */
|
|
/* test if x is not in reassembly window, that is x >= rx_next_highest */
|
|
if (sn_compare_rx(entity, x, entity->rx_next_highest) >= 0) {
|
|
entity->rx_next_highest = (x + 1) % entity->sn_modulus;
|
|
|
|
/* discard PDUs not in reassembly window */
|
|
while (entity->rx_list != NULL &&
|
|
sn_compare_rx(entity, entity->rx_list->sn,
|
|
entity->rx_next_highest) >= 0) {
|
|
nr_rlc_pdu_t *p = entity->rx_list;
|
|
entity->rx_size -= p->size;
|
|
entity->rx_list = p->next;
|
|
|
|
entity->common.stats.rxpdu_dd_pkts++;
|
|
/* we don't count PDU header bytes here, so be it */
|
|
entity->common.stats.rxpdu_dd_bytes += p->size;
|
|
|
|
entity->common.stats.rxpdu_ow_pkts++;
|
|
/* we don't count PDU header bytes here, so be it */
|
|
entity->common.stats.rxpdu_ow_bytes += p->size;
|
|
|
|
nr_rlc_free_pdu(p);
|
|
}
|
|
|
|
/* if rx_next_reassembly not in reassembly window */
|
|
if (sn_compare_rx(entity, entity->rx_next_reassembly,
|
|
entity->rx_next_highest) >= 0) {
|
|
int rx_next_reassembly;
|
|
/* set rx_next_reassembly to first SN >= rx_next_highest - window_size
|
|
* not delivered yet
|
|
*/
|
|
rx_next_reassembly = (entity->rx_next_highest - entity->window_size
|
|
+ entity->sn_modulus) % entity->sn_modulus;
|
|
while (sdu_delivered(entity, rx_next_reassembly))
|
|
rx_next_reassembly = (rx_next_reassembly + 1) % entity->sn_modulus;
|
|
entity->rx_next_reassembly = rx_next_reassembly;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (entity->t_reassembly_start) {
|
|
if (/* rx_timer_trigger <= rx_next_reassembly */
|
|
sn_compare_rx(entity, entity->rx_timer_trigger,
|
|
entity->rx_next_reassembly) <= 0 ||
|
|
/* or rx_timer_trigger outside of reassembly window and not equal
|
|
* to rx_next_highest, that is is > rx_next_highest
|
|
*/
|
|
sn_compare_rx(entity, entity->rx_timer_trigger,
|
|
entity->rx_next_highest) > 0 ||
|
|
/* or rx_next_highest == rx_next_reassembly + 1 and no missing byte
|
|
* for rx_next_reassembly
|
|
*/
|
|
(entity->rx_next_highest == (entity->rx_next_reassembly + 1) %
|
|
entity->sn_modulus &&
|
|
!sdu_has_missing_bytes(entity, entity->rx_next_reassembly)))
|
|
entity->t_reassembly_start = 0;
|
|
}
|
|
|
|
if (entity->t_reassembly_start == 0) {
|
|
if (sn_compare_rx(entity, entity->rx_next_highest,
|
|
(entity->rx_next_reassembly + 1)
|
|
% entity->sn_modulus) > 0 ||
|
|
(entity->rx_next_highest == (entity->rx_next_reassembly + 1)
|
|
% entity->sn_modulus &&
|
|
sdu_has_missing_bytes(entity, entity->rx_next_reassembly))) {
|
|
entity->t_reassembly_start = entity->t_current;
|
|
entity->rx_timer_trigger = entity->rx_next_highest;
|
|
}
|
|
}
|
|
}
|
|
|
|
void nr_rlc_entity_um_recv_pdu(nr_rlc_entity_t *_entity,
|
|
char *buffer, int size)
|
|
{
|
|
#define R(d) do { if (nr_rlc_pdu_decoder_in_error(&d)) goto err; } while (0)
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
nr_rlc_pdu_decoder_t decoder;
|
|
nr_rlc_pdu_t *pdu;
|
|
int si;
|
|
int sn;
|
|
int so = 0;
|
|
int data_size;
|
|
int is_first;
|
|
int is_last;
|
|
|
|
entity->common.stats.rxpdu_pkts++;
|
|
entity->common.stats.rxpdu_bytes += size;
|
|
|
|
nr_rlc_pdu_decoder_init(&decoder, buffer, size);
|
|
|
|
si = nr_rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
|
|
|
|
is_first = (si & 0x2) == 0;
|
|
is_last = (si & 0x1) == 0;
|
|
|
|
/* if full, deliver SDU */
|
|
if (is_first && is_last) {
|
|
if (size < 2) {
|
|
LOG_E(RLC, "%s:%d:%s: warning: discard PDU, no data\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
goto discard;
|
|
}
|
|
/* deliver */
|
|
entity->common.deliver_sdu(entity->common.deliver_sdu_data,
|
|
(nr_rlc_entity_t *)entity,
|
|
buffer + 1, size - 1);
|
|
|
|
entity->common.stats.txsdu_pkts++;
|
|
entity->common.stats.txsdu_bytes += size - 1;
|
|
|
|
return;
|
|
}
|
|
|
|
if (entity->sn_field_length == 12) {
|
|
nr_rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
|
|
}
|
|
|
|
sn = nr_rlc_pdu_decoder_get_bits(&decoder, entity->sn_field_length);
|
|
R(decoder);
|
|
|
|
if (!is_first) {
|
|
so = nr_rlc_pdu_decoder_get_bits(&decoder, 16); R(decoder);
|
|
if (so == 0) {
|
|
LOG_E(RLC, "%s:%d:%s: warning: discard PDU, bad so\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
goto discard;
|
|
}
|
|
}
|
|
|
|
data_size = size - decoder.byte;
|
|
|
|
/* dicard PDU if no data */
|
|
if (data_size <= 0) {
|
|
LOG_W(RLC, "%s:%d:%s: warning: discard PDU, no data\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
goto discard;
|
|
}
|
|
|
|
/* dicard PDU if rx buffer is full */
|
|
if (entity->rx_size + data_size > entity->rx_maxsize) {
|
|
LOG_W(RLC, "%s:%d:%s: warning: discard PDU, RX buffer full\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
goto discard;
|
|
}
|
|
|
|
/* discard PDU if sn < rx_next_reassembly */
|
|
if (sn_compare_rx(entity, sn, entity->rx_next_reassembly) < 0) {
|
|
LOG_W(RLC, "%s:%d:%s: warning: discard PDU, SN (%d) < rx_next_reassembly (%d)\n",
|
|
__FILE__, __LINE__, __FUNCTION__,
|
|
sn, entity->rx_next_reassembly);
|
|
goto discard;
|
|
}
|
|
|
|
/* put in pdu reception list */
|
|
entity->rx_size += data_size;
|
|
pdu = nr_rlc_new_pdu(sn, so, is_first, is_last,
|
|
buffer + size - data_size, data_size);
|
|
entity->rx_list = nr_rlc_pdu_list_add(sn_compare_rx, entity,
|
|
entity->rx_list, pdu);
|
|
|
|
/* do reception actions (38.322 5.2.2.2.3) */
|
|
reception_actions(entity, pdu);
|
|
|
|
return;
|
|
|
|
err:
|
|
LOG_W(RLC, "%s:%d:%s: error decoding PDU, discarding\n", __FILE__, __LINE__, __FUNCTION__);
|
|
goto discard;
|
|
|
|
discard:
|
|
entity->common.stats.rxpdu_dd_pkts++;
|
|
entity->common.stats.rxpdu_dd_bytes += size;
|
|
|
|
return;
|
|
|
|
#undef R
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* TX functions */
|
|
/*************************************************************************/
|
|
|
|
static int serialize_sdu(nr_rlc_entity_um_t *entity,
|
|
nr_rlc_sdu_segment_t *sdu, char *buffer, int bufsize)
|
|
{
|
|
nr_rlc_pdu_encoder_t encoder;
|
|
|
|
/* generate header */
|
|
nr_rlc_pdu_encoder_init(&encoder, buffer, bufsize);
|
|
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, 1-sdu->is_first,1);/* 1st bit of SI */
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, 1-sdu->is_last,1); /* 2nd bit of SI */
|
|
|
|
/* SN, if required */
|
|
if (sdu->is_first == 1 && sdu->is_last == 1) {
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, 0, 6); /* R */
|
|
} else {
|
|
if (entity->sn_field_length == 12)
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, 0, 2); /* R */
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, sdu->sdu->sn,
|
|
entity->sn_field_length); /* SN */
|
|
}
|
|
|
|
if (!sdu->is_first)
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, sdu->so, 16); /* SO */
|
|
|
|
/* data */
|
|
memcpy(buffer + encoder.byte, sdu->sdu->data + sdu->so, sdu->size);
|
|
|
|
return encoder.byte + sdu->size;
|
|
}
|
|
|
|
/* resize SDU/SDU segment for the corresponding PDU to fit into 'pdu_size'
|
|
* bytes
|
|
* - modifies SDU/SDU segment to become an SDU segment
|
|
* - returns a new SDU segment covering the remaining data bytes
|
|
* returns NULL if pdu_size is too small to contain the new segment
|
|
*/
|
|
static nr_rlc_sdu_segment_t *resegment(nr_rlc_sdu_segment_t *sdu,
|
|
nr_rlc_entity_um_t *entity,
|
|
int pdu_size)
|
|
{
|
|
nr_rlc_sdu_segment_t *next;
|
|
int pdu_header_size;
|
|
int over_size;
|
|
int old_is_last;
|
|
|
|
sdu->sdu->ref_count++;
|
|
|
|
/* clear is_last to compute header size */
|
|
old_is_last = sdu->is_last;
|
|
sdu->is_last = 0;
|
|
pdu_header_size = compute_pdu_header_size(entity, sdu);
|
|
sdu->is_last = old_is_last;
|
|
|
|
/* if no room for at least 1 data byte, do nothing */
|
|
if (pdu_header_size + 1 > pdu_size)
|
|
return NULL;
|
|
|
|
next = calloc(1, sizeof(nr_rlc_sdu_segment_t));
|
|
if (next == NULL) {
|
|
LOG_E(RLC, "%s:%d:%s: out of memory\n", __FILE__, __LINE__, __FUNCTION__);
|
|
exit(1);
|
|
}
|
|
*next = *sdu;
|
|
|
|
over_size = pdu_header_size + sdu->size - pdu_size;
|
|
|
|
/* update SDU */
|
|
sdu->size -= over_size;
|
|
sdu->is_last = 0;
|
|
|
|
/* create new segment */
|
|
next->size = over_size;
|
|
next->so = sdu->so + sdu->size;
|
|
next->is_first = 0;
|
|
|
|
entity->common.stats.txpdu_segmented++;
|
|
|
|
return next;
|
|
}
|
|
|
|
static int generate_tx_pdu(nr_rlc_entity_um_t *entity, char *buffer, int size)
|
|
{
|
|
nr_rlc_sdu_segment_t *sdu;
|
|
int pdu_header_size;
|
|
int pdu_size;
|
|
int ret;
|
|
|
|
if (entity->tx_list == NULL)
|
|
return 0;
|
|
|
|
sdu = entity->tx_list;
|
|
|
|
pdu_header_size = compute_pdu_header_size(entity, sdu);
|
|
|
|
/* not enough room for at least one byte of data? do nothing */
|
|
if (pdu_header_size + 1 > size)
|
|
return 0;
|
|
|
|
entity->tx_list = entity->tx_list->next;
|
|
if (entity->tx_list == NULL)
|
|
entity->tx_end = NULL;
|
|
|
|
/* assign SN to SDU */
|
|
sdu->sdu->sn = entity->tx_next;
|
|
|
|
pdu_size = pdu_header_size + sdu->size;
|
|
|
|
/* update buffer status */
|
|
entity->common.bstatus.tx_size -= pdu_size;
|
|
|
|
/* segment if necessary */
|
|
if (pdu_size > size) {
|
|
nr_rlc_sdu_segment_t *next_sdu;
|
|
next_sdu = resegment(sdu, entity, size);
|
|
if (next_sdu == NULL)
|
|
return 0;
|
|
/* put the second SDU back at the head of the TX list */
|
|
next_sdu->next = entity->tx_list;
|
|
entity->tx_list = next_sdu;
|
|
if (entity->tx_end == NULL)
|
|
entity->tx_end = entity->tx_list;
|
|
|
|
entity->common.stats.txpdu_segmented++;
|
|
/* update buffer status */
|
|
entity->common.bstatus.tx_size += compute_pdu_header_size(entity, next_sdu)
|
|
+ next_sdu->size;
|
|
}
|
|
|
|
/* update tx_next if the SDU is an SDU segment and is the last */
|
|
if (!sdu->is_first && sdu->is_last)
|
|
entity->tx_next = (entity->tx_next + 1) % entity->sn_modulus;
|
|
|
|
ret = serialize_sdu(entity, sdu, buffer, size);
|
|
|
|
entity->tx_size -= sdu->size;
|
|
|
|
entity->common.stats.txpdu_pkts++;
|
|
entity->common.stats.txpdu_bytes += size;
|
|
|
|
/* No need to 'zero' time-of-arrival;
|
|
Segmented packets do need to be duplicated in time-sensitive use cases */
|
|
if (entity->common.avg_time_is_on) {
|
|
uint64_t time_now = time_average_now();
|
|
uint64_t waited_time = time_now - sdu->sdu->time_of_arrival;
|
|
time_average_add(entity->common.txsdu_avg_time_to_tx, time_now, waited_time);
|
|
}
|
|
|
|
nr_rlc_free_sdu_segment(sdu);
|
|
|
|
return ret;
|
|
}
|
|
|
|
nr_rlc_entity_buffer_status_t nr_rlc_entity_um_buffer_status(nr_rlc_entity_t *_entity, int maxsize)
|
|
{
|
|
UNUSED(maxsize);
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
nr_rlc_entity_buffer_status_t ret;
|
|
|
|
ret.status_size = 0;
|
|
ret.tx_size = entity->common.bstatus.tx_size;
|
|
ret.retx_size = 0;
|
|
|
|
ret.oldest_sdu_arrival_ms = 0;
|
|
if (entity->tx_list && entity->tx_list->sdu)
|
|
ret.oldest_sdu_arrival_ms = entity->tx_list->sdu->arrival_ms;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int nr_rlc_entity_um_generate_pdu(nr_rlc_entity_t *_entity,
|
|
char *buffer, int size)
|
|
{
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
|
|
return generate_tx_pdu(entity, buffer, size);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* SDU RX functions */
|
|
/*************************************************************************/
|
|
|
|
void nr_rlc_entity_um_recv_sdu(nr_rlc_entity_t *_entity,
|
|
char *buffer, int size,
|
|
int sdu_id)
|
|
{
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
nr_rlc_sdu_segment_t *sdu;
|
|
|
|
entity->common.stats.rxsdu_pkts++;
|
|
entity->common.stats.rxsdu_bytes += size;
|
|
|
|
if (size > NR_SDU_MAX) {
|
|
LOG_E(RLC, "%s:%d:%s: fatal: SDU size too big (%d bytes)\n",
|
|
__FILE__, __LINE__, __FUNCTION__, size);
|
|
exit(1);
|
|
}
|
|
|
|
/* log SDUs rejected, at most once per second */
|
|
if (entity->sdu_rejected != 0
|
|
&& entity->t_current > entity->t_log_buffer_full + 1000) {
|
|
LOG_E(RLC, "%d SDU rejected, SDU buffer full\n", entity->sdu_rejected);
|
|
entity->sdu_rejected = 0;
|
|
entity->t_log_buffer_full = entity->t_current;
|
|
}
|
|
|
|
if (entity->tx_size + size > entity->tx_maxsize) {
|
|
entity->sdu_rejected++;
|
|
|
|
entity->common.stats.rxsdu_dd_pkts++;
|
|
entity->common.stats.rxsdu_dd_bytes += size;
|
|
|
|
return;
|
|
}
|
|
|
|
entity->tx_size += size;
|
|
|
|
sdu = nr_rlc_new_sdu(buffer, size, sdu_id);
|
|
|
|
nr_rlc_sdu_segment_list_append(&entity->tx_list, &entity->tx_end, sdu);
|
|
|
|
/* update buffer status */
|
|
entity->common.bstatus.tx_size += compute_pdu_header_size(entity, sdu)
|
|
+ sdu->size;
|
|
|
|
if (entity->common.avg_time_is_on)
|
|
sdu->sdu->time_of_arrival = time_average_now();
|
|
|
|
sdu->sdu->arrival_ms = entity->t_current;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* time/timers */
|
|
/*************************************************************************/
|
|
|
|
static void check_t_reassembly(nr_rlc_entity_um_t *entity)
|
|
{
|
|
nr_rlc_pdu_t *cur;
|
|
|
|
/* is t_reassembly running and if yes has it expired? */
|
|
if (entity->t_reassembly_start == 0 ||
|
|
entity->t_current <= entity->t_reassembly_start + entity->t_reassembly)
|
|
return;
|
|
|
|
/* stop timer */
|
|
entity->t_reassembly_start = 0;
|
|
|
|
LOG_D(RLC, "%s:%d:%s: t_reassembly expired\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
|
|
/* update rx_next_reassembly to first SN >= rx_timer_trigger not reassembled
|
|
* (ie. not delivered yet)
|
|
*/
|
|
entity->rx_next_reassembly = entity->rx_timer_trigger;
|
|
while (sdu_delivered(entity, entity->rx_next_reassembly))
|
|
entity->rx_next_reassembly = (entity->rx_next_reassembly + 1)
|
|
% entity->sn_modulus;
|
|
|
|
/* discard all segments < entity->rx_next_reassembly */
|
|
cur = entity->rx_list;
|
|
while (cur != NULL &&
|
|
sn_compare_rx(entity, cur->sn, entity->rx_next_reassembly) < 0) {
|
|
nr_rlc_pdu_t *p = cur;
|
|
cur = cur->next;
|
|
entity->rx_list = cur;
|
|
|
|
entity->common.stats.rxpdu_dd_pkts++;
|
|
/* we don't count PDU header bytes here, so be it */
|
|
entity->common.stats.rxpdu_dd_bytes += p->size;
|
|
|
|
nr_rlc_free_pdu(p);
|
|
}
|
|
|
|
if (sn_compare_rx(entity, entity->rx_next_highest,
|
|
(entity->rx_next_reassembly + 1)
|
|
% entity->sn_modulus) > 0 ||
|
|
(entity->rx_next_highest == entity->rx_next_reassembly + 1 &&
|
|
sdu_has_missing_bytes(entity, entity->rx_next_reassembly))) {
|
|
entity->t_reassembly_start = entity->t_current;
|
|
entity->rx_timer_trigger = entity->rx_next_highest;
|
|
}
|
|
}
|
|
|
|
void nr_rlc_entity_um_set_time(nr_rlc_entity_t *_entity, uint64_t now)
|
|
{
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
|
|
entity->t_current = now;
|
|
|
|
check_t_reassembly(entity);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* discard/re-establishment/delete */
|
|
/*************************************************************************/
|
|
|
|
void nr_rlc_entity_um_discard_sdu(nr_rlc_entity_t *_entity, int sdu_id)
|
|
{
|
|
/* implements 38.322 5.4 */
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
nr_rlc_sdu_segment_t head;
|
|
nr_rlc_sdu_segment_t *cur;
|
|
nr_rlc_sdu_segment_t *prev;
|
|
|
|
head.next = entity->tx_list;
|
|
cur = entity->tx_list;
|
|
prev = &head;
|
|
|
|
while (cur != NULL && cur->sdu->upper_layer_id != sdu_id) {
|
|
prev = cur;
|
|
cur = cur->next;
|
|
}
|
|
|
|
/* if sdu_id not found or some bytes have already been 'PDU-ized'
|
|
* then do nothing
|
|
*/
|
|
if (cur == NULL || !cur->is_first || !cur->is_last)
|
|
return;
|
|
|
|
/* remove SDU from tx_list */
|
|
prev->next = cur->next;
|
|
entity->tx_list = head.next;
|
|
if (entity->tx_end == cur) {
|
|
if (prev != &head)
|
|
entity->tx_end = prev;
|
|
else
|
|
entity->tx_end = NULL;
|
|
}
|
|
|
|
/* update buffer status */
|
|
entity->common.bstatus.tx_size -= compute_pdu_header_size(entity, cur)
|
|
+ cur->size;
|
|
|
|
nr_rlc_free_sdu_segment(cur);
|
|
}
|
|
|
|
static void clear_entity(nr_rlc_entity_um_t *entity)
|
|
{
|
|
nr_rlc_pdu_t *cur_rx;
|
|
|
|
entity->rx_next_highest = 0;
|
|
entity->rx_next_reassembly = 0;
|
|
entity->rx_timer_trigger = 0;
|
|
|
|
entity->tx_next = 0;
|
|
|
|
entity->t_current = 0;
|
|
|
|
entity->t_log_buffer_full = 0;
|
|
entity->sdu_rejected = 0;
|
|
|
|
entity->t_reassembly_start = 0;
|
|
|
|
cur_rx = entity->rx_list;
|
|
while (cur_rx != NULL) {
|
|
nr_rlc_pdu_t *p = cur_rx;
|
|
cur_rx = cur_rx->next;
|
|
nr_rlc_free_pdu(p);
|
|
}
|
|
entity->rx_list = NULL;
|
|
entity->rx_size = 0;
|
|
|
|
nr_rlc_free_sdu_segment_list(entity->tx_list);
|
|
|
|
entity->tx_list = NULL;
|
|
entity->tx_end = NULL;
|
|
entity->tx_size = 0;
|
|
|
|
entity->common.bstatus.tx_size = 0;
|
|
}
|
|
|
|
void nr_rlc_entity_um_reestablishment(nr_rlc_entity_t *_entity)
|
|
{
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
clear_entity(entity);
|
|
}
|
|
|
|
void nr_rlc_entity_um_delete(nr_rlc_entity_t *_entity)
|
|
{
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
clear_entity(entity);
|
|
time_average_free(entity->common.txsdu_avg_time_to_tx);
|
|
free(entity);
|
|
}
|
|
|
|
int nr_rlc_entity_um_available_tx_space(nr_rlc_entity_t *_entity)
|
|
{
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
return entity->tx_maxsize - entity->tx_size;
|
|
}
|
|
|
|
int nr_rlc_entity_um_tx_list_occupancy(nr_rlc_entity_t *_entity)
|
|
{
|
|
nr_rlc_entity_um_t *entity = (nr_rlc_entity_um_t *)_entity;
|
|
return entity->tx_size;
|
|
}
|