| /* Copied from tbf.cpp |
| * |
| * Copyright (C) 2012 Ivan Klyuchnikov |
| * Copyright (C) 2012 Andreas Eversberg <jolly@eversberg.eu> |
| * Copyright (C) 2013 by Holger Hans Peter Freyther |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| |
| #include <stdio.h> |
| |
| #include <osmocom/core/msgb.h> |
| |
| #include "bts.h" |
| #include "gprs_ms.h" |
| #include "pcu_utils.h" |
| #include "llc.h" |
| |
| void llc_init(struct gprs_llc *llc) |
| { |
| llc_reset(llc); |
| } |
| |
| /* reset LLC frame */ |
| void llc_reset(struct gprs_llc *llc) |
| { |
| llc->index = 0; |
| llc->length = 0; |
| |
| memset(llc->frame, 0x42, sizeof(llc->frame)); |
| } |
| |
| void llc_reset_frame_space(struct gprs_llc *llc) |
| { |
| llc->index = 0; |
| } |
| |
| /* Put an Unconfirmed Information (UI) Dummy command, see GSM 44.064, 6.4.2.2 */ |
| void llc_put_dummy_frame(struct gprs_llc *llc, size_t req_len) |
| { |
| /* The shortest dummy command (the spec requests at least 6 octets) */ |
| static const uint8_t llc_dummy_command[] = { |
| 0x43, 0xc0, 0x01, 0x2b, 0x2b, 0x2b |
| }; |
| static const size_t max_dummy_command_len = 79; |
| |
| llc_put_frame(llc, llc_dummy_command, sizeof(llc_dummy_command)); |
| |
| if (req_len > max_dummy_command_len) |
| req_len = max_dummy_command_len; |
| |
| /* Add further stuffing, if the requested length exceeds the minimum |
| * dummy command length */ |
| if (llc->length < req_len) { |
| memset(&llc->frame[llc->length], 0x2b, req_len - llc->length); |
| llc->length = req_len; |
| } |
| } |
| |
| void llc_put_frame(struct gprs_llc *llc, const uint8_t *data, size_t len) |
| { |
| /* only put frames when we are empty */ |
| OSMO_ASSERT(llc->index == 0 && llc->length == 0); |
| llc_append_frame(llc, data, len); |
| } |
| |
| void llc_append_frame(struct gprs_llc *llc, const uint8_t *data, size_t len) |
| { |
| /* TODO: bounds check */ |
| memcpy(llc->frame + llc->length, data, len); |
| llc->length += len; |
| } |
| |
| static bool llc_pdu_can_be_discarded(const uint8_t *data, size_t len) |
| { |
| const unsigned keep_small_thresh = 60; |
| |
| /* Is the frame small, perhaps only a TCP ACK? */ |
| if (len <= keep_small_thresh) |
| return false; |
| |
| if ((data[0] & 0x0f) == 1 /* GPRS_SAPI_GMM */) |
| return false; |
| |
| if ((data[0] & 0xe0) != 0xc0 /* LLC UI */) |
| /* It is not an LLC UI frame, see TS 44.064, 6.3 */ |
| return false; |
| |
| return true; |
| } |
| |
| void llc_queue_init(struct gprs_llc_queue *q, struct GprsMs *ms) |
| { |
| unsigned int i; |
| |
| q->ms = ms; |
| q->queue_size = 0; |
| q->queue_octets = 0; |
| q->avg_queue_delay = 0; |
| for (i = 0; i < ARRAY_SIZE(q->pq); i++) { |
| INIT_LLIST_HEAD(&q->pq[i].queue); |
| gprs_codel_init(&q->pq[i].codel_state); |
| } |
| } |
| |
| /* interval=0 -> don't use codel in the LLC queue */ |
| void llc_queue_set_codel_interval(struct gprs_llc_queue *q, unsigned int interval) |
| { |
| unsigned int i; |
| if (interval == LLC_CODEL_DISABLE) { |
| q->use_codel = false; |
| return; |
| } |
| q->use_codel = true; |
| for (i = 0; i < ARRAY_SIZE(q->pq); i++) |
| gprs_codel_set_interval(&q->pq[i].codel_state, interval); |
| } |
| |
| static enum gprs_llc_queue_prio llc_sapi2prio(uint8_t sapi) |
| { |
| switch (sapi) { |
| case 1: |
| return LLC_QUEUE_PRIO_GMM; |
| case 2: |
| case 7: |
| case 8: |
| return LLC_QUEUE_PRIO_TOM_SMS; |
| default: |
| return LLC_QUEUE_PRIO_OTHER; |
| } |
| } |
| |
| void llc_queue_enqueue(struct gprs_llc_queue *q, struct msgb *llc_msg, const struct timespec *expire_time) |
| { |
| struct MetaInfo *meta_storage; |
| struct gprs_llc_hdr *llc_hdr = (struct gprs_llc_hdr *)msgb_data(llc_msg); |
| enum gprs_llc_queue_prio prio; |
| |
| osmo_static_assert(sizeof(*meta_storage) <= sizeof(llc_msg->cb), info_does_not_fit); |
| |
| prio = llc_sapi2prio(llc_hdr->sapi); |
| |
| q->queue_size += 1; |
| q->queue_octets += msgb_length(llc_msg); |
| |
| meta_storage = (struct MetaInfo *)&llc_msg->cb[0]; |
| osmo_clock_gettime(CLOCK_MONOTONIC, &meta_storage->recv_time); |
| meta_storage->expire_time = *expire_time; |
| |
| msgb_enqueue(&q->pq[prio].queue, llc_msg); |
| } |
| |
| void llc_queue_clear(struct gprs_llc_queue *q, struct gprs_rlcmac_bts *bts) |
| { |
| struct msgb *msg; |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(q->pq); i++) { |
| while ((msg = msgb_dequeue(&q->pq[i].queue))) { |
| if (bts) |
| bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_DROPPED); |
| msgb_free(msg); |
| } |
| } |
| |
| q->queue_size = 0; |
| q->queue_octets = 0; |
| } |
| |
| void llc_queue_move_and_merge(struct gprs_llc_queue *q, struct gprs_llc_queue *o) |
| { |
| struct msgb *msg, *msg1 = NULL, *msg2 = NULL; |
| struct llist_head new_queue; |
| unsigned int i; |
| size_t queue_size = 0; |
| size_t queue_octets = 0; |
| INIT_LLIST_HEAD(&new_queue); |
| |
| for (i = 0; i < ARRAY_SIZE(q->pq); i++) { |
| while (1) { |
| if (msg1 == NULL) |
| msg1 = msgb_dequeue(&q->pq[i].queue); |
| |
| if (msg2 == NULL) |
| msg2 = msgb_dequeue(&o->pq[i].queue); |
| |
| if (msg1 == NULL && msg2 == NULL) |
| break; |
| |
| if (msg1 == NULL) { |
| msg = msg2; |
| msg2 = NULL; |
| } else if (msg2 == NULL) { |
| msg = msg1; |
| msg1 = NULL; |
| } else { |
| const struct MetaInfo *mi1 = (struct MetaInfo *)&msg1->cb[0]; |
| const struct MetaInfo *mi2 = (struct MetaInfo *)&msg2->cb[0]; |
| |
| if (timespeccmp(&mi2->recv_time, &mi1->recv_time, >)) { |
| msg = msg1; |
| msg1 = NULL; |
| } else { |
| msg = msg2; |
| msg2 = NULL; |
| } |
| } |
| |
| msgb_enqueue(&new_queue, msg); |
| queue_size += 1; |
| queue_octets += msgb_length(msg); |
| } |
| |
| OSMO_ASSERT(llist_empty(&q->pq[i].queue)); |
| OSMO_ASSERT(llist_empty(&o->pq[i].queue)); |
| llist_splice_init(&new_queue, &q->pq[i].queue); |
| } |
| |
| o->queue_size = 0; |
| o->queue_octets = 0; |
| q->queue_size = queue_size; |
| q->queue_octets = queue_octets; |
| } |
| |
| #define ALPHA 0.5f |
| |
| static struct msgb *llc_queue_pick_msg(struct gprs_llc_queue *q, enum gprs_llc_queue_prio *prio) |
| { |
| struct msgb *msg; |
| struct timespec tv_now, tv_result; |
| uint32_t lifetime; |
| unsigned int i; |
| const struct MetaInfo *meta_storage; |
| |
| for (i = 0; i < ARRAY_SIZE(q->pq); i++) { |
| if ((msg = msgb_dequeue(&q->pq[i].queue))) { |
| *prio = (enum gprs_llc_queue_prio)i; |
| break; |
| } |
| } |
| if (!msg) |
| return NULL; |
| |
| meta_storage = (struct MetaInfo *)&msg->cb[0]; |
| |
| q->queue_size -= 1; |
| q->queue_octets -= msgb_length(msg); |
| |
| /* take the second time */ |
| osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); |
| timespecsub(&tv_now, &meta_storage->recv_time, &tv_result); |
| |
| lifetime = tv_result.tv_sec*1000 + tv_result.tv_nsec/1000000; |
| q->avg_queue_delay = q->avg_queue_delay * ALPHA + lifetime * (1-ALPHA); |
| |
| return msg; |
| } |
| |
| struct msgb *llc_queue_dequeue(struct gprs_llc_queue *q) |
| { |
| struct msgb *msg; |
| struct timespec tv_now, tv_now2; |
| uint32_t octets = 0, frames = 0; |
| struct gprs_rlcmac_bts *bts = q->ms->bts; |
| struct gprs_pcu *pcu = bts->pcu; |
| struct timespec hyst_delta = {0, 0}; |
| enum gprs_llc_queue_prio prio; |
| |
| if (pcu->vty.llc_discard_csec) |
| csecs_to_timespec(pcu->vty.llc_discard_csec, &hyst_delta); |
| |
| osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); |
| timespecadd(&tv_now, &hyst_delta, &tv_now2); |
| |
| while ((msg = llc_queue_pick_msg(q, &prio))) { |
| const struct MetaInfo *info = (const struct MetaInfo *)&msg->cb[0]; |
| const struct timespec *tv_disc = &info->expire_time; |
| const struct timespec *tv_recv = &info->recv_time; |
| |
| gprs_bssgp_update_queue_delay(tv_recv, &tv_now); |
| |
| if (q->use_codel) { |
| int bytes = llc_queue_octets(q); |
| if (gprs_codel_control(&q->pq[prio].codel_state, tv_recv, &tv_now, bytes)) |
| goto drop_frame; |
| } |
| |
| /* Is the age below the low water mark? */ |
| if (!llc_queue_is_frame_expired(&tv_now2, tv_disc)) |
| break; |
| |
| /* Is the age below the high water mark */ |
| if (!llc_queue_is_frame_expired(&tv_now, tv_disc)) { |
| /* Has the previous message not been dropped? */ |
| if (frames == 0) |
| break; |
| |
| /* Hysteresis mode, try to discard LLC messages until |
| * the low water mark has been reached */ |
| |
| /* Check whether to abort the hysteresis mode: |
| * Can the PDU be discarded according to its type? */ |
| if (!llc_pdu_can_be_discarded(msg->data, msg->len)) |
| break; |
| } |
| |
| bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_TIMEDOUT); |
| drop_frame: |
| frames++; |
| octets += msg->len; |
| msgb_free(msg); |
| bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_DROPPED); |
| continue; |
| } |
| |
| if (frames) { |
| LOGPMS(q->ms, DTBFDL, LOGL_NOTICE, "Discarding LLC PDU " |
| "because lifetime limit reached, " |
| "count=%u new_queue_size=%zu\n", |
| frames, llc_queue_size(q)); |
| if (frames > 0xff) |
| frames = 0xff; |
| if (octets > 0xffffff) |
| octets = 0xffffff; |
| if (pcu->bssgp.bctx) |
| bssgp_tx_llc_discarded(pcu->bssgp.bctx, ms_tlli(q->ms), frames, octets); |
| } |
| |
| return msg; |
| } |
| |
| void llc_queue_calc_pdu_lifetime(struct gprs_rlcmac_bts *bts, const uint16_t pdu_delay_csec, struct timespec *tv) |
| { |
| uint16_t delay_csec; |
| if (bts->pcu->vty.force_llc_lifetime) |
| delay_csec = bts->pcu->vty.force_llc_lifetime; |
| else |
| delay_csec = pdu_delay_csec; |
| |
| /* keep timestamp at 0 for infinite delay */ |
| if (delay_csec == 0xffff) { |
| memset(tv, 0, sizeof(*tv)); |
| return; |
| } |
| |
| /* calculate timestamp of timeout */ |
| struct timespec now, csec; |
| osmo_clock_gettime(CLOCK_MONOTONIC, &now); |
| csecs_to_timespec(delay_csec, &csec); |
| |
| timespecadd(&now, &csec, tv); |
| } |
| |
| bool llc_queue_is_frame_expired(const struct timespec *tv_now, const struct timespec *tv) |
| { |
| /* Timeout is infinite */ |
| if (tv->tv_sec == 0 && tv->tv_nsec == 0) |
| return false; |
| |
| return timespeccmp(tv_now, tv, >); |
| } |