| /* gprs_ms.cpp |
| * |
| * Copyright (C) 2015 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 <gprs_coding_scheme.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> |
| |
| extern "C" { |
| #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/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; |
| } |
| |
| struct GprsMsDefaultCallback: public GprsMs::Callback { |
| virtual void ms_idle(class GprsMs *ms) { |
| delete ms; |
| } |
| virtual void ms_active(class GprsMs *) {} |
| }; |
| |
| static GprsMsDefaultCallback gprs_default_cb; |
| |
| GprsMs::Guard::Guard(GprsMs *ms) : |
| m_ms(ms ? ms->ref() : NULL) |
| { |
| } |
| |
| GprsMs::Guard::~Guard() |
| { |
| if (m_ms) |
| m_ms->unref(); |
| } |
| |
| bool GprsMs::Guard::is_idle() const |
| { |
| if (!m_ms) |
| return true; |
| |
| return !m_ms->m_ul_tbf && !m_ms->m_dl_tbf && m_ms->m_ref == 1; |
| } |
| |
| void GprsMs::timeout(void *priv_) |
| { |
| GprsMs *ms = static_cast<GprsMs *>(priv_); |
| |
| LOGP(DRLCMAC, LOGL_INFO, "Timeout for MS object, TLLI = 0x%08x\n", |
| ms->tlli()); |
| |
| if (ms->m_timer.data) { |
| ms->m_timer.data = NULL; |
| ms->unref(); |
| } |
| } |
| |
| GprsMs::GprsMs(BTS *bts, uint32_t tlli) : |
| m_bts(bts), |
| m_cb(&gprs_default_cb), |
| m_ul_tbf(NULL), |
| m_dl_tbf(NULL), |
| m_tlli(tlli), |
| m_new_ul_tlli(0), |
| m_new_dl_tlli(0), |
| m_ta(GSM48_TA_INVALID), |
| m_ms_class(0), |
| m_egprs_ms_class(0), |
| m_is_idle(true), |
| m_ref(0), |
| m_list(this), |
| m_delay(0), |
| m_nack_rate_dl(0), |
| m_reserved_dl_slots(0), |
| m_reserved_ul_slots(0), |
| m_current_trx(NULL), |
| m_codel_state(NULL), |
| m_mode(GPRS), |
| m_dl_ctrl_msg(0) |
| { |
| int codel_interval = LLC_CODEL_USE_DEFAULT; |
| |
| LOGP(DRLCMAC, LOGL_INFO, "Creating MS object, TLLI = 0x%08x\n", tlli); |
| |
| m_imsi[0] = 0; |
| memset(&m_timer, 0, sizeof(m_timer)); |
| m_timer.cb = GprsMs::timeout; |
| m_llc_queue.init(); |
| |
| set_mode(m_mode); |
| |
| if (m_bts) |
| codel_interval = m_bts->bts_data()->llc_codel_interval_msec; |
| |
| if (codel_interval) { |
| if (codel_interval == LLC_CODEL_USE_DEFAULT) |
| codel_interval = GPRS_CODEL_SLOW_INTERVAL_MS; |
| m_codel_state = talloc(this, struct gprs_codel); |
| gprs_codel_init(m_codel_state); |
| gprs_codel_set_interval(m_codel_state, codel_interval); |
| } |
| m_last_cs_not_low = now_msec(); |
| app_info_pending = false; |
| } |
| |
| GprsMs::~GprsMs() |
| { |
| LListHead<gprs_rlcmac_tbf> *pos, *tmp; |
| |
| LOGP(DRLCMAC, LOGL_INFO, "Destroying MS object, TLLI = 0x%08x\n", tlli()); |
| |
| set_reserved_slots(NULL, 0, 0); |
| |
| if (osmo_timer_pending(&m_timer)) |
| osmo_timer_del(&m_timer); |
| |
| if (m_ul_tbf) { |
| m_ul_tbf->set_ms(NULL); |
| m_ul_tbf = NULL; |
| } |
| |
| if (m_dl_tbf) { |
| m_dl_tbf->set_ms(NULL); |
| m_dl_tbf = NULL; |
| } |
| |
| llist_for_each_safe(pos, tmp, &m_old_tbfs) |
| pos->entry()->set_ms(NULL); |
| |
| m_llc_queue.clear(m_bts); |
| } |
| |
| void* GprsMs::operator new(size_t size) |
| { |
| static void *tall_ms_ctx = NULL; |
| if (!tall_ms_ctx) |
| tall_ms_ctx = talloc_named_const(tall_pcu_ctx, 0, __PRETTY_FUNCTION__); |
| |
| return talloc_size(tall_ms_ctx, size); |
| } |
| |
| void GprsMs::operator delete(void* p) |
| { |
| talloc_free(p); |
| } |
| |
| GprsMs *GprsMs::ref() |
| { |
| m_ref += 1; |
| return this; |
| } |
| |
| void GprsMs::unref() |
| { |
| OSMO_ASSERT(m_ref >= 0); |
| m_ref -= 1; |
| if (m_ref == 0) |
| update_status(); |
| } |
| |
| void GprsMs::start_timer() |
| { |
| if (m_delay == 0) |
| return; |
| |
| if (!m_timer.data) |
| m_timer.data = ref(); |
| |
| osmo_timer_schedule(&m_timer, m_delay, 0); |
| } |
| |
| void GprsMs::stop_timer() |
| { |
| if (!m_timer.data) |
| return; |
| |
| osmo_timer_del(&m_timer); |
| m_timer.data = NULL; |
| unref(); |
| } |
| |
| void GprsMs::set_mode(enum mcs_kind mode) |
| { |
| m_mode = mode; |
| |
| if (!m_bts) |
| return; |
| |
| switch (m_mode) { |
| case GPRS: |
| if (!mcs_is_gprs(m_current_cs_ul)) { |
| m_current_cs_ul = GprsCodingScheme::getGprsByNum( |
| m_bts->bts_data()->initial_cs_ul); |
| if (!m_current_cs_ul.isValid()) |
| m_current_cs_ul = CS1; |
| } |
| if (!mcs_is_gprs(m_current_cs_dl)) { |
| m_current_cs_dl = GprsCodingScheme::getGprsByNum( |
| m_bts->bts_data()->initial_cs_dl); |
| if (!m_current_cs_dl.isValid()) |
| m_current_cs_dl = CS1; |
| } |
| break; |
| |
| case EGPRS_GMSK: |
| case EGPRS: |
| if (!mcs_is_edge(m_current_cs_ul)) { |
| m_current_cs_ul = GprsCodingScheme::getEgprsByNum( |
| m_bts->bts_data()->initial_mcs_ul); |
| if (!m_current_cs_ul.isValid()) |
| m_current_cs_ul = MCS1; |
| } |
| if (!mcs_is_edge(m_current_cs_dl)) { |
| m_current_cs_dl = GprsCodingScheme::getEgprsByNum( |
| m_bts->bts_data()->initial_mcs_dl); |
| if (!m_current_cs_dl.isValid()) |
| m_current_cs_dl = MCS1; |
| } |
| break; |
| } |
| } |
| |
| void GprsMs::attach_tbf(struct gprs_rlcmac_tbf *tbf) |
| { |
| if (tbf->direction == GPRS_RLCMAC_DL_TBF) |
| attach_dl_tbf(as_dl_tbf(tbf)); |
| else |
| attach_ul_tbf(as_ul_tbf(tbf)); |
| } |
| |
| void GprsMs::attach_ul_tbf(struct gprs_rlcmac_ul_tbf *tbf) |
| { |
| if (m_ul_tbf == tbf) |
| return; |
| |
| LOGP(DRLCMAC, LOGL_INFO, "Attaching TBF to MS object, TLLI = 0x%08x, TBF = %s\n", |
| tlli(), tbf->name()); |
| |
| Guard guard(this); |
| |
| if (m_ul_tbf) |
| llist_add_tail(&m_ul_tbf->ms_list(), &m_old_tbfs); |
| |
| m_ul_tbf = tbf; |
| |
| if (tbf) |
| stop_timer(); |
| } |
| |
| void GprsMs::attach_dl_tbf(struct gprs_rlcmac_dl_tbf *tbf) |
| { |
| if (m_dl_tbf == tbf) |
| return; |
| |
| LOGP(DRLCMAC, LOGL_INFO, "Attaching TBF to MS object, TLLI = 0x%08x, TBF = %s\n", |
| tlli(), tbf->name()); |
| |
| Guard guard(this); |
| |
| if (m_dl_tbf) |
| llist_add_tail(&m_dl_tbf->ms_list(), &m_old_tbfs); |
| |
| m_dl_tbf = tbf; |
| |
| if (tbf) |
| stop_timer(); |
| } |
| |
| void GprsMs::detach_tbf(gprs_rlcmac_tbf *tbf) |
| { |
| if (tbf == static_cast<gprs_rlcmac_tbf *>(m_ul_tbf)) { |
| m_ul_tbf = NULL; |
| } else if (tbf == static_cast<gprs_rlcmac_tbf *>(m_dl_tbf)) { |
| m_dl_tbf = NULL; |
| } else { |
| bool found = false; |
| |
| LListHead<gprs_rlcmac_tbf> *pos, *tmp; |
| llist_for_each_safe(pos, tmp, &m_old_tbfs) { |
| if (pos->entry() == tbf) { |
| llist_del(pos); |
| 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", |
| tlli(), tbf->name()); |
| |
| if (tbf->ms() == this) |
| tbf->set_ms(NULL); |
| |
| if (!m_dl_tbf && !m_ul_tbf) { |
| set_reserved_slots(NULL, 0, 0); |
| |
| if (tlli() != 0) |
| start_timer(); |
| } |
| |
| update_status(); |
| } |
| |
| void GprsMs::update_status() |
| { |
| if (m_ref > 0) |
| return; |
| |
| if (is_idle() && !m_is_idle) { |
| m_is_idle = true; |
| m_cb->ms_idle(this); |
| /* this can be deleted by now, do not access it */ |
| return; |
| } |
| |
| if (!is_idle() && m_is_idle) { |
| m_is_idle = false; |
| m_cb->ms_active(this); |
| } |
| } |
| |
| void GprsMs::reset() |
| { |
| LOGP(DRLCMAC, LOGL_INFO, |
| "Clearing MS object, TLLI: 0x%08x, IMSI: '%s'\n", |
| tlli(), imsi()); |
| |
| stop_timer(); |
| |
| m_tlli = 0; |
| m_new_dl_tlli = 0; |
| m_new_ul_tlli = 0; |
| m_imsi[0] = '\0'; |
| } |
| |
| void GprsMs::merge_old_ms(GprsMs *old_ms) |
| { |
| if (old_ms == this) |
| return; |
| |
| if (strlen(imsi()) == 0 && strlen(old_ms->imsi()) != 0) |
| set_imsi(old_ms->imsi()); |
| |
| if (!ms_class() && old_ms->ms_class()) |
| set_ms_class(old_ms->ms_class()); |
| |
| m_llc_queue.move_and_merge(&old_ms->m_llc_queue); |
| |
| old_ms->reset(); |
| } |
| |
| void GprsMs::set_tlli(uint32_t tlli) |
| { |
| if (tlli == m_tlli || tlli == m_new_ul_tlli) |
| return; |
| |
| if (tlli != m_new_dl_tlli) { |
| LOGP(DRLCMAC, LOGL_INFO, |
| "Modifying MS object, UL TLLI: 0x%08x -> 0x%08x, " |
| "not yet confirmed\n", |
| this->tlli(), tlli); |
| m_new_ul_tlli = tlli; |
| return; |
| } |
| |
| LOGP(DRLCMAC, LOGL_INFO, |
| "Modifying MS object, TLLI: 0x%08x -> 0x%08x, " |
| "already confirmed partly\n", |
| m_tlli, tlli); |
| |
| m_tlli = tlli; |
| m_new_dl_tlli = 0; |
| m_new_ul_tlli = 0; |
| } |
| |
| bool GprsMs::confirm_tlli(uint32_t tlli) |
| { |
| if (tlli == m_tlli || tlli == m_new_dl_tlli) |
| return false; |
| |
| if (tlli != m_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() */ |
| m_new_dl_tlli = tlli; |
| return false; |
| } |
| |
| LOGP(DRLCMAC, LOGL_INFO, |
| "Modifying MS object, TLLI: 0x%08x confirmed\n", tlli); |
| |
| m_tlli = tlli; |
| m_new_dl_tlli = 0; |
| m_new_ul_tlli = 0; |
| |
| return true; |
| } |
| |
| void GprsMs::set_imsi(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, m_imsi) == 0) |
| return; |
| |
| LOGP(DRLCMAC, LOGL_INFO, |
| "Modifying MS object, TLLI = 0x%08x, IMSI '%s' -> '%s'\n", |
| tlli(), m_imsi, imsi); |
| |
| strncpy(m_imsi, imsi, sizeof(m_imsi)); |
| m_imsi[sizeof(m_imsi) - 1] = '\0'; |
| } |
| |
| void GprsMs::set_ta(uint8_t ta_) |
| { |
| if (ta_ == m_ta) |
| return; |
| |
| if (gsm48_ta_is_valid(ta_)) { |
| LOGP(DRLCMAC, LOGL_INFO, |
| "Modifying MS object, TLLI = 0x%08x, TA %d -> %d\n", |
| tlli(), m_ta, ta_); |
| m_ta = ta_; |
| } else |
| LOGP(DRLCMAC, LOGL_NOTICE, |
| "MS object, TLLI = 0x%08x, invalid TA %d rejected (old " |
| "value %d kept)\n", tlli(), ta_, m_ta); |
| } |
| |
| void GprsMs::set_ms_class(uint8_t ms_class_) |
| { |
| if (ms_class_ == m_ms_class) |
| return; |
| |
| LOGP(DRLCMAC, LOGL_INFO, |
| "Modifying MS object, TLLI = 0x%08x, MS class %d -> %d\n", |
| tlli(), m_ms_class, ms_class_); |
| |
| m_ms_class = ms_class_; |
| } |
| |
| void GprsMs::set_egprs_ms_class(uint8_t ms_class_) |
| { |
| if (ms_class_ == m_egprs_ms_class) |
| return; |
| |
| LOGP(DRLCMAC, LOGL_INFO, |
| "Modifying MS object, TLLI = 0x%08x, EGPRS MS class %d -> %d\n", |
| tlli(), m_egprs_ms_class, ms_class_); |
| |
| m_egprs_ms_class = ms_class_; |
| } |
| |
| void GprsMs::update_error_rate(gprs_rlcmac_tbf *tbf, int error_rate) |
| { |
| struct gprs_rlcmac_bts *bts_data; |
| int64_t now; |
| GprsCodingScheme max_cs_dl = this->max_cs_dl(); |
| |
| OSMO_ASSERT(max_cs_dl); |
| bts_data = m_bts->bts_data(); |
| |
| if (error_rate < 0) |
| return; |
| |
| now = now_msec(); |
| |
| /* TODO: Check for TBF direction */ |
| /* TODO: Support different CS values for UL and DL */ |
| |
| m_nack_rate_dl = error_rate; |
| |
| if (error_rate > bts_data->cs_adj_upper_limit) { |
| if (mcs_chan_code(m_current_cs_dl) > 0) { |
| m_current_cs_dl.dec(mode()); |
| LOGP(DRLCMACDL, LOGL_INFO, |
| "MS (IMSI %s): High error rate %d%%, " |
| "reducing CS level to %s\n", |
| imsi(), error_rate, mcs_name(m_current_cs_dl)); |
| m_last_cs_not_low = now; |
| } |
| } else if (error_rate < bts_data->cs_adj_lower_limit) { |
| if (m_current_cs_dl < max_cs_dl) { |
| if (now - m_last_cs_not_low > 1000) { |
| m_current_cs_dl.inc(mode()); |
| |
| LOGP(DRLCMACDL, LOGL_INFO, |
| "MS (IMSI %s): Low error rate %d%%, " |
| "increasing DL CS level to %s\n", |
| imsi(), error_rate, |
| mcs_name(m_current_cs_dl)); |
| m_last_cs_not_low = now; |
| } else { |
| LOGP(DRLCMACDL, LOGL_DEBUG, |
| "MS (IMSI %s): Low error rate %d%%, " |
| "ignored (within blocking period)\n", |
| imsi(), error_rate); |
| } |
| } |
| } else { |
| LOGP(DRLCMACDL, LOGL_DEBUG, |
| "MS (IMSI %s): Medium error rate %d%%, ignored\n", |
| imsi(), error_rate); |
| m_last_cs_not_low = now; |
| } |
| } |
| |
| GprsCodingScheme GprsMs::max_cs_ul() const |
| { |
| struct gprs_rlcmac_bts *bts_data; |
| |
| OSMO_ASSERT(m_bts != NULL); |
| bts_data = m_bts->bts_data(); |
| |
| if (mcs_is_gprs(m_current_cs_ul)) { |
| if (!bts_data->max_cs_ul) |
| return GprsCodingScheme(CS4); |
| |
| return GprsCodingScheme::getGprsByNum(bts_data->max_cs_ul); |
| } |
| |
| if (!mcs_is_edge(m_current_cs_ul)) |
| return GprsCodingScheme(); /* UNKNOWN */ |
| |
| if (bts_data->max_mcs_ul) |
| return GprsCodingScheme::getEgprsByNum(bts_data->max_mcs_ul); |
| else if (bts_data->max_cs_ul) |
| return GprsCodingScheme::getEgprsByNum(bts_data->max_cs_ul); |
| |
| return GprsCodingScheme(MCS4); |
| } |
| |
| void GprsMs::set_current_cs_dl(CodingScheme scheme) |
| { |
| m_current_cs_dl = scheme; |
| } |
| |
| GprsCodingScheme GprsMs::max_cs_dl() const |
| { |
| struct gprs_rlcmac_bts *bts_data; |
| |
| OSMO_ASSERT(m_bts != NULL); |
| bts_data = m_bts->bts_data(); |
| |
| if (mcs_is_gprs(m_current_cs_dl)) { |
| if (!bts_data->max_cs_dl) |
| return GprsCodingScheme(CS4); |
| |
| return GprsCodingScheme::getGprsByNum(bts_data->max_cs_dl); |
| } |
| |
| if (!mcs_is_edge(m_current_cs_dl)) |
| return GprsCodingScheme(); /* UNKNOWN */ |
| |
| if (bts_data->max_mcs_dl) |
| return GprsCodingScheme::getEgprsByNum(bts_data->max_mcs_dl); |
| else if (bts_data->max_cs_dl) |
| return GprsCodingScheme::getEgprsByNum(bts_data->max_cs_dl); |
| |
| return GprsCodingScheme(MCS4); |
| } |
| |
| void GprsMs::update_cs_ul(const pcu_l1_meas *meas) |
| { |
| struct gprs_rlcmac_bts *bts_data; |
| GprsCodingScheme max_cs_ul = this->max_cs_ul(); |
| |
| int old_link_qual; |
| int low; |
| int high; |
| GprsCodingScheme new_cs_ul = m_current_cs_ul; |
| uint8_t current_cs = mcs_chan_code(m_current_cs_ul); |
| |
| bts_data = m_bts->bts_data(); |
| |
| if (!max_cs_ul) { |
| LOGP(DRLCMACMEAS, LOGL_ERROR, |
| "max_cs_ul cannot be derived (current UL CS: %s)\n", |
| mcs_name(m_current_cs_ul)); |
| return; |
| } |
| |
| if (!m_current_cs_ul) { |
| LOGP(DRLCMACMEAS, LOGL_ERROR, |
| "Unable to update UL (M)CS because it's not set: %s\n", |
| mcs_name(m_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(m_current_cs_ul)); |
| return; |
| } |
| |
| if (mcs_is_gprs(m_current_cs_ul)) { |
| if (current_cs >= MAX_GPRS_CS) |
| current_cs = MAX_GPRS_CS - 1; |
| low = bts_data->cs_lqual_ranges[current_cs].low; |
| high = bts_data->cs_lqual_ranges[current_cs].high; |
| } else if (mcs_is_edge(m_current_cs_ul)) { |
| if (current_cs >= MAX_EDGE_MCS) |
| current_cs = MAX_EDGE_MCS - 1; |
| low = bts_data->mcs_lqual_ranges[current_cs].low; |
| high = bts_data->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(m_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 (m_l1_meas.have_link_qual) |
| old_link_qual = m_l1_meas.link_qual; |
| else |
| old_link_qual = meas->link_qual; |
| |
| if (meas->link_qual < low && old_link_qual < low) |
| new_cs_ul.dec(mode()); |
| else if (meas->link_qual > high && old_link_qual > high && |
| m_current_cs_ul < max_cs_ul) |
| new_cs_ul.inc(mode()); |
| |
| if (m_current_cs_ul != new_cs_ul) { |
| LOGP(DRLCMACMEAS, LOGL_INFO, |
| "MS (IMSI %s): " |
| "Link quality %ddB (old %ddB) left window [%d, %d], " |
| "modifying uplink CS level: %s -> %s\n", |
| imsi(), meas->link_qual, old_link_qual, |
| low, high, |
| mcs_name(m_current_cs_ul), mcs_name(new_cs_ul)); |
| |
| m_current_cs_ul = new_cs_ul; |
| } |
| } |
| |
| void GprsMs::update_l1_meas(const pcu_l1_meas *meas) |
| { |
| unsigned i; |
| |
| update_cs_ul(meas); |
| |
| if (meas->have_rssi) |
| m_l1_meas.set_rssi(meas->rssi); |
| if (meas->have_bto) |
| m_l1_meas.set_bto(meas->bto); |
| if (meas->have_ber) |
| m_l1_meas.set_ber(meas->ber); |
| if (meas->have_link_qual) |
| m_l1_meas.set_link_qual(meas->link_qual); |
| |
| if (meas->have_ms_rx_qual) |
| m_l1_meas.set_ms_rx_qual(meas->ms_rx_qual); |
| if (meas->have_ms_c_value) |
| m_l1_meas.set_ms_c_value(meas->ms_c_value); |
| if (meas->have_ms_sign_var) |
| m_l1_meas.set_ms_sign_var(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) |
| m_l1_meas.set_ms_i_level(i, meas->ts[i].ms_i_level); |
| else |
| m_l1_meas.ts[i].have_ms_i_level = 0; |
| } |
| } |
| } |
| |
| GprsCodingScheme GprsMs::current_cs_dl() const |
| { |
| GprsCodingScheme cs = m_current_cs_dl; |
| size_t unencoded_octets; |
| |
| if (!m_bts) |
| return cs; |
| |
| unencoded_octets = m_llc_queue.octets(); |
| |
| /* If the DL TBF is active, add number of unencoded chunk octets */ |
| if (m_dl_tbf) |
| unencoded_octets += m_dl_tbf->m_llc.chunk_size(); |
| |
| /* There are many unencoded octets, don't reduce */ |
| if (unencoded_octets >= m_bts->bts_data()->cs_downgrade_threshold) |
| return cs; |
| |
| /* RF conditions are good, don't reduce */ |
| if (m_nack_rate_dl < m_bts->bts_data()->cs_adj_lower_limit) |
| return cs; |
| |
| /* The throughput would probably be better if the CS level was reduced */ |
| cs.dec(mode()); |
| |
| /* CS-2 doesn't gain throughput with small packets, further reduce to CS-1 */ |
| if (cs == GprsCodingScheme(CS2)) |
| cs.dec(mode()); |
| |
| return cs; |
| } |
| |
| int GprsMs::first_common_ts() const |
| { |
| if (m_dl_tbf) |
| return m_dl_tbf->first_common_ts; |
| |
| if (m_ul_tbf) |
| return m_ul_tbf->first_common_ts; |
| |
| return -1; |
| } |
| |
| uint8_t GprsMs::dl_slots() const |
| { |
| uint8_t slots = 0; |
| |
| if (m_dl_tbf) |
| slots |= m_dl_tbf->dl_slots(); |
| |
| if (m_ul_tbf) |
| slots |= m_ul_tbf->dl_slots(); |
| |
| return slots; |
| } |
| |
| uint8_t GprsMs::ul_slots() const |
| { |
| uint8_t slots = 0; |
| |
| if (m_dl_tbf) |
| slots |= m_dl_tbf->ul_slots(); |
| |
| if (m_ul_tbf) |
| slots |= m_ul_tbf->ul_slots(); |
| |
| return slots; |
| } |
| |
| uint8_t GprsMs::current_pacch_slots() const |
| { |
| uint8_t slots = 0; |
| |
| bool is_dl_active = m_dl_tbf && m_dl_tbf->is_tfi_assigned(); |
| bool is_ul_active = m_ul_tbf && m_ul_tbf->is_tfi_assigned(); |
| |
| 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 = m_dl_tbf->dl_slots(); |
| else if (!is_dl_active && is_ul_active) |
| slots = m_ul_tbf->ul_slots(); |
| else |
| slots = m_ul_tbf->ul_slots() & m_dl_tbf->dl_slots(); |
| |
| /* Assume a multislot class 1 device */ |
| /* TODO: For class 2 devices, this could be removed */ |
| slots = pcu_lsb(slots); |
| |
| return slots; |
| } |
| |
| void GprsMs::set_reserved_slots(gprs_rlcmac_trx *trx, |
| uint8_t ul_slots, uint8_t dl_slots) |
| { |
| if (m_current_trx) { |
| m_current_trx->unreserve_slots(GPRS_RLCMAC_DL_TBF, |
| m_reserved_dl_slots); |
| m_current_trx->unreserve_slots(GPRS_RLCMAC_UL_TBF, |
| m_reserved_ul_slots); |
| m_reserved_dl_slots = 0; |
| m_reserved_ul_slots = 0; |
| } |
| m_current_trx = trx; |
| if (trx) { |
| m_reserved_dl_slots = dl_slots; |
| m_reserved_ul_slots = ul_slots; |
| m_current_trx->reserve_slots(GPRS_RLCMAC_DL_TBF, |
| m_reserved_dl_slots); |
| m_current_trx->reserve_slots(GPRS_RLCMAC_UL_TBF, |
| m_reserved_ul_slots); |
| } |
| } |
| |
| gprs_rlcmac_tbf *GprsMs::tbf(enum gprs_rlcmac_tbf_direction dir) const |
| { |
| switch (dir) { |
| case GPRS_RLCMAC_DL_TBF: return m_dl_tbf; |
| case GPRS_RLCMAC_UL_TBF: return m_ul_tbf; |
| } |
| |
| return NULL; |
| } |