When t_poll_retransmit expires we need to consider an SDU for retransmission. We used to take the first SDU (which could be a segment, not a full SDU) of the wait list and put it in the retransmit list. When testing with some UE (Amarisoft UE) a bad behavior was detected. This is what happens for the SDU with SN 6 (for illustration purpose). The gNB is sending an SDU in several pieces (it is segmented). slot n: rlc sn 6 [1 .. 119[ p=0 RECEIVED slot n+x: rlc sn 6 ]120 .. 180[ p=0 RECEIVED slot n+x+y: rlc sn 6 ]181 .. 210] p=1 not RECEIVED Then when t_poll_retransmit expires rlc retransmits only the PDU of slot n (with p=1 this time) and the UE replies with ack 6. So nothing happens on the gnb side. (We would have expected ack 7 + nack 6 181..end.) Then after t_poll_retransmit expires again rlc retransmits only PDU of slot n and the UE still replies with ack 6. This goes on forever (or some other timeout occurs.) The logic is now changed. When t_poll_retransmit expires we transfer all the SDUs in the wait list having the same SN as the head of the wait list into the retransmit list. Testing with Amarisoft UE, it seems to work properly.
1977 lines
61 KiB
C
1977 lines
61 KiB
C
/*
|
|
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The OpenAirInterface Software Alliance licenses this file to You under
|
|
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.openairinterface.org/?page_id=698
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*-------------------------------------------------------------------------------
|
|
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
|
* contact@openairinterface.org
|
|
*/
|
|
|
|
#include "nr_rlc_entity_am.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_am_t *entity,
|
|
nr_rlc_sdu_segment_t *sdu)
|
|
{
|
|
int header_size = 2;
|
|
/* one more byte if SN field length is 18 */
|
|
if (entity->sn_field_length == 18)
|
|
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_am_t *entity, int a)
|
|
{
|
|
/* as per 38.322 7.1, modulus base is rx_next */
|
|
int r = a - entity->rx_next;
|
|
if (r < 0) r += entity->sn_modulus;
|
|
return r;
|
|
}
|
|
|
|
static int modulus_tx(nr_rlc_entity_am_t *entity, int a)
|
|
{
|
|
int r = a - entity->tx_next_ack;
|
|
if (r < 0) r += entity->sn_modulus;
|
|
return r;
|
|
}
|
|
|
|
static int sn_in_recv_window(void *_entity, int sn)
|
|
{
|
|
nr_rlc_entity_am_t *entity = _entity;
|
|
int mod_sn = modulus_rx(entity, sn);
|
|
/* we simplify rx_next <= sn < rx_next + am_window_size */
|
|
return mod_sn < entity->window_size;
|
|
}
|
|
|
|
static int sn_compare_rx(void *_entity, int a, int b)
|
|
{
|
|
nr_rlc_entity_am_t *entity = _entity;
|
|
return modulus_rx(entity, a) - modulus_rx(entity, b);
|
|
}
|
|
|
|
static int sn_compare_tx(void *_entity, int a, int b)
|
|
{
|
|
nr_rlc_entity_am_t *entity = _entity;
|
|
return modulus_tx(entity, a) - modulus_tx(entity, b);
|
|
}
|
|
|
|
static int segment_already_received(nr_rlc_entity_am_t *entity,
|
|
int sn, int so, int size)
|
|
{
|
|
nr_rlc_pdu_t *l = entity->rx_list;
|
|
int covered;
|
|
|
|
while (l != NULL && size > 0) {
|
|
if (l->sn == sn) {
|
|
if (l->so <= so && so < l->so + l->size) {
|
|
covered = l->size - (so - l->so);
|
|
size -= covered;
|
|
so += covered;
|
|
} else if (l->so <= so+size-1 && so+size-1 < l->so + l->size) {
|
|
covered = size - (l->so - so);
|
|
size -= covered;
|
|
}
|
|
}
|
|
l = l->next;
|
|
}
|
|
|
|
return size <= 0;
|
|
}
|
|
|
|
/* 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_am_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_am_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_am_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_am_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) {
|
|
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 = 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_am_t *entity, nr_rlc_pdu_t *pdu)
|
|
{
|
|
int x = pdu->sn;
|
|
|
|
if (sn_compare_rx(entity, x, entity->rx_next_highest) >= 0)
|
|
entity->rx_next_highest = (x + 1) % entity->sn_modulus;
|
|
|
|
/* todo: room for optimization: we can run through rx_list only once */
|
|
if (sdu_full(entity, x)) {
|
|
reassemble_and_deliver(entity, x);
|
|
|
|
if (x == entity->rx_highest_status) {
|
|
int rx_highest_status = entity->rx_highest_status;
|
|
while (sdu_delivered(entity, rx_highest_status))
|
|
rx_highest_status = (rx_highest_status + 1) % entity->sn_modulus;
|
|
entity->rx_highest_status = rx_highest_status;
|
|
}
|
|
|
|
if (x == entity->rx_next) {
|
|
/* update rx_next and free all delivered SDUs at the head of the
|
|
* rx_list
|
|
*/
|
|
int rx_next = entity->rx_next;
|
|
while (entity->rx_list != NULL && entity->rx_list->data == NULL &&
|
|
entity->rx_list->sn == rx_next) {
|
|
/* free all segments of this SDU */
|
|
do {
|
|
nr_rlc_pdu_t *p = entity->rx_list;
|
|
entity->rx_list = p->next;
|
|
free(p);
|
|
} while (entity->rx_list != NULL &&
|
|
entity->rx_list->sn == rx_next);
|
|
rx_next = (rx_next + 1) % entity->sn_modulus;
|
|
}
|
|
entity->rx_next = rx_next;
|
|
}
|
|
}
|
|
|
|
if (entity->t_reassembly_start) {
|
|
if (entity->rx_next_status_trigger == entity->rx_next ||
|
|
(entity->rx_next_status_trigger == (entity->rx_next + 1)
|
|
% entity->sn_modulus &&
|
|
!sdu_has_missing_bytes(entity, entity->rx_next)) ||
|
|
(!sn_in_recv_window(entity, entity->rx_next_status_trigger) &&
|
|
entity->rx_next_status_trigger !=
|
|
(entity->rx_next + entity->window_size) % entity->sn_modulus)) {
|
|
entity->t_reassembly_start = 0;
|
|
}
|
|
}
|
|
|
|
if (entity->t_reassembly_start == 0) {
|
|
if (sn_compare_rx(entity, entity->rx_next_highest,
|
|
(entity->rx_next + 1) % entity->sn_modulus) > 0 ||
|
|
(entity->rx_next_highest == (entity->rx_next + 1)
|
|
% entity->sn_modulus &&
|
|
sdu_has_missing_bytes(entity, entity->rx_next))) {
|
|
entity->t_reassembly_start = entity->t_current;
|
|
entity->rx_next_status_trigger = entity->rx_next_highest;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int so_overlap(int s1, int e1, int s2, int e2)
|
|
{
|
|
if (s1 < s2) {
|
|
if (e1 == -1 || e1 >= s2)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
if (e2 == -1 || s1 <= e2)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void process_control_pdu(nr_rlc_entity_am_t *entity,
|
|
char *buffer, int size)
|
|
{
|
|
#define R(d) do { if (nr_rlc_pdu_decoder_in_error(&d)) goto err; } while (0)
|
|
nr_rlc_pdu_decoder_t decoder;
|
|
int i;
|
|
int cpt;
|
|
int ack_sn;
|
|
int nack_sn;
|
|
int e1;
|
|
int e2;
|
|
int e3;
|
|
int so_start;
|
|
int so_end;
|
|
int range;
|
|
int prev_nack_sn = -1;
|
|
int prev_so_start = 0;
|
|
int prev_so_end = 0;
|
|
int tx_next_ack;
|
|
nr_rlc_sdu_segment_t *cur_wait_list;
|
|
nr_rlc_sdu_segment_t *prev_wait_list;
|
|
nr_rlc_sdu_segment_t *end_wait_list = NULL;
|
|
nr_rlc_sdu_segment_t head_wait_list;
|
|
nr_rlc_sdu_segment_t *cur_retransmit_list;
|
|
nr_rlc_sdu_segment_t *new_retransmit_list;
|
|
nr_rlc_sdu_segment_t head_retransmit_list;
|
|
|
|
head_wait_list.next = entity->wait_list;
|
|
cur_wait_list = entity->wait_list;
|
|
prev_wait_list = &head_wait_list;
|
|
|
|
head_retransmit_list.next = NULL;
|
|
cur_retransmit_list = entity->retransmit_list;
|
|
new_retransmit_list = &head_retransmit_list;
|
|
|
|
nr_rlc_pdu_decoder_init(&decoder, buffer, size);
|
|
nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder); /* dc */
|
|
|
|
cpt = nr_rlc_pdu_decoder_get_bits(&decoder, 3); R(decoder);
|
|
if (cpt != 0) {
|
|
LOG_E(RLC, "%s:%d:%s: warning: discard PDU, CPT not 0 (%d)\n",
|
|
__FILE__, __LINE__, __FUNCTION__, cpt);
|
|
goto err;
|
|
}
|
|
ack_sn = nr_rlc_pdu_decoder_get_bits(&decoder, entity->sn_field_length); R(decoder);
|
|
e1 = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
|
/* r bits */
|
|
if (entity->sn_field_length == 18) {
|
|
nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
|
} else {
|
|
nr_rlc_pdu_decoder_get_bits(&decoder, 7); R(decoder);
|
|
}
|
|
|
|
/* 38.322 5.3.3.3 says to stop t_poll_retransmit if a ACK or NACK is
|
|
* received for the SN 'poll_sn' - check ACK case (NACK done below)
|
|
*/
|
|
if (sn_compare_tx(entity, entity->poll_sn, ack_sn) < 0)
|
|
entity->t_poll_retransmit_start = 0;
|
|
|
|
while (e1) {
|
|
nack_sn = nr_rlc_pdu_decoder_get_bits(&decoder, entity->sn_field_length); R(decoder);
|
|
e1 = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
|
e2 = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
|
e3 = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
|
/* r bits */
|
|
if (entity->sn_field_length == 18) {
|
|
nr_rlc_pdu_decoder_get_bits(&decoder, 3); R(decoder);
|
|
} else {
|
|
nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
|
}
|
|
if (e2) {
|
|
so_start = nr_rlc_pdu_decoder_get_bits(&decoder, 16); R(decoder);
|
|
so_end = nr_rlc_pdu_decoder_get_bits(&decoder, 16); R(decoder);
|
|
} else {
|
|
so_start = 0;
|
|
so_end = 0xffff;
|
|
}
|
|
if (e3) {
|
|
range = nr_rlc_pdu_decoder_get_bits(&decoder, 8); R(decoder);
|
|
} else {
|
|
range = 1;
|
|
}
|
|
/* special value 0xffff indicates 'all bytes to the end' */
|
|
if (so_end == 0xffff)
|
|
so_end = -1;
|
|
|
|
/* process nacks */
|
|
for (i = 0; i < range; i++) {
|
|
int cur_nack_sn = (nack_sn + i) % entity->sn_modulus;
|
|
int cur_so_start = i == 0 ? so_start : 0;
|
|
int cur_so_end = i == range - 1 ? so_end : -1;
|
|
|
|
/* check that current nack is > previous nack and <= ack
|
|
* if not then skip it and all following nacks, and
|
|
* do not touch t_poll_retransmit
|
|
*/
|
|
if (prev_nack_sn != -1) {
|
|
int cmp = sn_compare_tx(entity, cur_nack_sn, prev_nack_sn);
|
|
if (cmp < 0
|
|
|| (cmp == 0
|
|
&& (prev_so_end == -1
|
|
|| cur_so_start <= prev_so_end))) {
|
|
LOG_E(RLC, "%s:%d:%s: bad NACK (nack sn %d so start/end %d/%d, previous nack sn %d so start/end %d/%d), skip it and all following NACKs\n",
|
|
__FILE__, __LINE__, __FUNCTION__,
|
|
cur_nack_sn, cur_so_start, cur_so_end,
|
|
prev_nack_sn, prev_so_start, prev_so_end);
|
|
goto nacks_done;
|
|
}
|
|
}
|
|
if (sn_compare_tx(entity, cur_nack_sn, ack_sn) > 0) {
|
|
LOG_E(RLC, "%s:%d:%s: bad NACK (nack %d ack %d), skip it and all following NACKs\n",
|
|
__FILE__, __LINE__, __FUNCTION__,
|
|
cur_nack_sn, ack_sn);
|
|
goto nacks_done;
|
|
}
|
|
|
|
process_next_pdu:
|
|
/* process smallest SN either from wait_list or retransmit list */
|
|
if (cur_wait_list == NULL && cur_retransmit_list == NULL)
|
|
goto lists_over;
|
|
if (cur_wait_list == NULL)
|
|
goto process_retransmit_list_head;
|
|
if (cur_retransmit_list == NULL)
|
|
goto process_wait_list_head;
|
|
if (cur_wait_list->sdu->sn < cur_retransmit_list->sdu->sn
|
|
|| (cur_wait_list->sdu->sn == cur_retransmit_list->sdu->sn &&
|
|
cur_wait_list->so < cur_retransmit_list->so))
|
|
goto process_wait_list_head;
|
|
goto process_retransmit_list_head;
|
|
|
|
process_wait_list_head:
|
|
/* if nack overlaps with current segment, put it in retransmit list */
|
|
if (cur_wait_list->sdu->sn == cur_nack_sn
|
|
&& so_overlap(cur_so_start, cur_so_end,
|
|
cur_wait_list->so,
|
|
cur_wait_list->so + cur_wait_list->size - 1)) {
|
|
prev_wait_list->next = cur_wait_list->next;
|
|
if (cur_wait_list == entity->wait_end)
|
|
end_wait_list = prev_wait_list;
|
|
cur_wait_list->next = NULL;
|
|
new_retransmit_list->next = cur_wait_list;
|
|
new_retransmit_list = cur_wait_list;
|
|
/* increase retx count. Don't care about segmentation, so maybe we
|
|
* increase too much.
|
|
*/
|
|
cur_wait_list->sdu->retx_count++;
|
|
/* report max RETX reached for all retx_count >= max_retx_threshold
|
|
* (specs say to report if retx_count == max_retx_threshold).
|
|
* Upper layers should react (radio link failure), so no big deal.
|
|
* We deal with segmentation by requiring
|
|
* retx_count >= max_retx_threshold * number of segments.
|
|
* We may report max RETX reached too late/early. To be refined if
|
|
* this is a problem.
|
|
*/
|
|
if (cur_wait_list->sdu->retx_count
|
|
>= entity->max_retx_threshold * cur_wait_list->sdu->ref_count)
|
|
entity->common.max_retx_reached(entity->common.max_retx_reached_data,
|
|
(nr_rlc_entity_t *)entity);
|
|
/* update buffer status */
|
|
entity->common.bstatus.retx_size += compute_pdu_header_size(entity, cur_wait_list)
|
|
+ cur_wait_list->size;
|
|
/* and go process the next pdu, still for the current nack */
|
|
cur_wait_list = prev_wait_list->next;
|
|
goto process_next_pdu;
|
|
}
|
|
|
|
/* if current segment SN > current NACK, we can't classify it yet */
|
|
if (sn_compare_tx(entity, cur_wait_list->sdu->sn, cur_nack_sn) > 0
|
|
|| (cur_wait_list->sdu->sn == cur_nack_sn
|
|
&& cur_wait_list->so > cur_so_start))
|
|
goto done_nack;
|
|
|
|
/* if current segment is acked, free it, indicate successful delivery
|
|
* if fully acked
|
|
*/
|
|
if (sn_compare_tx(entity, cur_wait_list->sdu->sn, ack_sn) < 0) {
|
|
int upper_layer_id = cur_wait_list->sdu->upper_layer_id;
|
|
int sdu_size = cur_wait_list->sdu->size;
|
|
prev_wait_list->next = cur_wait_list->next;
|
|
if (cur_wait_list == entity->wait_end)
|
|
end_wait_list = prev_wait_list;
|
|
if (nr_rlc_free_sdu_segment(cur_wait_list)) {
|
|
entity->tx_size -= sdu_size;
|
|
entity->common.sdu_successful_delivery(
|
|
entity->common.sdu_successful_delivery_data,
|
|
(nr_rlc_entity_t *)entity, upper_layer_id);
|
|
}
|
|
cur_wait_list = prev_wait_list->next;
|
|
goto process_next_pdu;
|
|
}
|
|
|
|
/* if current segment SN > ack_sn, we're done with this list */
|
|
if (sn_compare_tx(entity, cur_wait_list->sdu->sn, ack_sn) > 0) {
|
|
cur_wait_list = NULL;
|
|
goto process_next_pdu;
|
|
}
|
|
|
|
/* current segment SN == ack_sn, skip this segment and move to the next.
|
|
* Not sure if correct, can we get a nack_sn == ack_sn? 38.322 5.3.4 says
|
|
* "set the ACK_SN to the SN of the next not received RLC SDU which is
|
|
* not indicated as missing in the resulting STATUS PDU." Is it okay to
|
|
* receive some NACK for a given SN and an ACK == this SN? I can see a
|
|
* case where it's possible (only parts of an SN were received and the
|
|
* TB is too small to report the several NACKs for this SN, then we
|
|
* can't set ACK = SN+1 but in this case, shouldn't we skip sending
|
|
* NACKs for this SN and only send ACK = SN? our current implementation
|
|
* does send both NACKs and ACK, see function generate_status()).
|
|
*/
|
|
prev_wait_list = cur_wait_list;
|
|
cur_wait_list = cur_wait_list->next;
|
|
goto process_next_pdu;
|
|
|
|
process_retransmit_list_head:
|
|
/* if nack overlaps with current segment, leave it in retransmit list */
|
|
if (cur_retransmit_list->sdu->sn == cur_nack_sn
|
|
&& so_overlap(cur_so_start, cur_so_end,
|
|
cur_retransmit_list->so,
|
|
cur_retransmit_list->so + cur_retransmit_list->size - 1)) {
|
|
new_retransmit_list->next = cur_retransmit_list;
|
|
cur_retransmit_list = cur_retransmit_list->next;
|
|
new_retransmit_list = new_retransmit_list->next;
|
|
new_retransmit_list->next = NULL;
|
|
/* go process the next pdu, still for the current nack */
|
|
goto process_next_pdu;
|
|
}
|
|
|
|
/* if current segment SN > current NACK, we can't classify it yet */
|
|
if (sn_compare_tx(entity, cur_retransmit_list->sdu->sn, cur_nack_sn) > 0
|
|
|| (cur_retransmit_list->sdu->sn == cur_nack_sn
|
|
&& cur_retransmit_list->so > cur_so_start))
|
|
goto done_nack;
|
|
|
|
/* if current segment is acked, free it, indicate successful delivery
|
|
* if fully acked
|
|
*/
|
|
if (sn_compare_tx(entity, cur_retransmit_list->sdu->sn, ack_sn) < 0) {
|
|
nr_rlc_sdu_segment_t *cur = cur_retransmit_list;
|
|
int upper_layer_id = cur->sdu->upper_layer_id;
|
|
int sdu_size = cur->sdu->size;
|
|
cur_retransmit_list = cur_retransmit_list->next;
|
|
/* update buffer status */
|
|
entity->common.bstatus.retx_size -= compute_pdu_header_size(entity, cur)
|
|
+ cur->size;
|
|
if (nr_rlc_free_sdu_segment(cur)) {
|
|
entity->tx_size -= sdu_size;
|
|
entity->common.sdu_successful_delivery(
|
|
entity->common.sdu_successful_delivery_data,
|
|
(nr_rlc_entity_t *)entity, upper_layer_id);
|
|
}
|
|
goto process_next_pdu;
|
|
}
|
|
|
|
/* current segment SN >= ack_sn
|
|
* if the wait list is empty, then put the remaining retransmit list at
|
|
* the end of the new retransmit list (just a speedup)
|
|
* if not, put only this segment
|
|
*/
|
|
if (cur_wait_list == NULL) {
|
|
new_retransmit_list->next = cur_retransmit_list;
|
|
cur_retransmit_list = NULL;
|
|
goto lists_over;
|
|
}
|
|
new_retransmit_list->next = cur_retransmit_list;
|
|
cur_retransmit_list = cur_retransmit_list->next;
|
|
new_retransmit_list = new_retransmit_list->next;
|
|
new_retransmit_list->next = NULL;
|
|
goto process_next_pdu;
|
|
|
|
done_nack:
|
|
prev_nack_sn = cur_nack_sn;
|
|
prev_so_start = cur_so_start;
|
|
prev_so_end = cur_so_end;
|
|
} /* for (i = 0; i < range; i++) */
|
|
|
|
lists_over:
|
|
/* 38.322 5.3.3.3 says to stop t_poll_retransmit if a ACK or NACK is
|
|
* received for the SN 'poll_sn' - check NACK case (ACK done above)
|
|
*/
|
|
if (sn_compare_tx(entity, nack_sn, entity->poll_sn) <= 0 &&
|
|
sn_compare_tx(entity, entity->poll_sn, (nack_sn + range) % entity->sn_modulus) < 0)
|
|
entity->t_poll_retransmit_start = 0;
|
|
} /* while (e1) */
|
|
|
|
nacks_done:
|
|
/* nacks done, finish with ack */
|
|
/* we may report successful delivery out of order, if it's a problem
|
|
* then we can have a single loop and deal with the smallest sn of
|
|
* the current head of wait list and the current head of retransmit list.
|
|
* (It's simpler to process those two lists the one after the other.)
|
|
*/
|
|
/* deal with wait list */
|
|
while (cur_wait_list != NULL
|
|
&& sn_compare_tx(entity, cur_wait_list->sdu->sn, ack_sn) < 0) {
|
|
/* current segment is acked, free it, indicate successful delivery
|
|
* if fully acked
|
|
*/
|
|
int upper_layer_id = cur_wait_list->sdu->upper_layer_id;
|
|
int sdu_size = cur_wait_list->sdu->size;
|
|
prev_wait_list->next = cur_wait_list->next;
|
|
if (cur_wait_list == entity->wait_end)
|
|
end_wait_list = prev_wait_list;
|
|
if (nr_rlc_free_sdu_segment(cur_wait_list)) {
|
|
entity->tx_size -= sdu_size;
|
|
entity->common.sdu_successful_delivery(
|
|
entity->common.sdu_successful_delivery_data,
|
|
(nr_rlc_entity_t *)entity, upper_layer_id);
|
|
}
|
|
cur_wait_list = prev_wait_list->next;
|
|
}
|
|
/* deal with retransmit list */
|
|
while (cur_retransmit_list != NULL
|
|
&& sn_compare_tx(entity, cur_retransmit_list->sdu->sn, ack_sn) < 0) {
|
|
/* current segment is acked, free it, indicate successful delivery
|
|
* if fully acked
|
|
*/
|
|
nr_rlc_sdu_segment_t *cur = cur_retransmit_list;
|
|
int upper_layer_id = cur->sdu->upper_layer_id;
|
|
int sdu_size = cur->sdu->size;
|
|
cur_retransmit_list = cur_retransmit_list->next;
|
|
/* update buffer status */
|
|
entity->common.bstatus.retx_size -= compute_pdu_header_size(entity, cur)
|
|
+ cur->size;
|
|
if (nr_rlc_free_sdu_segment(cur)) {
|
|
entity->tx_size -= sdu_size;
|
|
entity->common.sdu_successful_delivery(
|
|
entity->common.sdu_successful_delivery_data,
|
|
(nr_rlc_entity_t *)entity, upper_layer_id);
|
|
}
|
|
}
|
|
|
|
new_retransmit_list->next = cur_retransmit_list;
|
|
|
|
entity->wait_list = head_wait_list.next;
|
|
entity->retransmit_list = head_retransmit_list.next;
|
|
|
|
if (end_wait_list != NULL) {
|
|
if (end_wait_list == &head_wait_list)
|
|
entity->wait_end = NULL;
|
|
else
|
|
entity->wait_end = end_wait_list;
|
|
}
|
|
|
|
/* update tx_next_ack */
|
|
/* 38.322 5.2.3.1.1 says "set TX_Next_Ack equal to the SN of the RLC SDU
|
|
* with the smallest SN, whose SN falls within the range
|
|
* TX_Next_Ack <= SN <= TX_Next and for which a positive acknowledgment
|
|
* has not been received yet.
|
|
*/
|
|
/* let's start from highest possible value and go down as needed */
|
|
tx_next_ack = entity->tx_next;
|
|
if (entity->wait_list != NULL
|
|
&& sn_compare_tx(entity, entity->wait_list->sdu->sn, tx_next_ack) < 0)
|
|
tx_next_ack = entity->wait_list->sdu->sn;
|
|
if (entity->retransmit_list != NULL
|
|
&& sn_compare_tx(entity, entity->retransmit_list->sdu->sn, tx_next_ack) < 0)
|
|
tx_next_ack = entity->retransmit_list->sdu->sn;
|
|
if (sn_compare_tx(entity, ack_sn, tx_next_ack) < 0)
|
|
tx_next_ack = ack_sn;
|
|
entity->tx_next_ack = tx_next_ack;
|
|
|
|
return;
|
|
|
|
err:
|
|
LOG_E(RLC, "%s:%d:%s: error decoding PDU, NR RLC entity in inconsistent state\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
|
|
#undef R
|
|
}
|
|
|
|
void nr_rlc_entity_am_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_am_t *entity = (nr_rlc_entity_am_t *)_entity;
|
|
nr_rlc_pdu_decoder_t decoder;
|
|
nr_rlc_pdu_t *pdu;
|
|
int dc;
|
|
int p = 0;
|
|
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);
|
|
dc = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
|
|
|
if (dc == 0)
|
|
return process_control_pdu(entity, buffer, size);
|
|
|
|
/* data PDU */
|
|
p = nr_rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
|
|
si = nr_rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
|
|
|
|
is_first = (si & 0x2) == 0;
|
|
is_last = (si & 0x1) == 0;
|
|
|
|
if (entity->sn_field_length == 18) {
|
|
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_D(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_D(RLC, "%s:%d:%s: warning: discard PDU, RX buffer full\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
goto discard;
|
|
}
|
|
|
|
if (!sn_in_recv_window(entity, sn)) {
|
|
LOG_D(RLC, "%s:%d:%s: warning: discard PDU, sn out of window (sn %d rx_next %d)\n",
|
|
__FILE__, __LINE__, __FUNCTION__,
|
|
sn, entity->rx_next);
|
|
|
|
entity->common.stats.rxpdu_ow_pkts++;
|
|
entity->common.stats.rxpdu_ow_bytes += size;
|
|
|
|
goto discard;
|
|
}
|
|
|
|
/* discard segment if all the bytes of the segment are already there */
|
|
if (segment_already_received(entity, sn, so, data_size)) {
|
|
LOG_D(RLC, "%s:%d:%s: warning: discard PDU, already received\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
|
|
entity->common.stats.rxpdu_dup_pkts++;
|
|
entity->common.stats.rxpdu_dup_bytes += size;
|
|
|
|
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.3.2.3) */
|
|
reception_actions(entity, pdu);
|
|
|
|
if (p) {
|
|
/* 38.322 5.3.4 says status triggering should be delayed
|
|
* until x < rx_highest_status or x >= rx_next + am_window_size.
|
|
* This is not clear (what is x then? we keep the same?). So let's
|
|
* trigger no matter what.
|
|
* todo: delay status triggering properly
|
|
*/
|
|
int v = (entity->rx_next + entity->window_size) % entity->sn_modulus;
|
|
entity->status_triggered = 1;
|
|
if (!(sn_compare_rx(entity, sn, entity->rx_highest_status) < 0 ||
|
|
sn_compare_rx(entity, sn, v) >= 0)) {
|
|
LOG_D(RLC, "%s:%d:%s: warning: STATUS trigger should be delayed, according to specs\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
err:
|
|
LOG_W(RLC, "%s:%d:%s: error decoding PDU, discarding\n", __FILE__, __LINE__, __FUNCTION__);
|
|
goto discard;
|
|
|
|
discard:
|
|
if (p)
|
|
entity->status_triggered = 1;
|
|
|
|
entity->common.stats.rxpdu_dd_pkts++;
|
|
entity->common.stats.rxpdu_dd_bytes += size;
|
|
|
|
#undef R
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* TX functions */
|
|
/*************************************************************************/
|
|
|
|
static int is_window_stalling(nr_rlc_entity_am_t *entity)
|
|
{
|
|
/* we are stalling if tx_next is not:
|
|
* tx_next_ack <= tx_next < tx_next_ack + window_size
|
|
*/
|
|
return !(sn_compare_tx(entity, entity->tx_next_ack, entity->tx_next) <= 0 &&
|
|
sn_compare_tx(entity, entity->tx_next,
|
|
(entity->tx_next_ack + entity->window_size) %
|
|
entity->sn_modulus) < 0);
|
|
}
|
|
|
|
static void include_poll(nr_rlc_entity_am_t *entity, char *buffer)
|
|
{
|
|
/* set the P bit to 1 */
|
|
buffer[0] |= 0x40;
|
|
|
|
entity->pdu_without_poll = 0;
|
|
entity->byte_without_poll = 0;
|
|
|
|
/* set POLL_SN to highest SN submitted to lower layer
|
|
* (this is: entity->tx_next - 1) (todo: be sure of this)
|
|
*/
|
|
entity->poll_sn = (entity->tx_next - 1 + entity->sn_modulus)
|
|
% entity->sn_modulus;
|
|
|
|
/* start/restart t_poll_retransmit */
|
|
entity->t_poll_retransmit_start = entity->t_current;
|
|
}
|
|
|
|
static int check_poll_after_pdu_assembly(nr_rlc_entity_am_t *entity)
|
|
{
|
|
int retransmission_buffer_empty;
|
|
int transmission_buffer_empty;
|
|
|
|
/* is transmission buffer empty? */
|
|
if (entity->tx_list == NULL)
|
|
transmission_buffer_empty = 1;
|
|
else
|
|
transmission_buffer_empty = 0;
|
|
|
|
/* is retransmission buffer empty? */
|
|
if (entity->retransmit_list == NULL)
|
|
retransmission_buffer_empty = 1;
|
|
else
|
|
retransmission_buffer_empty = 0;
|
|
|
|
return (transmission_buffer_empty && retransmission_buffer_empty) ||
|
|
is_window_stalling(entity);
|
|
}
|
|
|
|
static int serialize_sdu(nr_rlc_entity_am_t *entity,
|
|
nr_rlc_sdu_segment_t *sdu, char *buffer, int bufsize,
|
|
int p)
|
|
{
|
|
nr_rlc_pdu_encoder_t encoder;
|
|
|
|
/* generate header */
|
|
nr_rlc_pdu_encoder_init(&encoder, buffer, bufsize);
|
|
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, 1, 1); /* D/C: 1 = data */
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, 0, 1); /* P: reserve, set later */
|
|
|
|
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 */
|
|
|
|
if (entity->sn_field_length == 18)
|
|
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);
|
|
|
|
if (p)
|
|
include_poll(entity, buffer);
|
|
|
|
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
|
|
*/
|
|
static nr_rlc_sdu_segment_t *resegment(nr_rlc_sdu_segment_t *sdu,
|
|
nr_rlc_entity_am_t *entity,
|
|
int pdu_size)
|
|
{
|
|
nr_rlc_sdu_segment_t *next;
|
|
int pdu_header_size;
|
|
int over_size;
|
|
|
|
sdu->sdu->ref_count++;
|
|
|
|
pdu_header_size = compute_pdu_header_size(entity, sdu);
|
|
|
|
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;
|
|
|
|
return next;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* TX functions - status reporting [begin] */
|
|
/*************************************************************************/
|
|
|
|
typedef struct {
|
|
/* data for missing bytes */
|
|
int sn_start; /* set to -1 when no more missing part to report */
|
|
int so_start;
|
|
int sn_end;
|
|
int so_end;
|
|
/* pdu to use for next call to 'next_missing' */
|
|
nr_rlc_pdu_t *next;
|
|
} missing_data_t;
|
|
|
|
/* todo: rewrite this function, too messy */
|
|
static missing_data_t next_missing(nr_rlc_entity_am_t *entity,
|
|
nr_rlc_pdu_t *cur, int check_head)
|
|
{
|
|
missing_data_t ret;
|
|
int cur_max_so;
|
|
int sn;
|
|
int max_so = 0;
|
|
int last_reached = 0;
|
|
|
|
/* special case: missing part before the head of RX list */
|
|
if (check_head) {
|
|
if (cur->sn != entity->rx_next || !cur->is_first) {
|
|
/* don't report if out of reporting window */
|
|
if (sn_compare_rx(entity, entity->rx_highest_status,
|
|
entity->rx_next) <= 0) {
|
|
ret.sn_start = -1;
|
|
return ret;
|
|
}
|
|
/* the missing part is starting from rx_next(0)
|
|
* going to min of:
|
|
* - cur->sn(cur->so-1) [if cur->sn is not first]
|
|
* or (cur->sn-1)(0xffff) [if cur->sn is first]
|
|
* - (entity->rx_highest_status-1)(0xffff)
|
|
*/
|
|
ret.sn_start = entity->rx_next;
|
|
ret.so_start = 0;
|
|
ret.next = cur;
|
|
goto set_end_different_sdu;
|
|
}
|
|
}
|
|
|
|
next_pdu:
|
|
sn = cur->sn;
|
|
cur_max_so = cur->so + cur->size - 1;
|
|
if (cur_max_so > max_so)
|
|
max_so = cur_max_so;
|
|
last_reached = last_reached | cur->is_last;
|
|
|
|
/* no next? */
|
|
if (cur->next == NULL) {
|
|
/* inform the caller that work is over */
|
|
ret.next = NULL;
|
|
|
|
/* already processed => next SDU to rx_highest_status - 1 to be nacked */
|
|
if (cur->data == NULL) {
|
|
ret.sn_start = (cur->sn + 1) % entity->sn_modulus;
|
|
/* don't report if out of reporting window */
|
|
if (sn_compare_rx(entity, entity->rx_highest_status,
|
|
ret.sn_start) <= 0) {
|
|
ret.sn_start = -1;
|
|
return ret;
|
|
}
|
|
ret.so_start = 0;
|
|
ret.sn_end = (entity->rx_highest_status - 1 + entity->sn_modulus) %
|
|
entity->sn_modulus;
|
|
ret.so_end = 0xffff;
|
|
return ret;
|
|
}
|
|
/* not already processed => all bytes after max_so (if any) then all SDU
|
|
* to rx_highest_status-1 to be nacked
|
|
*/
|
|
if (last_reached) {
|
|
ret.sn_start = (cur->sn + 1) % entity->sn_modulus;
|
|
ret.so_start = 0;
|
|
} else {
|
|
ret.sn_start = cur->sn;
|
|
ret.so_start = max_so + 1;
|
|
}
|
|
/* don't report if out of reporting window */
|
|
if (sn_compare_rx(entity, entity->rx_highest_status,
|
|
ret.sn_start) <= 0) {
|
|
ret.sn_start = -1;
|
|
return ret;
|
|
}
|
|
ret.sn_end = (entity->rx_highest_status - 1 + entity->sn_modulus) %
|
|
entity->sn_modulus;
|
|
ret.so_end = 0xffff;
|
|
return ret;
|
|
}
|
|
|
|
cur = cur->next;
|
|
|
|
/* no discontinuity in data => process to next PDU */
|
|
if (cur->sn == sn && max_so >= cur->so - 1)
|
|
goto next_pdu;
|
|
if (cur->sn == (sn + 1) % entity->sn_modulus && last_reached &&
|
|
cur->is_first) {
|
|
last_reached = 0;
|
|
max_so = 0;
|
|
goto next_pdu;
|
|
}
|
|
|
|
/* discontinuity in data */
|
|
|
|
/* remember where to start from for the next call */
|
|
ret.next = cur;
|
|
|
|
/* discontinuity in same SDU */
|
|
if (cur->sn == sn) {
|
|
ret.sn_start = sn;
|
|
/* don't report if out of reporting window */
|
|
if (sn_compare_rx(entity, entity->rx_highest_status,
|
|
ret.sn_start) <= 0) {
|
|
ret.sn_start = -1;
|
|
return ret;
|
|
}
|
|
ret.so_start = max_so + 1;
|
|
ret.sn_end = sn;
|
|
ret.so_end = cur->so - 1;
|
|
return ret;
|
|
}
|
|
|
|
/* discontinuity between different SDUs */
|
|
if (last_reached) {
|
|
ret.sn_start = (sn + 1) % entity->sn_modulus;
|
|
ret.so_start = 0;
|
|
} else {
|
|
ret.sn_start = sn;
|
|
ret.so_start = max_so + 1;
|
|
}
|
|
/* don't report if out of reporting window */
|
|
if (sn_compare_rx(entity, entity->rx_highest_status, ret.sn_start) <= 0) {
|
|
ret.sn_start = -1;
|
|
return ret;
|
|
}
|
|
|
|
set_end_different_sdu:
|
|
/* if cur is the head of a SDU, then use cur-1 */
|
|
if (cur->is_first) {
|
|
ret.sn_end = (cur->sn - 1 + entity->sn_modulus) % entity->sn_modulus;
|
|
ret.so_end = 0xffff;
|
|
} else {
|
|
ret.sn_end = cur->sn;
|
|
ret.so_end = cur->so - 1;
|
|
}
|
|
/* don't go more than rx_highest_status - 1 */
|
|
if (sn_compare_rx(entity, entity->rx_highest_status, ret.sn_end) <= 0) {
|
|
ret.sn_end = (entity->rx_highest_status - 1 + entity->sn_modulus) %
|
|
entity->sn_modulus;
|
|
ret.so_end = 0xffff;
|
|
ret.next = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int nack_size(nr_rlc_entity_am_t *entity, missing_data_t *m)
|
|
{
|
|
int nack_length = 2 + (entity->sn_field_length == 18);
|
|
|
|
if (m->sn_start == m->sn_end) {
|
|
/* only nack_sn, no so_start/end, no nack range */
|
|
if (m->so_start == 0 && m->so_end == 0xffff)
|
|
return nack_length;
|
|
/* nack_sn + so_start/end */
|
|
return nack_length + 4;
|
|
}
|
|
|
|
/* nack_sn + nack range, no so_start/end */
|
|
if (m->so_start == 0 && m->so_end == 0xffff)
|
|
return nack_length + 1;
|
|
|
|
/* nack_sn + so_start/end + nack range */
|
|
return nack_length + 5;
|
|
}
|
|
|
|
/* returns the e1 byte/bit position supposing the encoder points at
|
|
* the beginning of a nack_sn block
|
|
*/
|
|
static void get_e1_position(nr_rlc_entity_am_t *entity,
|
|
nr_rlc_pdu_encoder_t *encoder,
|
|
int *e1_byte, int *e1_bit)
|
|
{
|
|
if (entity->sn_field_length == 18) {
|
|
*e1_byte = encoder->byte + 2;
|
|
*e1_bit = 5;
|
|
} else {
|
|
*e1_byte = encoder->byte + 1;
|
|
*e1_bit = 3;
|
|
}
|
|
}
|
|
|
|
/* returns the last nack SN generated, -1 if nothing generated.
|
|
* If there is no more room in the status buffer,
|
|
* will set m->next = NULL (and may serialize
|
|
* less nacks than required by 'm'), also
|
|
* sets *generation_truncated to 1.
|
|
*/
|
|
static int generate_missing(nr_rlc_entity_am_t *entity,
|
|
nr_rlc_pdu_encoder_t *encoder,
|
|
missing_data_t *m, int *e1_byte, int *e1_bit,
|
|
int *generation_truncated,
|
|
unsigned char **so_end_address)
|
|
{
|
|
int r_bits = entity->sn_field_length == 18 ? 3 : 1;
|
|
int last_nack_generated = -1;
|
|
int sn_start;
|
|
int so_start;
|
|
int sn_end;
|
|
int so_end;
|
|
int sn_count;
|
|
missing_data_t m_nack;
|
|
int e2;
|
|
int e3;
|
|
|
|
/* be careful to limit a range to 255 SNs, that is: cut if needed */
|
|
sn_count = (m->sn_end - m->sn_start + entity->sn_modulus)
|
|
% entity->sn_modulus + 1;
|
|
|
|
sn_start = m->sn_start;
|
|
|
|
while (sn_count) {
|
|
int cur_sn_count = sn_count;
|
|
if (cur_sn_count > 255)
|
|
cur_sn_count = 255;
|
|
|
|
/* for first range, so_start is the one of the initial range
|
|
* for the following ones, it is 0
|
|
*/
|
|
if (sn_start == m->sn_start) {
|
|
/* first range */
|
|
so_start = m->so_start;
|
|
} else {
|
|
/* following ranges */
|
|
so_start = 0;
|
|
}
|
|
|
|
/* for the last range, sn_end/so_end are the ones of the initial range
|
|
* for the previous ones, it is sn_start+254/0xffff
|
|
*/
|
|
if (cur_sn_count == sn_count) {
|
|
/* last range */
|
|
sn_end = m->sn_end;
|
|
so_end = m->so_end;
|
|
} else {
|
|
/* previous ranges */
|
|
sn_end = (sn_start + 254) % entity->sn_modulus;
|
|
so_end = 0xffff;
|
|
}
|
|
|
|
/* check that there is room for a nack */
|
|
m_nack.sn_start = sn_start;
|
|
m_nack.so_start = so_start;
|
|
m_nack.sn_end = sn_end;
|
|
m_nack.so_end = so_end;
|
|
if (encoder->byte + nack_size(entity, &m_nack) > encoder->size) {
|
|
m->next = NULL;
|
|
*generation_truncated = 1;
|
|
break;
|
|
}
|
|
|
|
/* set the previous e1 bit to 1 */
|
|
encoder->buffer[*e1_byte] |= 1 << *e1_bit;
|
|
|
|
get_e1_position(entity, encoder, e1_byte, e1_bit);
|
|
|
|
if (sn_start == sn_end) {
|
|
if (so_start == 0 && so_end == 0xffff) {
|
|
/* only nack_sn, no so_start/end, no nack range */
|
|
e2 = 0;
|
|
e3 = 0;
|
|
} else {
|
|
/* nack_sn + so_start/end, no nack range */
|
|
e2 = 1;
|
|
e3 = 0;
|
|
}
|
|
} else {
|
|
if (so_start == 0 && so_end == 0xffff) {
|
|
/* nack_sn + nack range, no so_start/end */
|
|
e2 = 0;
|
|
e3 = 1;
|
|
} else {
|
|
/* nack_sn + so_start/end + nack range */
|
|
e2 = 1;
|
|
e3 = 1;
|
|
}
|
|
}
|
|
|
|
/* nack_sn */
|
|
nr_rlc_pdu_encoder_put_bits(encoder, sn_start,
|
|
entity->sn_field_length);
|
|
last_nack_generated = sn_start;
|
|
/* e1 = 0 (set later if needed) */
|
|
nr_rlc_pdu_encoder_put_bits(encoder, 0, 1);
|
|
/* e2 */
|
|
nr_rlc_pdu_encoder_put_bits(encoder, e2, 1);
|
|
/* e3 */
|
|
nr_rlc_pdu_encoder_put_bits(encoder, e3, 1);
|
|
/* r */
|
|
nr_rlc_pdu_encoder_put_bits(encoder, 0, r_bits);
|
|
/* so_start/so_end */
|
|
if (e2) {
|
|
nr_rlc_pdu_encoder_put_bits(encoder, so_start, 16);
|
|
*so_end_address = (unsigned char *)encoder->buffer + encoder->byte;
|
|
nr_rlc_pdu_encoder_put_bits(encoder, so_end, 16);
|
|
} else
|
|
*so_end_address = NULL;
|
|
/* nack range */
|
|
if (e3) {
|
|
nr_rlc_pdu_encoder_put_bits(encoder, cur_sn_count, 8);
|
|
last_nack_generated += cur_sn_count - 1;
|
|
}
|
|
|
|
sn_count -= cur_sn_count;
|
|
sn_start = (sn_start + cur_sn_count) % entity->sn_modulus;
|
|
}
|
|
|
|
return last_nack_generated;
|
|
}
|
|
|
|
static int generate_status(nr_rlc_entity_am_t *entity, char *buffer, int size)
|
|
{
|
|
int last_nack;
|
|
int ack_sn;
|
|
missing_data_t m;
|
|
nr_rlc_pdu_t *cur;
|
|
int check_head = 1;
|
|
nr_rlc_pdu_encoder_t encoder;
|
|
int e1_byte;
|
|
int e1_bit;
|
|
int generation_truncated;
|
|
int ln;
|
|
unsigned char *so_end_address = NULL;
|
|
|
|
/* if not enough room, do nothing */
|
|
if (size < 3)
|
|
return 0;
|
|
|
|
/* initial last_nack is rx_next - 1 */
|
|
last_nack = (entity->rx_next - 1 + entity->sn_modulus) % entity->sn_modulus;
|
|
|
|
nr_rlc_pdu_encoder_init(&encoder, buffer, size);
|
|
|
|
/* first 3 bytes, ack_sn and e1 will be set later */
|
|
nr_rlc_pdu_encoder_put_bits(&encoder, 0, 8*3);
|
|
|
|
cur = entity->rx_list;
|
|
|
|
/* store the position of the e1 bit to be set if
|
|
* there is a nack following
|
|
*/
|
|
e1_byte = 2;
|
|
e1_bit = entity->sn_field_length == 18 ? 1 : 7;
|
|
|
|
while (cur != NULL) {
|
|
m = next_missing(entity, cur, check_head);
|
|
check_head = 0;
|
|
|
|
/* stop here if no more nack to report */
|
|
if (m.sn_start == -1)
|
|
break;
|
|
|
|
generation_truncated = 0;
|
|
ln = generate_missing(entity, &encoder, &m, &e1_byte, &e1_bit,
|
|
&generation_truncated, &so_end_address);
|
|
/* remember the last nack put, if any */
|
|
if (ln != -1)
|
|
last_nack = ln;
|
|
/* if generation was truncated and so_end was put, we force its value to
|
|
* 0xffff (end of SDU) because we don't know what missing nack information
|
|
* was supposed to be put, so we nack until the end of the PDU to be sure
|
|
*/
|
|
if (generation_truncated && so_end_address != NULL) {
|
|
so_end_address[0] = 0xff;
|
|
so_end_address[1] = 0xff;
|
|
}
|
|
|
|
cur = m.next;
|
|
}
|
|
|
|
/* put ack_sn, which is last_nack + 1 */
|
|
ack_sn = (last_nack + 1) % entity->sn_modulus;
|
|
if (entity->sn_field_length == 12) {
|
|
buffer[0] = ack_sn >> 8;
|
|
buffer[1] = ack_sn & 255;
|
|
} else {
|
|
buffer[0] = ack_sn >> 14;
|
|
buffer[1] = (ack_sn >> 6) & 255;
|
|
buffer[2] |= (ack_sn & 0x3f) << 2;
|
|
}
|
|
|
|
/* reset the trigger */
|
|
entity->status_triggered = 0;
|
|
|
|
/* start t_status_prohibit */
|
|
entity->t_status_prohibit_start = entity->t_current;
|
|
|
|
entity->common.stats.txpdu_pkts++;
|
|
entity->common.stats.txpdu_bytes += encoder.byte;
|
|
entity->common.stats.txpdu_status_pkts++;
|
|
entity->common.stats.txpdu_status_bytes += encoder.byte;
|
|
|
|
return encoder.byte;
|
|
}
|
|
|
|
static int status_to_report(nr_rlc_entity_am_t *entity)
|
|
{
|
|
return entity->status_triggered &&
|
|
(entity->t_status_prohibit_start == 0 ||
|
|
entity->t_current - entity->t_status_prohibit_start >
|
|
entity->t_status_prohibit);
|
|
}
|
|
|
|
static int missing_size(nr_rlc_entity_am_t *entity, missing_data_t *m,
|
|
int *size, int maxsize)
|
|
{
|
|
int r_bits = entity->sn_field_length == 18 ? 3 : 1;
|
|
int range_count = 0;
|
|
int sn_start;
|
|
int so_start;
|
|
int sn_end;
|
|
int so_end;
|
|
int sn_count;
|
|
missing_data_t m_nack;
|
|
|
|
/* be careful to limit a range to 255 SNs, that is: cut if needed */
|
|
sn_count = (m->sn_end - m->sn_start + entity->sn_modulus)
|
|
% entity->sn_modulus + 1;
|
|
|
|
sn_start = m->sn_start;
|
|
|
|
while (sn_count) {
|
|
int cur_sn_count = sn_count;
|
|
if (cur_sn_count > 255)
|
|
cur_sn_count = 255;
|
|
|
|
/* for first range, so_start is the one of the initial range
|
|
* for the following ones, it is 0
|
|
*/
|
|
if (sn_start == m->sn_start) {
|
|
/* first range */
|
|
so_start = m->so_start;
|
|
} else {
|
|
/* following ranges */
|
|
so_start = 0;
|
|
}
|
|
|
|
/* for the last range, sn_end/so_end are the ones of the initial range
|
|
* for the previous ones, it is sn_start+254/0xffff
|
|
*/
|
|
if (cur_sn_count == sn_count) {
|
|
/* last range */
|
|
sn_end = m->sn_end;
|
|
so_end = m->so_end;
|
|
} else {
|
|
/* previous ranges */
|
|
sn_end = (sn_start + 254) % entity->sn_modulus;
|
|
so_end = 0xffff;
|
|
}
|
|
|
|
/* check that there is room for a nack */
|
|
m_nack.sn_start = sn_start;
|
|
m_nack.so_start = so_start;
|
|
m_nack.sn_end = sn_end;
|
|
m_nack.so_end = so_end;
|
|
if (*size + nack_size(entity, &m_nack) > maxsize) {
|
|
m->next = NULL;
|
|
break;
|
|
}
|
|
|
|
if (sn_start == sn_end) {
|
|
if (so_start == 0 && so_end == 0xffff) {
|
|
/* only nack_sn, no so_start/end, no nack range */
|
|
*size += (entity->sn_field_length + 3 + r_bits) / 8;
|
|
} else {
|
|
/* nack_sn + so_start/end, no nack range */
|
|
*size += (entity->sn_field_length + 3 + r_bits + 16*2) / 8;
|
|
}
|
|
} else {
|
|
if (so_start == 0 && so_end == 0xffff) {
|
|
/* nack_sn + nack range, no so_start/end */
|
|
*size += (entity->sn_field_length + 3 + r_bits + 8) / 8;
|
|
} else {
|
|
/* nack_sn + so_start/end + nack range */
|
|
*size += (entity->sn_field_length + 3 + r_bits + 16*2 + 8) / 8;
|
|
}
|
|
}
|
|
|
|
sn_count -= cur_sn_count;
|
|
sn_start = (sn_start + cur_sn_count) % entity->sn_modulus;
|
|
range_count++;
|
|
}
|
|
|
|
return range_count;
|
|
}
|
|
|
|
static int status_size(nr_rlc_entity_am_t *entity, int maxsize)
|
|
{
|
|
missing_data_t m;
|
|
nr_rlc_pdu_t *cur;
|
|
int nack_count = 0;
|
|
int size;
|
|
|
|
/* if not enough room, do nothing */
|
|
if (maxsize < 3)
|
|
return 0;
|
|
|
|
/* minimum 3 bytes */
|
|
size = 3;
|
|
|
|
cur = entity->rx_list;
|
|
|
|
while (cur != NULL) {
|
|
m = next_missing(entity, cur, nack_count == 0);
|
|
|
|
/* stop here if no more nack to report */
|
|
if (m.sn_start == -1)
|
|
break;
|
|
|
|
nack_count += missing_size(entity, &m, &size, maxsize);
|
|
|
|
cur = m.next;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* TX functions - status reporting [end] */
|
|
/*************************************************************************/
|
|
|
|
static int generate_retx_pdu(nr_rlc_entity_am_t *entity, char *buffer,
|
|
int size)
|
|
{
|
|
nr_rlc_sdu_segment_t *sdu;
|
|
int pdu_header_size;
|
|
int pdu_size;
|
|
int p;
|
|
|
|
sdu = entity->retransmit_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->retransmit_list = entity->retransmit_list->next;
|
|
|
|
sdu->next = NULL;
|
|
|
|
pdu_size = pdu_header_size + sdu->size;
|
|
|
|
/* update buffer status */
|
|
entity->common.bstatus.retx_size -= pdu_size;
|
|
|
|
/* segment if necessary */
|
|
if (pdu_size > size) {
|
|
nr_rlc_sdu_segment_t *next_sdu;
|
|
next_sdu = resegment(sdu, entity, size);
|
|
/* put the second SDU back at the head of the retransmit list */
|
|
next_sdu->next = entity->retransmit_list;
|
|
entity->retransmit_list = next_sdu;
|
|
/* update buffer status */
|
|
entity->common.bstatus.retx_size += compute_pdu_header_size(entity, next_sdu)
|
|
+ next_sdu->size;
|
|
|
|
entity->common.stats.txpdu_segmented++;
|
|
}
|
|
|
|
/* put SDU/SDU segment in the wait list */
|
|
/* speedup: check end of wait list, maybe the new sdu comes after */
|
|
if (entity->wait_end == NULL
|
|
|| sn_compare_tx(entity, sdu->sdu->sn, entity->wait_end->sdu->sn) > 0
|
|
|| (sn_compare_tx(entity, sdu->sdu->sn, entity->wait_end->sdu->sn) == 0
|
|
&& sdu->so > entity->wait_end->so))
|
|
nr_rlc_sdu_segment_list_append(&entity->wait_list, &entity->wait_end, sdu);
|
|
else {
|
|
entity->wait_list = nr_rlc_sdu_segment_list_add(sn_compare_tx, entity,
|
|
entity->wait_list, sdu);
|
|
if (entity->wait_list->next == NULL)
|
|
entity->wait_end = entity->wait_list;
|
|
}
|
|
|
|
p = check_poll_after_pdu_assembly(entity);
|
|
|
|
if (entity->force_poll) {
|
|
p = 1;
|
|
entity->force_poll = 0;
|
|
}
|
|
|
|
int ret_size = serialize_sdu(entity, sdu, buffer, size, p);
|
|
entity->common.stats.txpdu_pkts++;
|
|
entity->common.stats.txpdu_bytes += ret_size;
|
|
entity->common.stats.txpdu_retx_pkts++;
|
|
entity->common.stats.txpdu_retx_bytes += ret_size;
|
|
|
|
return ret_size;
|
|
// return serialize_sdu(entity, sdu, buffer, size, p);
|
|
}
|
|
|
|
static int generate_tx_pdu(nr_rlc_entity_am_t *entity, char *buffer, int size)
|
|
{
|
|
nr_rlc_sdu_segment_t *sdu;
|
|
int pdu_header_size;
|
|
int pdu_size;
|
|
int p;
|
|
|
|
/* sn out of window (that is: we have window stalling)? do nothing */
|
|
if (is_window_stalling(entity))
|
|
return 0;
|
|
|
|
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;
|
|
|
|
sdu->next = NULL;
|
|
|
|
pdu_size = pdu_header_size + sdu->size;
|
|
|
|
/* update buffer status */
|
|
entity->common.bstatus.tx_size -= pdu_size;
|
|
|
|
/* assign SN to SDU */
|
|
sdu->sdu->sn = entity->tx_next;
|
|
|
|
/* segment if necessary */
|
|
if (pdu_size > size) {
|
|
nr_rlc_sdu_segment_t *next_sdu;
|
|
next_sdu = resegment(sdu, entity, size);
|
|
/* 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 segment is the last */
|
|
if (sdu->is_last)
|
|
entity->tx_next = (entity->tx_next + 1) % entity->sn_modulus;
|
|
|
|
/* put SDU/SDU segment in the wait list */
|
|
/* speedup: check end of wait list, probably the new sdu comes after */
|
|
if (entity->wait_end == NULL
|
|
|| sn_compare_tx(entity, sdu->sdu->sn, entity->wait_end->sdu->sn) > 0
|
|
|| (sn_compare_tx(entity, sdu->sdu->sn, entity->wait_end->sdu->sn) == 0
|
|
&& sdu->so > entity->wait_end->so))
|
|
nr_rlc_sdu_segment_list_append(&entity->wait_list, &entity->wait_end, sdu);
|
|
else {
|
|
entity->wait_list = nr_rlc_sdu_segment_list_add(sn_compare_tx, entity,
|
|
entity->wait_list, sdu);
|
|
if (entity->wait_list->next == NULL)
|
|
entity->wait_end = entity->wait_list;
|
|
}
|
|
|
|
/* polling actions for a new PDU */
|
|
entity->pdu_without_poll++;
|
|
entity->byte_without_poll += sdu->size;
|
|
if ((entity->poll_pdu != -1 &&
|
|
entity->pdu_without_poll >= entity->poll_pdu) ||
|
|
(entity->poll_byte != -1 &&
|
|
entity->byte_without_poll >= entity->poll_byte))
|
|
p = 1;
|
|
else
|
|
p = check_poll_after_pdu_assembly(entity);
|
|
|
|
if (entity->force_poll) {
|
|
p = 1;
|
|
entity->force_poll = 0;
|
|
}
|
|
int ret_size = serialize_sdu(entity, sdu, buffer, size, p);
|
|
|
|
entity->common.stats.txpdu_pkts++;
|
|
entity->common.stats.txpdu_bytes += ret_size;
|
|
|
|
if (sdu->sdu->time_of_arrival) {
|
|
uint64_t time_now = time_average_now();
|
|
uint64_t waited_time = time_now - sdu->sdu->time_of_arrival;
|
|
/* set time_of_arrival to 0 so as to update stats only once */
|
|
sdu->sdu->time_of_arrival = 0;
|
|
time_average_add(entity->common.txsdu_avg_time_to_tx, time_now, waited_time);
|
|
}
|
|
|
|
return ret_size;
|
|
// return serialize_sdu(entity, sdu, buffer, size, p);
|
|
}
|
|
|
|
nr_rlc_entity_buffer_status_t nr_rlc_entity_am_buffer_status(
|
|
nr_rlc_entity_t *_entity, int maxsize)
|
|
{
|
|
nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
|
|
nr_rlc_entity_buffer_status_t ret;
|
|
|
|
if (status_to_report(entity))
|
|
ret.status_size = status_size(entity, maxsize);
|
|
else
|
|
ret.status_size = 0;
|
|
|
|
ret.tx_size = entity->common.bstatus.tx_size;
|
|
ret.retx_size = entity->common.bstatus.retx_size;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int nr_rlc_entity_am_generate_pdu(nr_rlc_entity_t *_entity,
|
|
char *buffer, int size)
|
|
{
|
|
nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
|
|
int ret;
|
|
|
|
if (status_to_report(entity)) {
|
|
ret = generate_status(entity, buffer, size);
|
|
if (ret != 0)
|
|
return ret;
|
|
}
|
|
|
|
if (entity->retransmit_list != NULL) {
|
|
ret = generate_retx_pdu(entity, buffer, size);
|
|
if (ret != 0)
|
|
return ret;
|
|
}
|
|
|
|
return generate_tx_pdu(entity, buffer, size);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* SDU RX functions */
|
|
/*************************************************************************/
|
|
|
|
void nr_rlc_entity_am_recv_sdu(nr_rlc_entity_t *_entity,
|
|
char *buffer, int size,
|
|
int sdu_id)
|
|
{
|
|
nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_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, "%s:%d:%s: warning: %d SDU rejected, SDU buffer full\n",
|
|
__FILE__, __LINE__, __FUNCTION__,
|
|
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++;
|
|
return;
|
|
}
|
|
|
|
entity->tx_size += size;
|
|
|
|
sdu = nr_rlc_new_sdu(buffer, size, sdu_id);
|
|
|
|
LOG_D(RLC, "Created new RLC SDU and append it to the RLC list \n");
|
|
|
|
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();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* time/timers */
|
|
/*************************************************************************/
|
|
|
|
static void check_t_poll_retransmit(nr_rlc_entity_am_t *entity)
|
|
{
|
|
nr_rlc_sdu_segment_t *cur;
|
|
|
|
/* 38.322 5.3.3.4 */
|
|
/* did t_poll_retransmit expire? */
|
|
if (entity->t_poll_retransmit_start == 0 ||
|
|
entity->t_current <= entity->t_poll_retransmit_start +
|
|
entity->t_poll_retransmit)
|
|
return;
|
|
|
|
/* stop timer */
|
|
entity->t_poll_retransmit_start = 0;
|
|
|
|
/* 38.322 5.3.3.4 says:
|
|
*
|
|
* - include a poll in a RLC data PDU as described in section 5.3.3.2
|
|
*
|
|
* That does not seem to be conditional. So we forcefully will send
|
|
* a poll as soon as we generate a PDU.
|
|
* Hopefully this interpretation is correct. In the worst case we generate
|
|
* more polling than necessary, but it's not a big deal. When
|
|
* 't_poll_retransmit' expires it means we didn't receive a status report,
|
|
* meaning a bad radio link, so things are quite bad at this point and
|
|
* asking again for a poll won't hurt much more.
|
|
*/
|
|
entity->force_poll = 1;
|
|
|
|
LOG_D(RLC, "%s:%d:%s: warning: t_poll_retransmit expired\n",
|
|
__FILE__, __LINE__, __FUNCTION__);
|
|
|
|
/* do we meet conditions of 38.322 5.3.3.4? */
|
|
if (!check_poll_after_pdu_assembly(entity))
|
|
return;
|
|
|
|
/* retransmit the SDU at the head of wait list, this is the case
|
|
* "consider any RLC SDU which has not been positively acknowledged for
|
|
* retransmission" of 36.322 5.3.3.4.
|
|
* We don't search for the highest SN, it's simpler to just take the head
|
|
* of wait list. This can be changed if needed.
|
|
*/
|
|
cur = entity->wait_list;
|
|
|
|
/* todo: do we need to for check cur == NULL?
|
|
* It seems that no, the wait list should not be empty here, but not sure.
|
|
*/
|
|
|
|
/* increase retx count. Don't care about segmentation, so maybe we
|
|
* increase too much.
|
|
*/
|
|
cur->sdu->retx_count++;
|
|
|
|
int sdu_retx_count = cur->sdu->retx_count;
|
|
int retransmit_sn = cur->sdu->sn;
|
|
|
|
/* 38.322 says "SDU", not "SDU segment", so let's retransmit all
|
|
* SDU segments with the retransmit SN in the wait list.
|
|
*/
|
|
do {
|
|
entity->wait_list = cur->next;
|
|
if (entity->wait_list == NULL)
|
|
entity->wait_end = NULL;
|
|
|
|
/* update buffer status */
|
|
entity->common.bstatus.retx_size += compute_pdu_header_size(entity, cur)
|
|
+ cur->size;
|
|
|
|
LOG_D(RLC, "put sn %d so %d size %d in retx list (retx_count %d)\n",
|
|
cur->sdu->sn, cur->so, cur->size, cur->sdu->retx_count);
|
|
|
|
/* put in retransmit list */
|
|
entity->retransmit_list = nr_rlc_sdu_segment_list_add(sn_compare_tx, entity,
|
|
entity->retransmit_list, cur);
|
|
|
|
cur = entity->wait_list;
|
|
} while (cur != NULL && cur->sdu->sn == retransmit_sn);
|
|
|
|
/* report max RETX reached for all retx_count >= max_retx_threshold
|
|
* (specs say to report if retx_count == max_retx_threshold).
|
|
* Upper layers should react (radio link failure), so no big deal.
|
|
* Because of segmentation, we may report too early/too late, not
|
|
* very clear. To refine if needed.
|
|
*/
|
|
if (sdu_retx_count >= entity->max_retx_threshold)
|
|
entity->common.max_retx_reached(entity->common.max_retx_reached_data,
|
|
(nr_rlc_entity_t *)entity);
|
|
}
|
|
|
|
static void check_t_reassembly(nr_rlc_entity_am_t *entity)
|
|
{
|
|
int sn;
|
|
|
|
/* 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_Highest_Status */
|
|
sn = entity->rx_next_status_trigger;
|
|
while (sdu_delivered(entity, sn))
|
|
sn = (sn + 1) % entity->sn_modulus;
|
|
entity->rx_highest_status = sn;
|
|
|
|
/* trigger status report */
|
|
entity->status_triggered = 1;
|
|
|
|
if (sn_compare_rx(entity, entity->rx_next_highest,
|
|
(entity->rx_highest_status+1) % entity->sn_modulus) > 0 ||
|
|
(entity->rx_next_highest ==
|
|
(entity->rx_highest_status+1) % entity->sn_modulus &&
|
|
sdu_has_missing_bytes(entity, entity->rx_highest_status))) {
|
|
entity->t_reassembly_start = entity->t_current;
|
|
entity->rx_next_status_trigger = entity->rx_next_highest;
|
|
}
|
|
}
|
|
|
|
void nr_rlc_entity_am_set_time(nr_rlc_entity_t *_entity, uint64_t now)
|
|
{
|
|
nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
|
|
|
|
entity->t_current = now;
|
|
|
|
check_t_poll_retransmit(entity);
|
|
|
|
check_t_reassembly(entity);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/* discard/re-establishment/delete */
|
|
/*************************************************************************/
|
|
|
|
void nr_rlc_entity_am_discard_sdu(nr_rlc_entity_t *_entity, int sdu_id)
|
|
{
|
|
/* implements 38.322 5.4 */
|
|
nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_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;
|
|
|
|
entity->tx_size -= cur->sdu->size;
|
|
nr_rlc_free_sdu_segment(cur);
|
|
}
|
|
|
|
static void clear_entity(nr_rlc_entity_am_t *entity)
|
|
{
|
|
nr_rlc_pdu_t *cur_rx;
|
|
|
|
entity->rx_next = 0;
|
|
entity->rx_next_status_trigger = 0;
|
|
entity->rx_highest_status = 0;
|
|
entity->rx_next_highest = 0;
|
|
|
|
entity->status_triggered = 0;
|
|
|
|
entity->tx_next = 0;
|
|
entity->tx_next_ack = 0;
|
|
entity->poll_sn = 0;
|
|
entity->pdu_without_poll = 0;
|
|
entity->byte_without_poll = 0;
|
|
entity->force_poll = 0;
|
|
|
|
entity->t_current = 0;
|
|
|
|
entity->t_log_buffer_full = 0;
|
|
entity->sdu_rejected = 0;
|
|
|
|
entity->t_poll_retransmit_start = 0;
|
|
entity->t_reassembly_start = 0;
|
|
entity->t_status_prohibit_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);
|
|
nr_rlc_free_sdu_segment_list(entity->wait_list);
|
|
nr_rlc_free_sdu_segment_list(entity->retransmit_list);
|
|
|
|
entity->tx_list = NULL;
|
|
entity->tx_end = NULL;
|
|
entity->tx_size = 0;
|
|
|
|
entity->wait_list = NULL;
|
|
entity->wait_end = NULL;
|
|
|
|
entity->retransmit_list = NULL;
|
|
|
|
entity->common.bstatus.tx_size = 0;
|
|
entity->common.bstatus.retx_size = 0;
|
|
}
|
|
|
|
void nr_rlc_entity_am_reestablishment(nr_rlc_entity_t *_entity)
|
|
{
|
|
nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
|
|
clear_entity(entity);
|
|
}
|
|
|
|
void nr_rlc_entity_am_delete(nr_rlc_entity_t *_entity)
|
|
{
|
|
nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
|
|
clear_entity(entity);
|
|
time_average_free(entity->common.txsdu_avg_time_to_tx);
|
|
free(entity);
|
|
}
|
|
|
|
int nr_rlc_entity_am_available_tx_space(nr_rlc_entity_t *_entity)
|
|
{
|
|
nr_rlc_entity_am_t *entity = (nr_rlc_entity_am_t *)_entity;
|
|
return entity->tx_maxsize - entity->tx_size;
|
|
}
|