Files
openairinterface5g/openair2/LAYER2/rlc_v2/rlc_entity_am.c
T
Cedric Roux eebf2cb938 rlc v2: improve buffer status reporting
The old method was wrong.
The new method is better, but not accurate, especially when the
TX buffer contains SDU of size > 2047 bytes.

Plus it's probably too slow.

To be refined at some point.
2020-04-17 10:40:05 +02:00

1701 lines
46 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 "rlc_entity_am.h"
#include "rlc_pdu.h"
#include <stdlib.h>
#include <string.h>
#include "LOG/log.h"
/*************************************************************************/
/* PDU RX functions */
/*************************************************************************/
static int modulus_rx(rlc_entity_am_t *entity, int a)
{
/* as per 36.322 7.1, modulus base is vr(r) and modulus is 1024 for rx */
int r = a - entity->vr_r;
if (r < 0) r += 1024;
return r;
}
/* used in both RX and TX processing */
static int modulus_tx(rlc_entity_am_t *entity, int a)
{
/* as per 36.322 7.1, modulus base is vt(a) and modulus is 1024 for tx */
int r = a - entity->vt_a;
if (r < 0) r += 1024;
return r;
}
static int sn_in_recv_window(void *_entity, int sn)
{
rlc_entity_am_t *entity = _entity;
int mod_sn = modulus_rx(entity, sn);
/* we simplify vr(r)<=sn<vr(mr). base is vr(r) and vr(mr) = vr(r) + 512 */
return mod_sn < 512;
}
static int sn_compare_rx(void *_entity, int a, int b)
{
rlc_entity_am_t *entity = _entity;
return modulus_rx(entity, a) - modulus_rx(entity, b);
}
/* used in both RX and TX processing */
static int sn_compare_tx(void *_entity, int a, int b)
{
rlc_entity_am_t *entity = _entity;
return modulus_tx(entity, a) - modulus_tx(entity, b);
}
static int segment_already_received(rlc_entity_am_t *entity,
int sn, int so, int data_size)
{
/* TODO: optimize */
rlc_rx_pdu_segment_t *l = entity->rx_list;
while (l != NULL) {
if (l->sn == sn && l->so <= so &&
l->so + l->size - l->data_offset >= so + data_size)
return 1;
l = l->next;
}
return 0;
}
static int rlc_am_segment_full(rlc_entity_am_t *entity, int sn)
{
rlc_rx_pdu_segment_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;
}
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 - l->data_offset - 1;
if (new_last_byte > last_byte)
last_byte = new_last_byte;
l = l->next;
}
return 0;
}
/* return 1 if the new segment has some data to consume, 0 if not */
static int rlc_am_reassemble_next_segment(rlc_am_reassemble_t *r)
{
int rf;
int sn;
r->sdu_offset = r->start->data_offset;
rlc_pdu_decoder_init(&r->dec, r->start->data, r->start->size);
rlc_pdu_decoder_get_bits(&r->dec, 1); /* dc */
rf = rlc_pdu_decoder_get_bits(&r->dec, 1);
rlc_pdu_decoder_get_bits(&r->dec, 1); /* p */
r->fi = rlc_pdu_decoder_get_bits(&r->dec, 2);
r->e = rlc_pdu_decoder_get_bits(&r->dec, 1);
sn = rlc_pdu_decoder_get_bits(&r->dec, 10);
if (rf) {
rlc_pdu_decoder_get_bits(&r->dec, 1); /* lsf */
r->so = rlc_pdu_decoder_get_bits(&r->dec, 15);
} else {
r->so = 0;
}
if (r->e) {
r->e = rlc_pdu_decoder_get_bits(&r->dec, 1);
r->sdu_len = rlc_pdu_decoder_get_bits(&r->dec, 11);
} else
r->sdu_len = r->start->size - r->sdu_offset;
/* new sn: read starts from PDU byte 0 */
if (sn != r->sn) {
r->pdu_byte = 0;
r->sn = sn;
}
r->data_pos = r->start->data_offset + r->pdu_byte - r->so;
/* TODO: remove this check, it is useless, data has been validated before */
if (r->pdu_byte < r->so) {
LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
exit(1);
}
/* if pdu_byte is not in [so .. so+len-1] then all bytes from this segment
* have already been consumed
*/
if (r->pdu_byte >= r->so + r->start->size - r->start->data_offset)
return 0;
/* go to correct SDU */
while (r->pdu_byte >= r->so + (r->sdu_offset - r->start->data_offset) + r->sdu_len) {
r->sdu_offset += r->sdu_len;
if (r->e) {
r->e = rlc_pdu_decoder_get_bits(&r->dec, 1);
r->sdu_len = rlc_pdu_decoder_get_bits(&r->dec, 11);
} else {
r->sdu_len = r->start->size - r->sdu_offset;
}
}
return 1;
}
static void rlc_am_reassemble(rlc_entity_am_t *entity)
{
rlc_am_reassemble_t *r = &entity->reassemble;
while (r->start != NULL) {
if (r->sdu_pos >= SDU_MAX) {
/* TODO: proper error handling (discard PDUs with current sn from
* reassembly queue? something else?)
*/
LOG_E(RLC, "%s:%d:%s: bad RLC PDU\n", __FILE__, __LINE__, __FUNCTION__);
exit(1);
}
r->sdu[r->sdu_pos] = r->start->data[r->data_pos];
r->sdu_pos++;
r->data_pos++;
r->pdu_byte++;
if (r->data_pos == r->sdu_offset + r->sdu_len) {
/* all bytes of SDU are consumed, check if SDU is fully there.
* It is if the data pointer is not at the end of the PDU segment
* or if 'fi' & 1 == 0
*/
if (r->data_pos != r->start->size ||
(r->fi & 1) == 0) {
/* SDU is full - deliver to higher layer */
entity->common.deliver_sdu(entity->common.deliver_sdu_data,
(rlc_entity_t *)entity,
r->sdu, r->sdu_pos);
r->sdu_pos = 0;
}
if (r->data_pos != r->start->size) {
/* not at the end, process next SDU */
r->sdu_offset += r->sdu_len;
if (r->e) {
r->e = rlc_pdu_decoder_get_bits(&r->dec, 1);
r->sdu_len = rlc_pdu_decoder_get_bits(&r->dec, 11);
} else
r->sdu_len = r->start->size - r->sdu_offset;
} else {
/* all bytes are consumend, go to next segment not already fully
* processed, if any
*/
do {
rlc_rx_pdu_segment_t *e = r->start;
entity->rx_size -= e->size;
r->start = r->start->next;
rlc_rx_free_pdu_segment(e);
} while (r->start != NULL && !rlc_am_reassemble_next_segment(r));
}
}
}
}
static void rlc_am_reception_actions(rlc_entity_am_t *entity,
rlc_rx_pdu_segment_t *pdu_segment)
{
int x = pdu_segment->sn;
int vr_ms;
int vr_r;
if (modulus_rx(entity, x) >= modulus_rx(entity, entity->vr_h))
entity->vr_h = (x + 1) % 1024;
vr_ms = entity->vr_ms;
while (rlc_am_segment_full(entity, vr_ms))
vr_ms = (vr_ms + 1) % 1024;
entity->vr_ms = vr_ms;
if (x == entity->vr_r) {
vr_r = entity->vr_r;
while (rlc_am_segment_full(entity, vr_r)) {
/* move segments with sn=vr(r) from rx list to end of reassembly list */
while (entity->rx_list != NULL && entity->rx_list->sn == vr_r) {
rlc_rx_pdu_segment_t *e = entity->rx_list;
entity->rx_list = e->next;
e->next = NULL;
if (entity->reassemble.start == NULL) {
entity->reassemble.start = e;
/* the list was empty, we need to init decoder */
entity->reassemble.sn = -1;
if (!rlc_am_reassemble_next_segment(&entity->reassemble)) {
/* TODO: proper error recovery (or remove the test, it should not happen) */
LOG_E(RLC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
exit(1);
}
} else {
entity->reassemble.end->next = e;
}
entity->reassemble.end = e;
}
/* update vr_r */
vr_r = (vr_r + 1) % 1024;
}
entity->vr_r = vr_r;
}
rlc_am_reassemble(entity);
if (entity->t_reordering_start) {
int vr_x = entity->vr_x;
if (vr_x < entity->vr_r) vr_x += 1024;
if (vr_x == entity->vr_r || vr_x > entity->vr_r + 512)
entity->t_reordering_start = 0;
}
if (entity->t_reordering_start == 0) {
if (sn_compare_rx(entity, entity->vr_h, entity->vr_r) > 0) {
entity->t_reordering_start = entity->t_current;
entity->vr_x = entity->vr_h;
}
}
}
static void process_received_ack(rlc_entity_am_t *entity, int sn)
{
rlc_tx_pdu_segment_t head;
rlc_tx_pdu_segment_t *cur;
rlc_tx_pdu_segment_t *prev;
/* put all PDUs from wait and retransmit lists with SN < 'sn' to ack_list */
/* process wait list */
head.next = entity->wait_list;
prev = &head;
cur = entity->wait_list;
while (cur != NULL) {
if (sn_compare_tx(entity, cur->sn, sn) < 0) {
/* remove from wait list */
prev->next = cur->next;
/* put the PDU in the ack list */
entity->ack_list = rlc_tx_pdu_list_add(sn_compare_tx, entity,
entity->ack_list, cur);
cur = prev->next;
} else {
prev = cur;
cur = cur->next;
}
}
entity->wait_list = head.next;
/* process retransmit list */
head.next = entity->retransmit_list;
prev = &head;
cur = entity->retransmit_list;
while (cur != NULL) {
if (sn_compare_tx(entity, cur->sn, sn) < 0) {
/* dec. retx_count in case we put this segment back in retransmit list
* in 'process_received_nack'
*/
cur->retx_count--;
/* remove from retransmit list */
prev->next = cur->next;
/* put the PDU in the ack list */
entity->ack_list = rlc_tx_pdu_list_add(sn_compare_tx, entity,
entity->ack_list, cur);
cur = prev->next;
} else {
prev = cur;
cur = cur->next;
}
}
entity->retransmit_list = head.next;
}
static void consider_retransmission(rlc_entity_am_t *entity,
rlc_tx_pdu_segment_t *cur)
{
cur->retx_count++;
/* let's 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 actually.
*/
if (cur->retx_count >= entity->max_retx_threshold) {
entity->common.max_retx_reached(entity->common.max_retx_reached_data,
(rlc_entity_t *)entity);
}
/* let's put in retransmit list even if we are over max_retx_threshold.
* upper layers should deal with this condition, internally it's better
* for the RLC code to keep going with this segment (we only remove
* a segment that was ACKed)
*/
entity->retransmit_list = rlc_tx_pdu_list_add(sn_compare_tx, entity,
entity->retransmit_list, cur);
}
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_received_nack(rlc_entity_am_t *entity, int sn,
int so_start, int so_end)
{
/* put all PDU segments with SN == 'sn' and with an overlapping so start/end
* to the retransmit list
* source lists are ack list and wait list.
* Not sure if we should consider wait list, isn't the other end supposed
* to only NACK SNs lower than the ACK SN sent in the status PDU, in which
* case all potential PDU segments should all be in ack list when calling
* the current function? in doubt let's accept anything and thus process
* also wait list.
*/
rlc_tx_pdu_segment_t head;
rlc_tx_pdu_segment_t *cur;
rlc_tx_pdu_segment_t *prev;
/* check that VT(A) <= sn < VT(S) */
if (!(sn_compare_tx(entity, entity->vt_a, sn) <= 0 &&
sn_compare_tx(entity, sn, entity->vt_s) < 0))
return;
/* process wait list */
head.next = entity->wait_list;
prev = &head;
cur = entity->wait_list;
while (cur != NULL) {
if (cur->sn == sn &&
so_overlap(so_start, so_end, cur->so, cur->so + cur->data_size - 1)) {
/* remove from wait list */
prev->next = cur->next;
/* consider the PDU segment for retransmission */
consider_retransmission(entity, cur);
cur = prev->next;
} else {
prev = cur;
cur = cur->next;
}
}
entity->wait_list = head.next;
/* process ack list */
head.next = entity->ack_list;
prev = &head;
cur = entity->ack_list;
while (cur != NULL) {
if (cur->sn == sn &&
so_overlap(so_start, so_end, cur->so, cur->so + cur->data_size - 1)) {
/* remove from ack list */
prev->next = cur->next;
/* consider the PDU segment for retransmission */
consider_retransmission(entity, cur);
cur = prev->next;
} else {
prev = cur;
cur = cur->next;
}
}
entity->ack_list = head.next;
}
int tx_pdu_in_ack_list_full(rlc_tx_pdu_segment_t *pdu)
{
int sn = pdu->sn;
int last_byte = -1;
int new_last_byte;
int is_last_seen = 0;
while (pdu != NULL && pdu->sn == sn) {
if (pdu->so > last_byte + 1) return 0;
if (pdu->is_last)
is_last_seen = 1;
new_last_byte = pdu->so + pdu->data_size - 1;
if (new_last_byte > last_byte)
last_byte = new_last_byte;
pdu = pdu->next;
}
return is_last_seen == 1;
}
int tx_pdu_in_ack_list_size(rlc_tx_pdu_segment_t *pdu)
{
int sn = pdu->sn;
int ret = 0;
while (pdu != NULL && pdu->sn == sn) {
ret += pdu->data_size;
pdu = pdu->next;
}
return ret;
}
void ack_sdu_bytes(rlc_sdu_t *start, int start_byte, int sdu_size)
{
rlc_sdu_t *cur = start;
int remaining_size = sdu_size;
while (remaining_size) {
int cursize = cur->size - start_byte;
if (cursize > remaining_size)
cursize = remaining_size;
cur->acked_bytes += cursize;
remaining_size -= cursize;
/* start_byte is only meaningful for the 1st SDU, then it is 0 */
start_byte = 0;
cur = cur->next;
}
}
rlc_tx_pdu_segment_t *tx_list_remove_sn(rlc_tx_pdu_segment_t *list, int sn)
{
rlc_tx_pdu_segment_t head;
rlc_tx_pdu_segment_t *cur;
rlc_tx_pdu_segment_t *prev;
head.next = list;
cur = list;
prev = &head;
while (cur != NULL) {
if (cur->sn == sn) {
prev->next = cur->next;
rlc_tx_free_pdu(cur);
cur = prev->next;
} else {
prev = cur;
cur = cur->next;
}
}
return head.next;
}
void cleanup_sdu_list(rlc_entity_am_t *entity)
{
rlc_sdu_t head;
rlc_sdu_t *cur;
rlc_sdu_t *prev;
/* remove fully acked SDUs, indicate successful delivery to upper layer */
head.next = entity->tx_list;
cur = entity->tx_list;
prev = &head;
while (cur != NULL) {
if (cur->acked_bytes == cur->size) {
prev->next = cur->next;
entity->tx_size -= cur->size;
entity->common.sdu_successful_delivery(
entity->common.sdu_successful_delivery_data,
(rlc_entity_t *)entity, cur->upper_layer_id);
rlc_free_sdu(cur);
entity->tx_end = prev;
cur = prev->next;
} else {
entity->tx_end = cur;
prev = cur;
cur = cur->next;
}
}
entity->tx_list = head.next;
/* if tx_end == head then it means that the list is now empty */
if (entity->tx_end == &head)
entity->tx_end = NULL;
}
static void finalize_ack_nack_processing(rlc_entity_am_t *entity)
{
int sn;
rlc_tx_pdu_segment_t *cur = entity->ack_list;
int pdu_size;
if (cur == NULL)
return;
/* Remove full PDUs and ack the SDU bytes they cover. Start from SN == VT(A)
* and process increasing SNs until end of list or missing ACK or PDU not
* fully ACKed.
*/
while (cur != NULL && cur->sn == entity->vt_a &&
tx_pdu_in_ack_list_full(cur)) {
sn = cur->sn;
entity->vt_a = (entity->vt_a + 1) % 1024;
pdu_size = tx_pdu_in_ack_list_size(cur);
ack_sdu_bytes(cur->start_sdu, cur->sdu_start_byte, pdu_size);
while (cur != NULL && cur->sn == sn)
cur = cur->next;
entity->ack_list = tx_list_remove_sn(entity->ack_list, sn);
}
cleanup_sdu_list(entity);
}
void rlc_entity_am_recv_pdu(rlc_entity_t *_entity, char *buffer, int size)
{
#define R(d) do { if (rlc_pdu_decoder_in_error(&d)) goto err; } while (0)
rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
rlc_pdu_decoder_t decoder;
rlc_pdu_decoder_t data_decoder;
rlc_pdu_decoder_t control_decoder;
int dc;
int rf;
int p = 0;
int fi;
int e;
int sn;
int lsf;
int so;
int cpt;
int e1;
int e2;
int ack_sn;
int nack_sn;
int so_start;
int so_end;
int control_e1;
int control_e2;
int data_e;
int data_li;
int packet_count;
int data_size;
int data_start;
int indicated_data_size;
rlc_rx_pdu_segment_t *pdu_segment;
rlc_pdu_decoder_init(&decoder, buffer, size);
dc = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
if (dc == 0) goto control;
/* data PDU */
rf = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
p = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
fi = rlc_pdu_decoder_get_bits(&decoder, 2); R(decoder);
e = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
sn = rlc_pdu_decoder_get_bits(&decoder, 10); R(decoder);
/* dicard PDU if rx buffer is full */
if (entity->rx_size + 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 vr_r %d)\n",
__FILE__, __LINE__, __FUNCTION__,
sn, entity->vr_r);
goto discard;
}
if (rf) {
lsf = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
so = rlc_pdu_decoder_get_bits(&decoder, 15); R(decoder);
} else {
lsf = 1;
so = 0;
}
packet_count = 1;
/* go to start of data */
indicated_data_size = 0;
data_decoder = decoder;
data_e = e;
while (data_e) {
data_e = rlc_pdu_decoder_get_bits(&data_decoder, 1); R(data_decoder);
data_li = rlc_pdu_decoder_get_bits(&data_decoder, 11); R(data_decoder);
if (data_li == 0) {
LOG_D(RLC, "%s:%d:%s: warning: discard PDU, li == 0\n",
__FILE__, __LINE__, __FUNCTION__);
goto discard;
}
indicated_data_size += data_li;
packet_count++;
}
rlc_pdu_decoder_align(&data_decoder);
data_start = data_decoder.byte;
data_size = size - data_start;
if (data_size <= 0) {
LOG_D(RLC, "%s:%d:%s: warning: discard PDU, wrong data size (sum of LI %d data size %d)\n",
__FILE__, __LINE__, __FUNCTION__,
indicated_data_size, data_size);
goto discard;
}
if (indicated_data_size >= data_size) {
LOG_D(RLC, "%s:%d:%s: warning: discard PDU, bad LIs (sum of LI %d data size %d)\n",
__FILE__, __LINE__, __FUNCTION__,
indicated_data_size, data_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__);
goto discard;
}
char *fi_str[] = {
"first byte: YES last byte: YES",
"first byte: YES last byte: NO",
"first byte: NO last byte: YES",
"first byte: NO last byte: NO",
};
LOG_D(RLC, "found %d packets, data size %d data start %d [fi %d %s] (sn %d) (p %d)\n",
packet_count, data_size, data_decoder.byte, fi, fi_str[fi], sn, p);
/* put in pdu reception list */
entity->rx_size += size;
pdu_segment = rlc_rx_new_pdu_segment(sn, so, size, lsf, buffer, data_start);
entity->rx_list = rlc_rx_pdu_segment_list_add(sn_compare_rx, entity,
entity->rx_list, pdu_segment);
/* do reception actions (36.322 5.1.3.2.3) */
rlc_am_reception_actions(entity, pdu_segment);
if (p) {
/* 36.322 5.2.3 says status triggering should be delayed
* until x < VR(MS) or x >= VR(MR). This is not clear (what
* is x then? we keep the same?). So let's trigger no matter what.
*/
int vr_mr = (entity->vr_r + 512) % 1024;
entity->status_triggered = 1;
if (!(sn_compare_rx(entity, sn, entity->vr_ms) < 0 ||
sn_compare_rx(entity, sn, vr_mr) >= 0)) {
LOG_D(RLC, "%s:%d:%s: warning: STATUS trigger should be delayed, according to specs\n",
__FILE__, __LINE__, __FUNCTION__);
}
}
return;
control:
cpt = rlc_pdu_decoder_get_bits(&decoder, 3); R(decoder);
if (cpt != 0) {
LOG_D(RLC, "%s:%d:%s: warning: discard PDU, CPT not 0 (%d)\n",
__FILE__, __LINE__, __FUNCTION__, cpt);
goto discard;
}
ack_sn = rlc_pdu_decoder_get_bits(&decoder, 10); R(decoder);
e1 = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
/* let's try to parse the control PDU once to check consistency */
control_decoder = decoder;
control_e1 = e1;
while (control_e1) {
rlc_pdu_decoder_get_bits(&control_decoder, 10); R(control_decoder); /* NACK_SN */
control_e1 = rlc_pdu_decoder_get_bits(&control_decoder, 1); R(control_decoder);
control_e2 = rlc_pdu_decoder_get_bits(&control_decoder, 1); R(control_decoder);
if (control_e2) {
rlc_pdu_decoder_get_bits(&control_decoder, 15); R(control_decoder); /* SOstart */
rlc_pdu_decoder_get_bits(&control_decoder, 15); R(control_decoder); /* SOend */
}
}
/* 36.322 5.2.2.2 says to stop t_poll_retransmit if a ACK or NACK is
* received for the SN 'poll_sn'
*/
if (sn_compare_tx(entity, entity->poll_sn, ack_sn) < 0)
entity->t_poll_retransmit_start = 0;
/* at this point, accept the PDU even if the actual values
* may be incorrect (eg. if so_start > so_end)
*/
process_received_ack(entity, ack_sn);
while (e1) {
nack_sn = rlc_pdu_decoder_get_bits(&decoder, 10); R(decoder);
e1 = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
e2 = rlc_pdu_decoder_get_bits(&decoder, 1); R(decoder);
if (e2) {
so_start = rlc_pdu_decoder_get_bits(&decoder, 15); R(decoder);
so_end = rlc_pdu_decoder_get_bits(&decoder, 15); R(decoder);
if (so_end < so_start) {
LOG_W(RLC, "%s:%d:%s: warning, bad so start/end, NACK the whole PDU (sn %d)\n",
__FILE__, __LINE__, __FUNCTION__, nack_sn);
so_start = 0;
so_end = -1;
}
/* special value 0x7fff indicates 'all bytes to the end' */
if (so_end == 0x7fff)
so_end = -1;
} else {
so_start = 0;
so_end = -1;
}
process_received_nack(entity, nack_sn, so_start, so_end);
/* 36.322 5.2.2.2 says to stop t_poll_retransmit if a ACK or NACK is
* received for the SN 'poll_sn'
*/
if (entity->poll_sn == nack_sn)
entity->t_poll_retransmit_start = 0;
}
finalize_ack_nack_processing(entity);
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;
#undef R
}
/*************************************************************************/
/* TX functions */
/*************************************************************************/
static int pdu_size(rlc_entity_am_t *entity, rlc_tx_pdu_segment_t *pdu)
{
int header_size;
int sdu_count;
int data_size;
int li_bits;
rlc_sdu_t *sdu;
header_size = 2;
if (pdu->is_segment)
header_size += 2;
data_size = pdu->data_size;
sdu = pdu->start_sdu;
sdu_count = 1;
data_size -= sdu->size - pdu->sdu_start_byte;
sdu = sdu->next;
while (data_size > 0) {
sdu_count++;
data_size -= sdu->size;
sdu = sdu->next;
}
li_bits = 12 * (sdu_count - 1);
header_size += (li_bits + 7) / 8;
return header_size + pdu->data_size;
}
static int header_size(int sdu_count)
{
int bits = 16 + 12 * (sdu_count - 1);
/* padding if we have to */
return (bits + 7) / 8;
}
typedef struct {
int sdu_count;
int data_size;
int header_size;
} tx_pdu_size_t;
static tx_pdu_size_t compute_new_pdu_size(rlc_entity_am_t *entity, int maxsize)
{
tx_pdu_size_t ret;
int sdu_count;
int sdu_size;
int pdu_data_size;
rlc_sdu_t *sdu;
int vt_ms = (entity->vt_a + 512) % 1024;
ret.sdu_count = 0;
ret.data_size = 0;
ret.header_size = 0;
/* sn out of window? nothing to do */
if (!(sn_compare_tx(entity, entity->vt_s, entity->vt_a) >= 0 &&
sn_compare_tx(entity, entity->vt_s, vt_ms) < 0))
return ret;
/* TX PDU - let's make the biggest PDU we can with the SDUs we have */
sdu_count = 0;
pdu_data_size = 0;
sdu = entity->tx_list;
while (sdu != NULL) {
/* include SDU only if it has not been fully included in PDUs already */
if (sdu->next_byte != sdu->size) {
int new_header_size = header_size(sdu_count + 1);
/* if we cannot put new header + at least 1 byte of data then over */
if (new_header_size + pdu_data_size + 1 > maxsize)
break;
sdu_count++;
/* only include the bytes of this SDU not included in PDUs already */
sdu_size = sdu->size - sdu->next_byte;
/* don't feed more than 'maxsize' bytes */
if (new_header_size + pdu_data_size + sdu_size > maxsize)
sdu_size = maxsize - new_header_size - pdu_data_size;
pdu_data_size += sdu_size;
/* if we put more than 2^11-1 bytes then the LI field cannot be used,
* so this is the last SDU we can put
*/
if (sdu_size > 2047)
break;
}
sdu = sdu->next;
}
if (sdu_count) {
ret.sdu_count = sdu_count;
ret.data_size = pdu_data_size;
ret.header_size = header_size(sdu_count);
}
return ret;
}
static int status_size(rlc_entity_am_t *entity, int maxsize)
{
/* let's count bits */
int bits = 15; /* minimum size is 15 (header+ack_sn+e1) */
int sn;
maxsize *= 8;
if (bits > maxsize) {
LOG_W(RLC, "%s:%d:%s: warning: cannot generate status PDU, not enough room\n",
__FILE__, __LINE__, __FUNCTION__);
return 0;
}
/* each NACK adds 12 bits */
sn = entity->vr_r;
while (bits + 12 <= maxsize && sn_compare_rx(entity, sn, entity->vr_ms) < 0) {
if (!(rlc_am_segment_full(entity, sn)))
bits += 12;
sn = (sn + 1) % 1024;
}
return (bits + 7) / 8;
}
static int generate_status(rlc_entity_am_t *entity, char *buffer, int size)
{
/* let's count bits */
int bits = 15; /* minimum size is 15 (header+ack_sn+e1) */
int sn;
rlc_pdu_encoder_t encoder;
int has_nack = 0;
int ack;
rlc_pdu_encoder_init(&encoder, buffer, size);
size *= 8;
if (bits > size) {
LOG_W(RLC, "%s:%d:%s: warning: cannot generate status PDU, not enough room\n",
__FILE__, __LINE__, __FUNCTION__);
return 0;
}
/* header */
rlc_pdu_encoder_put_bits(&encoder, 0, 1); /* D/C */
rlc_pdu_encoder_put_bits(&encoder, 0, 3); /* CPT */
/* reserve room for ACK (it will be set after putting the NACKs) */
rlc_pdu_encoder_put_bits(&encoder, 0, 10);
/* at this point, ACK is VR(R) */
ack = entity->vr_r;
/* each NACK adds 12 bits */
sn = entity->vr_r;
while (bits + 12 <= size && sn_compare_rx(entity, sn, entity->vr_ms) < 0) {
if (!(rlc_am_segment_full(entity, sn))) {
/* put previous e1 (is 1) */
rlc_pdu_encoder_put_bits(&encoder, 1, 1);
/* if previous was NACK, put previous e2 (0, we don't do 'so' thing) */
if (has_nack)
rlc_pdu_encoder_put_bits(&encoder, 0, 1);
/* put NACKed sn */
rlc_pdu_encoder_put_bits(&encoder, sn, 10);
has_nack = 1;
bits += 12;
} else {
/* this sn is full and we put all NACKs before it, use it for ACK */
ack = (sn + 1) % 1024;
}
sn = (sn + 1) % 1024;
}
/* go to highest full sn+1 for ACK, VR(MS) is the limit */
while (sn_compare_rx(entity, sn, entity->vr_ms) < 0 &&
rlc_am_segment_full(entity, sn)) {
ack = (sn + 1) % 1024;
sn = (sn + 1) % 1024;
}
/* at this point, if last put was NACK then put 2 bits else put 1 bit */
if (has_nack)
rlc_pdu_encoder_put_bits(&encoder, 0, 2);
else
rlc_pdu_encoder_put_bits(&encoder, 0, 1);
rlc_pdu_encoder_align(&encoder);
/* let's put the ACK */
buffer[0] |= ack >> 6;
buffer[1] |= (ack & 0x3f) << 2;
/* reset the trigger */
entity->status_triggered = 0;
/* start t_status_prohibit */
entity->t_status_prohibit_start = entity->t_current;
return encoder.byte;
}
int transmission_buffer_empty(rlc_entity_am_t *entity)
{
rlc_sdu_t *sdu;
/* is transmission buffer empty? */
sdu = entity->tx_list;
while (sdu != NULL) {
if (sdu->next_byte != sdu->size)
return 0;
sdu = sdu->next;
}
return 1;
}
int check_poll_after_pdu_assembly(rlc_entity_am_t *entity)
{
int retransmission_buffer_empty;
int window_stalling;
int vt_ms;
/* is retransmission buffer empty? */
if (entity->retransmit_list == NULL)
retransmission_buffer_empty = 1;
else
retransmission_buffer_empty = 0;
/* is window stalling? */
vt_ms = (entity->vt_a + 512) % 1024;
if (!(sn_compare_tx(entity, entity->vt_s, entity->vt_a) >= 0 &&
sn_compare_tx(entity, entity->vt_s, vt_ms) < 0))
window_stalling = 1;
else
window_stalling = 0;
return (transmission_buffer_empty(entity) && retransmission_buffer_empty) ||
window_stalling;
}
void include_poll(rlc_entity_am_t *entity, char *buffer)
{
/* set the P bit to 1 */
buffer[0] |= 0x20;
entity->pdu_without_poll = 0;
entity->byte_without_poll = 0;
/* set POLL_SN to VT(S) - 1 */
entity->poll_sn = (entity->vt_s + 1023) % 1024;
/* start t_poll_retransmit */
entity->t_poll_retransmit_start = entity->t_current;
}
static int serialize_pdu(rlc_entity_am_t *entity, char *buffer, int bufsize,
rlc_tx_pdu_segment_t *pdu, int p)
{
int first_sdu_full;
int last_sdu_full;
int sdu_next_byte;
rlc_sdu_t *sdu;
int i;
int cursize;
rlc_pdu_encoder_t encoder;
int fi;
int e;
int li;
char *out;
int outpos;
int sdu_count;
int header_size;
int sdu_start_byte;
first_sdu_full = pdu->sdu_start_byte == 0;
/* is last SDU full? (and also compute sdu_count) */
last_sdu_full = 1;
sdu = pdu->start_sdu;
sdu_next_byte = pdu->sdu_start_byte;
cursize = 0;
sdu_count = 0;
while (cursize != pdu->data_size) {
int sdu_size = sdu->size - sdu_next_byte;
sdu_count++;
if (cursize + sdu_size > pdu->data_size) {
last_sdu_full = 0;
break;
}
cursize += sdu_size;
sdu = sdu->next;
sdu_next_byte = 0;
}
/* generate header */
rlc_pdu_encoder_init(&encoder, buffer, bufsize);
rlc_pdu_encoder_put_bits(&encoder, 1, 1); /* D/C: 1 = data */
rlc_pdu_encoder_put_bits(&encoder, pdu->is_segment, 1); /* RF */
rlc_pdu_encoder_put_bits(&encoder, 0, 1); /* P: reserve, set later */
fi = 0;
if (!first_sdu_full)
fi |= 0x02;
if (!last_sdu_full)
fi |= 0x01;
rlc_pdu_encoder_put_bits(&encoder, fi, 2); /* FI */
/* to understand the logic for Es and LIs:
* If we have:
* 1 SDU: E=0
*
* 2 SDUs: E=1
* then: E=0 LI(sdu[0])
*
* 3 SDUs: E=1
* then: E=1 LI(sdu[0])
* then: E=0 LI(sdu[1])
*
* 4 SDUs: E=1
* then: E=1 LI(sdu[0])
* then: E=1 LI(sdu[1])
* then: E=0 LI(sdu[2])
*/
if (sdu_count >= 2)
e = 1;
else
e = 0;
rlc_pdu_encoder_put_bits(&encoder, e, 1); /* E */
rlc_pdu_encoder_put_bits(&encoder, pdu->sn, 10); /* SN */
if (pdu->is_segment) {
rlc_pdu_encoder_put_bits(&encoder, pdu->is_last, 1); /* LSF */
rlc_pdu_encoder_put_bits(&encoder, pdu->so, 15); /* SO */
}
/* put LIs */
sdu = pdu->start_sdu;
/* first SDU */
li = sdu->size - pdu->sdu_start_byte;
/* put E+LI only if at least 2 SDUs */
if (sdu_count >= 2) {
/* E is 1 if at least 3 SDUs */
if (sdu_count >= 3)
e = 1;
else
e = 0;
rlc_pdu_encoder_put_bits(&encoder, e, 1); /* E */
rlc_pdu_encoder_put_bits(&encoder, li, 11); /* LI */
}
/* next SDUs, but not the last (no LI for the last) */
sdu = sdu->next;
for (i = 2; i < sdu_count; i++, sdu = sdu->next) {
if (i != sdu_count - 1)
e = 1;
else
e = 0;
li = sdu->size;
rlc_pdu_encoder_put_bits(&encoder, e, 1); /* E */
rlc_pdu_encoder_put_bits(&encoder, li, 11); /* LI */
}
rlc_pdu_encoder_align(&encoder);
header_size = encoder.byte;
/* generate data */
out = buffer + header_size;
sdu = pdu->start_sdu;
sdu_start_byte = pdu->sdu_start_byte;
outpos = 0;
for (i = 0; i < sdu_count; i++, sdu = sdu->next) {
li = sdu->size - sdu_start_byte;
if (outpos + li >= pdu->data_size)
li = pdu->data_size - outpos;
memcpy(out+outpos, sdu->data + sdu_start_byte, li);
outpos += li;
sdu_start_byte = 0;
}
if (p)
include_poll(entity, buffer);
return header_size + pdu->data_size;
}
static int generate_tx_pdu(rlc_entity_am_t *entity, char *buffer, int bufsize)
{
int vt_ms;
tx_pdu_size_t pdu_size;
rlc_sdu_t *sdu;
int i;
int cursize;
int p;
rlc_tx_pdu_segment_t *pdu;
/* sn out of window? do nothing */
vt_ms = (entity->vt_a + 512) % 1024;
if (!(sn_compare_tx(entity, entity->vt_s, entity->vt_a) >= 0 &&
sn_compare_tx(entity, entity->vt_s, vt_ms) < 0))
return 0;
pdu_size = compute_new_pdu_size(entity, bufsize);
if (pdu_size.sdu_count == 0)
return 0;
pdu = rlc_tx_new_pdu();
pdu->sn = entity->vt_s;
entity->vt_s = (entity->vt_s + 1) % 1024;
/* go to first SDU (skip those already fully processed) */
sdu = entity->tx_list;
while (sdu->next_byte == sdu->size)
sdu = sdu->next;
pdu->start_sdu = sdu;
pdu->sdu_start_byte = sdu->next_byte;
pdu->so = 0;
pdu->is_segment = 0;
pdu->is_last = 1;
/* to conform to specs' logic, put -1 (specs say "for 1st retransmission
* put 0 otherwise increase", let's put -1 and always increase when the
* segment goes to retransmit list)
*/
pdu->retx_count = -1;
/* reserve SDU bytes */
cursize = 0;
for (i = 0; i < pdu_size.sdu_count; i++, sdu = sdu->next) {
int sdu_size = sdu->size - sdu->next_byte;
if (cursize + sdu_size > pdu_size.data_size)
sdu_size = pdu_size.data_size - cursize;
sdu->next_byte += sdu_size;
cursize += sdu_size;
}
pdu->data_size = cursize;
/* put PDU at the end of the wait list */
entity->wait_list = rlc_tx_pdu_list_append(entity->wait_list, pdu);
/* polling actions for a new PDU */
entity->pdu_without_poll++;
entity->byte_without_poll += pdu_size.data_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;
}
return serialize_pdu(entity, buffer, bufsize, pdu, p);
}
static void resegment(rlc_tx_pdu_segment_t *pdu, int size)
{
rlc_tx_pdu_segment_t *new_pdu;
rlc_sdu_t *sdu;
int sdu_count;
int pdu_header_size;
int pdu_data_size;
int sdu_pos;
int sdu_bytes_to_take;
/* PDU segment too big, cut in two parts so that first part fits into
* size bytes (including header)
*/
sdu = pdu->start_sdu;
pdu_data_size = 0;
sdu_pos = pdu->sdu_start_byte;
sdu_count = 0;
while (1) {
/* can we put a new header and at least one byte of data? */
/* header has 2 more bytes for SO */
pdu_header_size = 2 + header_size(sdu_count + 1);
if (pdu_header_size + pdu_data_size + 1 > size) {
/* no we can't, stop here */
break;
}
/* yes we can, go ahead */
sdu_count++;
sdu_bytes_to_take = sdu->size - sdu_pos;
if (pdu_header_size + pdu_data_size + sdu_bytes_to_take > size) {
sdu_bytes_to_take = size - (pdu_header_size + pdu_data_size);
}
sdu_pos += sdu_bytes_to_take;
if (sdu_pos == sdu->size) {
sdu = sdu->next;
sdu_pos = 0;
}
pdu_data_size += sdu_bytes_to_take;
}
new_pdu = rlc_tx_new_pdu();
pdu->is_segment = 1;
*new_pdu = *pdu;
new_pdu->so = pdu->so + pdu_data_size;
new_pdu->data_size = pdu->data_size - pdu_data_size;
new_pdu->start_sdu = sdu;
new_pdu->sdu_start_byte = sdu_pos;
pdu->is_last = 0;
pdu->data_size = pdu_data_size;
pdu->next = new_pdu;
}
static int generate_retx_pdu(rlc_entity_am_t *entity, char *buffer, int size)
{
rlc_tx_pdu_segment_t *pdu;
int orig_size;
int p;
pdu = entity->retransmit_list;
orig_size = pdu_size(entity, pdu);
if (orig_size > size) {
/* we can't resegment if size is less than 5
* (4 bytes for header, 1 byte for data)
*/
if (size < 5)
return 0;
resegment(pdu, size);
}
/* remove from retransmit list and put in wait list */
entity->retransmit_list = pdu->next;
entity->wait_list = rlc_tx_pdu_list_add(sn_compare_tx, entity,
entity->wait_list, pdu);
p = check_poll_after_pdu_assembly(entity);
if (entity->force_poll) {
p = 1;
entity->force_poll = 0;
}
return serialize_pdu(entity, buffer, orig_size, pdu, p);
}
static int status_to_report(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 retx_pdu_size(rlc_entity_am_t *entity, int maxsize)
{
int size;
if (entity->retransmit_list == NULL)
return 0;
size = pdu_size(entity, entity->retransmit_list);
if (size <= maxsize)
return size;
/* we can segment head of retransmist list if maxsize is large enough
* to hold a PDU segment with at least 1 data byte (so 5 bytes: 4 bytes
* header + 1 byte data)
*/
if (maxsize < 5)
return 0;
/* a later segmentation of the head of retransmit list will generate a pdu
* of maximum size 'maxsize' (can be less)
*/
return maxsize;
}
rlc_entity_buffer_status_t rlc_entity_am_buffer_status(
rlc_entity_t *_entity, int maxsize)
{
rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
rlc_entity_buffer_status_t ret;
tx_pdu_size_t tx_size;
/* status PDU, if we have to */
if (status_to_report(entity))
ret.status_size = status_size(entity, maxsize);
else
ret.status_size = 0;
/* TX PDU */
/* todo: if an SDU has size >2047 in the tx list then processing
* stops and computed size will not be accurate. Change the computation
* to be more accurate (if needed).
*/
tx_size = compute_new_pdu_size(entity, maxsize);
ret.tx_size = tx_size.data_size + tx_size.header_size;
/* reTX PDU */
/* todo: report size of all available data, not just first PDU */
ret.retx_size = retx_pdu_size(entity, maxsize);
return ret;
}
int rlc_entity_am_generate_pdu(rlc_entity_t *_entity, char *buffer, int size)
{
rlc_entity_am_t *entity = (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 rlc_entity_am_recv_sdu(rlc_entity_t *_entity, char *buffer, int size,
int sdu_id)
{
rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
rlc_sdu_t *sdu;
if (size > SDU_MAX) {
LOG_E(RLC, "%s:%d:%s: fatal: SDU size too big (%d bytes)\n",
__FILE__, __LINE__, __FUNCTION__, size);
exit(1);
}
if (entity->tx_size + size > entity->tx_maxsize) {
LOG_D(RLC, "%s:%d:%s: warning: SDU rejected, SDU buffer full\n",
__FILE__, __LINE__, __FUNCTION__);
return;
}
entity->tx_size += size;
sdu = rlc_new_sdu(buffer, size, sdu_id);
rlc_sdu_list_add(&entity->tx_list, &entity->tx_end, sdu);
}
/*************************************************************************/
/* time/timers */
/*************************************************************************/
static void check_t_poll_retransmit(rlc_entity_am_t *entity)
{
rlc_tx_pdu_segment_t head;
rlc_tx_pdu_segment_t *cur;
rlc_tx_pdu_segment_t *prev;
int sn;
/* 36.322 5.2.2.3 */
/* 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;
/* 36.322 5.2.2.3 says:
*
* - include a poll in a RLC data PDU as described in section 5.2.2.1
*
* 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 36.322 5.2.2.3? */
if (!check_poll_after_pdu_assembly(entity))
return;
/* search wait list for PDU with SN = VT(S)-1 */
sn = (entity->vt_s + 1023) % 1024;
head.next = entity->wait_list;
cur = entity->wait_list;
prev = &head;
while (cur != NULL) {
if (cur->sn == sn)
break;
prev = cur;
cur = cur->next;
}
/* PDU with SN = VT(S)-1 not found?, take the head of wait list */
if (cur == NULL) {
cur = entity->wait_list;
prev = &head;
sn = cur->sn;
}
/* 36.322 says "PDU", not "PDU segment", so let's retransmit all
* PDU segments with this SN
*/
while (cur != NULL && cur->sn == sn) {
prev->next = cur->next;
entity->wait_list = head.next;
/* put in retransmit list */
consider_retransmission(entity, cur);
cur = prev->next;
}
}
static void check_t_reordering(rlc_entity_am_t *entity)
{
int sn;
/* is t_reordering running and if yes has it expired? */
if (entity->t_reordering_start == 0 ||
entity->t_current <= entity->t_reordering_start + entity->t_reordering)
return;
/* stop timer */
entity->t_reordering_start = 0;
LOG_D(RLC, "%s:%d:%s: t_reordering expired\n", __FILE__, __LINE__, __FUNCTION__);
/* update VR(MS) to first SN >= VR(X) for which not all PDU segments
* have been received
*/
sn = entity->vr_x;
while (rlc_am_segment_full(entity, sn))
sn = (sn + 1) % 1024;
entity->vr_ms = sn;
if (sn_compare_rx(entity, entity->vr_h, entity->vr_ms) > 0) {
entity->t_reordering_start = entity->t_current;
entity->vr_x = entity->vr_h;
}
/* trigger STATUS report */
entity->status_triggered = 1;
}
void rlc_entity_am_set_time(rlc_entity_t *_entity, uint64_t now)
{
rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
entity->t_current = now;
check_t_poll_retransmit(entity);
check_t_reordering(entity);
/* t_status_prohibit is handled by generate_status */
}
/*************************************************************************/
/* discard/re-establishment/delete */
/*************************************************************************/
void rlc_entity_am_discard_sdu(rlc_entity_t *_entity, int sdu_id)
{
/* implements 36.322 5.3 */
rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
rlc_sdu_t head;
rlc_sdu_t *cur;
rlc_sdu_t *prev;
head.next = entity->tx_list;
cur = entity->tx_list;
prev = &head;
while (cur != NULL && cur->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->next_byte != 0)
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;
}
rlc_free_sdu(cur);
}
static void free_pdu_segment_list(rlc_tx_pdu_segment_t *l)
{
rlc_tx_pdu_segment_t *cur;
while (l != NULL) {
cur = l;
l = l->next;
rlc_tx_free_pdu(cur);
}
}
static void clear_entity(rlc_entity_am_t *entity)
{
rlc_rx_pdu_segment_t *cur_rx;
rlc_sdu_t *cur_tx;
entity->vr_r = 0;
entity->vr_x = 0;
entity->vr_ms = 0;
entity->vr_h = 0;
entity->status_triggered = 0;
entity->vt_a = 0;
entity->vt_s = 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_reordering_start = 0;
entity->t_status_prohibit_start = 0;
entity->t_poll_retransmit_start = 0;
cur_rx = entity->rx_list;
while (cur_rx != NULL) {
rlc_rx_pdu_segment_t *p = cur_rx;
cur_rx = cur_rx->next;
rlc_rx_free_pdu_segment(p);
}
entity->rx_list = NULL;
entity->rx_size = 0;
memset(&entity->reassemble, 0, sizeof(rlc_am_reassemble_t));
cur_tx = entity->tx_list;
while (cur_tx != NULL) {
rlc_sdu_t *p = cur_tx;
cur_tx = cur_tx->next;
rlc_free_sdu(p);
}
entity->tx_list = NULL;
entity->tx_end = NULL;
entity->tx_size = 0;
free_pdu_segment_list(entity->wait_list);
free_pdu_segment_list(entity->retransmit_list);
free_pdu_segment_list(entity->ack_list);
entity->wait_list = NULL;
entity->retransmit_list = NULL;
entity->ack_list = NULL;
}
void rlc_entity_am_reestablishment(rlc_entity_t *_entity)
{
rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
/* 36.322 5.4 says to deliver SDUs if possible.
* Let's not do that, it makes the code simpler.
* TODO: change this behavior if wanted/needed.
*/
clear_entity(entity);
}
void rlc_entity_am_delete(rlc_entity_t *_entity)
{
rlc_entity_am_t *entity = (rlc_entity_am_t *)_entity;
clear_entity(entity);
free(entity);
}