diff options
Diffstat (limited to 'src/gprs_ms.c')
-rw-r--r-- | src/gprs_ms.c | 885 |
1 files changed, 885 insertions, 0 deletions
diff --git a/src/gprs_ms.c b/src/gprs_ms.c new file mode 100644 index 00000000..94f69cde --- /dev/null +++ b/src/gprs_ms.c @@ -0,0 +1,885 @@ +/* gprs_ms.c + * + * Copyright (C) 2015-2020 by Sysmocom s.f.m.c. GmbH + * Author: Jacob Erlbeck <jerlbeck@sysmocom.de> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "gprs_ms.h" +#include "bts.h" +#include "tbf.h" +#include "tbf_ul.h" +#include "gprs_debug.h" +#include "gprs_codel.h" +#include "pcu_utils.h" + +#include <time.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/timer.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/core/logging.h> +#include "coding_scheme.h" + +#define GPRS_CODEL_SLOW_INTERVAL_MS 4000 + +extern void *tall_pcu_ctx; + +static int64_t now_msec() +{ + struct timespec ts; + osmo_clock_gettime(CLOCK_MONOTONIC, &ts); + + return (int64_t)(ts.tv_sec) * 1000 + ts.tv_nsec / 1000000; +} + +void gprs_default_cb_ms_idle(struct GprsMs *ms) +{ + talloc_free(ms); +} + +void gprs_default_cb_ms_active(struct GprsMs *ms) +{ + /* do nothing */ +} + +static struct gpr_ms_callback gprs_default_cb = { + .ms_idle = gprs_default_cb_ms_idle, + .ms_active = gprs_default_cb_ms_active, +}; + +void ms_timeout(void *data) +{ + struct GprsMs *ms = (struct GprsMs *) data; + LOGP(DRLCMAC, LOGL_INFO, "Timeout for MS object, TLLI = 0x%08x\n", + ms_tlli(ms)); + + if (ms->timer.data) { + ms->timer.data = NULL; + ms_unref(ms); + } +} + +static int ms_talloc_destructor(struct GprsMs *ms); +struct GprsMs *ms_alloc(struct BTS *bts, uint32_t tlli) +{ + struct GprsMs *ms = talloc_zero(tall_pcu_ctx, struct GprsMs); + + talloc_set_destructor(ms, ms_talloc_destructor); + + ms->bts = bts; + ms->cb = gprs_default_cb; + ms->tlli = tlli; + ms->new_ul_tlli = GSM_RESERVED_TMSI; + ms->new_dl_tlli = GSM_RESERVED_TMSI; + ms->ta = GSM48_TA_INVALID; + ms->current_cs_ul = UNKNOWN; + ms->current_cs_dl = UNKNOWN; + ms->is_idle = true; + INIT_LLIST_HEAD(&ms->list); + INIT_LLIST_HEAD(&ms->old_tbfs); + + int codel_interval = LLC_CODEL_USE_DEFAULT; + + LOGP(DRLCMAC, LOGL_INFO, "Creating MS object, TLLI = 0x%08x\n", tlli); + + ms->imsi[0] = '\0'; + memset(&ms->timer, 0, sizeof(ms->timer)); + ms->timer.cb = ms_timeout; + llc_queue_init(&ms->llc_queue); + + ms_set_mode(ms, GPRS); + + if (ms->bts) + codel_interval = bts_data(ms->bts)->llc_codel_interval_msec; + + if (codel_interval) { + if (codel_interval == LLC_CODEL_USE_DEFAULT) + codel_interval = GPRS_CODEL_SLOW_INTERVAL_MS; + ms->codel_state = talloc(ms, struct gprs_codel); + gprs_codel_init(ms->codel_state); + gprs_codel_set_interval(ms->codel_state, codel_interval); + } + ms->last_cs_not_low = now_msec(); + ms->app_info_pending = false; + return ms; +} + +static int ms_talloc_destructor(struct GprsMs *ms) +{ + struct llist_item *pos, *tmp; + + LOGP(DRLCMAC, LOGL_INFO, "Destroying MS object, TLLI = 0x%08x\n", ms_tlli(ms)); + + ms_set_reserved_slots(ms, NULL, 0, 0); + + if (osmo_timer_pending(&ms->timer)) + osmo_timer_del(&ms->timer); + + if (ms->ul_tbf) { + tbf_set_ms((struct gprs_rlcmac_tbf *)ms->ul_tbf, NULL); + ms->ul_tbf = NULL; + } + + if (ms->dl_tbf) { + tbf_set_ms((struct gprs_rlcmac_tbf *)ms->dl_tbf, NULL); + ms->dl_tbf = NULL; + } + + llist_for_each_entry_safe(pos, tmp, &ms->old_tbfs, list) { + struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)pos->entry; + tbf_set_ms(tbf, NULL); + } + + llc_queue_clear(&ms->llc_queue, ms->bts); + return 0; +} + + +void ms_set_callback(struct GprsMs *ms, struct gpr_ms_callback *cb) +{ + if (cb) + ms->cb = *cb; + else + ms->cb = gprs_default_cb; +} + +static void ms_update_status(struct GprsMs *ms) +{ + if (ms->ref > 0) + return; + + if (ms_is_idle(ms) && !ms->is_idle) { + ms->is_idle = true; + ms->cb.ms_idle(ms); + /* this can be deleted by now, do not access it */ + return; + } + + if (!ms_is_idle(ms) && ms->is_idle) { + ms->is_idle = false; + ms->cb.ms_active(ms); + } +} + +struct GprsMs *ms_ref(struct GprsMs *ms) +{ + ms->ref += 1; + return ms; +} + +void ms_unref(struct GprsMs *ms) +{ + OSMO_ASSERT(ms->ref >= 0); + ms->ref -= 1; + if (ms->ref == 0) + ms_update_status(ms); +} + +void ms_start_timer(struct GprsMs *ms) +{ + if (ms->delay == 0) + return; + + if (!ms->timer.data) + ms->timer.data = ms_ref(ms); + + osmo_timer_schedule(&ms->timer, ms->delay, 0); +} + +void ms_stop_timer(struct GprsMs *ms) +{ + if (!ms->timer.data) + return; + + osmo_timer_del(&ms->timer); + ms->timer.data = NULL; + ms_unref(ms); +} + +void ms_set_mode(struct GprsMs *ms, enum mcs_kind mode) +{ + ms->mode = mode; + + if (!ms->bts) + return; + + switch (ms->mode) { + case GPRS: + if (!mcs_is_gprs(ms->current_cs_ul)) { + ms->current_cs_ul = mcs_get_gprs_by_num( + bts_data(ms->bts)->initial_cs_ul); + if (!mcs_is_valid(ms->current_cs_ul)) + ms->current_cs_ul = CS1; + } + if (!mcs_is_gprs(ms->current_cs_dl)) { + ms->current_cs_dl = mcs_get_gprs_by_num( + bts_data(ms->bts)->initial_cs_dl); + if (!mcs_is_valid(ms->current_cs_dl)) + ms->current_cs_dl = CS1; + } + break; + + case EGPRS_GMSK: + case EGPRS: + if (!mcs_is_edge(ms->current_cs_ul)) { + ms->current_cs_ul = mcs_get_egprs_by_num( + bts_data(ms->bts)->initial_mcs_ul); + if (!mcs_is_valid(ms->current_cs_ul)) + ms->current_cs_ul = MCS1; + } + if (!mcs_is_edge(ms->current_cs_dl)) { + ms->current_cs_dl = mcs_get_egprs_by_num( + bts_data(ms->bts)->initial_mcs_dl); + if (!mcs_is_valid(ms->current_cs_dl)) + ms->current_cs_dl = MCS1; + } + break; + } +} + +static void ms_attach_ul_tbf(struct GprsMs *ms, struct gprs_rlcmac_ul_tbf *tbf) +{ + if (ms->ul_tbf == tbf) + return; + + LOGP(DRLCMAC, LOGL_INFO, "Attaching TBF to MS object, TLLI = 0x%08x, TBF = %s\n", + ms_tlli(ms), tbf_name((struct gprs_rlcmac_tbf *)tbf)); + + ms_ref(ms); + + if (ms->ul_tbf) + llist_add_tail(tbf_ms_list((struct gprs_rlcmac_tbf *)ms->ul_tbf), &ms->old_tbfs); + + ms->ul_tbf = tbf; + + if (tbf) + ms_stop_timer(ms); + + ms_unref(ms); +} + +static void ms_attach_dl_tbf(struct GprsMs *ms, struct gprs_rlcmac_dl_tbf *tbf) +{ + if (ms->dl_tbf == tbf) + return; + + LOGP(DRLCMAC, LOGL_INFO, "Attaching TBF to MS object, TLLI = 0x%08x, TBF = %s\n", + ms_tlli(ms), tbf_name((struct gprs_rlcmac_tbf *)tbf)); + + ms_ref(ms); + + if (ms->dl_tbf) + llist_add_tail(tbf_ms_list((struct gprs_rlcmac_tbf *)ms->dl_tbf), &ms->old_tbfs); + + ms->dl_tbf = tbf; + + if (tbf) + ms_stop_timer(ms); + + ms_unref(ms); +} + +void ms_attach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf) +{ + if (tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF) + ms_attach_dl_tbf(ms, as_dl_tbf(tbf)); + else + ms_attach_ul_tbf(ms, as_ul_tbf(tbf)); +} + +void ms_detach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf) +{ + if (tbf == (struct gprs_rlcmac_tbf *)(ms->ul_tbf)) { + ms->ul_tbf = NULL; + } else if (tbf == (struct gprs_rlcmac_tbf *)(ms->dl_tbf)) { + ms->dl_tbf = NULL; + } else { + bool found = false; + + struct llist_item *pos, *tmp; + llist_for_each_entry_safe(pos, tmp, &ms->old_tbfs, list) { + struct gprs_rlcmac_tbf *tmp_tbf = (struct gprs_rlcmac_tbf *)pos->entry; + if (tmp_tbf == tbf) { + llist_del(&pos->list); + found = true; + break; + } + } + + /* Protect against recursive calls via set_ms() */ + if (!found) + return; + } + + LOGP(DRLCMAC, LOGL_INFO, "Detaching TBF from MS object, TLLI = 0x%08x, TBF = %s\n", + ms_tlli(ms), tbf_name(tbf)); + + if (tbf_ms(tbf) == ms) + tbf_set_ms(tbf, NULL); + + if (!ms->dl_tbf && !ms->ul_tbf) { + ms_set_reserved_slots(ms, NULL, 0, 0); + + if (ms_tlli(ms) != 0) + ms_start_timer(ms); + } + + ms_update_status(ms); +} + +void ms_reset(struct GprsMs *ms) +{ + LOGP(DRLCMAC, LOGL_INFO, + "Clearing MS object, TLLI: 0x%08x, IMSI: '%s'\n", + ms_tlli(ms), ms_imsi(ms)); + + ms_stop_timer(ms); + + ms->tlli = GSM_RESERVED_TMSI; + ms->new_dl_tlli = ms->tlli; + ms->new_ul_tlli = ms->tlli; + ms->imsi[0] = '\0'; +} + +static void ms_merge_old_ms(struct GprsMs *ms, struct GprsMs *old_ms) +{ + OSMO_ASSERT(old_ms != ms); + + if (strlen(ms_imsi(ms)) == 0 && strlen(ms_imsi(old_ms)) != 0) + osmo_strlcpy(ms->imsi, ms_imsi(old_ms), sizeof(ms->imsi)); + + if (!ms_ms_class(ms) && ms_ms_class(old_ms)) + ms_set_ms_class(ms, ms_ms_class(old_ms)); + + if (!ms_egprs_ms_class(ms) && ms_egprs_ms_class(old_ms)) + ms_set_egprs_ms_class(ms, ms_egprs_ms_class(old_ms)); + + llc_queue_move_and_merge(&ms->llc_queue, &old_ms->llc_queue); + + ms_reset(old_ms); +} + +void ms_merge_and_clear_ms(struct GprsMs *ms, struct GprsMs *old_ms) +{ + OSMO_ASSERT(old_ms != ms); + + ms_ref(old_ms); + + /* Clean up the old MS object */ + /* TODO: Use timer? */ + if (ms_ul_tbf(old_ms) && !tbf_timers_pending((struct gprs_rlcmac_tbf *)ms_ul_tbf(old_ms), T_MAX)) + tbf_free((struct gprs_rlcmac_tbf *)ms_ul_tbf(old_ms)); + if (ms_dl_tbf(old_ms) && !tbf_timers_pending((struct gprs_rlcmac_tbf *)ms_dl_tbf(old_ms), T_MAX)) + tbf_free((struct gprs_rlcmac_tbf *)ms_dl_tbf(old_ms)); + + ms_merge_old_ms(ms, old_ms); + + ms_unref(old_ms); +} + +void ms_set_tlli(struct GprsMs *ms, uint32_t tlli) +{ + if (tlli == ms->tlli || tlli == ms->new_ul_tlli) + return; + + if (tlli != ms->new_dl_tlli) { + LOGP(DRLCMAC, LOGL_INFO, + "Modifying MS object, UL TLLI: 0x%08x -> 0x%08x, " + "not yet confirmed\n", + ms_tlli(ms), tlli); + ms->new_ul_tlli = tlli; + return; + } + + LOGP(DRLCMAC, LOGL_INFO, + "Modifying MS object, TLLI: 0x%08x -> 0x%08x, " + "already confirmed partly\n", + ms->tlli, tlli); + + ms->tlli = tlli; + ms->new_dl_tlli = GSM_RESERVED_TMSI; + ms->new_ul_tlli = GSM_RESERVED_TMSI; +} + +bool ms_confirm_tlli(struct GprsMs *ms, uint32_t tlli) +{ + if (tlli == ms->tlli || tlli == ms->new_dl_tlli) + return false; + + if (tlli != ms->new_ul_tlli) { + /* The MS has not sent a message with the new TLLI, which may + * happen according to the spec [TODO: add reference]. */ + + LOGP(DRLCMAC, LOGL_INFO, + "The MS object cannot fully confirm an unexpected TLLI: 0x%08x, " + "partly confirmed\n", tlli); + /* Use the network's idea of TLLI as candidate, this does not + * change the result value of tlli() */ + ms->new_dl_tlli = tlli; + return false; + } + + LOGP(DRLCMAC, LOGL_INFO, + "Modifying MS object, TLLI: 0x%08x confirmed\n", tlli); + + ms->tlli = tlli; + ms->new_dl_tlli = GSM_RESERVED_TMSI; + ms->new_ul_tlli = GSM_RESERVED_TMSI; + + return true; +} + +void ms_set_imsi(struct GprsMs *ms, const char *imsi) +{ + if (!imsi) { + LOGP(DRLCMAC, LOGL_ERROR, "Expected IMSI!\n"); + return; + } + + if (imsi[0] && strlen(imsi) < 3) { + LOGP(DRLCMAC, LOGL_ERROR, "No valid IMSI '%s'!\n", + imsi); + return; + } + + if (strcmp(imsi, ms->imsi) == 0) + return; + + LOGP(DRLCMAC, LOGL_INFO, + "Modifying MS object, TLLI = 0x%08x, IMSI '%s' -> '%s'\n", + ms_tlli(ms), ms->imsi, imsi); + + struct GprsMs *old_ms = bts_ms_by_imsi(ms->bts, imsi); + /* Check if we are going to store a different MS object with already + existing IMSI. This is probably a bug in code calling this function, + since it should take care of this explicitly */ + if (old_ms) { + /* We cannot find ms->ms by IMSI since we know that it has a + * different IMSI */ + OSMO_ASSERT(old_ms != ms); + + LOGPMS(ms, DRLCMAC, LOGL_NOTICE, + "IMSI '%s' was already assigned to another " + "MS object: TLLI = 0x%08x, that IMSI will be removed\n", + imsi, ms_tlli(old_ms)); + + ms_merge_and_clear_ms(ms, old_ms); + } + + + osmo_strlcpy(ms->imsi, imsi, sizeof(ms->imsi)); +} + +void ms_set_ta(struct GprsMs *ms, uint8_t ta_) +{ + if (ta_ == ms->ta) + return; + + if (gsm48_ta_is_valid(ta_)) { + LOGP(DRLCMAC, LOGL_INFO, + "Modifying MS object, TLLI = 0x%08x, TA %d -> %d\n", + ms_tlli(ms), ms->ta, ta_); + ms->ta = ta_; + } else + LOGP(DRLCMAC, LOGL_NOTICE, + "MS object, TLLI = 0x%08x, invalid TA %d rejected (old " + "value %d kept)\n", ms_tlli(ms), ta_, ms->ta); +} + +void ms_set_ms_class(struct GprsMs *ms, uint8_t ms_class_) +{ + if (ms_class_ == ms->ms_class) + return; + + LOGP(DRLCMAC, LOGL_INFO, + "Modifying MS object, TLLI = 0x%08x, MS class %d -> %d\n", + ms_tlli(ms), ms->ms_class, ms_class_); + + ms->ms_class = ms_class_; +} + +void ms_set_egprs_ms_class(struct GprsMs *ms, uint8_t ms_class_) +{ + if (ms_class_ == ms->egprs_ms_class) + return; + + LOGP(DRLCMAC, LOGL_INFO, + "Modifying MS object, TLLI = 0x%08x, EGPRS MS class %d -> %d\n", + ms_tlli(ms), ms->egprs_ms_class, ms_class_); + + ms->egprs_ms_class = ms_class_; + + if (!bts_max_mcs_ul(ms->bts) || !bts_max_mcs_dl(ms->bts)) { + LOGPMS(ms, DRLCMAC, LOGL_DEBUG, + "Avoid enabling EGPRS because use of MCS is disabled: ul=%u dl=%u\n", + bts_max_mcs_ul(ms->bts), bts_max_mcs_dl(ms->bts)); + return; + } + + if (mcs_is_edge_gmsk(mcs_get_egprs_by_num(bts_max_mcs_ul(ms->bts))) && + mcs_is_edge_gmsk(mcs_get_egprs_by_num(bts_max_mcs_dl(ms->bts))) && + ms_mode(ms) != EGPRS) + { + ms_set_mode(ms, EGPRS_GMSK); + } else { + ms_set_mode(ms, EGPRS); + } + LOGPMS(ms, DRLCMAC, LOGL_INFO, "Enabled EGPRS, mode %s\n", mode_name(ms_mode(ms))); +} + +void ms_update_error_rate(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, int error_rate) +{ + struct gprs_rlcmac_bts *bts_; + int64_t now; + enum CodingScheme max_cs_dl = ms_max_cs_dl(ms); + + OSMO_ASSERT(max_cs_dl); + bts_ = bts_data(ms->bts); + + if (error_rate < 0) + return; + + now = now_msec(); + + /* TODO: Check for TBF direction */ + /* TODO: Support different CS values for UL and DL */ + + ms->nack_rate_dl = error_rate; + + if (error_rate > bts_->cs_adj_upper_limit) { + if (mcs_chan_code(ms->current_cs_dl) > 0) { + mcs_dec_kind(&ms->current_cs_dl, ms_mode(ms)); + LOGP(DRLCMACDL, LOGL_INFO, + "MS (IMSI %s): High error rate %d%%, " + "reducing CS level to %s\n", + ms_imsi(ms), error_rate, mcs_name(ms->current_cs_dl)); + ms->last_cs_not_low = now; + } + } else if (error_rate < bts_->cs_adj_lower_limit) { + if (ms->current_cs_dl < max_cs_dl) { + if (now - ms->last_cs_not_low > 1000) { + mcs_inc_kind(&ms->current_cs_dl, ms_mode(ms)); + + LOGP(DRLCMACDL, LOGL_INFO, + "MS (IMSI %s): Low error rate %d%%, " + "increasing DL CS level to %s\n", + ms_imsi(ms), error_rate, + mcs_name(ms->current_cs_dl)); + ms->last_cs_not_low = now; + } else { + LOGP(DRLCMACDL, LOGL_DEBUG, + "MS (IMSI %s): Low error rate %d%%, " + "ignored (within blocking period)\n", + ms_imsi(ms), error_rate); + } + } + } else { + LOGP(DRLCMACDL, LOGL_DEBUG, + "MS (IMSI %s): Medium error rate %d%%, ignored\n", + ms_imsi(ms), error_rate); + ms->last_cs_not_low = now; + } +} + +enum CodingScheme ms_max_cs_ul(const struct GprsMs *ms) +{ + OSMO_ASSERT(ms->bts != NULL); + + if (mcs_is_gprs(ms->current_cs_ul)) { + if (!bts_max_cs_ul(ms->bts)) { + return CS4; + } + + return mcs_get_gprs_by_num(bts_max_cs_ul(ms->bts)); + } + + if (!mcs_is_edge(ms->current_cs_ul)) + return UNKNOWN; + + if (bts_max_mcs_ul(ms->bts)) + return mcs_get_egprs_by_num(bts_max_mcs_ul(ms->bts)); + else if (bts_max_cs_ul(ms->bts)) + return mcs_get_gprs_by_num(bts_max_cs_ul(ms->bts)); + + return MCS4; +} + +void ms_set_current_cs_dl(struct GprsMs *ms, enum CodingScheme scheme) +{ + ms->current_cs_dl = scheme; +} + +enum CodingScheme ms_max_cs_dl(const struct GprsMs *ms) +{ + OSMO_ASSERT(ms->bts != NULL); + + if (mcs_is_gprs(ms->current_cs_dl)) { + if (!bts_max_cs_dl(ms->bts)) { + return CS4; + } + + return mcs_get_gprs_by_num(bts_max_cs_dl(ms->bts)); + } + + if (!mcs_is_edge(ms->current_cs_dl)) + return UNKNOWN; + + if (bts_max_mcs_dl(ms->bts)) + return mcs_get_egprs_by_num(bts_max_mcs_dl(ms->bts)); + else if (bts_max_cs_dl(ms->bts)) + return mcs_get_gprs_by_num(bts_max_cs_dl(ms->bts)); + + return MCS4; +} + +void ms_update_cs_ul(struct GprsMs *ms, const struct pcu_l1_meas *meas) +{ + struct gprs_rlcmac_bts *bts_; + enum CodingScheme max_cs_ul = ms_max_cs_ul(ms); + + int old_link_qual; + int low; + int high; + enum CodingScheme new_cs_ul = ms->current_cs_ul; + uint8_t current_cs = mcs_chan_code(ms->current_cs_ul); + + bts_ = bts_data(ms->bts); + + if (!max_cs_ul) { + LOGP(DRLCMACMEAS, LOGL_ERROR, + "max_cs_ul cannot be derived (current UL CS: %s)\n", + mcs_name(ms->current_cs_ul)); + return; + } + + if (!ms->current_cs_ul) { + LOGP(DRLCMACMEAS, LOGL_ERROR, + "Unable to update UL (M)CS because it's not set: %s\n", + mcs_name(ms->current_cs_ul)); + return; + } + + if (!meas->have_link_qual) { + LOGP(DRLCMACMEAS, LOGL_ERROR, + "Unable to update UL (M)CS %s because we don't have link quality measurements.\n", + mcs_name(ms->current_cs_ul)); + return; + } + + if (mcs_is_gprs(ms->current_cs_ul)) { + if (current_cs >= MAX_GPRS_CS) + current_cs = MAX_GPRS_CS - 1; + low = bts_->cs_lqual_ranges[current_cs].low; + high = bts_->cs_lqual_ranges[current_cs].high; + } else if (mcs_is_edge(ms->current_cs_ul)) { + if (current_cs >= MAX_EDGE_MCS) + current_cs = MAX_EDGE_MCS - 1; + low = bts_->mcs_lqual_ranges[current_cs].low; + high = bts_->mcs_lqual_ranges[current_cs].high; + } else { + LOGP(DRLCMACMEAS, LOGL_ERROR, + "Unable to update UL (M)CS because it's neither GPRS nor EDGE: %s\n", + mcs_name(ms->current_cs_ul)); + return; + } + + /* To avoid rapid changes of the coding scheme, we also take + * the old link quality value into account (if present). */ + if (ms->l1_meas.have_link_qual) + old_link_qual = ms->l1_meas.link_qual; + else + old_link_qual = meas->link_qual; + + if (meas->link_qual < low && old_link_qual < low) + mcs_dec_kind(&new_cs_ul, ms_mode(ms)); + else if (meas->link_qual > high && old_link_qual > high && + ms->current_cs_ul < max_cs_ul) + mcs_inc_kind(&new_cs_ul, ms_mode(ms)); + + if (ms->current_cs_ul != new_cs_ul) { + LOGPMS(ms, DRLCMACMEAS, LOGL_INFO, + "Link quality %ddB (old %ddB) left window [%d, %d], " + "modifying uplink CS level: %s -> %s\n", + meas->link_qual, old_link_qual, + low, high, + mcs_name(ms->current_cs_ul), mcs_name(new_cs_ul)); + + ms->current_cs_ul = new_cs_ul; + } +} + +void ms_update_l1_meas(struct GprsMs *ms, const struct pcu_l1_meas *meas) +{ + unsigned i; + + ms_update_cs_ul(ms, meas); + + if (meas->have_rssi) + pcu_l1_meas_set_rssi(&ms->l1_meas, meas->rssi); + if (meas->have_bto) + pcu_l1_meas_set_bto(&ms->l1_meas, meas->bto); + if (meas->have_ber) + pcu_l1_meas_set_ber(&ms->l1_meas, meas->ber); + if (meas->have_link_qual) + pcu_l1_meas_set_link_qual(&ms->l1_meas, meas->link_qual); + + if (meas->have_ms_rx_qual) + pcu_l1_meas_set_ms_rx_qual(&ms->l1_meas, meas->ms_rx_qual); + if (meas->have_ms_c_value) + pcu_l1_meas_set_ms_c_value(&ms->l1_meas, meas->ms_c_value); + if (meas->have_ms_sign_var) + pcu_l1_meas_set_ms_sign_var(&ms->l1_meas, meas->ms_sign_var); + + if (meas->have_ms_i_level) { + for (i = 0; i < ARRAY_SIZE(meas->ts); ++i) { + if (meas->ts[i].have_ms_i_level) + pcu_l1_meas_set_ms_i_level(&ms->l1_meas, i, meas->ts[i].ms_i_level); + else + ms->l1_meas.ts[i].have_ms_i_level = 0; + } + } +} + +enum CodingScheme ms_current_cs_dl(const struct GprsMs *ms) +{ + enum CodingScheme cs = ms->current_cs_dl; + size_t unencoded_octets; + + if (!ms->bts) + return cs; + + unencoded_octets = llc_queue_octets(&ms->llc_queue); + + /* If the DL TBF is active, add number of unencoded chunk octets */ + if (ms->dl_tbf) + unencoded_octets += llc_chunk_size(tbf_llc((struct gprs_rlcmac_tbf *)ms->dl_tbf)); + + /* There are many unencoded octets, don't reduce */ + if (unencoded_octets >= bts_data(ms->bts)->cs_downgrade_threshold) + return cs; + + /* RF conditions are good, don't reduce */ + if (ms->nack_rate_dl < bts_data(ms->bts)->cs_adj_lower_limit) + return cs; + + /* The throughput would probably be better if the CS level was reduced */ + mcs_dec_kind(&cs, ms_mode(ms)); + + /* CS-2 doesn't gain throughput with small packets, further reduce to CS-1 */ + if (cs == CS2) + mcs_dec_kind(&cs, ms_mode(ms)); + + return cs; +} + +int ms_first_common_ts(const struct GprsMs *ms) +{ + if (ms->dl_tbf) + return tbf_first_common_ts((struct gprs_rlcmac_tbf *)ms->dl_tbf); + + if (ms->ul_tbf) + return tbf_first_common_ts((struct gprs_rlcmac_tbf *)ms->ul_tbf); + + return -1; +} + +uint8_t ms_dl_slots(const struct GprsMs *ms) +{ + uint8_t slots = 0; + + if (ms->dl_tbf) + slots |= tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf); + + if (ms->ul_tbf) + slots |= tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf); + + return slots; +} + +uint8_t ms_ul_slots(const struct GprsMs *ms) +{ + uint8_t slots = 0; + + if (ms->dl_tbf) + slots |= tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf); + + if (ms->ul_tbf) + slots |= tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf); + + return slots; +} + +uint8_t ms_current_pacch_slots(const struct GprsMs *ms) +{ + uint8_t slots = 0; + + bool is_dl_active = ms->dl_tbf && tbf_is_tfi_assigned((struct gprs_rlcmac_tbf *)ms->dl_tbf); + bool is_ul_active = ms->ul_tbf && tbf_is_tfi_assigned((struct gprs_rlcmac_tbf *)ms->ul_tbf); + + if (!is_dl_active && !is_ul_active) + return 0; + + /* see TS 44.060, 8.1.1.2.2 */ + if (is_dl_active && !is_ul_active) + slots = tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf); + else if (!is_dl_active && is_ul_active) + slots = tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf); + else + slots = tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf) & + tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf); + + /* Assume a multislot class 1 device */ + /* TODO: For class 2 devices, this could be removed */ + slots = pcu_lsb(slots); + + return slots; +} + +void ms_set_reserved_slots(struct GprsMs *ms, struct gprs_rlcmac_trx *trx, + uint8_t ul_slots, uint8_t dl_slots) +{ + if (ms->current_trx) { + bts_trx_unreserve_slots(ms->current_trx, GPRS_RLCMAC_DL_TBF, + ms->reserved_dl_slots); + bts_trx_unreserve_slots(ms->current_trx, GPRS_RLCMAC_UL_TBF, + ms->reserved_ul_slots); + ms->reserved_dl_slots = 0; + ms->reserved_ul_slots = 0; + } + ms->current_trx = trx; + if (trx) { + ms->reserved_dl_slots = dl_slots; + ms->reserved_ul_slots = ul_slots; + bts_trx_reserve_slots(ms->current_trx, GPRS_RLCMAC_DL_TBF, + ms->reserved_dl_slots); + bts_trx_reserve_slots(ms->current_trx, GPRS_RLCMAC_UL_TBF, + ms->reserved_ul_slots); + } +} + +struct gprs_rlcmac_tbf *ms_tbf(const struct GprsMs *ms, enum gprs_rlcmac_tbf_direction dir) +{ + switch (dir) { + case GPRS_RLCMAC_DL_TBF: return (struct gprs_rlcmac_tbf *)ms->dl_tbf; + case GPRS_RLCMAC_UL_TBF: return (struct gprs_rlcmac_tbf *)ms->ul_tbf; + } + + return NULL; +} |