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