aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/timeslot_fsm.c
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2018-05-14 18:14:15 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2018-07-28 12:18:23 +0200
commit31f525e7560ad13e32cfc5e0b5f1862c0efcb991 (patch)
tree305b224d3e555eb69798d09e0edb269c32699418 /src/osmo-bsc/timeslot_fsm.c
parent596c402835bab2a01fba7d7233512009eb4142f5 (diff)
large refactoring: use FSMs for lchans; add inter-BSC HO
Add FSMs: - timeslot_fsm: handle dynamic timeslots and OML+RSL availability. - lchan_fsm: handle an individual lchan activation, RTP stream and release, signal the appropriate calling FSMs on success, failure, release. - mgw_endpoint_fsm: handle one entire endpoint with several CI. - assignment_fsm: BSSMAP Assignment Request. - handover_fsm: all of intra, inter-MO and inter-MT handover. Above FSMs absorb large parts of the gscon FSM. The gscon FSM was surpassing the maximum amount events (32), and it is more logical to treat assignment, handover and MGW procedures in separate FSMs. - Add logging macros for each FSM type: - LOG_TS() - LOG_LCHAN() - LOG_MGWEP(), LOG_CI() - LOG_ASSIGNMENT() - LOG_HO() These log with the osmo_fsm_inst where present. New style decision: logging without a final newline char is awkward, especially for gsmtap logging and when other logs interleave LOGPC() calls; we have various cases where the final \n goes missing, and also this invokes the log category checking N times instead of once. So I decided to make these macros *always* append a newline, but only if there is no final newline yet. I hope that the compiler optimizes the strlen() of the constant format strings away. Thus I can log with or without typing "\n" and always get an \n termination anyway. General: - replace osmo_timers, state enums and program-wide osmo_signal_dispatch() with dedicated FSM timeouts, states and events. - introduce a common way to handle Tnnn timers: gsm_timers.h/.c: struct T_def. These can be used (with some macro magic) to define a state's timeout once, and not make mistakes for each osmo_fsm_inst_state_chg(). Details: bsc_subscr_conn_fsm.c: - move most states of this FSM to lchan_fsm, assignment_fsm, handover_fsm and mgw_endpoint_fsm. - There is exactly one state for an ongoing Assignment, with all details handled in conn->assignment.fi. The state relies on the assignment_fsm's timeout. - There is one state for an ongoing Handover; except for an incoming Handover from a remote BSS, the gscon remains in ST_INIT until the new lchan and conn are both established. - move bssmap_add_lcls_status() to osmo_bsc_lcls.c abis_rsl.c: - move all dynamic timeslot logic away into timeslot_fsm. Only keep plain send/receive functions in abis_rsl.c - reduce some rsl functions to merely send a message, rename to "_tx_". - rsl_ipacc_mdcx(): add '_tx_' in the name; move parts that change the lchan state out into the lchan_fsm, the lchan->abis_ip.* are now set there prior to invoking this function. - move all timers and error/release handling away into various FSMs. - tweak ipa_smod_s_for_lchan() and ipa_rtp_pt_for_lchan() to not require an lchan passed, but just mode,type that they require. Rename to ipacc_speech_mode*() and ipacc_payload_type(). - add rsl_forward_layer3_info, used for inter-BSC HO MO, to just send the RR message received during BSSMAP Handover Command. - move various logging to LOG_LCHAN() in order to log with the lchan FSM instance. One drawback is that the lchan FSM is limited to one logging category, i.e. this moves some logging from DRR to DRSL. It might actually make sense to combine those categories. - lose LOGP...LOGPC logging cascades: they are bad for gsmtap logging and for performance. - handle_classmark_chg(): change logging, move cm2 len check out of the cm3 condition (I hope that's correct). - gsm48_send_ho_cmd(): split off gsm48_make_ho_cmd() which doesn't send right away, so that during inter-bsc HO we can make an RR Handover Command to send via the MSC to the remote BSS. assignment_fsm.c: - the Chan Mode Modify in case of re-using the same lchan is not implemented yet, because this was also missing in the previous implementation (OS#3357). osmo_bsc_api.c: - simplify bsc_mr_config() and move to lchan_fsm.c, the only caller; rename to lchan_mr_config(). (bsc_mr_config() used to copy the values to mr_bts_lv twice, once by member assignment and then again with a memcpy.) - During handover, we used to copy the MR config from the old lchan. Since we may handover between FR and HR, rather set the MR Config anew every time, so that FR rates are always available on FR lchans, and never on HR lchans. Depends: I03ee7ce840ecfa0b6a33358e7385528aabd4873f (libosmocore), I1f2918418c38918c5ac70acaa51a47adfca12b5e (libosmocore) Change-Id: I82e3f918295daa83274a4cf803f046979f284366
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);
+}