aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/chan_alloc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/chan_alloc.c')
-rw-r--r--src/osmo-bsc/chan_alloc.c582
1 files changed, 7 insertions, 575 deletions
diff --git a/src/osmo-bsc/chan_alloc.c b/src/osmo-bsc/chan_alloc.c
index c73ecb74b..9c434145f 100644
--- a/src/osmo-bsc/chan_alloc.c
+++ b/src/osmo-bsc/chan_alloc.c
@@ -31,576 +31,12 @@
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/core/talloc.h>
-bool ts_is_usable(const struct gsm_bts_trx_ts *ts)
-{
- if (!trx_is_usable(ts->trx)) {
- LOGP(DRLL, LOGL_DEBUG, "%s not usable\n", gsm_trx_name(ts->trx));
- return false;
- }
-
- /* If a TCH/F_PDCH TS is busy changing, it is already taken or not
- * yet available. */
- if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
- if (ts->flags & TS_F_PDCH_PENDING_MASK) {
- LOGP(DRLL, LOGL_DEBUG, "%s in switchover, not available\n",
- gsm_ts_and_pchan_name(ts));
- return false;
- }
- }
-
- /* If a dynamic channel is busy changing, it is already taken or not
- * yet available. */
- if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
- if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
- LOGP(DRLL, LOGL_DEBUG, "%s in switchover, not available\n",
- gsm_ts_and_pchan_name(ts));
- return false;
- }
- }
-
- return true;
-}
-
-static int trx_count_free_ts(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan)
-{
- struct gsm_bts_trx_ts *ts;
- int j, ss;
- int count = 0;
-
- if (!trx_is_usable(trx))
- return 0;
-
- for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
- enum gsm_phys_chan_config ts_pchan_is;
- ts = &trx->ts[j];
- if (!ts_is_usable(ts))
- continue;
-
- ts_pchan_is = ts_pchan(ts);
-
- if (ts_pchan_is == GSM_PCHAN_PDCH) {
- /* Dynamic timeslots in PDCH mode will become TCH if needed. */
- switch (ts->pchan) {
- case GSM_PCHAN_TCH_F_PDCH:
- if (pchan == GSM_PCHAN_TCH_F)
- count++;
- continue;
-
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
- if (pchan == GSM_PCHAN_TCH_F)
- count++;
- else if (pchan == GSM_PCHAN_TCH_H)
- count += 2;
- continue;
-
- default:
- /* Not dynamic, not applicable. */
- continue;
- }
- }
-
- if (ts_pchan_is != pchan)
- continue;
- /* check if all sub-slots are allocated yet */
- for (ss = 0; ss < ts_subslots(ts); ss++) {
- struct gsm_lchan *lc = &ts->lchan[ss];
- if (lc->type == GSM_LCHAN_NONE &&
- lc->state == LCHAN_S_NONE)
- count++;
- }
- }
-
- return count;
-}
-
-/* Count number of free TS of given pchan type */
-int bts_count_free_ts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
-{
- struct gsm_bts_trx *trx;
- int count = 0;
-
- llist_for_each_entry(trx, &bts->trx_list, list)
- count += trx_count_free_ts(trx, pchan);
-
- return count;
-}
-
-static bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts,
- enum gsm_phys_chan_config as_pchan)
-{
- switch (ts->pchan) {
- case GSM_PCHAN_TCH_F_PDCH:
- if (ts->flags & TS_F_PDCH_PENDING_MASK) {
- /* currently being switched over. Not usable. */
- return false;
- }
- switch (as_pchan) {
- case GSM_PCHAN_TCH_F:
- case GSM_PCHAN_PDCH:
- /* continue to check below. */
- break;
- default:
- return false;
- }
- break;
-
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
- if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
- /* currently being switched over. Not usable. */
- return false;
- }
- switch (as_pchan) {
- case GSM_PCHAN_TCH_F:
- case GSM_PCHAN_TCH_H:
- case GSM_PCHAN_PDCH:
- /* continue to check below. */
- break;
- default:
- return false;
- }
- break;
-
- default:
- /* static timeslots never switch. */
- return ts->pchan == as_pchan;
- }
-
- /* Dynamic timeslots -- Checks depending on the current actual pchan mode: */
- switch (ts_pchan(ts)) {
- case GSM_PCHAN_NONE:
- /* Not initialized, possibly because GPRS was disabled. We may switch. */
- return true;
-
- case GSM_PCHAN_PDCH:
- /* This slot is in PDCH mode and available to switch pchan mode. But check for
- * error states: */
- if (ts->lchan->state != LCHAN_S_NONE && ts->lchan->state != LCHAN_S_ACTIVE)
- return false;
- return true;
-
- case GSM_PCHAN_TCH_F:
- case GSM_PCHAN_TCH_H:
- /* No need to switch at all? */
- if (ts_pchan(ts) == as_pchan)
- return true;
-
- /* If any lchan is in use, we can't change the pchan kind */
- {
- int ss;
- int subslots = ts_subslots(ts);
- for (ss = 0; ss < subslots; ss++) {
- struct gsm_lchan *lc = &ts->lchan[ss];
- if (lc->type != GSM_LCHAN_NONE || lc->state != LCHAN_S_NONE)
- return false;
- }
- }
- return true;
-
- default:
- /* Not implemented. */
- return false;
- }
-}
-
-static struct gsm_lchan *
-_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan,
- enum gsm_phys_chan_config as_pchan)
-{
- struct gsm_bts_trx_ts *ts;
- int j, start, stop, dir, ss;
- int check_subslots;
-
-#define LOGPLCHANALLOC(fmt, args...) \
- LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s as %s: " fmt, \
- gsm_pchan_name(pchan), gsm_pchan_name(as_pchan), ## args)
-
- if (!trx_is_usable(trx)) {
- LOGPLCHANALLOC("%s trx not usable\n", gsm_trx_name(trx));
- return NULL;
- }
-
- if (trx->bts->chan_alloc_reverse) {
- /* check TS 7..0 */
- start = 7;
- stop = -1;
- dir = -1;
- } else {
- /* check TS 0..7 */
- start = 0;
- stop = 8;
- dir = 1;
- }
-
- for (j = start; j != stop; j += dir) {
- ts = &trx->ts[j];
- if (!ts_is_usable(ts))
- continue;
- /* The caller first selects what kind of TS to search in, e.g. looking for exact
- * GSM_PCHAN_TCH_F, or maybe among dynamic GSM_PCHAN_TCH_F_TCH_H_PDCH... */
- if (ts->pchan != pchan) {
- LOGPLCHANALLOC("%s is != %s\n", gsm_ts_and_pchan_name(ts),
- gsm_pchan_name(pchan));
- continue;
- }
- /* Next, is this timeslot in or can it be switched to the pchan we want to use it for? */
- if (!ts_usable_as_pchan(ts, as_pchan)) {
- LOGPLCHANALLOC("%s is not usable as %s\n", gsm_ts_and_pchan_name(ts),
- gsm_pchan_name(as_pchan));
- continue;
- }
- /* If we need to switch it, after above check we are also allowed to switch it, and we
- * will always use the first lchan after the switch. Return that lchan and rely on the
- * caller to perform the pchan switchover. */
- if (ts_pchan(ts) != as_pchan) {
- LOGPLCHANALLOC("%s is a match, will switch to %s\n", gsm_ts_and_pchan_name(ts),
- gsm_pchan_name(as_pchan));
- return ts->lchan;
- }
-
- /* TS is in desired pchan mode. Go ahead and check for an available lchan. */
- check_subslots = ts_subslots(ts);
- for (ss = 0; ss < check_subslots; ss++) {
- struct gsm_lchan *lc = &ts->lchan[ss];
- if (lc->type == GSM_LCHAN_NONE &&
- lc->state == LCHAN_S_NONE) {
- LOGPLCHANALLOC("%s ss=%d is available\n", gsm_ts_and_pchan_name(ts),
- lc->nr);
- return lc;
- }
- LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n",
- gsm_ts_and_pchan_name(ts), lc->nr, gsm_lchant_name(lc->type),
- gsm_lchans_name(lc->state));
- }
- }
-
- return NULL;
-#undef LOGPLCHANALLOC
-}
-
-static struct gsm_lchan *
-_lc_dyn_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan,
- enum gsm_phys_chan_config dyn_as_pchan)
-{
- struct gsm_bts_trx *trx;
- struct gsm_lchan *lc;
-
- if (bts->chan_alloc_reverse) {
- llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
- lc = _lc_find_trx(trx, pchan, dyn_as_pchan);
- if (lc)
- return lc;
- }
- } else {
- llist_for_each_entry(trx, &bts->trx_list, list) {
- lc = _lc_find_trx(trx, pchan, dyn_as_pchan);
- if (lc)
- return lc;
- }
- }
-
- return NULL;
-}
-
-static struct gsm_lchan *
-_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
-{
- return _lc_dyn_find_bts(bts, pchan, pchan);
-}
-
-/* Allocate a logical channel.
- *
- * Dynamic channel types: we always prefer a dedicated TS, and only pick +
- * switch a dynamic TS if no pure TS of the requested PCHAN is available.
- *
- * TCH_F/PDCH: if we pick a PDCH ACT style dynamic TS as TCH/F channel, PDCH
- * will be disabled in rsl_chan_activate_lchan(); there is no need to check
- * whether PDCH mode is currently active, here.
- */
-struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type,
- int allow_bigger)
-{
- struct gsm_lchan *lchan = NULL;
- enum gsm_phys_chan_config first, first_cbch, second, second_cbch;
-
- LOGP(DRLL, LOGL_DEBUG, "(bts=%d) lchan_alloc(%s)\n", bts->nr, gsm_lchant_name(type));
-
- switch (type) {
- case GSM_LCHAN_SDCCH:
- if (bts->chan_alloc_reverse) {
- first = GSM_PCHAN_SDCCH8_SACCH8C;
- first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
- second = GSM_PCHAN_CCCH_SDCCH4;
- second_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH;
- } else {
- first = GSM_PCHAN_CCCH_SDCCH4;
- first_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH;
- second = GSM_PCHAN_SDCCH8_SACCH8C;
- second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
- }
-
- lchan = _lc_find_bts(bts, first);
- if (lchan == NULL)
- lchan = _lc_find_bts(bts, first_cbch);
- if (lchan == NULL)
- lchan = _lc_find_bts(bts, second);
- if (lchan == NULL)
- lchan = _lc_find_bts(bts, second_cbch);
-
- /* allow to assign bigger channels */
- if (allow_bigger) {
- if (lchan == NULL) {
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
- if (lchan)
- type = GSM_LCHAN_TCH_H;
- }
-
- if (lchan == NULL) {
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
-
- /* try dynamic TCH/F_PDCH */
- if (lchan == NULL) {
- lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH,
- GSM_PCHAN_TCH_F);
- /* TCH/F_PDCH will be used as TCH/F */
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
-
- /* try fully dynamic TCH/F_TCH/H_PDCH */
- if (lchan == NULL) {
- lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_TCH_H_PDCH,
- GSM_PCHAN_TCH_H);
- if (lchan)
- type = GSM_LCHAN_TCH_H;
- }
- /*
- * No need to check fully dynamic channels for TCH/F:
- * if no TCH/H was available, neither will be TCH/F.
- */
- }
- break;
- case GSM_LCHAN_TCH_F:
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
- /* If we don't have TCH/F available, fall-back to TCH/H */
- if (!lchan) {
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
- if (lchan)
- type = GSM_LCHAN_TCH_H;
- }
- /* If we don't have TCH/H either, try dynamic TCH/F_PDCH */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH,
- GSM_PCHAN_TCH_F);
- /* TCH/F_PDCH used as TCH/F -- here, type is already
- * set to GSM_LCHAN_TCH_F, but for clarity's sake... */
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
-
- /* Try fully dynamic TCH/F_TCH/H_PDCH as TCH/F... */
- if (!lchan && bts->network->dyn_ts_allow_tch_f) {
- lchan = _lc_dyn_find_bts(bts,
- GSM_PCHAN_TCH_F_TCH_H_PDCH,
- GSM_PCHAN_TCH_F);
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
- /* ...and as TCH/H. */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts,
- GSM_PCHAN_TCH_F_TCH_H_PDCH,
- GSM_PCHAN_TCH_H);
- if (lchan)
- type = GSM_LCHAN_TCH_H;
- }
- break;
- case GSM_LCHAN_TCH_H:
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
- /* If we don't have TCH/H available, fall-back to TCH/F */
- if (!lchan) {
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
- /* No dedicated TCH/x available -- try fully dynamic
- * TCH/F_TCH/H_PDCH */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts,
- GSM_PCHAN_TCH_F_TCH_H_PDCH,
- GSM_PCHAN_TCH_H);
- if (lchan)
- type = GSM_LCHAN_TCH_H;
- }
- /*
- * No need to check TCH/F_TCH/H_PDCH channels for TCH/F:
- * if no TCH/H was available, neither will be TCH/F.
- */
- /* If we don't have TCH/F either, try dynamic TCH/F_PDCH */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH,
- GSM_PCHAN_TCH_F);
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
- break;
- default:
- LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
- }
-
- if (lchan) {
- lchan->type = type;
-
- LOGP(DRLL, LOGL_INFO, "%s Allocating lchan=%u as %s\n",
- gsm_ts_and_pchan_name(lchan->ts),
- lchan->nr, gsm_lchant_name(lchan->type));
-
- /* reset measurement report counter and index */
- lchan->meas_rep_count = 0;
- lchan->meas_rep_idx = 0;
- lchan->meas_rep_last_seen_nr = 255;
-
- /* clear sapis */
- memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis));
-
- /* clear multi rate config */
- memset(&lchan->mr_ms_lv, 0, sizeof(lchan->mr_ms_lv));
- memset(&lchan->mr_bts_lv, 0, sizeof(lchan->mr_bts_lv));
- lchan->broken_reason = "";
- } else {
- struct challoc_signal_data sig;
-
- LOGP(DRLL, LOGL_ERROR, "(bts=%d) Failed to allocate %s channel\n",
- bts->nr, gsm_lchant_name(type));
-
- sig.bts = bts;
- sig.type = type;
- osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig);
- }
-
- return lchan;
-}
-
-/* Free a logical channel */
-void lchan_free(struct gsm_lchan *lchan)
-{
- struct challoc_signal_data sig;
- int i;
-
- sig.type = lchan->type;
- lchan->type = GSM_LCHAN_NONE;
-
-
- if (lchan->conn
- && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
- && lchan->ts->dyn.pchan_is != lchan->ts->dyn.pchan_want)) {
- struct lchan_signal_data sig;
-
- /* We might kill an active channel... */
- sig.lchan = lchan;
- sig.mr = NULL;
- osmo_signal_dispatch(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig);
- }
-
- /* stop the timer */
- osmo_timer_del(&lchan->T3101);
-
- /* clear cached measuement reports */
- lchan->meas_rep_idx = 0;
- for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) {
- lchan->meas_rep[i].flags = 0;
- lchan->meas_rep[i].nr = 0;
- }
- for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++)
- lchan->neigh_meas[i].arfcn = 0;
-
- if (lchan->rqd_ref) {
- talloc_free(lchan->rqd_ref);
- lchan->rqd_ref = NULL;
- lchan->rqd_ta = 0;
- }
-
- sig.lchan = lchan;
- sig.bts = lchan->ts->trx->bts;
- osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_FREED, &sig);
-
- if (lchan->conn
- && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
- && lchan->ts->dyn.pchan_is != lchan->ts->dyn.pchan_want)) {
- LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n");
- lchan->conn = NULL;
- }
-
- /* FIXME: ts_free() the timeslot, if we're the last logical
- * channel using it */
-
- /* reset RTP voice connection related data */
- memset(&lchan->abis_ip, 0, sizeof(lchan->abis_ip));
-}
-
-/*
- * There was an error with the TRX and we need to forget
- * any state so that a lchan can be allocated again after
- * the trx is fully usable.
- *
- * This should be called after lchan_free to force a channel
- * be available for allocation again. This means that this
- * method will stop the "delay after error"-timer and set the
- * state to LCHAN_S_NONE.
- */
-void lchan_reset(struct gsm_lchan *lchan)
-{
- osmo_timer_del(&lchan->T3101);
- osmo_timer_del(&lchan->T3109);
- osmo_timer_del(&lchan->T3111);
- osmo_timer_del(&lchan->error_timer);
-
- lchan->type = GSM_LCHAN_NONE;
- rsl_lchan_set_state(lchan, LCHAN_S_NONE);
-}
-
-/* Drive the release process of the lchan */
-static void _lchan_handle_release(struct gsm_lchan *lchan,
- int sacch_deact, int mode)
-{
- /* Release all SAPIs on the local end and continue */
- rsl_release_sapis_from(lchan, 1, RSL_REL_LOCAL_END);
-
- /*
- * Shall we send a RR Release, start T3109 and wait for the
- * release indication from the BTS or just take it down (e.g.
- * on assignment requests)
- */
- if (sacch_deact) {
- gsm48_send_rr_release(lchan);
-
- /* Deactivate the SACCH on the BTS side */
- rsl_deact_sacch(lchan);
- rsl_start_t3109(lchan);
- } else if (lchan->sapis[0] == LCHAN_SAPI_UNUSED) {
- rsl_direct_rf_release(lchan);
- } else {
- rsl_release_request(lchan, 0, mode);
- }
-}
-
-/* Consider releasing the channel now */
-int lchan_release(struct gsm_lchan *lchan, int sacch_deact, enum rsl_rel_mode mode)
-{
- DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan));
- rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
-
- lchan->conn = NULL;
- _lchan_handle_release(lchan, sacch_deact, mode);
- return 1;
-}
-
void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
{
struct gsm_bts_trx *trx;
@@ -615,22 +51,18 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
struct gsm_bts_trx_ts *ts = &trx->ts[i];
- struct load_counter *pl = &cl->pchan[ts->pchan];
- int j;
- int subslots;
+ struct load_counter *pl = &cl->pchan[ts->pchan_on_init];
+ struct gsm_lchan *lchan;
/* skip administratively deactivated timeslots */
if (!nm_is_running(&ts->mo.nm_state))
continue;
- subslots = ts_subslots(ts);
- for (j = 0; j < subslots; j++) {
- struct gsm_lchan *lchan = &ts->lchan[j];
-
+ ts_for_each_lchan(lchan, ts) {
pl->total++;
- switch (lchan->state) {
- case LCHAN_S_NONE:
+ switch (lchan->fi->state) {
+ case LCHAN_ST_UNUSED:
break;
default:
pl->used++;