/* (C) 2008-2018 by Harald Welte * * All Rights Reserved * * 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 . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void *tall_bsc_ctx = NULL; void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr, uint8_t e1_ts, uint8_t e1_ts_ss) { ts->e1_link.e1_nr = e1_nr; ts->e1_link.e1_ts = e1_ts; ts->e1_link.e1_ts_ss = e1_ts_ss; } /* Search for a BTS in the given Location Area; optionally start searching * with start_bts (for continuing to search after the first result) */ struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac, struct gsm_bts *start_bts) { int i; struct gsm_bts *bts; int skip = 0; if (start_bts) skip = 1; for (i = 0; i < net->num_bts; i++) { bts = gsm_bts_num(net, i); if (skip) { if (start_bts == bts) skip = 0; continue; } if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac) return bts; } return NULL; } static const struct value_string bts_gprs_mode_names[] = { { BTS_GPRS_NONE, "none" }, { BTS_GPRS_GPRS, "gprs" }, { BTS_GPRS_EGPRS, "egprs" }, { 0, NULL } }; enum bts_gprs_mode bts_gprs_mode_parse(const char *arg, int *valid) { int rc; rc = get_string_value(bts_gprs_mode_names, arg); if (valid) *valid = rc != -EINVAL; return rc; } const char *bts_gprs_mode_name(enum bts_gprs_mode mode) { return get_value_string(bts_gprs_mode_names, mode); } struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, uint8_t bsic) { struct gsm_bts_model *model = bts_model_find(type); struct gsm_bts *bts; if (!model && type != GSM_BTS_TYPE_UNKNOWN) return NULL; bts = gsm_bts_alloc(net, net->num_bts); if (!bts) return NULL; net->num_bts++; bts->type = type; bts->model = model; bts->bsic = bsic; llist_add_tail(&bts->list, &net->bts_list); return bts; } void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts) { *raid = (struct gprs_ra_id){ .mcc = bts->network->plmn.mcc, .mnc = bts->network->plmn.mnc, .mnc_3_digits = bts->network->plmn.mnc_3_digits, .lac = bts->location_area_code, .rac = bts->gprs.rac, }; } void gsm48_ra_id_by_bts(struct gsm48_ra_id *buf, struct gsm_bts *bts) { struct gprs_ra_id raid; gprs_ra_id_by_bts(&raid, bts); gsm48_encode_ra(buf, &raid); } void gsm_abis_mo_reset(struct gsm_abis_mo *mo) { mo->nm_state.operational = NM_OPSTATE_NULL; mo->nm_state.availability = NM_AVSTATE_POWER_OFF; } void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts, uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3) { mo->bts = bts; mo->obj_class = obj_class; mo->obj_inst.bts_nr = p1; mo->obj_inst.trx_nr = p2; mo->obj_inst.ts_nr = p3; gsm_abis_mo_reset(mo); } const struct value_string gsm_chreq_descs[] = { { GSM_CHREQ_REASON_EMERG, "emergency call" }, { GSM_CHREQ_REASON_PAG, "answer to paging" }, { GSM_CHREQ_REASON_CALL, "call re-establishment" }, { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" }, { GSM_CHREQ_REASON_PDCH, "one phase packet access" }, { GSM_CHREQ_REASON_OTHER, "other" }, { 0, NULL } }; const struct value_string gsm_pchant_names[] = { { 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_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)" }, { GSM_PCHAN_CCCH_SDCCH4, "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" }, { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" }, { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" }, { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" }, { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" }, { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" }, { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" }, { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" }, { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" }, { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" }, { 0, NULL } }; const char *gsm_pchan_name(enum gsm_phys_chan_config c) { return get_value_string(gsm_pchant_names, c); } enum gsm_phys_chan_config gsm_pchan_parse(const char *name) { return get_string_value(gsm_pchant_names, name); } /* TODO: move to libosmocore, next to gsm_chan_t_names? */ const char *gsm_lchant_name(enum gsm_chan_t c) { return get_value_string(gsm_chan_t_names, c); } static const struct value_string chreq_names[] = { { GSM_CHREQ_REASON_EMERG, "EMERGENCY" }, { GSM_CHREQ_REASON_PAG, "PAGING" }, { GSM_CHREQ_REASON_CALL, "CALL" }, { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" }, { GSM_CHREQ_REASON_OTHER, "OTHER" }, { 0, NULL } }; const char *gsm_chreq_name(enum gsm_chreq_reason_t c) { return get_value_string(chreq_names, c); } struct gsm_bts *gsm_bts_num(const struct gsm_network *net, int num) { struct gsm_bts *bts; if (num >= net->num_bts) return NULL; llist_for_each_entry(bts, &net->bts_list, list) { if (bts->nr == num) return bts; } return NULL; } /* 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, 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; } static char ts2str[255]; char *gsm_ts_name(const struct gsm_bts_trx_ts *ts) { snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)", ts->trx->bts->nr, ts->trx->nr, ts->nr); return ts2str; } /*! Log timeslot number with full pchan information */ char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts) { 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_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; } char *gsm_lchan_name_compute(const struct gsm_lchan *lchan) { struct gsm_bts_trx_ts *ts = lchan->ts; snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)", ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr); return ts2str; } /* obtain the MO structure for a given object instance */ static inline struct gsm_abis_mo * gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class, const struct abis_om_obj_inst *obj_inst) { struct gsm_bts_trx *trx; struct gsm_abis_mo *mo = NULL; switch (obj_class) { case NM_OC_BTS: mo = &bts->mo; break; case NM_OC_RADIO_CARRIER: if (obj_inst->trx_nr >= bts->num_trx) { return NULL; } trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); mo = &trx->mo; break; case NM_OC_BASEB_TRANSC: if (obj_inst->trx_nr >= bts->num_trx) { return NULL; } trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); mo = &trx->bb_transc.mo; break; case NM_OC_CHANNEL: if (obj_inst->trx_nr >= bts->num_trx) { return NULL; } trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); if (obj_inst->ts_nr >= TRX_NR_TS) return NULL; mo = &trx->ts[obj_inst->ts_nr].mo; break; case NM_OC_SITE_MANAGER: mo = &bts->site_mgr.mo; break; case NM_OC_BS11: switch (obj_inst->bts_nr) { case BS11_OBJ_CCLK: mo = &bts->bs11.cclk.mo; break; case BS11_OBJ_BBSIG: if (obj_inst->ts_nr > bts->num_trx) return NULL; trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); mo = &trx->bs11.bbsig.mo; break; case BS11_OBJ_PA: if (obj_inst->ts_nr > bts->num_trx) return NULL; trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); mo = &trx->bs11.pa.mo; break; default: return NULL; } break; case NM_OC_BS11_RACK: mo = &bts->bs11.rack.mo; break; case NM_OC_BS11_ENVABTSE: if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse)) return NULL; mo = &bts->bs11.envabtse[obj_inst->trx_nr].mo; break; case NM_OC_GPRS_NSE: mo = &bts->gprs.nse.mo; break; case NM_OC_GPRS_CELL: mo = &bts->gprs.cell.mo; break; case NM_OC_GPRS_NSVC: if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) return NULL; mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo; break; } return mo; } /* obtain the gsm_nm_state data structure for a given object instance */ struct gsm_nm_state * gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class, const struct abis_om_obj_inst *obj_inst) { struct gsm_abis_mo *mo; mo = gsm_objclass2mo(bts, obj_class, obj_inst); if (!mo) return NULL; return &mo->nm_state; } /* obtain the in-memory data structure of a given object instance */ void * gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class, const struct abis_om_obj_inst *obj_inst) { struct gsm_bts_trx *trx; void *obj = NULL; switch (obj_class) { case NM_OC_BTS: obj = bts; break; case NM_OC_RADIO_CARRIER: if (obj_inst->trx_nr >= bts->num_trx) { return NULL; } trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); obj = trx; break; case NM_OC_BASEB_TRANSC: if (obj_inst->trx_nr >= bts->num_trx) { return NULL; } trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); obj = &trx->bb_transc; break; case NM_OC_CHANNEL: if (obj_inst->trx_nr >= bts->num_trx) { return NULL; } trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); if (obj_inst->ts_nr >= TRX_NR_TS) return NULL; obj = &trx->ts[obj_inst->ts_nr]; break; case NM_OC_SITE_MANAGER: obj = &bts->site_mgr; break; case NM_OC_GPRS_NSE: obj = &bts->gprs.nse; break; case NM_OC_GPRS_CELL: obj = &bts->gprs.cell; break; case NM_OC_GPRS_NSVC: if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) return NULL; obj = &bts->gprs.nsvc[obj_inst->trx_nr]; break; } return obj; } /* See Table 10.5.25 of GSM04.08 */ uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, uint8_t ts_nr, uint8_t lchan_nr) { uint8_t cbits, chan_nr; switch (pchan) { case GSM_PCHAN_TCH_F: case GSM_PCHAN_TCH_F_PDCH: OSMO_ASSERT(lchan_nr == 0); cbits = 0x01; break; case GSM_PCHAN_PDCH: OSMO_ASSERT(lchan_nr == 0); cbits = RSL_CHAN_OSMO_PDCH >> 3; break; case GSM_PCHAN_TCH_H: OSMO_ASSERT(lchan_nr < 2); cbits = 0x02; cbits += lchan_nr; break; case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: /* * As a special hack for BCCH, lchan_nr == 4 may be passed * here. This should never be sent in an RSL message. * See osmo-bts-xxx/oml.c:opstart_compl(). */ if (lchan_nr == CCCH_LCHAN) chan_nr = 0; else OSMO_ASSERT(lchan_nr < 4); cbits = 0x04; cbits += lchan_nr; break; case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: OSMO_ASSERT(lchan_nr < 8); cbits = 0x08; cbits += lchan_nr; break; default: case GSM_PCHAN_CCCH: OSMO_ASSERT(lchan_nr == 0); cbits = 0x10; break; } chan_nr = (cbits << 3) | (ts_nr & 0x7); return chan_nr; } uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan) { /* 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); } static const uint8_t subslots_per_pchan[] = { [GSM_PCHAN_NONE] = 0, [GSM_PCHAN_CCCH] = 0, [GSM_PCHAN_PDCH] = 0, [GSM_PCHAN_CCCH_SDCCH4] = 4, [GSM_PCHAN_TCH_F] = 1, [GSM_PCHAN_TCH_H] = 2, [GSM_PCHAN_SDCCH8_SACCH8C] = 8, [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4, [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8, /* Dyn TS: maximum allowed subslots */ [GSM_PCHAN_TCH_F_TCH_H_PDCH] = 2, [GSM_PCHAN_TCH_F_PDCH] = 1, }; /*! According to ts->pchan and possibly ts->dyn_pchan, return the number of * logical channels available in the timeslot. */ uint8_t pchan_subslots(enum gsm_phys_chan_config pchan) { 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) { switch (pchan) { case GSM_PCHAN_TCH_F: case GSM_PCHAN_TCH_H: return true; default: return false; } } bool ts_is_tch(struct gsm_bts_trx_ts *ts) { return pchan_is_tch(ts->pchan_is); } struct gsm_bts *conn_get_bts(struct gsm_subscriber_connection *conn) { if (!conn || !conn->lchan) return NULL; return conn->lchan->ts->trx->bts; } static void _chan_desc_fill_tail(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan) { if (!lchan->ts->hopping.enabled) { uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff; cd->h0.tsc = gsm_ts_tsc(lchan->ts); cd->h0.h = 0; cd->h0.arfcn_high = arfcn >> 8; cd->h0.arfcn_low = arfcn & 0xff; } else { cd->h1.tsc = gsm_ts_tsc(lchan->ts); cd->h1.h = 1; cd->h1.maio_high = lchan->ts->hopping.maio >> 2; cd->h1.maio_low = lchan->ts->hopping.maio & 0x03; cd->h1.hsn = lchan->ts->hopping.hsn; } } void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan) { cd->chan_nr = gsm_lchan2chan_nr(lchan); _chan_desc_fill_tail(cd, lchan); } /* like gsm48_lchan2chan_desc() above, but use ts->pchan_from_config to * return a channel description based on what is configured, rather than * what the current state of the pchan type is */ void gsm48_lchan2chan_desc_as_configured(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan) { cd->chan_nr = gsm_pchan2chan_nr(lchan->ts->pchan_from_config, lchan->ts->nr, lchan->nr); _chan_desc_fill_tail(cd, lchan); } uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts) { if (ts->tsc != -1) return ts->tsc; else return ts->trx->bts->bsic & 7; } bool nm_is_running(const struct gsm_nm_state *s) { if (s->operational != NM_OPSTATE_ENABLED) return false; if ((s->availability != NM_AVSTATE_OK) && (s->availability != 0xff)) return false; if (s->administrative != NM_STATE_UNLOCKED) return false; return true; } /* 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; } } 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; } void conn_update_ms_power_class(struct gsm_subscriber_connection *conn, uint8_t power_class) { struct gsm_bts *bts = conn_get_bts(conn); LOGP(DRLL, LOGL_DEBUG, "MS Power class update: %" PRIu8 " -> %" PRIu8 "\n", conn->ms_power_class, power_class); conn->ms_power_class = power_class; /* If there's an associated lchan, attempt to update its max power to be on track with band maximum values */ if (bts && conn->lchan) lchan_update_ms_power_ctrl_level(conn->lchan, bts->ms_max_power); } void lchan_update_ms_power_ctrl_level(struct gsm_lchan *lchan, int ms_power_dbm) { struct gsm_bts *bts = lchan->ts->trx->bts; struct gsm_subscriber_connection *conn = lchan->conn; int max_pwr_dbm_pwclass, new_pwr; bool send_pwr_ctrl_msg = false; LOG_LCHAN(lchan, LOGL_DEBUG, "MS Power level update requested: %d dBm\n", ms_power_dbm); if (!conn) goto ms_power_default; if (conn->ms_power_class == 0) goto ms_power_default; if ((max_pwr_dbm_pwclass = (int)ms_class_gmsk_dbm(bts->band, conn->ms_power_class)) < 0) { LOG_LCHAN(lchan, LOGL_INFO, "Failed getting max ms power for power class %" PRIu8 " on band %s, providing default max ms power\n", conn->ms_power_class, gsm_band_name(bts->band)); goto ms_power_default; } /* Current configured max pwr is above maximum one allowed on current band + ms power class, so use that one. */ if (ms_power_dbm > max_pwr_dbm_pwclass) ms_power_dbm = max_pwr_dbm_pwclass; ms_power_default: if ((new_pwr = ms_pwr_ctl_lvl(bts->band, ms_power_dbm)) < 0) { LOG_LCHAN(lchan, LOGL_INFO, "Failed getting max ms power level %d on band %s," " providing default max ms power\n", ms_power_dbm, gsm_band_name(bts->band)); return; } LOG_LCHAN(lchan, LOGL_DEBUG, "MS Power level update (power class %" PRIu8 "): %" PRIu8 " -> %d\n", conn ? conn->ms_power_class : 0, lchan->ms_power, new_pwr); /* If chan was already activated and max ms_power changes (due to power classmark received), send an MS Power Control message */ if (lchan->activate.activ_ack && new_pwr != lchan->ms_power) send_pwr_ctrl_msg = true; lchan->ms_power = new_pwr; if (send_pwr_ctrl_msg) rsl_chan_ms_power_ctrl(lchan); } 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), {} }; /* This may be specific to RR Channel Release, and the mappings were chosen by pure naive guessing without a proper * specification available. */ enum gsm48_rr_cause bsc_gsm48_rr_cause_from_gsm0808_cause(enum gsm0808_cause c) { switch (c) { case GSM0808_CAUSE_PREEMPTION: return GSM48_RR_CAUSE_PREMPTIVE_REL; case GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE: case GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS: case GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING: case GSM0808_CAUSE_INCORRECT_VALUE: case GSM0808_CAUSE_UNKNOWN_MESSAGE_TYPE: case GSM0808_CAUSE_UNKNOWN_INFORMATION_ELEMENT: return GSM48_RR_CAUSE_PROT_ERROR_UNSPC; case GSM0808_CAUSE_CALL_CONTROL: case GSM0808_CAUSE_HANDOVER_SUCCESSFUL: case GSM0808_CAUSE_BETTER_CELL: case GSM0808_CAUSE_DIRECTED_RETRY: case GSM0808_CAUSE_REDUCE_LOAD_IN_SERVING_CELL: case GSM0808_CAUSE_RELOCATION_TRIGGERED: case GSM0808_CAUSE_ALT_CHAN_CONFIG_REQUESTED: return GSM48_RR_CAUSE_NORMAL; default: return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; } } /* Map RSL_ERR_* cause codes to gsm48_rr_cause codes. * The mappings were chosen by naive guessing without a proper specification available. */ enum gsm48_rr_cause bsc_gsm48_rr_cause_from_rsl_cause(uint8_t c) { switch (c) { case RSL_ERR_NORMAL_UNSPEC: return GSM48_RR_CAUSE_NORMAL; case RSL_ERR_MAND_IE_ERROR: return GSM48_RR_CAUSE_INVALID_MAND_INF; case RSL_ERR_OPT_IE_ERROR: return GSM48_RR_CAUSE_COND_IE_ERROR; case RSL_ERR_INVALID_MESSAGE: case RSL_ERR_MSG_DISCR: case RSL_ERR_MSG_TYPE: case RSL_ERR_MSG_SEQ: case RSL_ERR_IE_ERROR: case RSL_ERR_IE_NONEXIST: case RSL_ERR_IE_LENGTH: case RSL_ERR_IE_CONTENT: case RSL_ERR_PROTO: return GSM48_RR_CAUSE_PROT_ERROR_UNSPC; default: return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; } }