osmo-pcu/src/tbf.cpp

1227 lines
33 KiB
C++
Raw Normal View History

/* Copied from gprs_bssgp_pcu.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.
*
* 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 <new>
#include <sstream>
#include <bts.h>
#include <tbf.h>
#include <tbf_dl.h>
#include <tbf_ul.h>
#include <rlc.h>
#include <encoding.h>
#include <gprs_rlcmac.h>
#include <gprs_debug.h>
#include <gprs_ms.h>
#include <pcu_utils.h>
#include <gprs_ms_storage.h>
#include <sba.h>
#include <pdch.h>
extern "C" {
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/timer_compat.h>
#include <osmocom/core/bitvec.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include "gsm_rlcmac.h"
#include "coding_scheme.h"
#include "nacc_fsm.h"
}
#include <errno.h>
#include <string.h>
extern void *tall_pcu_ctx;
unsigned int next_tbf_ctr_group_id = 0; /* Incrementing group id */
static void tbf_timer_cb(void *_tbf);
const struct value_string gprs_rlcmac_tbf_dl_ass_state_names[] = {
OSMO_VALUE_STRING(GPRS_RLCMAC_DL_ASS_NONE),
OSMO_VALUE_STRING(GPRS_RLCMAC_DL_ASS_SEND_ASS),
OSMO_VALUE_STRING(GPRS_RLCMAC_DL_ASS_WAIT_ACK),
{ 0, NULL }
};
const struct value_string gprs_rlcmac_tbf_ul_ass_state_names[] = {
OSMO_VALUE_STRING(GPRS_RLCMAC_UL_ASS_NONE),
OSMO_VALUE_STRING(GPRS_RLCMAC_UL_ASS_SEND_ASS),
OSMO_VALUE_STRING(GPRS_RLCMAC_UL_ASS_SEND_ASS_REJ),
OSMO_VALUE_STRING(GPRS_RLCMAC_UL_ASS_WAIT_ACK),
{ 0, NULL }
};
const struct value_string gprs_rlcmac_tbf_ul_ack_state_names[] = {
OSMO_VALUE_STRING(GPRS_RLCMAC_UL_ACK_NONE),
OSMO_VALUE_STRING(GPRS_RLCMAC_UL_ACK_SEND_ACK), /* send acknowledge on next RTS */
OSMO_VALUE_STRING(GPRS_RLCMAC_UL_ACK_WAIT_ACK), /* wait for PACKET CONTROL ACK */
{ 0, NULL }
};
static const struct value_string tbf_counters_names[] = {
OSMO_VALUE_STRING(N3101),
OSMO_VALUE_STRING(N3103),
OSMO_VALUE_STRING(N3105),
{ 0, NULL }
};
static const struct value_string tbf_timers_names[] = {
OSMO_VALUE_STRING(T0),
OSMO_VALUE_STRING(T3141),
OSMO_VALUE_STRING(T3169),
OSMO_VALUE_STRING(T3191),
OSMO_VALUE_STRING(T3193),
OSMO_VALUE_STRING(T3195),
{ 0, NULL }
};
static const struct rate_ctr_desc tbf_ctr_description[] = {
{ "rlc:nacked", "RLC Nacked " },
};
const struct rate_ctr_group_desc tbf_ctrg_desc = {
"pcu:tbf",
"TBF Statistics",
OSMO_STATS_CLASS_SUBSCRIBER,
ARRAY_SIZE(tbf_ctr_description),
tbf_ctr_description,
};
gprs_rlcmac_tbf::Meas::Meas() :
rssi_sum(0),
rssi_num(0)
{
timespecclear(&rssi_tv);
}
gprs_rlcmac_tbf::gprs_rlcmac_tbf(struct gprs_rlcmac_bts *bts_, GprsMs *ms, gprs_rlcmac_tbf_direction dir) :
state_flags(0),
direction(dir),
trx(NULL),
first_ts(0),
first_common_ts(0),
control_ts(0xff),
fT(0),
num_fT_exp(0),
was_releasing(0),
upgrade_to_multislot(0),
bts(bts_),
m_tfi(0),
m_created_ts(0),
m_ctrs(NULL),
state(GPRS_RLCMAC_NULL),
m_ms(ms),
dl_ass_state(GPRS_RLCMAC_DL_ASS_NONE),
ul_ass_state(GPRS_RLCMAC_UL_ASS_NONE),
ul_ack_state(GPRS_RLCMAC_UL_ACK_NONE),
m_egprs_enabled(false)
{
/* The classes of these members do not have proper constructors yet.
* Just set them to 0 like talloc_zero did */
memset(&pdch, 0, sizeof(pdch));
memset(&Tarr, 0, sizeof(Tarr));
memset(&Narr, 0, sizeof(Narr));
memset(&m_ms_list, 0, sizeof(m_ms_list));
m_ms_list.entry = this;
memset(&m_trx_list, 0, sizeof(m_trx_list));
m_trx_list.entry = this;
m_rlc.init();
m_llc.init();
m_name_buf[0] = '\0';
}
uint32_t gprs_rlcmac_tbf::tlli() const
{
return m_ms ? ms_tlli(m_ms) : GSM_RESERVED_TMSI;
}
const char *gprs_rlcmac_tbf::imsi() const
{
return ms_imsi(m_ms);
}
uint8_t gprs_rlcmac_tbf::ta() const
{
return ms_ta(m_ms);
}
void gprs_rlcmac_tbf::set_ta(uint8_t ta)
{
ms_set_ta(m_ms, ta);
}
uint8_t gprs_rlcmac_tbf::ms_class() const
{
return ms_ms_class(m_ms);
}
enum CodingScheme gprs_rlcmac_tbf::current_cs() const
{
enum CodingScheme cs;
enum mcs_kind req_mcs_kind = is_egprs_enabled() ? EGPRS : GPRS;
if (direction == GPRS_RLCMAC_UL_TBF)
cs = ms_current_cs_ul(m_ms);
else
cs = ms_current_cs_dl(m_ms, req_mcs_kind);
return cs;
}
gprs_llc_queue *gprs_rlcmac_tbf::llc_queue()
{
return ms_llc_queue(m_ms);
}
const gprs_llc_queue *gprs_rlcmac_tbf::llc_queue() const
{
return ms_llc_queue(m_ms);
}
void gprs_rlcmac_tbf::set_ms(GprsMs *ms)
{
if (m_ms == ms)
return;
if (m_ms) {
ms_detach_tbf(m_ms, this);
}
m_ms = ms;
if (m_ms)
ms_attach_tbf(m_ms, this);
}
void gprs_rlcmac_tbf::update_ms(uint32_t tlli, enum gprs_rlcmac_tbf_direction dir)
{
if (tlli == GSM_RESERVED_TMSI)
return;
/* TODO: When the TLLI does not match the ms, check if there is another
* MS object that belongs to that TLLI and if yes make sure one of them
* gets deleted. This is the same problem that can arise with
* IMSI in gprs_rlcmac_dl_tbf::handle() so there should be a unified solution */
if (!ms_check_tlli(ms(), tlli)) {
GprsMs *old_ms;
old_ms = bts_ms_store(bts)->get_ms(tlli, 0, NULL);
if (old_ms)
ms_merge_and_clear_ms(ms(), old_ms);
}
if (dir == GPRS_RLCMAC_UL_TBF)
ms_set_tlli(ms(), tlli);
else
ms_confirm_tlli(ms(), tlli);
tbf,bts: Keep track of new TBF for dl/ul assignment in m_new_tbf There are a couple of possibilities where one TBF is used to assign a new one: 1. Assign a DL TBF from a UL TBF 2. Assign a UL TBF from a DL TBF 3. Assign a DL TBF from a DL TBF which is in wait-release state (T3193 is running) In these cases the assignment is sent on the existing TBF and triggers the assignement of the new TBF (with different TFI/direction). The current code detects these situations by looking at dl/ul_ass_state and then chosing the TBF with the opposite direction (DL/UL) that has the same TLLI. This does not work in the case 3 above where a new DL TBF is triggered for a DL TBF. The current code reuses the old TBF (and TFI), but this violates the spec. This patch introduces a m_new_tbf member which is set to the new TBF to be assigned. When receiving a control ack the code looks up the n_new_tbf member of the tbf that requested the control ack and completes the ul/dl assignment. If the old TBF was in the wait release state (T3193 is running) it is released. From 3GPP TS 04.60 9.3.2.6: """ If the network has received the PACKET DOWNLINK ACK/NACK message with the Final Ack Indicator bit set to '1' and has new data to transmit for the mobile station, the network may establish a new downlink TBF for the mobile station by sending the PACKET DOWNLINK ASSIGNMENT or PACKET TIMESLOT RECONFIGURE message with the Control Ack bit set to '1' on PACCH. In case the network establishes a new downlink TBF for the mobile station, the network shall stop timer T3193. """ reuse_tbf() is modified to allocate a new TBF with a new TFI and trigger a dl assignment for that TBF on the old TBF. All pending data is moved to the new TBF. Ticket: SYS#382 Sponsored-by: On-Waves ehf
2014-08-15 14:52:09 +00:00
}
static void tbf_unlink_pdch(struct gprs_rlcmac_tbf *tbf)
{
int ts;
for (ts = 0; ts < 8; ts++) {
if (!tbf->pdch[ts])
continue;
tbf->pdch[ts]->detach_tbf(tbf);
tbf->pdch[ts] = NULL;
}
}
void tbf_free(struct gprs_rlcmac_tbf *tbf)
{
/* update counters */
if (tbf->direction == GPRS_RLCMAC_UL_TBF) {
gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(tbf);
bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_UL_FREED);
if (tbf->state_is(GPRS_RLCMAC_FLOW))
bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_UL_ABORTED);
rate_ctr_group_free(ul_tbf->m_ul_egprs_ctrs);
rate_ctr_group_free(ul_tbf->m_ul_gprs_ctrs);
} else {
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf);
if (tbf->is_egprs_enabled()) {
rate_ctr_group_free(dl_tbf->m_dl_egprs_ctrs);
} else {
rate_ctr_group_free(dl_tbf->m_dl_gprs_ctrs);
}
bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_DL_FREED);
if (tbf->state_is(GPRS_RLCMAC_FLOW))
bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_DL_ABORTED);
}
/* Give final measurement report */
gprs_rlcmac_rssi_rep(tbf);
if (tbf->direction == GPRS_RLCMAC_DL_TBF) {
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf);
dl_tbf->abort();
dl_tbf->cleanup();
}
LOGPTBF(tbf, LOGL_INFO, "free\n");
tbf->stop_timers("freeing TBF");
/* TODO: Could/Should generate bssgp_tx_llc_discarded */
tbf_unlink_pdch(tbf);
llist_del(tbf_trx_list(tbf));
2013-10-27 08:50:15 +00:00
if (tbf->ms())
tbf->set_ms(NULL);
rate_ctr_group_free(tbf->m_ctrs);
LOGP(DTBF, LOGL_DEBUG, "********** %s-TBF ends here **********\n",
(tbf->direction != GPRS_RLCMAC_UL_TBF) ? "DL" : "UL");
talloc_free(tbf);
}
uint16_t egprs_window_size(const struct gprs_rlcmac_bts *bts, uint8_t slots)
{
uint8_t num_pdch = pcu_bitcount(slots);
return OSMO_MIN((num_pdch != 1) ? (128 * num_pdch) : 192,
OSMO_MAX(64, (the_pcu->vty.ws_base + num_pdch * the_pcu->vty.ws_pdch) / 32 * 32));
}
int gprs_rlcmac_tbf::update()
{
int rc;
if (direction != GPRS_RLCMAC_DL_TBF)
return -EINVAL;
LOGP(DTBF, LOGL_DEBUG, "********** DL-TBF update **********\n");
tbf_unlink_pdch(this);
rc = the_pcu->alloc_algorithm(bts, this, false, -1);
/* if no resource */
if (rc < 0) {
LOGPTBF(this, LOGL_ERROR, "No resource after update???\n");
bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_FAIL);
return -rc;
}
if (is_egprs_enabled()) {
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(this);
if (dl_tbf)
dl_tbf->set_window_size();
}
return 0;
}
int tbf_assign_control_ts(struct gprs_rlcmac_tbf *tbf)
{
if (tbf->control_ts == 0xff)
LOGPTBF(tbf, LOGL_INFO, "Setting Control TS %d\n",
tbf->first_common_ts);
else if (tbf->control_ts != tbf->first_common_ts)
LOGPTBF(tbf, LOGL_INFO, "Changing Control TS %d -> %d\n",
tbf->control_ts, tbf->first_common_ts);
tbf->control_ts = tbf->first_common_ts;
return 0;
}
const char *gprs_rlcmac_tbf::tbf_state_name[] = {
"NULL",
"ASSIGN",
"FLOW",
"FINISHED",
"WAIT RELEASE",
"RELEASING",
};
void gprs_rlcmac_tbf::n_reset(enum tbf_counters n)
{
if (n >= N_MAX) {
LOGPTBF(this, LOGL_ERROR, "attempting to reset unknown counter %s\n",
get_value_string(tbf_counters_names, n));
return;
}
Narr[n] = 0;
}
/* Increment counter and check for MAX value (return true if we hit it) */
bool gprs_rlcmac_tbf::n_inc(enum tbf_counters n)
{
uint8_t chk;
if (n >= N_MAX) {
LOGPTBF(this, LOGL_ERROR, "attempting to increment unknown counter %s\n",
get_value_string(tbf_counters_names, n));
return true;
}
Narr[n]++;
switch(n) {
case N3101:
chk = bts->n3101;
break;
case N3103:
chk = bts->n3103;
break;
case N3105:
chk = bts->n3105;
break;
default:
LOGPTBF(this, LOGL_ERROR, "unhandled counter %s\n",
get_value_string(tbf_counters_names, n));
return true;
}
if (Narr[n] == chk) {
LOGPTBF(this, LOGL_NOTICE, "%s exceeded MAX (%u)\n",
get_value_string(tbf_counters_names, n), chk);
return true;
} else {
LOGPTBF(this, LOGL_DEBUG, "%s %" PRIu8 " => %" PRIu8 " (< MAX %" PRIu8 ")\n",
get_value_string(tbf_counters_names, n), Narr[n] - 1, Narr[n], chk);
return false;
}
}
void gprs_rlcmac_tbf::t_stop(enum tbf_timers t, const char *reason)
{
if (t >= T_MAX) {
LOGPTBF(this, LOGL_ERROR, "attempting to stop unknown timer %s [%s]\n",
get_value_string(tbf_timers_names, t), reason);
return;
}
if (osmo_timer_pending(&Tarr[t])) {
LOGPTBF(this, LOGL_DEBUG, "stopping timer %s [%s]\n",
get_value_string(tbf_timers_names, t), reason);
osmo_timer_del(&Tarr[t]);
}
}
/* check if any of T31xx timer(s) are pending */
bool gprs_rlcmac_tbf::timers_pending(enum tbf_timers t)
{
uint8_t i;
if (t != T_MAX)
return osmo_timer_pending(&Tarr[t]);
/* we don't start with T0 because it's internal timer which requires special handling */
for (i = T3141; i < T_MAX; i++)
if (osmo_timer_pending(&Tarr[i]))
return true;
return false;
}
void gprs_rlcmac_tbf::stop_timers(const char *reason)
{
uint8_t i;
/* we start with T0 because timer reset does not require any special handling */
for (i = T0; i < T_MAX; i++)
t_stop((enum tbf_timers)i, reason);
}
static inline void tbf_timeout_free(struct gprs_rlcmac_tbf *tbf, enum tbf_timers t, bool run_diag)
{
LOGPTBF(tbf, LOGL_NOTICE, "%s timeout expired, freeing TBF\n",
get_value_string(tbf_timers_names, t));
if (run_diag) {
LOGPTBF(tbf, LOGL_NOTICE, "%s timeout expired, freeing TBF: %s\n",
get_value_string(tbf_timers_names, t), tbf->rlcmac_diag().c_str());
} else {
LOGPTBF(tbf, LOGL_NOTICE, "%s timeout expired, freeing TBF\n",
get_value_string(tbf_timers_names, t));
}
tbf_free(tbf);
}
#define T_CBACK(t, diag) static void cb_##t(void *_tbf) { tbf_timeout_free((struct gprs_rlcmac_tbf *)_tbf, t, diag); }
/* 3GPP TS 44.018 sec 3.5.2.1.5: On the network side, if timer T3141 elapses
* before a successful contention resolution procedure is completed, the newly
* allocated temporary block flow is released as specified in 3GPP TS 44.060 and
* the packet access is forgotten.*/
T_CBACK(T3141, true)
T_CBACK(T3169, true)
T_CBACK(T3191, true)
T_CBACK(T3193, false)
T_CBACK(T3195, true)
void gprs_rlcmac_tbf::t_start(enum tbf_timers t, int T, const char *reason, bool force,
const char *file, unsigned line)
{
int current_fn = bts_current_frame_number(bts);
int sec;
int microsec;
struct osmo_tdef *tdef;
if (!(tdef = osmo_tdef_get_entry(bts->T_defs_bts, T)))
tdef = osmo_tdef_get_entry(bts->pcu->T_defs, T);
if (t >= T_MAX || !tdef) {
LOGPSRC(DTBF, LOGL_ERROR, file, line, "%s attempting to start unknown timer %s [%s], cur_fn=%d\n",
tbf_name(this), get_value_string(tbf_timers_names, t), reason, current_fn);
return;
}
if (!force && osmo_timer_pending(&Tarr[t]))
return;
switch (tdef->unit) {
case OSMO_TDEF_MS:
sec = 0;
microsec = tdef->val * 1000;
break;
case OSMO_TDEF_S:
sec = tdef->val;
microsec = 0;
break;
default:
/* so far only timers using MS and S */
OSMO_ASSERT(false);
}
LOGPSRC(DTBF, LOGL_DEBUG, file, line, "%s %sstarting timer %s [%s] with %u sec. %u microsec, cur_fn=%d\n",
tbf_name(this), osmo_timer_pending(&Tarr[t]) ? "re" : "",
get_value_string(tbf_timers_names, t), reason, sec, microsec, current_fn);
Tarr[t].data = this;
switch(t) {
case T0:
Tarr[t].cb = tbf_timer_cb;
break;
case T3141:
Tarr[t].cb = cb_T3141;
break;
case T3169:
Tarr[t].cb = cb_T3169;
break;
case T3191:
Tarr[t].cb = cb_T3191;
break;
case T3193:
Tarr[t].cb = cb_T3193;
break;
case T3195:
Tarr[t].cb = cb_T3195;
break;
default:
LOGPSRC(DTBF, LOGL_ERROR, file, line, "%s attempting to set callback for unknown timer %s [%s], cur_fn=%d\n",
tbf_name(this), get_value_string(tbf_timers_names, t), reason, current_fn);
}
osmo_timer_schedule(&Tarr[t], sec, microsec);
}
int gprs_rlcmac_tbf::check_polling(uint32_t fn, uint8_t ts,
uint32_t *poll_fn_, unsigned int *rrbp_) const
{
int rc;
if (!is_control_ts(ts)) {
LOGPTBF(this, LOGL_DEBUG, "Polling cannot be "
"scheduled in this TS %d (first control TS %d)\n",
ts, control_ts);
return -EINVAL;
}
if ((rc = pdch_ulc_get_next_free_rrbp_fn(trx->pdch[ts].ulc, fn, poll_fn_, rrbp_)) < 0) {
LOGPTBF(this, LOGL_DEBUG,
"(bts=%u,trx=%u,ts=%u) FN=%u No suitable free RRBP offset found!\n",
trx->bts->nr, trx->trx_no, ts, fn);
return rc;
}
return 0;
}
void gprs_rlcmac_tbf::set_polling(uint32_t new_poll_fn, uint8_t ts, enum pdch_ulc_tbf_poll_reason reason)
{
const char *chan = "UNKNOWN";
if (state_flags & (1 << (GPRS_RLCMAC_FLAG_CCCH)))
chan = "CCCH";
if (state_flags & (1 << (GPRS_RLCMAC_FLAG_PACCH)))
chan = "PACCH";
if ((state_flags & (1 << (GPRS_RLCMAC_FLAG_PACCH))) && (state_flags & (1 << (GPRS_RLCMAC_FLAG_CCCH))))
LOGPTBFDL(this, LOGL_ERROR,
"Attempt to schedule polling on %s (FN=%d, TS=%d) with both CCCH and PACCH flags set - FIXME!\n",
chan, new_poll_fn, ts);
/* schedule polling */
if (pdch_ulc_reserve_tbf_poll(trx->pdch[ts].ulc, new_poll_fn, this, reason) < 0) {
LOGPTBFDL(this, LOGL_ERROR, "Failed scheduling poll on %s (FN=%d, TS=%d)\n",
chan, new_poll_fn, ts);
return;
}
switch (reason) {
case PDCH_ULC_POLL_UL_ASS:
ul_ass_state = GPRS_RLCMAC_UL_ASS_WAIT_ACK;
LOGPTBFDL(this, LOGL_INFO, "Scheduled UL Assignment polling on %s (FN=%d, TS=%d)\n",
chan, new_poll_fn, ts);
break;
case PDCH_ULC_POLL_DL_ASS:
dl_ass_state = GPRS_RLCMAC_DL_ASS_WAIT_ACK;
LOGPTBFDL(this, LOGL_INFO, "Scheduled DL Assignment polling on %s (FN=%d, TS=%d)\n",
chan, new_poll_fn, ts);
break;
case PDCH_ULC_POLL_UL_ACK:
ul_ack_state = GPRS_RLCMAC_UL_ACK_WAIT_ACK;
LOGPTBFUL(this, LOGL_DEBUG, "Scheduled UL Acknowledgement polling on %s (FN=%d, TS=%d)\n",
chan, new_poll_fn, ts);
break;
case PDCH_ULC_POLL_DL_ACK:
LOGPTBFDL(this, LOGL_DEBUG, "Scheduled DL Acknowledgement polling on %s (FN=%d, TS=%d)\n",
chan, new_poll_fn, ts);
break;
case PDCH_ULC_POLL_CELL_CHG_CONTINUE:
LOGPTBFDL(this, LOGL_DEBUG, "Scheduled 'Packet Cell Change Continue' polling on %s (FN=%d, TS=%d)\n",
chan, new_poll_fn, ts);
break;
}
}
void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_fn, enum pdch_ulc_tbf_poll_reason reason)
{
uint16_t pgroup;
gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(this);
LOGPTBF(this, LOGL_NOTICE, "poll timeout for FN=%d, TS=%d (curr FN %d)\n",
poll_fn, pdch->ts_no, bts_current_frame_number(bts));
if (ul_tbf && ul_tbf->handle_ctrl_ack(reason)) {
if (!ul_tbf->ctrl_ack_to_toggle()) {
LOGPTBF(this, LOGL_NOTICE,
"Timeout for polling PACKET CONTROL ACK for PACKET UPLINK ACK: %s\n",
rlcmac_diag().c_str());
}
bts_do_rate_ctr_inc(bts, CTR_RLC_ACK_TIMEDOUT);
bts_do_rate_ctr_inc(bts, CTR_PUAN_POLL_TIMEDOUT);
if (state_is(GPRS_RLCMAC_FINISHED)) {
if (ul_tbf->n_inc(N3103)) {
bts_do_rate_ctr_inc(bts, CTR_PUAN_POLL_FAILED);
TBF_SET_STATE(ul_tbf, GPRS_RLCMAC_RELEASING);
T_START(ul_tbf, T3169, 3169, "MAX N3103 reached", false);
return;
}
/* reschedule UL ack */
ul_tbf->ul_ack_state = GPRS_RLCMAC_UL_ACK_SEND_ACK;
}
} else if (ul_ass_state == GPRS_RLCMAC_UL_ASS_WAIT_ACK) {
if (!(state_flags & (1 << GPRS_RLCMAC_FLAG_TO_UL_ASS))) {
LOGPTBF(this, LOGL_NOTICE,
"Timeout for polling PACKET CONTROL ACK for PACKET UPLINK ASSIGNMENT: %s\n",
rlcmac_diag().c_str());
state_flags |= (1 << GPRS_RLCMAC_FLAG_TO_UL_ASS);
}
ul_ass_state = GPRS_RLCMAC_UL_ASS_NONE;
bts_do_rate_ctr_inc(bts, CTR_RLC_ASS_TIMEDOUT);
bts_do_rate_ctr_inc(bts, CTR_PUA_POLL_TIMEDOUT);
if (n_inc(N3105)) {
TBF_SET_STATE(this, GPRS_RLCMAC_RELEASING);
T_START(this, T3195, 3195, "MAX N3105 reached", true);
bts_do_rate_ctr_inc(bts, CTR_RLC_ASS_FAILED);
bts_do_rate_ctr_inc(bts, CTR_PUA_POLL_FAILED);
return;
}
/* reschedule UL assignment */
ul_ass_state = GPRS_RLCMAC_UL_ASS_SEND_ASS;
} else if (dl_ass_state == GPRS_RLCMAC_DL_ASS_WAIT_ACK) {
if (!(state_flags & (1 << GPRS_RLCMAC_FLAG_TO_DL_ASS))) {
LOGPTBF(this, LOGL_NOTICE,
"Timeout for polling PACKET CONTROL ACK for PACKET DOWNLINK ASSIGNMENT: %s\n",
rlcmac_diag().c_str());
state_flags |= (1 << GPRS_RLCMAC_FLAG_TO_DL_ASS);
}
dl_ass_state = GPRS_RLCMAC_DL_ASS_NONE;
bts_do_rate_ctr_inc(bts, CTR_RLC_ASS_TIMEDOUT);
bts_do_rate_ctr_inc(bts, CTR_PDA_POLL_TIMEDOUT);
if (n_inc(N3105)) {
TBF_SET_STATE(this, GPRS_RLCMAC_RELEASING);
T_START(this, T3195, 3195, "MAX N3105 reached", true);
bts_do_rate_ctr_inc(bts, CTR_RLC_ASS_FAILED);
bts_do_rate_ctr_inc(bts, CTR_PDA_POLL_FAILED);
return;
}
/* reschedule DL assignment */
dl_ass_state = GPRS_RLCMAC_DL_ASS_SEND_ASS;
} else if (m_ms->nacc && m_ms->nacc->fi->state == NACC_ST_WAIT_CELL_CHG_CONTINUE_ACK &&
m_ms->nacc->continue_poll_fn == poll_fn && m_ms->nacc->continue_poll_ts == pdch->ts_no) {
/* Timeout waiting for CTRL ACK acking Pkt Cell Change Continue */
osmo_fsm_inst_dispatch(m_ms->nacc->fi, NACC_EV_TIMEOUT_CELL_CHG_CONTINUE, NULL);
return;
} else if (direction == GPRS_RLCMAC_DL_TBF) {
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(this);
if (!(dl_tbf->state_flags & (1 << GPRS_RLCMAC_FLAG_TO_DL_ACK))) {
LOGPTBF(this, LOGL_NOTICE,
"Timeout for polling PACKET DOWNLINK ACK: %s\n",
dl_tbf->rlcmac_diag().c_str());
dl_tbf->state_flags |= (1 << GPRS_RLCMAC_FLAG_TO_DL_ACK);
}
if (dl_tbf->state_is(GPRS_RLCMAC_RELEASING))
bts_do_rate_ctr_inc(bts, CTR_RLC_REL_TIMEDOUT);
else {
bts_do_rate_ctr_inc(bts, CTR_RLC_ACK_TIMEDOUT);
bts_do_rate_ctr_inc(bts, CTR_PDAN_POLL_TIMEDOUT);
}
if (dl_tbf->n_inc(N3105)) {
TBF_SET_STATE(dl_tbf, GPRS_RLCMAC_RELEASING);
T_START(dl_tbf, T3195, 3195, "MAX N3105 reached", true);
bts_do_rate_ctr_inc(bts, CTR_PDAN_POLL_FAILED);
bts_do_rate_ctr_inc(bts, CTR_RLC_ACK_FAILED);
return;
}
/* resend IMM.ASS on CCCH on timeout */
if ((dl_tbf->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH))
&& !(dl_tbf->state_flags & (1 << GPRS_RLCMAC_FLAG_DL_ACK))) {
LOGPTBF(dl_tbf, LOGL_DEBUG, "Re-send dowlink assignment on PCH (IMSI=%s)\n",
imsi());
/* send immediate assignment */
if ((pgroup = imsi2paging_group(imsi())) > 999)
LOGPTBF(dl_tbf, LOGL_ERROR, "IMSI to paging group failed! (%s)\n", imsi());
bts_snd_dl_ass(dl_tbf->bts, dl_tbf, pgroup);
dl_tbf->m_wait_confirm = 1;
}
} else
LOGPTBF(this, LOGL_ERROR, "Poll Timeout, but no event!\n");
}
int gprs_rlcmac_tbf::setup(int8_t use_trx, bool single_slot)
{
int rc;
if (ms_mode(m_ms) != GPRS)
enable_egprs();
m_created_ts = time(NULL);
/* select algorithm */
rc = the_pcu->alloc_algorithm(bts, this, single_slot, use_trx);
/* if no resource */
if (rc < 0) {
LOGPTBF(this, LOGL_NOTICE,
"Timeslot Allocation failed: trx = %d, single_slot = %d\n",
use_trx, single_slot);
bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_FAIL);
return -1;
}
/* assign control ts */
rc = tbf_assign_control_ts(this);
/* if no resource */
if (rc < 0) {
LOGPTBF(this, LOGL_ERROR, "Failed to assign control TS\n");
return -1;
}
/* set timestamp */
osmo_clock_gettime(CLOCK_MONOTONIC, &meas.rssi_tv);
LOGPTBF(this, LOGL_INFO,
"Allocated: trx = %d, ul_slots = %02x, dl_slots = %02x\n",
this->trx->trx_no, ul_slots(), dl_slots());
m_ctrs = rate_ctr_group_alloc(this, &tbf_ctrg_desc, next_tbf_ctr_group_id++);
if (!m_ctrs) {
LOGPTBF(this, LOGL_ERROR, "Couldn't allocate TBF counters\n");
return -1;
}
ms_attach_tbf(m_ms, this);
return 0;
}
static void tbf_timer_cb(void *_tbf)
{
struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)_tbf;
tbf->handle_timeout();
}
void gprs_rlcmac_tbf::handle_timeout()
{
int current_fn = bts_current_frame_number(bts);
LOGPTBF(this, LOGL_DEBUG, "timer 0 expired. cur_fn=%d\n", current_fn);
/* PACCH assignment timeout (see timers X2000, X2001) */
if ((state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH))) {
if (state_is(GPRS_RLCMAC_ASSIGN)) {
LOGPTBF(this, LOGL_NOTICE, "releasing due to PACCH assignment timeout.\n");
tbf_free(this);
return;
} else
LOGPTBF(this, LOGL_ERROR, "Error: TBF is not in assign state\n");
}
/* Finish waiting after IMM.ASS confirm timer for CCCH assignment (see timer X2002) */
if ((state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH))) {
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(this);
dl_tbf->m_wait_confirm = 0;
if (dl_tbf->state_is(GPRS_RLCMAC_ASSIGN)) {
tbf_assign_control_ts(dl_tbf);
if (!dl_tbf->upgrade_to_multislot) {
/* change state to FLOW, so scheduler
* will start transmission */
TBF_SET_STATE(dl_tbf, GPRS_RLCMAC_FLOW);
return;
}
/* This tbf can be upgraded to use multiple DL
* timeslots and now that there is already one
* slot assigned send another DL assignment via
* PDCH. */
/* keep to flags */
dl_tbf->state_flags &= GPRS_RLCMAC_FLAG_TO_MASK;
dl_tbf->update();
dl_tbf->trigger_ass(dl_tbf);
} else
LOGPTBF(dl_tbf, LOGL_NOTICE, "Continue flow after IMM.ASS confirm\n");
}
}
std::string gprs_rlcmac_tbf::rlcmac_diag()
{
std::ostringstream os;
os << "|";
if ((state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)))
os << "Assignment was on CCCH|";
if ((state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH)))
os << "Assignment was on PACCH|";
if ((state_flags & (1 << GPRS_RLCMAC_FLAG_UL_DATA)))
os << "Uplink data was received|";
else if (direction == GPRS_RLCMAC_UL_TBF)
os << "No uplink data received yet|";
if ((state_flags & (1 << GPRS_RLCMAC_FLAG_DL_ACK)))
os << "Downlink ACK was received|";
else if (direction == GPRS_RLCMAC_DL_TBF)
os << "No downlink ACK received yet|";
return os.str();
}
struct msgb *gprs_rlcmac_tbf::create_dl_ass(uint32_t fn, uint8_t ts)
{
struct msgb *msg;
struct gprs_rlcmac_dl_tbf *new_dl_tbf = NULL;
RlcMacDownlink_t *mac_control_block = NULL;
int poll_ass_dl = 1;
unsigned int rrbp = 0;
uint32_t new_poll_fn = 0;
int rc;
bool old_tfi_is_valid = is_tfi_assigned();
if (direction == GPRS_RLCMAC_DL_TBF && !is_control_ts(ts)) {
LOGPTBF(this, LOGL_NOTICE,
"Cannot poll for downlink assignment, because MS cannot reply. (TS=%d, first common TS=%d)\n",
ts, first_common_ts);
poll_ass_dl = 0;
}
if (poll_ass_dl) {
if (ul_ass_state == GPRS_RLCMAC_UL_ASS_WAIT_ACK)
{
LOGPTBF(this, LOGL_DEBUG,
"Polling is already scheduled, so we must wait for the uplink assignment...\n");
return NULL;
}
rc = check_polling(fn, ts, &new_poll_fn, &rrbp);
if (rc < 0)
return NULL;
}
/* on uplink TBF we get the downlink TBF to be assigned. */
if (direction == GPRS_RLCMAC_UL_TBF) {
gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(this);
/* be sure to check first, if contention resolution is done,
* otherwise we cannot send the assignment yet (3GPP TS 44.060 sec 7.1.3.1) */
if (!ul_tbf->m_contention_resolution_done) {
LOGPTBF(this, LOGL_DEBUG,
"Cannot assign DL TBF now, because contention resolution is not finished.\n");
return NULL;
}
tbf,bts: Keep track of new TBF for dl/ul assignment in m_new_tbf There are a couple of possibilities where one TBF is used to assign a new one: 1. Assign a DL TBF from a UL TBF 2. Assign a UL TBF from a DL TBF 3. Assign a DL TBF from a DL TBF which is in wait-release state (T3193 is running) In these cases the assignment is sent on the existing TBF and triggers the assignement of the new TBF (with different TFI/direction). The current code detects these situations by looking at dl/ul_ass_state and then chosing the TBF with the opposite direction (DL/UL) that has the same TLLI. This does not work in the case 3 above where a new DL TBF is triggered for a DL TBF. The current code reuses the old TBF (and TFI), but this violates the spec. This patch introduces a m_new_tbf member which is set to the new TBF to be assigned. When receiving a control ack the code looks up the n_new_tbf member of the tbf that requested the control ack and completes the ul/dl assignment. If the old TBF was in the wait release state (T3193 is running) it is released. From 3GPP TS 04.60 9.3.2.6: """ If the network has received the PACKET DOWNLINK ACK/NACK message with the Final Ack Indicator bit set to '1' and has new data to transmit for the mobile station, the network may establish a new downlink TBF for the mobile station by sending the PACKET DOWNLINK ASSIGNMENT or PACKET TIMESLOT RECONFIGURE message with the Control Ack bit set to '1' on PACCH. In case the network establishes a new downlink TBF for the mobile station, the network shall stop timer T3193. """ reuse_tbf() is modified to allocate a new TBF with a new TFI and trigger a dl assignment for that TBF on the old TBF. All pending data is moved to the new TBF. Ticket: SYS#382 Sponsored-by: On-Waves ehf
2014-08-15 14:52:09 +00:00
}
if (ms())
new_dl_tbf = ms_dl_tbf(ms());
if (!new_dl_tbf) {
LOGPTBFDL(this, LOGL_ERROR,
"We have a schedule for downlink assignment, but there is no downlink TBF\n");
dl_ass_state = GPRS_RLCMAC_DL_ASS_NONE;
return NULL;
}
if (new_dl_tbf == as_dl_tbf(this))
LOGPTBF(this, LOGL_DEBUG, "New and old TBF are the same.\n");
if (old_tfi_is_valid && !new_dl_tbf->is_tlli_valid()) {
LOGPTBF(this, LOGL_ERROR,
"The old TFI is not assigned and there is no TLLI. New TBF %s\n",
new_dl_tbf->name());
dl_ass_state = GPRS_RLCMAC_DL_ASS_NONE;
return NULL;
}
new_dl_tbf->was_releasing = was_releasing;
msg = msgb_alloc(GSM_MACBLOCK_LEN, "rlcmac_dl_ass");
if (!msg)
return NULL;
/* Initialize a bit vector that uses allocated msgb as the data buffer.
* Old G++ does not support non-trivial designated initializers. Sigh. */
struct bitvec bv = { };
bv.data = msgb_put(msg, GSM_MACBLOCK_LEN);
bv.data_len = GSM_MACBLOCK_LEN;
bitvec_unhex(&bv, DUMMY_VEC);
LOGPTBF(new_dl_tbf, LOGL_INFO, "start Packet Downlink Assignment (PACCH)\n");
mac_control_block = (RlcMacDownlink_t *)talloc_zero(tall_pcu_ctx, RlcMacDownlink_t);
Encoding::write_packet_downlink_assignment(mac_control_block,
old_tfi_is_valid, m_tfi, (direction == GPRS_RLCMAC_DL_TBF),
new_dl_tbf, poll_ass_dl, rrbp,
bts_get_ms_pwr_alpha(new_dl_tbf->bts), the_pcu->vty.gamma, -1, 0,
is_egprs_enabled());
LOGP(DTBF, LOGL_DEBUG, "+++++++++++++++++++++++++ TX : Packet Downlink Assignment +++++++++++++++++++++++++\n");
rc = encode_gsm_rlcmac_downlink(&bv, mac_control_block);
if (rc < 0) {
LOGP(DTBF, LOGL_ERROR, "Encoding of Packet Downlink Ass failed (%d)\n", rc);
goto free_ret;
}
LOGP(DTBF, LOGL_DEBUG, "------------------------- TX : Packet Downlink Assignment -------------------------\n");
bts_do_rate_ctr_inc(bts, CTR_PKT_DL_ASSIGNMENT);
if (poll_ass_dl) {
set_polling(new_poll_fn, ts, PDCH_ULC_POLL_DL_ASS);
} else {
dl_ass_state = GPRS_RLCMAC_DL_ASS_NONE;
TBF_SET_STATE(new_dl_tbf, GPRS_RLCMAC_FLOW);
tbf_assign_control_ts(new_dl_tbf);
/* stop pending assignment timer */
new_dl_tbf->t_stop(T0, "assignment (DL-TBF)");
}
talloc_free(mac_control_block);
return msg;
free_ret:
talloc_free(mac_control_block);
msgb_free(msg);
return NULL;
}
struct msgb *gprs_rlcmac_tbf::create_packet_access_reject()
{
struct msgb *msg;
msg = msgb_alloc(GSM_MACBLOCK_LEN, "rlcmac_ul_ass_rej");
bitvec *packet_access_rej = bitvec_alloc(GSM_MACBLOCK_LEN, tall_pcu_ctx);
bitvec_unhex(packet_access_rej, DUMMY_VEC);
Encoding::write_packet_access_reject(packet_access_rej, tlli(),
osmo_tdef_get(bts->T_defs_bts, 3172, OSMO_TDEF_MS, -1));
bts_do_rate_ctr_inc(bts, CTR_PKT_ACCESS_REJ);
bitvec_pack(packet_access_rej, msgb_put(msg, GSM_MACBLOCK_LEN));
bitvec_free(packet_access_rej);
ul_ass_state = GPRS_RLCMAC_UL_ASS_NONE;
/* Start release only if it is UL TBF */
if (direction == GPRS_RLCMAC_UL_TBF) {
/* tbf_free() called in gprs_rlcmac_tbf::handle_timeout */
T_START(this, T0, -2000, "reject (PACCH)", true);
}
return msg;
}
struct msgb *gprs_rlcmac_tbf::create_ul_ass(uint32_t fn, uint8_t ts)
{
struct msgb *msg = NULL;
struct gprs_rlcmac_ul_tbf *new_tbf = NULL;
RlcMacDownlink_t *mac_control_block = NULL;
int rc;
unsigned int rrbp;
uint32_t new_poll_fn;
if (ul_ass_state == GPRS_RLCMAC_UL_ASS_WAIT_ACK) {
LOGPTBFUL(this, LOGL_DEBUG,
"Polling is already scheduled, so we must wait for the uplink assignment...\n");
return NULL;
}
rc = check_polling(fn, ts, &new_poll_fn, &rrbp);
if (rc < 0)
return NULL;
if (ms())
new_tbf = ms_ul_tbf(ms());
if (!new_tbf) {
LOGPTBFUL(this, LOGL_ERROR,
"We have a schedule for uplink assignment, but there is no uplink TBF\n");
ul_ass_state = GPRS_RLCMAC_UL_ASS_NONE;
return NULL;
}
msg = msgb_alloc(GSM_MACBLOCK_LEN, "rlcmac_ul_ass");
if (!msg)
return NULL;
/* Initialize a bit vector that uses allocated msgb as the data buffer.
* Old G++ does not support non-trivial designated initializers. Sigh. */
struct bitvec bv = { };
bv.data = msgb_put(msg, GSM_MACBLOCK_LEN);
bv.data_len = GSM_MACBLOCK_LEN;
bitvec_unhex(&bv, DUMMY_VEC);
LOGPTBF(new_tbf, LOGL_INFO, "start Packet Uplink Assignment (PACCH)\n");
mac_control_block = (RlcMacDownlink_t *)talloc_zero(tall_pcu_ctx, RlcMacDownlink_t);
Encoding::write_packet_uplink_assignment(mac_control_block, m_tfi,
(direction == GPRS_RLCMAC_DL_TBF), tlli(),
is_tlli_valid(), new_tbf, 1, rrbp, bts_get_ms_pwr_alpha(new_tbf->bts),
the_pcu->vty.gamma, -1, is_egprs_enabled());
LOGP(DTBF, LOGL_DEBUG, "+++++++++++++++++++++++++ TX : Packet Uplink Assignment +++++++++++++++++++++++++\n");
rc = encode_gsm_rlcmac_downlink(&bv, mac_control_block);
if (rc < 0) {
LOGP(DTBF, LOGL_ERROR, "Encoding of Packet Uplink Ass failed (%d)\n", rc);
goto free_ret;
}
LOGP(DTBF, LOGL_DEBUG, "------------------------- TX : Packet Uplink Assignment -------------------------\n");
bts_do_rate_ctr_inc(bts, CTR_PKT_UL_ASSIGNMENT);
set_polling(new_poll_fn, ts, PDCH_ULC_POLL_UL_ASS);
talloc_free(mac_control_block);
return msg;
free_ret:
talloc_free(mac_control_block);
msgb_free(msg);
return NULL;
}
int gprs_rlcmac_tbf::establish_dl_tbf_on_pacch()
{
struct gprs_rlcmac_dl_tbf *new_tbf = NULL;
bts_do_rate_ctr_inc(bts, CTR_TBF_REUSED);
new_tbf = tbf_alloc_dl_tbf(bts, ms(),
this->trx->trx_no, false);
if (!new_tbf) {
LOGP(DTBF, LOGL_NOTICE, "No PDCH resource\n");
return -1;
}
LOGPTBF(this, LOGL_DEBUG, "Trigger downlink assignment on PACCH\n");
new_tbf->trigger_ass(this);
return 0;
}
const char *tbf_name(const gprs_rlcmac_tbf *tbf)
{
return tbf ? tbf->name() : "(no TBF)";
}
const char *gprs_rlcmac_tbf::name() const
{
snprintf(m_name_buf, sizeof(m_name_buf) - 1,
"TBF(TFI=%d TLLI=0x%08x DIR=%s STATE=%s%s)",
m_tfi, tlli(),
direction == GPRS_RLCMAC_UL_TBF ? "UL" : "DL",
state_name(),
is_egprs_enabled() ? " EGPRS" : ""
);
m_name_buf[sizeof(m_name_buf) - 1] = '\0';
return m_name_buf;
}
void gprs_rlcmac_tbf::rotate_in_list()
{
llist_del(tbf_trx_list((struct gprs_rlcmac_tbf *)this));
if (direction == GPRS_RLCMAC_UL_TBF)
llist_add(tbf_trx_list((struct gprs_rlcmac_tbf *)this), &trx->ul_tbfs);
else
llist_add(tbf_trx_list((struct gprs_rlcmac_tbf *)this), &trx->dl_tbfs);
}
uint8_t gprs_rlcmac_tbf::tsc() const
{
return trx->pdch[first_ts].tsc;
}
uint8_t gprs_rlcmac_tbf::dl_slots() const
{
uint8_t slots = 0;
size_t i;
if (direction == GPRS_RLCMAC_UL_TBF)
return 0;
for (i = 0; i < ARRAY_SIZE(pdch); i += 1)
if (pdch[i])
slots |= 1 << i;
return slots;
}
uint8_t gprs_rlcmac_tbf::ul_slots() const
{
uint8_t slots = 0;
size_t i;
if (direction == GPRS_RLCMAC_DL_TBF) {
if (control_ts < 8)
slots |= 1 << control_ts;
if (first_common_ts < 8)
slots |= 1 << first_common_ts;
return slots;
}
for (i = 0; i < ARRAY_SIZE(pdch); i += 1)
if (pdch[i])
slots |= 1 << i;
return slots;
}
bool gprs_rlcmac_tbf::is_control_ts(uint8_t ts) const
{
return ts == control_ts;
}
/* C API */
enum gprs_rlcmac_tbf_state tbf_state(const struct gprs_rlcmac_tbf *tbf)
{
return tbf->state;
}
enum gprs_rlcmac_tbf_direction tbf_direction(const struct gprs_rlcmac_tbf *tbf)
{
return tbf->direction;
}
void tbf_set_ms(struct gprs_rlcmac_tbf *tbf, GprsMs *ms)
{
tbf->set_ms(ms);
}
struct llist_head *tbf_ms_list(struct gprs_rlcmac_tbf *tbf)
{
return &tbf->m_ms_list.list;
}
struct llist_head *tbf_trx_list(struct gprs_rlcmac_tbf *tbf)
{
return &tbf->m_trx_list.list;
}
struct GprsMs *tbf_ms(const struct gprs_rlcmac_tbf *tbf)
{
return tbf->ms();
}
bool tbf_timers_pending(struct gprs_rlcmac_tbf *tbf, enum tbf_timers t)
{
return tbf->timers_pending(t);
}
struct gprs_llc *tbf_llc(struct gprs_rlcmac_tbf *tbf)
{
return &tbf->m_llc;
}
uint8_t tbf_first_common_ts(const struct gprs_rlcmac_tbf *tbf)
{
return tbf->first_common_ts;
}
uint8_t tbf_dl_slots(const struct gprs_rlcmac_tbf *tbf)
{
return tbf->dl_slots();
}
uint8_t tbf_ul_slots(const struct gprs_rlcmac_tbf *tbf)
{
return tbf->ul_slots();
}
bool tbf_is_tfi_assigned(const struct gprs_rlcmac_tbf *tbf)
{
return tbf->is_tfi_assigned();
}
uint8_t tbf_tfi(const struct gprs_rlcmac_tbf *tbf)
{
return tbf->tfi();
}
int tbf_check_polling(const struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts, uint32_t *poll_fn, unsigned int *rrbp)
{
return tbf->check_polling(fn, ts, poll_fn, rrbp);
}
void tbf_set_polling(struct gprs_rlcmac_tbf *tbf, uint32_t new_poll_fn, uint8_t ts, enum pdch_ulc_tbf_poll_reason t)
{
return tbf->set_polling(new_poll_fn, ts, t);
}
void tbf_poll_timeout(struct gprs_rlcmac_tbf *tbf, struct gprs_rlcmac_pdch *pdch, uint32_t poll_fn, enum pdch_ulc_tbf_poll_reason reason)
{
tbf->poll_timeout(pdch, poll_fn, reason);
}