diff options
Diffstat (limited to 'src/osmo-bsc/gsm_data.c')
-rw-r--r-- | src/osmo-bsc/gsm_data.c | 667 |
1 files changed, 485 insertions, 182 deletions
diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c index 0ff17f2c6..fee54b78b 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" }, @@ -550,7 +554,7 @@ const char *gsm_chreq_name(enum gsm_chreq_reason_t c) return get_value_string(chreq_names, c); } -struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) +struct gsm_bts *gsm_bts_num(const struct gsm_network *net, int num) { struct gsm_bts *bts; @@ -565,61 +569,63 @@ struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) return NULL; } -bool gsm_bts_matches_cell_id(struct gsm_bts *bts, const struct gsm0808_cell_id *ci) +bool gsm_bts_matches_lai(const struct gsm_bts *bts, + const struct osmo_location_area_id *lai) { - if (!bts || !ci) + return osmo_plmn_cmp(&lai->plmn, &bts->network->plmn) == 0 + && lai->lac == bts->location_area_code; +} + +bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cell_id *cell_id) +{ + const union gsm0808_cell_id_u *id = &cell_id->id; + if (!bts || !cell_id) return false; - switch (ci->id_discr) { + + switch (cell_id->id_discr) { case CELL_IDENT_WHOLE_GLOBAL: - if (osmo_plmn_cmp(&bts->network->plmn, &ci->id.global.lai.plmn)) - return false; - if (bts->location_area_code != ci->id.global.lai.lac) - return false; - if (bts->cell_identity != ci->id.global.cell_identity) - return false; - return true; + return gsm_bts_matches_lai(bts, &id->global.lai) + && id->global.cell_identity == bts->cell_identity; case CELL_IDENT_LAC_AND_CI: - if (bts->location_area_code != ci->id.lac_and_ci.lac) - return false; - if (bts->cell_identity != ci->id.lac_and_ci.ci) - return false; - return true; + return id->lac_and_ci.lac == bts->location_area_code + && id->lac_and_ci.ci == bts->cell_identity; case CELL_IDENT_CI: - if (bts->cell_identity != ci->id.ci) - return false; - return true; + return id->ci == bts->cell_identity; case CELL_IDENT_NO_CELL: return false; case CELL_IDENT_LAI_AND_LAC: - if (osmo_plmn_cmp(&bts->network->plmn, &ci->id.lai_and_lac.plmn)) - return false; - if (bts->location_area_code != ci->id.lai_and_lac.lac) - return false; - return true; + return gsm_bts_matches_lai(bts, &id->lai_and_lac); case CELL_IDENT_LAC: - if (bts->location_area_code != ci->id.lac) - return false; - return true; + return id->lac == bts->location_area_code; case CELL_IDENT_BSS: return true; case CELL_IDENT_UTRAN_PLMN_LAC_RNC: case CELL_IDENT_UTRAN_RNC: case CELL_IDENT_UTRAN_LAC_RNC: - /* Not implemented */ - default: return false; + default: + OSMO_ASSERT(false); } } -struct gsm_bts *gsm_bts_by_cell_id(struct gsm_network *net, const struct gsm0808_cell_id *ci) +/* From a list of local BTSes that match the cell_id, return the Nth one, or NULL if there is no such + * match. */ +struct gsm_bts *gsm_bts_by_cell_id(const struct gsm_network *net, + const struct gsm0808_cell_id *cell_id, + int match_idx) { struct gsm_bts *bts; - + int i = 0; llist_for_each_entry(bts, &net->bts_list, list) { - if (gsm_bts_matches_cell_id(bts, ci)) - return bts; + if (!gsm_bts_matches_cell_id(bts, cell_id)) + continue; + if (i < match_idx) { + /* this is only the i'th match, we're looking for a later one... */ + i++; + continue; + } + return bts; } - return NULL; } @@ -697,13 +703,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); @@ -818,7 +825,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; @@ -938,49 +945,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; } @@ -1198,20 +1186,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) */ @@ -1220,12 +1197,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; } @@ -1243,48 +1220,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; @@ -1304,33 +1264,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) @@ -1346,7 +1291,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) @@ -1361,34 +1306,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, @@ -1417,3 +1348,375 @@ 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; + } +} + +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), + {} +}; + +/* Helper function for bsc_match_codec_pref(), looks up a matching permitted speech + * value for a given msc audio codec pref */ +static enum gsm0808_permitted_speech audio_support_to_gsm88(const struct gsm_audio_support *audio) +{ + if (audio->hr) { + switch (audio->ver) { + case 1: + return GSM0808_PERM_HR1; + break; + case 2: + return GSM0808_PERM_HR2; + break; + case 3: + return GSM0808_PERM_HR3; + break; + default: + LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: hr%d, using hr1 instead\n", + audio->ver); + return GSM0808_PERM_HR1; + } + } else { + switch (audio->ver) { + case 1: + return GSM0808_PERM_FR1; + break; + case 2: + return GSM0808_PERM_FR2; + break; + case 3: + return GSM0808_PERM_FR3; + break; + default: + LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: fr%d, using fr1 instead\n", + audio->ver); + return GSM0808_PERM_FR1; + } + } +} + +/* Helper function for bsc_match_codec_pref(), looks up a matching chan mode for + * a given permitted speech value */ +static enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech speech) +{ + switch (speech) { + case GSM0808_PERM_HR1: + case GSM0808_PERM_FR1: + return GSM48_CMODE_SPEECH_V1; + break; + case GSM0808_PERM_HR2: + case GSM0808_PERM_FR2: + return GSM48_CMODE_SPEECH_EFR; + break; + case GSM0808_PERM_HR3: + case GSM0808_PERM_FR3: + return GSM48_CMODE_SPEECH_AMR; + break; + default: + LOGP(DMSC, LOGL_FATAL, + "Unsupported permitted speech selected, assuming AMR as channel mode...\n"); + return GSM48_CMODE_SPEECH_AMR; + } +} + +/* Helper function for bsc_match_codec_pref(), tests if a given audio support + * matches one of the permitted speech settings of the channel type element. + * The matched permitted speech value is then also compared against the + * speech codec list. (optional, only relevant for AoIP) */ +static bool test_codec_pref(const struct gsm0808_channel_type *ct, + const struct gsm0808_speech_codec_list *scl, + uint8_t perm_spch) +{ + unsigned int i; + bool match = false; + struct gsm0808_speech_codec sc; + int rc; + + /* Try to finde the given permitted speech value in the + * codec list of the channel type element */ + for (i = 0; i < ct->perm_spch_len; i++) { + if (ct->perm_spch[i] == perm_spch) { + match = true; + break; + } + } + + /* If we do not have a speech codec list to test against, + * we just exit early (will be always the case in non-AoIP networks) */ + if (!scl || !scl->len) + return match; + + /* If we failed to match until here, there is no + * point in testing further */ + if (match == false) + return false; + + /* Extrapolate speech codec data */ + rc = gsm0808_speech_codec_from_chan_type(&sc, perm_spch); + if (rc < 0) + return false; + + /* Try to find extrapolated speech codec data in + * the speech codec list */ + for (i = 0; i < scl->len; i++) { + if (sc.type == scl->codec[i].type) + return true; + } + + return false; +} + +/*! Match the codec preferences from local config with a received codec preferences IEs received from the + * MSC. + * \param[out] chan_mode GSM 04.08 channel mode. + * \param[out] full_rate true iff full-rate. + * \param[in] ct GSM 08.08 channel type received from MSC. + * \param[in] scl GSM 08.08 speech codec list received from MSC (optional). + * \param[in] audio_support List of allowed codecs as from local config. + * \param[in] audio_length Number of items in audio_support. + * \returns 0 on success, -1 in case no match was found */ +int bsc_match_codec_pref(enum gsm48_chan_mode *chan_mode, + bool *full_rate, + const struct gsm0808_channel_type *ct, + const struct gsm0808_speech_codec_list *scl, + struct gsm_audio_support * const *audio_support, + int audio_length) +{ + unsigned int i; + uint8_t perm_spch; + bool match = false; + + for (i = 0; i < audio_length; i++) { + perm_spch = audio_support_to_gsm88(audio_support[i]); + if (test_codec_pref(ct, scl, perm_spch)) { + match = true; + break; + } + } + + /* Exit without result, in case no match can be deteched */ + if (!match) { + *full_rate = false; + *chan_mode = GSM48_CMODE_SIGN; + return -1; + } + + /* Check if the result is a half or full rate codec */ + switch (perm_spch) { + case GSM0808_PERM_HR1: + case GSM0808_PERM_HR2: + case GSM0808_PERM_HR3: + case GSM0808_PERM_HR4: + case GSM0808_PERM_HR6: + *full_rate = false; + break; + + case GSM0808_PERM_FR1: + case GSM0808_PERM_FR2: + case GSM0808_PERM_FR3: + case GSM0808_PERM_FR4: + case GSM0808_PERM_FR5: + *full_rate = true; + break; + + default: + LOGP(DMSC, LOGL_ERROR, "Invalid permitted-speech value: %u\n", perm_spch); + return -EINVAL; + } + + /* Lookup a channel mode for the selected codec */ + *chan_mode = gsm88_to_chan_mode(perm_spch); + + return 0; +} + +bool sockaddr_to_str_and_uint(char *rtp_addr, size_t rtp_addr_len, uint16_t *rtp_port, + const struct sockaddr_storage *sa) +{ + int rc; + const struct sockaddr_in *sin; + + sin = (const struct sockaddr_in *)sa; + *rtp_port = osmo_ntohs(sin->sin_port); + + rc = osmo_strlcpy(rtp_addr, inet_ntoa(sin->sin_addr), rtp_addr_len); + if (rc <= 0 || rc >= rtp_addr_len) + return false; + + return true; +} |