osmo-pcu/src/gprs_ms.cpp

390 lines
7.8 KiB
C++

/* 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 "bts.h"
#include "tbf.h"
#include "gprs_debug.h"
extern "C" {
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
}
extern void *tall_pcu_ctx;
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();
}
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(0),
m_ms_class(0),
m_current_cs_ul(1),
m_current_cs_dl(1),
m_is_idle(true),
m_ref(0),
m_list(this)
{
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();
if (m_bts) {
m_current_cs_ul = m_bts->bts_data()->initial_cs_ul;
if (m_current_cs_ul < 1)
m_current_cs_ul = 1;
m_current_cs_dl = m_bts->bts_data()->initial_cs_dl;
if (m_current_cs_dl < 1)
m_current_cs_dl = 1;
}
}
GprsMs::~GprsMs()
{
LOGP(DRLCMAC, LOGL_INFO, "Destroying MS object, TLLI = 0x%08x\n", tlli());
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;
}
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::attach_tbf(struct gprs_rlcmac_tbf *tbf)
{
if (tbf->direction == GPRS_RLCMAC_DL_TBF)
attach_dl_tbf(static_cast<gprs_rlcmac_dl_tbf *>(tbf));
else
attach_ul_tbf(static_cast<gprs_rlcmac_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)
detach_tbf(m_ul_tbf);
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)
detach_tbf(m_dl_tbf);
m_dl_tbf = tbf;
if (tbf)
stop_timer();
}
void GprsMs::detach_tbf(gprs_rlcmac_tbf *tbf)
{
if (m_ul_tbf && tbf == static_cast<gprs_rlcmac_tbf *>(m_ul_tbf))
m_ul_tbf = NULL;
else if (m_dl_tbf && tbf == static_cast<gprs_rlcmac_tbf *>(m_dl_tbf))
m_dl_tbf = NULL;
else
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_dl_tbf)
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::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;
LOGP(DRLCMAC, LOGL_INFO,
"Modifying MS object, TLLI = 0x%08x, TA %d -> %d\n",
tlli(), m_ta, ta_);
m_ta = 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::update_error_rate(gprs_rlcmac_tbf *tbf, int error_rate)
{
struct gprs_rlcmac_bts *bts_data;
OSMO_ASSERT(m_bts != NULL);
bts_data = m_bts->bts_data();
if (error_rate < 0)
return;
/* TODO: Support different CS values for UL and DL */
if (error_rate > bts_data->cs_adj_upper_limit) {
if (m_current_cs_dl > 1) {
m_current_cs_dl -= 1;
m_current_cs_ul = m_current_cs_dl;
LOGP(DRLCMACDL, LOGL_INFO,
"MS (IMSI %s): High error rate %d%%, "
"reducing CS level to %d\n",
imsi(), error_rate, m_current_cs_dl);
}
} else if (error_rate < bts_data->cs_adj_lower_limit) {
if (m_current_cs_dl < 4) {
m_current_cs_dl += 1;
m_current_cs_ul = m_current_cs_dl;
LOGP(DRLCMACDL, LOGL_INFO,
"MS (IMSI %s): Low error rate %d%%, "
"increasing CS level to %d\n",
imsi(), error_rate, m_current_cs_dl);
}
} else {
LOGP(DRLCMACDL, LOGL_DEBUG,
"MS (IMSI %s): Medium error rate %d%%, ignored\n",
imsi(), error_rate);
}
}