diff options
Diffstat (limited to 'src/osmo-bsc/gsm_data.c')
-rw-r--r-- | src/osmo-bsc/gsm_data.c | 480 |
1 files changed, 332 insertions, 148 deletions
diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c index 99438bc3c..3cd3432ea 100644 --- a/src/osmo-bsc/gsm_data.c +++ b/src/osmo-bsc/gsm_data.c @@ -25,9 +25,10 @@ #include <ctype.h> #include <stdbool.h> #include <netinet/in.h> +#include <talloc.h> #include <osmocom/core/linuxlist.h> -#include <osmocom/core/talloc.h> +#include <osmocom/core/byteswap.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/abis_nm.h> #include <osmocom/core/statistics.h> @@ -40,6 +41,9 @@ #include <osmocom/bsc/abis_nm.h> #include <osmocom/bsc/handover_cfg.h> #include <osmocom/bsc/gsm_timers.h> +#include <osmocom/bsc/timeslot_fsm.h> +#include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/mgw_endpoint_fsm.h> void *tall_bsc_ctx = NULL; @@ -471,7 +475,7 @@ const struct value_string gsm_chreq_descs[] = { { 0, NULL } }; -const struct value_string gsm_pchant_names[13] = { +const struct value_string gsm_pchant_names[] = { { GSM_PCHAN_NONE, "NONE" }, { GSM_PCHAN_CCCH, "CCCH" }, { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" }, @@ -487,6 +491,22 @@ const struct value_string gsm_pchant_names[13] = { { 0, NULL } }; +const struct value_string gsm_pchan_ids[] = { + { GSM_PCHAN_NONE, "NONE" }, + { GSM_PCHAN_CCCH, "CCCH" }, + { GSM_PCHAN_CCCH_SDCCH4,"CCCH_SDCCH4" }, + { GSM_PCHAN_TCH_F, "TCH_F" }, + { GSM_PCHAN_TCH_H, "TCH_H" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" }, + { GSM_PCHAN_PDCH, "PDCH" }, + { GSM_PCHAN_TCH_F_PDCH, "TCH_F_PDCH" }, + { GSM_PCHAN_UNKNOWN, "UNKNOWN" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH_SDCCH4_CBCH" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8_CBCH" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH_F_TCH_H_PDCH" }, + { 0, NULL } +}; + const struct value_string gsm_pchant_descs[13] = { { GSM_PCHAN_NONE, "Physical Channel not configured" }, { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" }, @@ -520,22 +540,6 @@ const char *gsm_lchant_name(enum gsm_chan_t c) return get_value_string(gsm_chan_t_names, c); } -static const struct value_string lchan_s_names[] = { - { LCHAN_S_NONE, "NONE" }, - { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" }, - { LCHAN_S_ACTIVE, "ACTIVE" }, - { LCHAN_S_INACTIVE, "INACTIVE" }, - { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, - { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, - { LCHAN_S_BROKEN, "BROKEN UNUSABLE" }, - { 0, NULL } -}; - -const char *gsm_lchans_name(enum gsm_lchan_state s) -{ - return get_value_string(lchan_s_names, s); -} - static const struct value_string chreq_names[] = { { GSM_CHREQ_REASON_EMERG, "EMERGENCY" }, { GSM_CHREQ_REASON_PAG, "PAGING" }, @@ -698,13 +702,14 @@ struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) struct gsm_bts_trx_ts *ts = &trx->ts[k]; int l; + ts->trx = trx; ts->nr = k; - ts->pchan = GSM_PCHAN_NONE; - ts->dyn.pchan_is = GSM_PCHAN_NONE; - ts->dyn.pchan_want = GSM_PCHAN_NONE; + ts->pchan_from_config = ts->pchan_on_init = ts->pchan_is = GSM_PCHAN_NONE; ts->tsc = -1; + ts_fsm_alloc(ts); + gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL, bts->nr, trx->nr, ts->nr); @@ -819,7 +824,7 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) talloc_free(bts); return NULL; } - bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4; + bts->c0->ts[0].pchan_from_config = GSM_PCHAN_CCCH_SDCCH4; /* TODO: really?? */ bts->rach_b_thresh = -1; bts->rach_ldavg_slots = -1; @@ -947,49 +952,30 @@ char *gsm_ts_name(const struct gsm_bts_trx_ts *ts) /*! Log timeslot number with full pchan information */ char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts) { - switch (ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - if (ts->dyn.pchan_is == ts->dyn.pchan_want) - snprintf(ts2str, sizeof(ts2str), - "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), - gsm_pchan_name(ts->dyn.pchan_is)); - else - snprintf(ts2str, sizeof(ts2str), - "(bts=%d,trx=%d,ts=%d,pchan=%s" - " switching %s -> %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), - gsm_pchan_name(ts->dyn.pchan_is), - gsm_pchan_name(ts->dyn.pchan_want)); - break; - case GSM_PCHAN_TCH_F_PDCH: - if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) - snprintf(ts2str, sizeof(ts2str), - "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), - (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" - : "TCH/F"); - else - snprintf(ts2str, sizeof(ts2str), - "(bts=%d,trx=%d,ts=%d,pchan=%s" - " switching %s -> %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), - (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" - : "TCH/F", - (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" - : "TCH/F"); - break; - default: - snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)", + if (!ts->fi) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan_from_config=%s, not allocated)", ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan)); - break; - } - + gsm_pchan_name(ts->pchan_from_config)); + else if (ts->fi->state == TS_ST_NOT_INITIALIZED) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan_from_config=%s,state=%s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan_from_config), + osmo_fsm_inst_state_name(ts->fi)); + else if (ts->pchan_is == ts->pchan_on_init) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s,state=%s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan_is), + osmo_fsm_inst_state_name(ts->fi)); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan_on_init=%s,pchan=%s,state=%s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan_on_init), + gsm_pchan_name(ts->pchan_is), + osmo_fsm_inst_state_name(ts->fi)); return ts2str; } @@ -1207,20 +1193,9 @@ uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan) { - enum gsm_phys_chan_config pchan = lchan->ts->pchan; - if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) - return gsm_lchan_as_pchan2chan_nr(lchan, - lchan->ts->dyn.pchan_is); - return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr); -} - -uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan, - enum gsm_phys_chan_config as_pchan) -{ - if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && as_pchan == GSM_PCHAN_PDCH) - return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK); - return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr); + /* Note: non-standard Osmocom style dyn TS PDCH mode chan_nr is only used within + * rsl_tx_dyn_ts_pdch_act_deact(). */ + return gsm_pchan2chan_nr(lchan->ts->pchan_is, lchan->ts->nr, lchan->nr); } /* return the gsm_lchan for the CBCH (if it exists at all) */ @@ -1229,12 +1204,12 @@ struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts) struct gsm_lchan *lchan = NULL; struct gsm_bts_trx *trx = bts->c0; - if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + if (trx->ts[0].pchan_is == GSM_PCHAN_CCCH_SDCCH4_CBCH) lchan = &trx->ts[0].lchan[2]; else { int i; for (i = 0; i < 8; i++) { - if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) { + if (trx->ts[i].pchan_is == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) { lchan = &trx->ts[i].lchan[2]; break; } @@ -1252,48 +1227,31 @@ struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t cbits = chan_nr >> 3; uint8_t lch_idx; struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; - bool ok = true; + bool ok; if (rc) *rc = -EINVAL; if (cbits == 0x01) { - lch_idx = 0; /* TCH/F */ - if (ts->pchan != GSM_PCHAN_TCH_F && - ts->pchan != GSM_PCHAN_PDCH && - ts->pchan != GSM_PCHAN_TCH_F_PDCH - && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && (ts->dyn.pchan_is == GSM_PCHAN_TCH_F - || ts->dyn.pchan_want == GSM_PCHAN_TCH_F))) - ok = false; + lch_idx = 0; /* TCH/F */ + ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_F) + || ts->pchan_on_init == GSM_PCHAN_PDCH; /* PDCH? really? */ } else if ((cbits & 0x1e) == 0x02) { lch_idx = cbits & 0x1; /* TCH/H */ - if (ts->pchan != GSM_PCHAN_TCH_H - && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && (ts->dyn.pchan_is == GSM_PCHAN_TCH_H - || ts->dyn.pchan_want == GSM_PCHAN_TCH_H))) - ok = false; + ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_H); } else if ((cbits & 0x1c) == 0x04) { lch_idx = cbits & 0x3; /* SDCCH/4 */ - if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && - ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) - ok = false; + ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH_SDCCH4); } else if ((cbits & 0x18) == 0x08) { lch_idx = cbits & 0x7; /* SDCCH/8 */ - if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C && - ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH) - ok = false; + ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_SDCCH8_SACCH8C); } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { - lch_idx = 0; - if (ts->pchan != GSM_PCHAN_CCCH && - ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && - ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) - ok = false; + lch_idx = 0; /* CCCH? */ + ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH); /* FIXME: we should not return first sdcch4 !!! */ } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) { lch_idx = 0; - if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) - ok = false; + ok = (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH); } else return NULL; @@ -1313,33 +1271,18 @@ static const uint8_t subslots_per_pchan[] = { [GSM_PCHAN_SDCCH8_SACCH8C] = 8, [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4, [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8, - /* - * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be - * part of this, those TS are handled according to their dynamic state. - */ + /* Dyn TS: maximum allowed subslots */ + [GSM_PCHAN_TCH_F_TCH_H_PDCH] = 2, + [GSM_PCHAN_TCH_F_PDCH] = 1, }; -/*! Return the actual pchan type, also heeding dynamic TS. */ -enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts) -{ - switch (ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - return ts->dyn.pchan_is; - case GSM_PCHAN_TCH_F_PDCH: - if (ts->flags & TS_F_PDCH_ACTIVE) - return GSM_PCHAN_PDCH; - else - return GSM_PCHAN_TCH_F; - default: - return ts->pchan; - } -} - /*! According to ts->pchan and possibly ts->dyn_pchan, return the number of * logical channels available in the timeslot. */ -uint8_t ts_subslots(struct gsm_bts_trx_ts *ts) +uint8_t pchan_subslots(enum gsm_phys_chan_config pchan) { - return subslots_per_pchan[ts_pchan(ts)]; + if (pchan < 0 || pchan >= ARRAY_SIZE(subslots_per_pchan)) + return 0; + return subslots_per_pchan[pchan]; } static bool pchan_is_tch(enum gsm_phys_chan_config pchan) @@ -1355,7 +1298,7 @@ static bool pchan_is_tch(enum gsm_phys_chan_config pchan) bool ts_is_tch(struct gsm_bts_trx_ts *ts) { - return pchan_is_tch(ts_pchan(ts)); + return pchan_is_tch(ts->pchan_is); } bool trx_is_usable(const struct gsm_bts_trx *trx) @@ -1370,34 +1313,20 @@ bool trx_is_usable(const struct gsm_bts_trx *trx) return true; } -void gsm_trx_mark_all_ts_uninitialized(struct gsm_bts_trx *trx) +void gsm_trx_all_ts_dispatch(struct gsm_bts_trx *trx, uint32_t ts_ev, void *data) { int i; for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { struct gsm_bts_trx_ts *ts = &trx->ts[i]; - ts->initialized = false; + osmo_fsm_inst_dispatch(ts->fi, ts_ev, data); } } -void gsm_bts_mark_all_ts_uninitialized(struct gsm_bts *bts) +void gsm_bts_all_ts_dispatch(struct gsm_bts *bts, uint32_t ts_ev, void *data) { struct gsm_bts_trx *trx; llist_for_each_entry(trx, &bts->trx_list, list) - gsm_trx_mark_all_ts_uninitialized(trx); -} - -/* Trigger initial timeslot actions iff both OML and RSL are setup. */ -void gsm_ts_check_init(struct gsm_bts_trx_ts *ts) -{ - struct gsm_bts *bts = ts->trx->bts; - if (bts->model->oml_is_ts_ready - && !bts->model->oml_is_ts_ready(ts)) - return; - if (!ts->trx->rsl_link) - return; - if (ts->initialized) - return; - ts->initialized = on_gsm_ts_init(ts); + gsm_trx_all_ts_dispatch(trx, ts_ev, data); } void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, @@ -1426,3 +1355,258 @@ bool nm_is_running(const struct gsm_nm_state *s) { (s->availability == 0xff) ); } + +/* determine the logical channel type based on the physical channel type */ +int gsm_lchan_type_by_pchan(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_TCH_F: + return GSM_LCHAN_TCH_F; + case GSM_PCHAN_TCH_H: + return GSM_LCHAN_TCH_H; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + return GSM_LCHAN_SDCCH; + default: + return -1; + } +} + +enum gsm_phys_chan_config gsm_pchan_by_lchan_type(enum gsm_chan_t type) +{ + switch (type) { + case GSM_LCHAN_TCH_F: + return GSM_PCHAN_TCH_F; + case GSM_LCHAN_TCH_H: + return GSM_PCHAN_TCH_H; + case GSM_LCHAN_NONE: + case GSM_LCHAN_PDTCH: + /* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only + * used in osmo-bts. Maybe set PDTCH and drop the NONE case + * here. */ + return GSM_PCHAN_PDCH; + default: + return GSM_PCHAN_UNKNOWN; + } +} + +/* Can the timeslot in principle be used as this PCHAN kind? */ +bool ts_is_capable_of_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan) +{ + switch (ts->pchan_on_init) { + case GSM_PCHAN_TCH_F_PDCH: + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + return true; + default: + return false; + } + + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_PDCH: + return true; + default: + return false; + } + + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH: + return true; + default: + return false; + } + + case GSM_PCHAN_CCCH_SDCCH4: + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH: + return true; + default: + return false; + } + + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + switch (pchan) { + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + case GSM_PCHAN_SDCCH8_SACCH8C: + return true; + default: + return false; + } + + default: + return ts->pchan_on_init == pchan; + } +} + +bool ts_is_capable_of_lchant(struct gsm_bts_trx_ts *ts, enum gsm_chan_t type) +{ + switch (ts->pchan_on_init) { + + case GSM_PCHAN_TCH_F: + switch (type) { + case GSM_LCHAN_TCH_F: + return true; + default: + return false; + } + + case GSM_PCHAN_TCH_H: + switch (type) { + case GSM_LCHAN_TCH_H: + return true; + default: + return false; + } + + case GSM_PCHAN_TCH_F_PDCH: + switch (type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_PDTCH: + return true; + default: + return false; + } + + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + switch (type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_PDTCH: + return true; + default: + return false; + } + + case GSM_PCHAN_PDCH: + switch (type) { + case GSM_LCHAN_PDTCH: + return true; + default: + return false; + } + + case GSM_PCHAN_CCCH: + switch (type) { + case GSM_LCHAN_CCCH: + return true; + default: + return false; + } + break; + + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + switch (type) { + case GSM_LCHAN_CCCH: + case GSM_LCHAN_SDCCH: + return true; + default: + return false; + } + + default: + return false; + } +} + +static int trx_count_free_ts(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan) +{ + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int j; + int count = 0; + + if (!trx_is_usable(trx)) + return 0; + + for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { + ts = &trx->ts[j]; + if (!ts_is_usable(ts)) + continue; + + if (ts->pchan_is == GSM_PCHAN_PDCH) { + /* Dynamic timeslots in PDCH mode will become TCH if needed. */ + switch (ts->pchan_on_init) { + 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; + + ts_for_each_lchan(lchan, ts) { + if (lchan_state_is(lchan, LCHAN_ST_UNUSED)) + 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; +} + +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 (!ts->fi) + return false; + + switch (ts->fi->state) { + case TS_ST_NOT_INITIALIZED: + case TS_ST_BORKEN: + return false; + default: + break; + } + + return true; +} + +const struct value_string lchan_activate_mode_names[] = { + OSMO_VALUE_STRING(FOR_NONE), + OSMO_VALUE_STRING(FOR_MS_CHANNEL_REQUEST), + OSMO_VALUE_STRING(FOR_ASSIGNMENT), + OSMO_VALUE_STRING(FOR_HANDOVER), + OSMO_VALUE_STRING(FOR_VTY), + {} +}; |