aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/timeslot_fsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/timeslot_fsm.c')
-rw-r--r--src/osmo-bsc/timeslot_fsm.c913
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);
+}