diff options
Diffstat (limited to 'src/osmo-bsc/timeslot_fsm.c')
-rw-r--r-- | src/osmo-bsc/timeslot_fsm.c | 913 |
1 files changed, 913 insertions, 0 deletions
diff --git a/src/osmo-bsc/timeslot_fsm.c b/src/osmo-bsc/timeslot_fsm.c new file mode 100644 index 000000000..84d80f8f8 --- /dev/null +++ b/src/osmo-bsc/timeslot_fsm.c @@ -0,0 +1,913 @@ +/* osmo-bsc API to manage timeslot status: init and switch of dynamic PDCH. + * + * (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/logging.h> + +#include <osmocom/bsc/debug.h> + +#include <osmocom/bsc/timeslot_fsm.h> +#include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/pcu_if.h> + +static struct osmo_fsm ts_fsm; + +#define CHAN_ACT_DEACT_TIMEOUT 4 /* TODO: proper T number? */ + +enum ts_fsm_T { + T_CHAN_ACT_DEACT=23001, +}; + +struct gsm_bts_trx_ts *ts_fi_ts(struct osmo_fsm_inst *fi) +{ + OSMO_ASSERT(fi); + OSMO_ASSERT(fi->fsm == &ts_fsm); + OSMO_ASSERT(fi->priv); + return fi->priv; +} + +static void ts_fsm_update_id(struct gsm_bts_trx_ts *ts) +{ + osmo_fsm_inst_update_id_f(ts->fi, "%u-%u-%u-%s", ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_id(ts->pchan_on_init)); +} + +void ts_fsm_init() +{ + OSMO_ASSERT(osmo_fsm_register(&ts_fsm) == 0); +} + +void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) +{ + OSMO_ASSERT(!ts->fi); + OSMO_ASSERT(ts->trx); + ts->fi = osmo_fsm_inst_alloc(&ts_fsm, ts->trx, ts, LOGL_DEBUG, NULL); + OSMO_ASSERT(ts->fi); +} + +enum lchan_sanity { + LCHAN_IS_INSANE = -1, + LCHAN_IS_READY_TO_GO, + LCHAN_NEEDS_PCHAN_CHANGE, +}; + +static enum lchan_sanity is_lchan_sane(struct gsm_bts_trx_ts *ts, struct gsm_lchan *lchan) +{ + OSMO_ASSERT(ts); + OSMO_ASSERT(lchan); + if (lchan->ts != ts) + return LCHAN_IS_INSANE; + if (!lchan->fi) + return LCHAN_IS_INSANE; + + if (lchan->type == gsm_lchan_type_by_pchan(ts->pchan_is)) + return LCHAN_IS_READY_TO_GO; + + switch (ts->pchan_on_init) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (lchan->type == GSM_LCHAN_TCH_H) + return LCHAN_NEEDS_PCHAN_CHANGE; + /* fall thru */ + case GSM_PCHAN_TCH_F_PDCH: + if (lchan->type == GSM_LCHAN_TCH_F) + return LCHAN_NEEDS_PCHAN_CHANGE; + /* fall thru */ + default: + return LCHAN_IS_INSANE; + } + +} + +static void lchan_dispatch(struct gsm_lchan *lchan, uint32_t lchan_ev) +{ + if (!lchan->fi) + return; + osmo_fsm_inst_dispatch(lchan->fi, lchan_ev, NULL); + OSMO_ASSERT(lchan->fi->state != LCHAN_ST_WAIT_TS_READY); +} + +static int ts_count_active_lchans(struct gsm_bts_trx_ts *ts) +{ + struct gsm_lchan *lchan; + int count = 0; + + ts_for_each_lchan(lchan, ts) { + if (lchan->fi->state == LCHAN_ST_UNUSED) + continue; + count++; + } + + return count; +} + +static void ts_lchans_dispatch(struct gsm_bts_trx_ts *ts, int lchan_state, uint32_t lchan_ev) +{ + struct gsm_lchan *lchan; + + ts_for_each_lchan(lchan, ts) { + if (lchan_state >= 0 + && !lchan_state_is(lchan, lchan_state)) + continue; + lchan_dispatch(lchan, lchan_ev); + } +} + +static void ts_terminate_lchan_fsms(struct gsm_bts_trx_ts *ts) +{ + struct gsm_lchan *lchan; + + ts_for_each_lchan(lchan, ts) { + osmo_fsm_inst_term(lchan->fi, OSMO_FSM_TERM_REQUEST, NULL); + } +} + +static int ts_lchans_waiting(struct gsm_bts_trx_ts *ts) +{ + struct gsm_lchan *lchan; + int count = 0; + ts_for_each_lchan(lchan, ts) + if (lchan->fi->state == LCHAN_ST_WAIT_TS_READY) + count++; + return count; +} + +static void ts_fsm_error(struct osmo_fsm_inst *fi, uint32_t state_chg, const char *fmt, ...) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + + char *errmsg = NULL; + + if (fmt) { + va_list ap; + + va_start(ap, fmt); + errmsg = talloc_vasprintf(ts->trx, fmt, ap); + va_end(ap); + } + + if (ts->last_errmsg) + talloc_free(ts->last_errmsg); + ts->last_errmsg = errmsg; + + if (errmsg) + LOG_TS(ts, LOGL_ERROR, "%s\n", errmsg); + + ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_ERROR); + if (fi->state != state_chg) + osmo_fsm_inst_state_chg(fi, state_chg, 0, 0); +} + +static void ts_fsm_err_ready_to_go_in_pdch(struct osmo_fsm_inst *fi, struct gsm_lchan *lchan) +{ + /* This shouldn't ever happen, so aggressively mark it. */ + ts_fsm_error(fi, TS_ST_BORKEN, + "Internal error: lchan marked as 'ready to go', but activating" + " any lchan should need PCHAN switchover in state %s (lchan: %s)", + osmo_fsm_inst_state_name(fi), gsm_lchan_name(lchan)); +} + +static void ts_setup_lchans(struct gsm_bts_trx_ts *ts) +{ + int i, max_lchans; + + ts->pchan_on_init = ts->pchan_from_config; + ts_fsm_update_id(ts); + + max_lchans = pchan_subslots(ts->pchan_on_init); + LOG_TS(ts, LOGL_DEBUG, "max lchans: %d\n", max_lchans); + + for (i = 0; i < max_lchans; i++) { + /* If we receive more than one Channel OPSTART ACK, don't fail on the second init. */ + if (ts->lchan[i].fi) + continue; + lchan_fsm_alloc(&ts->lchan[i]); + } + + switch (ts->pchan_on_init) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->pchan_is = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + ts->pchan_is = GSM_PCHAN_TCH_F; + break; + default: + ts->pchan_is = ts->pchan_on_init; + break; + } + + LOG_TS(ts, LOGL_DEBUG, "lchans initialized: %d\n", max_lchans); +} + +static void ts_fsm_not_initialized(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + struct gsm_bts *bts = ts->trx->bts; + switch (event) { + + case TS_EV_OML_READY: + ts->pdch_act_allowed = true; + ts_setup_lchans(ts); + if (!ts->trx->rsl_link) { + LOG_TS(ts, LOGL_DEBUG, "No RSL link yet\n"); + return; + } + /* -> UNUSED below */ + break; + + case TS_EV_RSL_READY: + ts->pdch_act_allowed = true; + if (bts->model->oml_is_ts_ready + && !bts->model->oml_is_ts_ready(ts)) { + LOG_TS(ts, LOGL_DEBUG, "OML not ready yet\n"); + return; + } + /* -> UNUSED below */ + break; + + case TS_EV_LCHAN_REQUESTED: + { + /* TS is not initialized, no lchan can be requested. */ + struct gsm_lchan *lchan = data; + if (lchan && lchan->fi) + osmo_fsm_inst_dispatch(fi, LCHAN_EV_TS_ERROR, NULL); + } + return; + + case TS_EV_LCHAN_UNUSED: + /* ignored. */ + return; + + default: + OSMO_ASSERT(false); + } + + osmo_fsm_inst_state_chg(fi, TS_ST_UNUSED, 0, 0); +} + +static void ts_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + struct gsm_bts *bts = ts->trx->bts; + + /* We are entering the unused state. There must by definition not be any lchans waiting to be + * activated. */ + if (ts_lchans_waiting(ts)) { + ts_fsm_error(fi, TS_ST_BORKEN, + "Internal error: entering UNUSED state, but there are lchans waiting to be" + " activated. Not activating them to prevent infinite loops."); + return; + } + + switch (ts->pchan_on_init) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_TCH_F_PDCH: + if (bts->gprs.mode == BTS_GPRS_NONE) { + LOG_TS(ts, LOGL_DEBUG, "GPRS mode is 'none': not activating PDCH.\n"); + return; + } + if (!ts->pdch_act_allowed) { + LOG_TS(ts, LOGL_DEBUG, "PDCH is disabled for this timeslot," + " either due to a PDCH ACT NACK, or from manual VTY command:" + " not activating PDCH. (last error: %s)\n", + ts->last_errmsg ? : "-"); + return; + } + osmo_fsm_inst_state_chg(fi, TS_ST_WAIT_PDCH_ACT, CHAN_ACT_DEACT_TIMEOUT, + T_CHAN_ACT_DEACT); + break; + + default: + /* nothing to do */ + break; + } +} + +static void ts_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + + switch (event) { + + case TS_EV_LCHAN_REQUESTED: + { + struct gsm_lchan *lchan = data; + switch (is_lchan_sane(ts, lchan)) { + case LCHAN_NEEDS_PCHAN_CHANGE: + /* Osmocom style dyn TS: in UNUSED state, PDCH is already switched off, + * we merely need to RSL Chan Activ the new lchan. For ip.access style + * dyn TS this is already TCH/F, and we should never hit this. */ + case LCHAN_IS_READY_TO_GO: + osmo_fsm_inst_state_chg(fi, TS_ST_IN_USE, 0, 0); + return; + default: + osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_TS_ERROR, NULL); + return; + } + } + + case TS_EV_LCHAN_UNUSED: + /* ignored. */ + return; + + default: + OSMO_ASSERT(false); + } +} + +static inline void ts_fsm_pdch_deact(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_state_chg(fi, TS_ST_WAIT_PDCH_DEACT, CHAN_ACT_DEACT_TIMEOUT, T_CHAN_ACT_DEACT); +} + +static void ts_fsm_wait_pdch_act_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + int rc; + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + + rc = rsl_tx_dyn_ts_pdch_act_deact(ts, true); + + /* On error, we couldn't send the activation message and remain unused. */ + if (rc) + ts_fsm_error(fi, TS_ST_UNUSED, "Unable to send PDCH activation"); +} + +static void ts_fsm_wait_pdch_act(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + switch (event) { + + case TS_EV_PDCH_ACT_ACK: + osmo_fsm_inst_state_chg(fi, TS_ST_PDCH, 0, 0); + return; + + case TS_EV_PDCH_ACT_NACK: + if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH) + rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + else + rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]); + ts->pdch_act_allowed = false; + ts_fsm_error(fi, TS_ST_UNUSED, "Received PDCH activation NACK"); + return; + + case TS_EV_LCHAN_REQUESTED: + { + struct gsm_lchan *lchan = data; + switch (is_lchan_sane(ts, lchan)) { + case LCHAN_IS_READY_TO_GO: + /* PDCH activation has not been acked, the previous pchan kind may still + * linger in ts->pchan and make it look like the ts is usable right away. + * But we've started the switchover and must finish that first. */ + case LCHAN_NEEDS_PCHAN_CHANGE: + /* PDCH onenter will see that the lchan is waiting and continue to switch + * off PDCH right away. */ + return; + + default: + lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); + return; + } + } + + case TS_EV_LCHAN_UNUSED: + /* ignored. */ + return; + + default: + OSMO_ASSERT(false); + } +} + +static void ts_fsm_pdch_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + int count; + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + + /* Set pchan = PDCH status, but double check. */ + switch (ts->pchan_on_init) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_TCH_F_PDCH: + case GSM_PCHAN_PDCH: + ts->pchan_is = GSM_PCHAN_PDCH; + break; + default: + ts_fsm_error(fi, TS_ST_BORKEN, "pchan %s is incapable of activating PDCH", + gsm_pchan_name(ts->pchan_on_init)); + return; + } + + /* PDCH use has changed, tell the PCU about it. */ + pcu_info_update(ts->trx->bts); + + /* If we received TS_EV_LCHAN_REQUESTED in the meantime, go right out of PDCH again. */ + if ((count = ts_lchans_waiting(ts))) { + LOG_TS(ts, LOGL_DEBUG, "%d lchan(s) waiting for usable timeslot\n", count); + ts_fsm_pdch_deact(fi); + } +} + +static void ts_fsm_pdch(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + switch (event) { + + case TS_EV_LCHAN_REQUESTED: + { + struct gsm_lchan *lchan = data; + switch (is_lchan_sane(ts, lchan)) { + case LCHAN_NEEDS_PCHAN_CHANGE: + ts_fsm_pdch_deact(fi); + return; + + case LCHAN_IS_READY_TO_GO: + ts_fsm_err_ready_to_go_in_pdch(fi, lchan); + return; + + default: + /* Reject just this lchan. */ + lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); + return; + } + } + + case TS_EV_LCHAN_UNUSED: + /* ignored */ + return; + + default: + OSMO_ASSERT(false); + } +} + +static void ts_fsm_wait_pdch_deact_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + int rc; + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + + rc = rsl_tx_dyn_ts_pdch_act_deact(ts, false); + + /* On error, we couldn't send the deactivation message and remain in PDCH. */ + if (rc) + ts_fsm_error(fi, TS_ST_PDCH, "Unable to send PDCH deactivation"); +} + +static void ts_fsm_wait_pdch_deact(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + switch (event) { + + case TS_EV_PDCH_DEACT_ACK: + /* Remove pchan = PDCH status, but double check. */ + switch (ts->pchan_on_init) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->pchan_is = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + ts->pchan_is = GSM_PCHAN_TCH_F; + break; + default: + ts_fsm_error(fi, TS_ST_BORKEN, "pchan %s is incapable of deactivating PDCH", + gsm_pchan_name(ts->pchan_on_init)); + return; + } + osmo_fsm_inst_state_chg(fi, TS_ST_IN_USE, 0, 0); + /* IN_USE onenter will signal all waiting lchans. */ + + /* PDCH use has changed, tell the PCU about it. */ + pcu_info_update(ts->trx->bts); + return; + + case TS_EV_PDCH_DEACT_NACK: + if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH) + rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + /* For Osmocom style dyn TS, there actually is no NACK, since there is no RF Channel + * Release NACK message in RSL. */ + ts_fsm_error(fi, TS_ST_BORKEN, "Received PDCH deactivation NACK"); + return; + + case TS_EV_LCHAN_REQUESTED: + { + struct gsm_lchan *lchan = data; + switch (is_lchan_sane(ts, lchan)) { + case LCHAN_NEEDS_PCHAN_CHANGE: + /* IN_USE onenter will see that the lchan is waiting and signal it. */ + return; + + case LCHAN_IS_READY_TO_GO: + ts_fsm_err_ready_to_go_in_pdch(fi, lchan); + return; + + default: + /* Reject just this lchan. */ + lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); + return; + } + } + + default: + OSMO_ASSERT(false); + } +} + +static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + bool ok; + struct gsm_lchan *lchan; + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + enum gsm_chan_t activating_type = GSM_LCHAN_NONE; + + /* After being in use, allow PDCH act again, if appropriate. */ + ts->pdch_act_allowed = true; + + /* For static TS, check validity. For dyn TS, figure out which PCHAN this should become. */ + ts_as_pchan_for_each_lchan(lchan, ts, ts->pchan_on_init) { + if (lchan_state_is(lchan, LCHAN_ST_UNUSED)) + continue; + + switch (lchan->type) { + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_SDCCH: + ok = ts_is_capable_of_lchant(ts, lchan->type); + break; + default: + ok = false; + break; + } + + if (!ok && lchan_state_is(lchan, LCHAN_ST_WAIT_TS_READY)) { + LOG_TS(ts, LOGL_ERROR, "lchan activation of %s is not permitted for %s (%s)\n", + gsm_lchant_name(lchan->type), gsm_pchan_name(ts->pchan_on_init), + gsm_lchan_name(lchan)); + lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); + } + + if (!ok) + continue; + + if (activating_type == GSM_LCHAN_NONE) + activating_type = lchan->type; + else if (activating_type != lchan->type) { + LOG_TS(ts, LOGL_ERROR, "lchan type %s mismatches %s (%s)\n", + gsm_lchant_name(lchan->type), gsm_lchant_name(activating_type), + gsm_lchan_name(lchan)); + lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); + } + } + + ok = false; + switch (activating_type) { + case GSM_LCHAN_SDCCH: + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + ok = ts_is_capable_of_lchant(ts, activating_type); + break; + + case GSM_LCHAN_NONE: + LOG_TS(ts, LOGL_DEBUG, "Entered IN_USE state but no lchans are actually in use now.\n"); + break; + + default: + LOG_TS(ts, LOGL_ERROR, "cannot use timeslot as %s\n", gsm_lchant_name(activating_type)); + ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_ERROR); + break; + } + + if (!ok) { + osmo_fsm_inst_state_chg(fi, TS_ST_UNUSED, 0, 0); + return; + } + + /* Make sure dyn TS pchan_is is updated. For TCH/F_PDCH, there are only PDCH or TCH/F modes, but + * for Osmocom style TCH/F_TCH/H_PDCH the pchan_is == NONE until an lchan is activated. */ + if (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH) + ts->pchan_is = gsm_pchan_by_lchan_type(activating_type); + ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_READY); +} + +static void ts_fsm_in_use(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + switch (event) { + case TS_EV_LCHAN_UNUSED: + if (!ts_count_active_lchans(ts)) + osmo_fsm_inst_state_chg(fi, TS_ST_UNUSED, 0, 0); + return; + + case TS_EV_LCHAN_REQUESTED: + { + struct gsm_lchan *lchan = data; + switch (is_lchan_sane(ts, lchan)) { + case LCHAN_IS_READY_TO_GO: + osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_TS_READY, NULL); + return; + + case LCHAN_NEEDS_PCHAN_CHANGE: + LOG_TS(ts, LOGL_ERROR, + "cannot activate lchan of mismatching pchan type" + " when the TS is already in use: %s\n", + gsm_lchan_name(lchan)); + /* fall thru */ + default: + /* Reject just this lchan. */ + lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); + return; + } + } + + default: + OSMO_ASSERT(false); + } +} + +static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case TS_EV_LCHAN_UNUSED: + /* ignored */ + return; + + case TS_EV_LCHAN_REQUESTED: + { + struct gsm_lchan *lchan = data; + lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); + return; + } + + default: + OSMO_ASSERT(false); + } +} + +static int ts_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch (fi->state) { + case TS_ST_WAIT_PDCH_ACT: + ts_fsm_error(fi, TS_ST_BORKEN, "PDCH activation timeout"); + return 0; + + case TS_ST_WAIT_PDCH_DEACT: + ts_fsm_error(fi, TS_ST_BORKEN, "PDCH deactivation timeout"); + return 0; + + default: + ts_fsm_error(fi, TS_ST_BORKEN, "Unknown timeout in state %s", + osmo_fsm_inst_state_name(fi)); + return 0; + } +} + +static void ts_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + switch (event) { + case TS_EV_OML_DOWN: + if (fi->state != TS_ST_NOT_INITIALIZED) + osmo_fsm_inst_state_chg(fi, TS_ST_NOT_INITIALIZED, 0, 0); + OSMO_ASSERT(fi->state == TS_ST_NOT_INITIALIZED); + ts_terminate_lchan_fsms(ts); + ts->pchan_is = ts->pchan_on_init = GSM_PCHAN_NONE; + ts_fsm_update_id(ts); + break; + + case TS_EV_RSL_DOWN: + if (fi->state != TS_ST_NOT_INITIALIZED) + osmo_fsm_inst_state_chg(fi, TS_ST_NOT_INITIALIZED, 0, 0); + OSMO_ASSERT(fi->state == TS_ST_NOT_INITIALIZED); + ts->pchan_is = GSM_PCHAN_NONE; + ts_lchans_dispatch(ts, -1, LCHAN_EV_TS_ERROR); + break; + + default: + OSMO_ASSERT(false); + } +} + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state ts_fsm_states[] = { + [TS_ST_NOT_INITIALIZED] = { + .name = "NOT_INITIALIZED", + .action = ts_fsm_not_initialized, + .in_event_mask = 0 + | S(TS_EV_OML_READY) + | S(TS_EV_RSL_READY) + | S(TS_EV_LCHAN_REQUESTED) + | S(TS_EV_LCHAN_UNUSED) + , + .out_state_mask = 0 + | S(TS_ST_UNUSED) + , + }, + [TS_ST_UNUSED] = { + .name = "UNUSED", + .onenter = ts_fsm_unused_onenter, + .action = ts_fsm_unused, + .in_event_mask = 0 + | S(TS_EV_LCHAN_REQUESTED) + | S(TS_EV_LCHAN_UNUSED) + , + .out_state_mask = 0 + | S(TS_ST_WAIT_PDCH_ACT) + | S(TS_ST_IN_USE) + | S(TS_ST_NOT_INITIALIZED) + , + }, + [TS_ST_WAIT_PDCH_ACT] = { + .name = "WAIT_PDCH_ACT", + .onenter = ts_fsm_wait_pdch_act_onenter, + .action = ts_fsm_wait_pdch_act, + .in_event_mask = 0 + | S(TS_EV_PDCH_ACT_ACK) + | S(TS_EV_PDCH_ACT_NACK) + | S(TS_EV_LCHAN_REQUESTED) + | S(TS_EV_LCHAN_UNUSED) + , + .out_state_mask = 0 + | S(TS_ST_PDCH) + | S(TS_ST_UNUSED) + | S(TS_ST_BORKEN) + | S(TS_ST_NOT_INITIALIZED) + , + }, + [TS_ST_PDCH] = { + .name = "PDCH", + .onenter = ts_fsm_pdch_onenter, + .action = ts_fsm_pdch, + .in_event_mask = 0 + | S(TS_EV_LCHAN_REQUESTED) + | S(TS_EV_LCHAN_UNUSED) + , + .out_state_mask = 0 + | S(TS_ST_WAIT_PDCH_DEACT) + | S(TS_ST_NOT_INITIALIZED) + , + }, + [TS_ST_WAIT_PDCH_DEACT] = { + .name = "WAIT_PDCH_DEACT", + .onenter = ts_fsm_wait_pdch_deact_onenter, + .action = ts_fsm_wait_pdch_deact, + .in_event_mask = 0 + | S(TS_EV_PDCH_DEACT_ACK) + | S(TS_EV_PDCH_DEACT_NACK) + | S(TS_EV_LCHAN_REQUESTED) + | S(TS_EV_LCHAN_UNUSED) + , + .out_state_mask = 0 + | S(TS_ST_IN_USE) + | S(TS_ST_UNUSED) + | S(TS_ST_BORKEN) + | S(TS_ST_NOT_INITIALIZED) + , + }, + [TS_ST_IN_USE] = { + .name = "IN_USE", + .onenter = ts_fsm_in_use_onenter, + .action = ts_fsm_in_use, + .in_event_mask = 0 + | S(TS_EV_LCHAN_REQUESTED) + | S(TS_EV_LCHAN_UNUSED) + , + .out_state_mask = 0 + | S(TS_ST_UNUSED) + | S(TS_ST_NOT_INITIALIZED) + , + }, + [TS_ST_BORKEN] = { + .name = "BORKEN", + .action = ts_fsm_borken, + .in_event_mask = 0 + | S(TS_EV_LCHAN_REQUESTED) + | S(TS_EV_LCHAN_UNUSED) + , + .out_state_mask = 0 + | S(TS_ST_NOT_INITIALIZED) + , + }, + +}; + +static const struct value_string ts_fsm_event_names[] = { + OSMO_VALUE_STRING(TS_EV_OML_READY), + OSMO_VALUE_STRING(TS_EV_OML_DOWN), + OSMO_VALUE_STRING(TS_EV_RSL_READY), + OSMO_VALUE_STRING(TS_EV_RSL_DOWN), + OSMO_VALUE_STRING(TS_EV_LCHAN_REQUESTED), + OSMO_VALUE_STRING(TS_EV_LCHAN_UNUSED), + OSMO_VALUE_STRING(TS_EV_PDCH_ACT_ACK), + OSMO_VALUE_STRING(TS_EV_PDCH_ACT_NACK), + OSMO_VALUE_STRING(TS_EV_PDCH_DEACT_ACK), + OSMO_VALUE_STRING(TS_EV_PDCH_DEACT_NACK), + {} +}; + +static struct osmo_fsm ts_fsm = { + .name = "timeslot", + .states = ts_fsm_states, + .num_states = ARRAY_SIZE(ts_fsm_states), + .timer_cb = ts_fsm_timer_cb, + .log_subsys = DRSL, + .event_names = ts_fsm_event_names, + .allstate_event_mask = 0 + | S(TS_EV_OML_DOWN) + | S(TS_EV_RSL_DOWN) + , + .allstate_action = ts_fsm_allstate, +}; + +/* Return true if any lchans are waiting for this timeslot to become a specific PCHAN. If target_pchan is + * not NULL, also return the PCHAN being waited for. */ +bool ts_is_lchan_waiting_for_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config *target_pchan) +{ + struct gsm_lchan *lchan; + ts_for_each_lchan(lchan, ts) { + if (lchan->fi->state == LCHAN_ST_WAIT_TS_READY) { + if (target_pchan) + *target_pchan = gsm_pchan_by_lchan_type(lchan->type); + return true; + } + } + + if (target_pchan) + *target_pchan = ts->pchan_is; + return false; +} + +/* Return true if we are busy changing the PCHAN kind. If target_pchan is not NULL, also return the PCHAN + * (ultimately) being switched to. */ +bool ts_is_pchan_switching(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config *target_pchan) +{ + switch (ts->fi->state) { + case TS_ST_NOT_INITIALIZED: + case TS_ST_BORKEN: + return false; + default: + break; + } + + /* If an lchan is waiting, return the final pchan after all switching is done. */ + if (ts_is_lchan_waiting_for_pchan(ts, target_pchan)) + return true; + + /* No lchans waiting. Return any ongoing switching. */ + + switch (ts->fi->state) { + case TS_ST_WAIT_PDCH_ACT: + if (target_pchan) + *target_pchan = GSM_PCHAN_PDCH; + return true; + + case TS_ST_WAIT_PDCH_DEACT: + /* If we were switching to a specific pchan kind, an lchan would be waiting. So this must + * be NONE then. */ + if (target_pchan) + *target_pchan = GSM_PCHAN_NONE; + return true; + + default: + return false; + } +} + +/* Does the timeslot's *current* state allow use as this PCHAN kind? If the ts is in switchover, return + * true if the switchover's target PCHAN matches, i.e. an lchan for this pchan kind could be requested + * and will be served after the switch. (Do not check whether any lchans are actually available.) */ +bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) +{ + if (!ts_is_usable(ts)) + return false; + + switch (ts->fi->state) { + case TS_ST_IN_USE: + return ts->pchan_is == as_pchan; + + default: + break; + } + + { + enum gsm_phys_chan_config target_pchan; + if (ts_is_lchan_waiting_for_pchan(ts, &target_pchan)) + return target_pchan == as_pchan; + } + + return ts_is_capable_of_pchan(ts, as_pchan); +} |