diff options
Diffstat (limited to 'src/osmo-bsc')
95 files changed, 29522 insertions, 12938 deletions
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am index 573988549..10265235e 100644 --- a/src/osmo-bsc/Makefile.am +++ b/src/osmo-bsc/Makefile.am @@ -21,51 +21,63 @@ AM_LDFLAGS = \ $(COVERAGE_LDFLAGS) \ $(NULL) -bin_PROGRAMS = \ - osmo-bsc \ - $(NULL) +noinst_LTLIBRARIES = libbsc.la -osmo_bsc_SOURCES = \ +libbsc_la_SOURCES = \ a_reset.c \ abis_nm.c \ abis_nm_vty.c \ abis_om2000.c \ abis_om2000_vty.c \ + abis_osmo.c \ abis_rsl.c \ acc.c \ - arfcn_range_encode.c \ assignment_fsm.c \ - bsc_ctrl_commands.c \ + bsc_ctrl.c \ bsc_ctrl_lookup.c \ bsc_init.c \ bsc_rf_ctrl.c \ bsc_rll.c \ bsc_sccp.c \ + bsc_stats.c \ bsc_subscr_conn_fsm.c \ bsc_subscriber.c \ bsc_vty.c \ bts.c \ bts_trx.c \ + bts_trx_ctrl.c \ + bts_trx_ts_ctrl.c \ + bts_trx_ts_lchan_ctrl.c \ bts_ericsson_rbs2000.c \ bts_init.c \ bts_ipaccess_nanobts.c \ bts_ipaccess_nanobts_omlattr.c \ bts_nokia_site.c \ bts_siemens_bs11.c \ - bts_sysmobts.c \ + bts_sm.c \ + bts_osmobts.c \ bts_unknown.c \ + bts_ctrl.c \ + bts_setup_ramp.c \ + bts_vty.c \ + bts_trx_vty.c \ chan_alloc.c \ + chan_counts.c \ codec_pref.c \ + data_rate_pref.c \ e1_config.c \ gsm_04_08_rr.c \ gsm_data.c \ handover_cfg.c \ + handover_ctrl.c \ handover_decision.c \ handover_decision_2.c \ handover_fsm.c \ handover_logic.c \ handover_vty.c \ + vgcs_fsm.c \ lb.c \ + lchan.c \ lchan_fsm.c \ lchan_rtp_fsm.c \ lchan_select.c \ @@ -75,20 +87,22 @@ osmo_bsc_SOURCES = \ meas_rep.c \ neighbor_ident.c \ neighbor_ident_vty.c \ + neighbor_ident_ctrl.c \ net_init.c \ nm_common_fsm.c \ nm_bb_transc_fsm.c \ nm_bts_sm_fsm.c \ nm_bts_fsm.c \ + nm_gprs_cell_fsm.c \ + nm_gprs_nse_fsm.c \ + nm_gprs_nsvc_fsm.c \ nm_channel_fsm.c \ nm_rcarrier_fsm.c \ gsm_08_08.c \ osmo_bsc_bssap.c \ - osmo_bsc_ctrl.c \ osmo_bsc_filter.c \ osmo_bsc_grace.c \ osmo_bsc_lcls.c \ - osmo_bsc_main.c \ osmo_bsc_mgcp.c \ osmo_bsc_msc.c \ osmo_bsc_sigtran.c \ @@ -96,15 +110,38 @@ osmo_bsc_SOURCES = \ pcu_sock.c \ penalty_timers.c \ bssmap_reset.c \ - rest_octets.c \ system_information.c \ timeslot_fsm.c \ smscb.c \ + smscb_vty.c \ cbch_scheduler.c \ cbsp_link.c \ + power_control.c \ + $(NULL) + +libbsc_la_LIBADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOSIGTRAN_LIBS) \ + $(LIBOSMOMGCPCLIENT_LIBS) \ + -lm \ + $(NULL) + +bin_PROGRAMS = \ + osmo-bsc \ + $(NULL) + +osmo_bsc_SOURCES = \ + osmo_bsc_main.c \ $(NULL) osmo_bsc_LDADD = \ + libbsc.la \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ diff --git a/src/osmo-bsc/a_reset.c b/src/osmo-bsc/a_reset.c index 23714992c..c2c89e37b 100644 --- a/src/osmo-bsc/a_reset.c +++ b/src/osmo-bsc/a_reset.c @@ -25,6 +25,7 @@ #include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/osmo_bsc_sigtran.h> #include <osmocom/bsc/bssmap_reset.h> +#include <osmocom/bsc/bsc_stats.h> static void a_reset_tx_reset(void *data) { @@ -41,16 +42,18 @@ static void a_reset_tx_reset_ack(void *data) static void a_reset_link_up(void *data) { struct bsc_msc_data *msc = data; - LOGP(DMSC, LOGL_NOTICE, "(msc%d) BSSMAP assocation is up\n", msc->nr); - osmo_stat_item_inc(msc->msc_statg->items[MSC_STAT_MSC_LINKS_ACTIVE], 1); + LOGP(DMSC, LOGL_NOTICE, "(msc%d) BSSMAP association is up\n", msc->nr); + osmo_stat_item_inc(osmo_stat_item_group_get_item(msc->msc_statg, MSC_STAT_MSC_LINKS_ACTIVE), 1); + osmo_stat_item_inc(osmo_stat_item_group_get_item(msc->network->bsc_statg, BSC_STAT_NUM_MSC_CONNECTED), 1); osmo_signal_dispatch(SS_MSC, S_MSC_CONNECTED, msc); } static void a_reset_link_lost(void *data) { struct bsc_msc_data *msc = data; - LOGP(DMSC, LOGL_NOTICE, "(msc%d) BSSMAP assocation is down\n", msc->nr); - osmo_stat_item_dec(msc->msc_statg->items[MSC_STAT_MSC_LINKS_ACTIVE], 1); + LOGP(DMSC, LOGL_NOTICE, "(msc%d) BSSMAP association is down\n", msc->nr); + osmo_stat_item_dec(osmo_stat_item_group_get_item(msc->msc_statg, MSC_STAT_MSC_LINKS_ACTIVE), 1); + osmo_stat_item_dec(osmo_stat_item_group_get_item(msc->network->bsc_statg, BSC_STAT_NUM_MSC_CONNECTED), 1); osmo_signal_dispatch(SS_MSC, S_MSC_LOST, msc); osmo_bsc_sigtran_reset(msc); } @@ -76,6 +79,7 @@ void a_reset_alloc(struct bsc_msc_data *msc, const char *name) } msc->a.bssmap_reset = bssmap_reset_alloc(msc, name, &cfg); + osmo_stat_item_inc(osmo_stat_item_group_get_item(msc->network->bsc_statg, BSC_STAT_NUM_MSC_TOTAL), 1); } /* Confirm that we successfully received a reset acknowledge message */ diff --git a/src/osmo-bsc/abis_nm.c b/src/osmo-bsc/abis_nm.c index 73dc2d018..afb7abc6b 100644 --- a/src/osmo-bsc/abis_nm.c +++ b/src/osmo-bsc/abis_nm.c @@ -49,12 +49,24 @@ #include <osmocom/abis/e1_input.h> #include <osmocom/bsc/chan_alloc.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/nm_common_fsm.h> #include <osmocom/gsm/bts_features.h> +#include <osmocom/bsc/ipaccess.h> +#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h> #define OM_ALLOC_SIZE 1024 #define OM_HEADROOM_SIZE 128 #define IPACC_SEGMENT_SIZE 245 +/* max number of SW Description IEs we can parse */ +#define SW_DESCR_MAX 5 + +#define LOGPMO(mo, ss, lvl, fmt, args ...) \ + LOGP(ss, lvl, "OC=%s(%02x) INST=(%02x,%02x,%02x): " fmt, \ + get_value_string(abis_nm_obj_class_names, (mo)->obj_class), \ + (mo)->obj_class, (mo)->obj_inst.bts_nr, (mo)->obj_inst.trx_nr, \ + (mo)->obj_inst.ts_nr, ## args) + int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len) { if (!bts->model) @@ -202,7 +214,7 @@ static int abis_nm_rcvmsg_sw(struct msgb *mb); static int update_admstate(struct gsm_bts *bts, uint8_t obj_class, struct abis_om_obj_inst *obj_inst, uint8_t adm_state) { - struct gsm_nm_state *nm_state, new_state; + struct gsm_nm_state *nm_state; struct nm_statechg_signal_data nsd; memset(&nsd, 0, sizeof(nsd)); @@ -214,18 +226,18 @@ static int update_admstate(struct gsm_bts *bts, uint8_t obj_class, if (!nm_state) return -1; - new_state = *nm_state; - new_state.administrative = adm_state; - nsd.bts = bts; nsd.obj_class = obj_class; - nsd.old_state = nm_state; - nsd.new_state = &new_state; + nsd.old_state = *nm_state; + nsd.new_state = *nm_state; nsd.obj_inst = obj_inst; - osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd); + nsd.new_state.administrative = adm_state; + + /* Update current state before emitting signal: */ nm_state->administrative = adm_state; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd); return 0; } @@ -236,72 +248,67 @@ static int abis_nm_rx_statechg_rep(struct msgb *mb) struct e1inp_sign_link *sign_link = mb->dst; struct gsm_bts *bts = sign_link->trx->bts; struct tlv_parsed tp; - struct gsm_nm_state *nm_state, new_state; + struct gsm_nm_state *nm_state; + struct nm_statechg_signal_data nsd; - memset(&new_state, 0, sizeof(new_state)); + memset(&nsd, 0, sizeof(nsd)); + nsd.obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); + if (!nsd.obj) { + LOGPFOH(DNM, LOGL_ERROR, foh, "unknown managed object\n"); + return -EINVAL; + } nm_state = gsm_objclass2nmstate(bts, foh->obj_class, &foh->obj_inst); if (!nm_state) { LOGPFOH(DNM, LOGL_ERROR, foh, "unknown managed object\n"); return -EINVAL; } - new_state = *nm_state; + nsd.obj_class = foh->obj_class; + nsd.old_state = *nm_state; + nsd.new_state = *nm_state; + nsd.obj_inst = &foh->obj_inst; + nsd.bts = bts; + + if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } DEBUGPFOH(DNM, foh, "STATE CHG: "); - abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); if (TLVP_PRESENT(&tp, NM_ATT_OPER_STATE)) { - new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE); + nsd.new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE); DEBUGPC(DNM, "OP_STATE=%s ", - abis_nm_opstate_name(new_state.operational)); + abis_nm_opstate_name(nsd.new_state.operational)); } if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) { if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0) - new_state.availability = 0xff; + nsd.new_state.availability = NM_AVSTATE_OK; else - new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS); + nsd.new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS); DEBUGPC(DNM, "AVAIL=%s(%02x) ", - abis_nm_avail_name(new_state.availability), - new_state.availability); + abis_nm_avail_name(nsd.new_state.availability), + nsd.new_state.availability); } else - new_state.availability = 0xff; + nsd.new_state.availability = NM_AVSTATE_OK; if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) { - new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); + nsd.new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); DEBUGPC(DNM, "ADM=%2s ", get_value_string(abis_nm_adm_state_names, - new_state.administrative)); + nsd.new_state.administrative)); } - if ((new_state.administrative != 0 && nm_state->administrative == 0) || - new_state.operational != nm_state->operational || - new_state.availability != nm_state->availability) { + if ((nsd.new_state.administrative != 0 && nsd.old_state.administrative == 0) || + nsd.new_state.operational != nsd.old_state.operational || + nsd.new_state.availability != nsd.old_state.availability) { DEBUGPC(DNM, "\n"); - /* Update the operational state of a given object in our in-memory data + /* Update the state of a given object in our in-memory data * structures and send an event to the higher layer */ - struct nm_statechg_signal_data nsd; - nsd.obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); - nsd.obj_class = foh->obj_class; - nsd.old_state = nm_state; - nsd.new_state = &new_state; - nsd.obj_inst = &foh->obj_inst; - nsd.bts = bts; - osmo_signal_dispatch(SS_NM, S_NM_STATECHG_OPER, &nsd); - nm_state->operational = new_state.operational; - nm_state->availability = new_state.availability; - if (nm_state->administrative == 0) - nm_state->administrative = new_state.administrative; + *nm_state = nsd.new_state; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd); } else { DEBUGPC(DNM, "(No State change detected)\n"); } -#if 0 - if (op_state == 1) { - /* try to enable objects that are disabled */ - abis_nm_opstart(bts, foh->obj_class, - foh->obj_inst.bts_nr, - foh->obj_inst.trx_nr, - foh->obj_inst.ts_nr); - } -#endif return 0; } @@ -312,7 +319,7 @@ static inline void log_oml_fail_rep(const struct gsm_bts *bts, const char *type, enum abis_nm_pcause_type pcause = p_val[0]; enum abis_mm_event_causes cause = osmo_load16be(p_val + 1); - LOGP(DNM, LOGL_ERROR, "BTS %u: Failure Event Report: ", bts->nr); + LOGPMO(&bts->mo, DNM, LOGL_ERROR, "Failure Event Report: "); if (type) LOGPC(DNM, LOGL_ERROR, "Type=%s, ", type); if (severity) @@ -342,10 +349,10 @@ static inline void handle_manufact_report(struct gsm_bts *bts, const uint8_t *p_ switch (cause) { case OSMO_EVT_PCU_VERS: if (text) { - LOGP(DNM, LOGL_NOTICE, "BTS %u reported connected PCU version %s\n", bts->nr, text); + LOGPMO(&bts->mo, DNM, LOGL_NOTICE, "Reported connected PCU version %s\n", text); osmo_strlcpy(bts->pcu_version, text, sizeof(bts->pcu_version)); } else { - LOGP(DNM, LOGL_ERROR, "BTS %u reported PCU disconnection.\n", bts->nr); + LOGPMO(&bts->mo, DNM, LOGL_ERROR, "Reported PCU disconnection.\n"); bts->pcu_version[0] = '\0'; } break; @@ -367,8 +374,10 @@ struct nm_fail_rep_signal_data *abis_nm_fail_evt_rep_parse(struct msgb *mb, stru sd = talloc_zero(tall_bsc_ctx, struct nm_fail_rep_signal_data); OSMO_ASSERT(sd); - if (abis_nm_tlv_parse(&sd->tp, bts, foh->data, oh->length-sizeof(*foh)) < 0) + if (abis_nm_tlv_parse(&sd->tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); goto fail; + } if (TLVP_PRESENT(&sd->tp, NM_ATT_ADD_TEXT)) { const uint8_t *val = TLVP_VAL(&sd->tp, NM_ATT_ADD_TEXT); @@ -522,10 +531,10 @@ static inline bool handle_attr(const struct gsm_bts *bts, enum bts_attribute id, { switch (id) { case BTS_TYPE_VARIANT: - LOGP(DNM, LOGL_NOTICE, "BTS%u reported variant: %s\n", bts->nr, val); + LOGPMO(&bts->mo, DNM, LOGL_NOTICE, "Reported variant: %s\n", val); break; case BTS_SUB_MODEL: - LOGP(DNM, LOGL_NOTICE, "BTS%u reported submodel: %s\n", bts->nr, val); + LOGPMO(&bts->mo, DNM, LOGL_NOTICE, "Reported submodel: %s\n", val); break; default: return false; @@ -555,6 +564,45 @@ static inline const uint8_t *parse_attr_resp_info_unreported(const struct abis_o return ari + num_unreported + 1; /* we have to account for 1st byte with number of unreported attributes */ } +/* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.30 Manufacturer Id */ +static void parse_osmo_bts_features(struct gsm_bts *bts, + const uint8_t *data, uint16_t data_len) +{ + /* log potential BTS feature vector overflow */ + if (data_len > sizeof(bts->_features_data)) { + LOGPMO(&bts->mo, DNM, LOGL_NOTICE, + "Get Attributes Response: feature vector is truncated " + "(from %u to %zu bytes)\n", data_len, sizeof(bts->_features_data)); + data_len = sizeof(bts->_features_data); + } + + /* check that max. expected BTS attribute is above given feature vector length */ + if (data_len > OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT)) { + LOGPMO(&bts->mo, DNM, LOGL_NOTICE, + "Get Attributes Response: reported unexpectedly long (%u bytes) " + "feature vector - most likely it was compiled against newer BSC headers. " + "Consider upgrading your BSC to later version.\n", data_len); + } + + memcpy(bts->_features_data, data, data_len); + bts->features_known = true; + + /* Log each BTS feature in the reported vector */ + for (unsigned int i = 0; i < data_len * 8; i++) { + if (!osmo_bts_has_feature(&bts->features, i)) + continue; + + if (i >= _NUM_BTS_FEAT) { + LOGPMO(&bts->mo, DNM, LOGL_NOTICE, + "Get Attributes Response: unknown feature 0x%02x is supported\n", i); + } else { + LOGPMO(&bts->mo, DNM, LOGL_NOTICE, + "Get Attributes Response: feature '%s' is supported\n", + osmo_bts_features_name(i)); + } + } +} + /* Handle 3GPP TS 52.021 §8.11.3 Get Attribute Response (with nanoBTS specific attribute formatting) */ static int parse_attr_resp_info_attr(struct gsm_bts *bts, const struct gsm_bts_trx *trx, struct abis_om_fom_hdr *foh, struct tlv_parsed *tp) { @@ -565,37 +613,22 @@ static int parse_attr_resp_info_attr(struct gsm_bts *bts, const struct gsm_bts_t uint16_t port; struct in_addr ia = {0}; char unit_id[40]; - struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR]; - - /* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.30 Manufacturer Id */ - if (TLVP_PRES_LEN(tp, NM_ATT_MANUF_ID, 2)) { - len = TLVP_LEN(tp, NM_ATT_MANUF_ID); - - /* log potential BTS feature vector overflow */ - if (len > sizeof(bts->_features_data)) { - LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: feature vector is truncated " - "(from %u to %zu bytes)\n", bts->nr, len, sizeof(bts->_features_data)); - len = sizeof(bts->_features_data); - } - /* check that max. expected BTS attribute is above given feature vector length */ - if (len > OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT)) { - LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: reported unexpectedly long (%u bytes) " - "feature vector - most likely it was compiled against newer BSC headers. " - "Consider upgrading your BSC to later version.\n", - bts->nr, len); + switch (bts->type) { + case GSM_BTS_TYPE_OSMOBTS: + if (TLVP_PRES_LEN(tp, NM_ATT_MANUF_ID, 2)) { + parse_osmo_bts_features(bts, TLVP_VAL(tp, NM_ATT_MANUF_ID), + TLVP_LEN(tp, NM_ATT_MANUF_ID)); } - - memcpy(bts->_features_data, TLVP_VAL(tp, NM_ATT_MANUF_ID), len); - - for (i = 0; i < _NUM_BTS_FEAT; i++) { - if (osmo_bts_has_feature(&bts->features, i) != osmo_bts_has_feature(&bts->model->features, i)) { - LOGP(DNM, LOGL_NOTICE, "BTS%u feature '%s' reported via OML does not match statically " - "set feature: %u != %u. Please fix.\n", bts->nr, - get_value_string(osmo_bts_features_descs, i), - osmo_bts_has_feature(&bts->features, i), osmo_bts_has_feature(&bts->model->features, i)); - } + /* fall-through */ + case GSM_BTS_TYPE_NANOBTS: + if (TLVP_PRESENT(tp, NM_ATT_IPACC_SUPP_FEATURES)) { + ipacc_parse_supp_features(bts, foh, TLVP_VAL(tp, NM_ATT_IPACC_SUPP_FEATURES), + TLVP_LEN(tp, NM_ATT_IPACC_SUPP_FEATURES)); } + break; + default: + break; } /* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.28 Manufacturer Dependent State */ @@ -607,6 +640,7 @@ static int parse_attr_resp_info_attr(struct gsm_bts *bts, const struct gsm_bts_t /* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.61 SW Configuration */ if (TLVP_PRESENT(tp, NM_ATT_SW_CONFIG)) { + struct abis_nm_sw_desc sw_descr[SW_DESCR_MAX]; data = TLVP_VAL(tp, NM_ATT_SW_CONFIG); len = TLVP_LEN(tp, NM_ATT_SW_CONFIG); /* after parsing manufacturer-specific attributes there's list of replies in form of sw-conf structure: */ @@ -663,23 +697,34 @@ static int parse_attr_resp_info(struct gsm_bts *bts, const struct gsm_bts_trx *t /* After parsing unreported attribute id list inside Response info, there's a list of reported attribute ids and their values, in a TLV list form. */ - abis_nm_tlv_parse(tp, bts, data, data_len); + if (abis_nm_tlv_parse(tp, bts, data, data_len) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } + return parse_attr_resp_info_attr(bts, trx, foh, tp); } /* Handle 3GPP TS 52.021 §8.11.3 Get Attribute Response */ -static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *trx) +static int abis_nm_rx_get_attr_resp(struct msgb *mb) { struct abis_om_hdr *oh = msgb_l2(mb); struct abis_om_fom_hdr *foh = msgb_l3(mb); struct e1inp_sign_link *sign_link = mb->dst; - struct gsm_bts *bts = trx ? trx->bts : sign_link->trx->bts; + struct gsm_bts *bts = sign_link->trx->bts; + const struct gsm_bts_trx *trx; struct tlv_parsed tp; int rc; + trx = foh->obj_class == NM_OC_BASEB_TRANSC ? + gsm_bts_trx_num(bts, foh->obj_inst.trx_nr) : NULL; + DEBUGPFOH(DNM, foh, "Get Attributes Response\n"); - abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); + if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } /* nanoBTS doesn't send Get Attribute Response Info, uses its own format */ if (bts->type != GSM_BTS_TYPE_NANOBTS) @@ -687,6 +732,12 @@ static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *t else rc = parse_attr_resp_info_attr(bts, trx, foh, &tp); + if (gsm_bts_check_cfg(bts) != 0) { + LOGP(DLINP, LOGL_ERROR, "(bts=%u) BTS config invalid, dropping BTS!\n", bts->nr); + ipaccess_drop_oml_deferred(bts); + return -EINVAL; + } + osmo_signal_dispatch(SS_NM, S_NM_GET_ATTR_REP, mb); return rc; @@ -701,21 +752,30 @@ static int abis_nm_rx_sw_act_req(struct msgb *mb) struct tlv_parsed tp; const uint8_t *sw_config; int ret, sw_config_len, len; - struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR]; + struct abis_nm_sw_desc sw_descr[SW_DESCR_MAX]; DEBUGPFOH(DNM, foh, "Software Activate Request, ACKing and Activating\n"); + if (oh->length < sizeof(*foh)) { + LOGPFOH(DNM, LOGL_ERROR, foh, "Software Activate Request with length too small: %u\n", oh->length); + return -EINVAL; + } + ret = abis_nm_sw_act_req_ack(sign_link->trx->bts, foh->obj_class, foh->obj_inst.bts_nr, foh->obj_inst.trx_nr, foh->obj_inst.ts_nr, 0, - foh->data, oh->length-sizeof(*foh)); + foh->data, oh->length - sizeof(*foh)); if (ret != 0) { LOGPFOH(DNM, LOGL_ERROR, foh, "Sending SW ActReq ACK failed: %d\n", ret); return ret; } - abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } + sw_config = TLVP_VAL(&tp, NM_ATT_SW_CONFIG); sw_config_len = TLVP_LEN(&tp, NM_ATT_SW_CONFIG); if (!TLVP_PRESENT(&tp, NM_ATT_SW_CONFIG)) { @@ -752,7 +812,11 @@ static int abis_nm_rx_chg_adm_state_ack(struct msgb *mb) struct tlv_parsed tp; uint8_t adm_state; - abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } + if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) return -EINVAL; @@ -771,8 +835,12 @@ static int abis_nm_rx_lmt_event(struct msgb *mb) struct e1inp_sign_link *sign_link = mb->dst; struct tlv_parsed tp; + if (abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } + DEBUGPFOH(DNM, foh, "LMT Event "); - abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) && TLVP_LEN(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) >= 1) { uint8_t onoff = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_LOGON_SESSION); @@ -866,16 +934,16 @@ bool all_trx_rsl_connected_unlocked(const struct gsm_bts *bts) if (bts->gprs.cell.mo.nm_state.administrative == NM_STATE_LOCKED) return false; - if (bts->gprs.nse.mo.nm_state.administrative == NM_STATE_LOCKED) + if (bts->site_mgr->gprs.nse.mo.nm_state.administrative == NM_STATE_LOCKED) return false; - if (bts->gprs.nsvc[0].mo.nm_state.administrative == NM_STATE_LOCKED && - bts->gprs.nsvc[1].mo.nm_state.administrative == NM_STATE_LOCKED) + if (bts->site_mgr->gprs.nsvc[0].mo.nm_state.administrative == NM_STATE_LOCKED && + bts->site_mgr->gprs.nsvc[1].mo.nm_state.administrative == NM_STATE_LOCKED) return false; } llist_for_each_entry(trx, &bts->trx_list, list) { - if (!trx->rsl_link) + if (!trx->rsl_link_primary) return false; if (!trx_is_usable(trx)) @@ -924,9 +992,12 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb) struct nm_nack_signal_data nack_data; struct tlv_parsed tp; - LOGPFOH(DNM, LOGL_NOTICE, foh, "%s NACK ", abis_nm_nack_name(mt)); + if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } - abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); + LOGPFOH(DNM, LOGL_NOTICE, foh, "%s NACK ", abis_nm_nack_name(mt)); if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) LOGPC(DNM, LOGL_NOTICE, "CAUSE=%s\n", abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); @@ -994,7 +1065,7 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb) abis_nm_rx_set_bts_attr_ack(mb); break; case NM_MT_GET_ATTR_RESP: - ret = abis_nm_rx_get_attr_resp(mb, gsm_bts_trx_num(bts, (foh)->obj_inst.trx_nr)); + ret = abis_nm_rx_get_attr_resp(mb); break; case NM_MT_ESTABLISH_TEI_ACK: case NM_MT_CONN_TERR_SIGN_ACK: @@ -1023,17 +1094,17 @@ static int abis_nm_rcvmsg_manuf(struct msgb *mb) { int rc; struct e1inp_sign_link *sign_link = mb->dst; - int bts_type = sign_link->trx->bts->type; + struct gsm_bts *bts = sign_link->trx->bts; - switch (bts_type) { + switch (bts->type) { case GSM_BTS_TYPE_NANOBTS: case GSM_BTS_TYPE_OSMOBTS: rc = abis_nm_rx_ipacc(mb); - abis_nm_queue_send_next(sign_link->trx->bts); + abis_nm_queue_send_next(bts); break; default: - LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this " - "BTS type (%s)\n", btstype2str(bts_type)); + LOGPMO(&bts->mo, DNM, LOGL_ERROR, "don't know how to parse OML for this " + "BTS type (%s)\n", btstype2str(bts->type)); rc = 0; break; } @@ -1178,7 +1249,7 @@ static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg) msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, sw->file_version); } else { - LOGP(DNM, LOGL_ERROR, "Please implement this for the BTS.\n"); + LOGPMO(&sw->bts->mo, DNM, LOGL_ERROR, "Please implement this for the BTS.\n"); } } @@ -1276,7 +1347,7 @@ static int sw_load_segment(struct abis_nm_sw *sw) break; } default: - LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n"); + LOGPMO(&sw->bts->mo, DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n"); /* FIXME: Other BTS types */ return -1; } @@ -1324,56 +1395,51 @@ static int sw_activate(struct abis_nm_sw *sw) return abis_nm_sendmsg(sw->bts, msg); } -struct sdp_firmware { - char magic[4]; - char more_magic[4]; - unsigned int header_length; - unsigned int file_length; -} __attribute__ ((packed)); - static int parse_sdp_header(struct abis_nm_sw *sw) { + const struct gsm_abis_mo *mo = &sw->bts->mo; struct sdp_firmware firmware_header; int rc; struct stat stat; rc = read(sw->fd, &firmware_header, sizeof(firmware_header)); if (rc != sizeof(firmware_header)) { - LOGP(DNM, LOGL_ERROR, "Could not read SDP file header.\n"); + LOGPMO(mo, DNM, LOGL_ERROR, "Could not read SDP file header.\n"); return -1; } if (strncmp(firmware_header.magic, " SDP", 4) != 0) { - LOGP(DNM, LOGL_ERROR, "The magic number1 is wrong.\n"); + LOGPMO(mo, DNM, LOGL_ERROR, "The magic number is wrong.\n"); return -1; } if (firmware_header.more_magic[0] != 0x10 || - firmware_header.more_magic[1] != 0x02 || - firmware_header.more_magic[2] != 0x00 || - firmware_header.more_magic[3] != 0x00) { - LOGP(DNM, LOGL_ERROR, "The more magic number is wrong.\n"); + firmware_header.more_magic[1] != 0x02) { + LOGPMO(mo, DNM, LOGL_ERROR, "The more magic number is wrong.\n"); return -1; } + if (firmware_header.more_more_magic != 0x0000) { + LOGPMO(mo, DNM, LOGL_ERROR, "The more more magic number is wrong.\n"); + return -1; + } if (fstat(sw->fd, &stat) == -1) { - LOGP(DNM, LOGL_ERROR, "Could not stat the file.\n"); + LOGPMO(mo, DNM, LOGL_ERROR, "Could not stat the file.\n"); return -1; } if (ntohl(firmware_header.file_length) != stat.st_size) { - LOGP(DNM, LOGL_ERROR, "The filesizes do not match.\n"); + LOGPMO(mo, DNM, LOGL_ERROR, "The filesizes do not match.\n"); return -1; } /* go back to the start as we checked the whole filesize.. */ lseek(sw->fd, 0l, SEEK_SET); - LOGP(DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood." - " There might be checksums in the file that are not" - " verified and incomplete firmware might be flashed." - " There is absolutely no WARRANTY that flashing will" - " work.\n"); + LOGPMO(mo, DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood. " + "There might be checksums in the file that are not verified and incomplete " + "firmware might be flashed. There is absolutely no WARRANTY that " + "flashing will work.\n"); return 0; } @@ -1615,7 +1681,7 @@ int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname, struct abis_nm_sw *sw = &g_sw; int rc; - DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n", bts->nr, fname); + LOGPMO(&bts->mo, DNM, LOGL_DEBUG, "Software Load (file \"%s\")\n", fname); if (sw->state != SW_STATE_NONE) return -EBUSY; @@ -1632,13 +1698,13 @@ int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname, break; case GSM_BTS_TYPE_NANOBTS: sw->obj_class = NM_OC_BASEB_TRANSC; - sw->obj_instance[0] = sw->bts->nr; + sw->obj_instance[0] = sw->bts->bts_nr; sw->obj_instance[1] = sw->trx_nr; sw->obj_instance[2] = 0xff; break; case GSM_BTS_TYPE_UNKNOWN: default: - LOGP(DNM, LOGL_ERROR, "Software Load not properly implemented.\n"); + LOGPMO(&bts->mo, DNM, LOGL_ERROR, "Software Load not properly implemented.\n"); return -1; break; } @@ -1683,7 +1749,7 @@ int abis_nm_software_activate(struct gsm_bts *bts, const char *fname, struct abis_nm_sw *sw = &g_sw; int rc; - DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n", bts->nr, fname); + LOGPMO(&bts->mo, DNM, LOGL_DEBUG, "Activating Software (file \"%s\")\n", fname); if (sw->state != SW_STATE_NONE) return -EBUSY; @@ -1780,9 +1846,8 @@ int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts, ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); - DEBUGP(DNM, "CONNECT TERR TRAF Um=%s E1=(%u,%u,%u)\n", - gsm_ts_name(ts), - e1_port, e1_timeslot, e1_subslot); + LOGPMO(&ts->mo, DNM, LOGL_DEBUG, "CONNECT TERR TRAF E1=(%u,%u,%u)\n", + e1_port, e1_timeslot, e1_subslot); return abis_nm_sendmsg(bts, msg); } @@ -1804,8 +1869,8 @@ int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class, uint8_t bts_nr, uin struct msgb *msg; if (bts->type != GSM_BTS_TYPE_OSMOBTS && bts->type != GSM_BTS_TYPE_NANOBTS) { - LOGP(DNM, LOGL_NOTICE, "Getting attributes from BTS%d type %s is not supported.\n", - bts->nr, btstype2str(bts->type)); + LOGPMO(&bts->mo, DNM, LOGL_NOTICE, "Getting attributes from BTS " + "type %s is not supported.\n", btstype2str(bts->type)); return -EINVAL; } @@ -1827,7 +1892,7 @@ int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len) struct msgb *msg = nm_msgb_alloc(); uint8_t *cur; - LOG_BTS(bts, DNM, LOGL_DEBUG, "Set BTS Attr\n"); + LOGPMO(&bts->mo, DNM, LOGL_DEBUG, "Set BTS Attr\n"); oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); fill_om_fom_hdr(oh, attr_len, NM_MT_SET_BTS_ATTR, NM_OC_BTS, bts->bts_nr, 0xff, 0xff); @@ -1875,7 +1940,7 @@ static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb, switch (chan_comb) { case NM_CHANC_TCHHalf: case NM_CHANC_TCHHalf2: - case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + case NM_CHANC_OSMO_DYN: /* not supported */ *reason = "TCH/H is not supported."; return -EINVAL; @@ -1973,7 +2038,7 @@ static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb, case NM_CHANC_TCHHalf: case NM_CHANC_IPAC_TCHFull_TCHHalf: case NM_CHANC_IPAC_TCHFull_PDCH: - case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + case NM_CHANC_OSMO_DYN: return 0; default: *reason = "TS1 must carry a CBCH, SDCCH or TCH."; @@ -2005,7 +2070,7 @@ static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb, return 0; case NM_CHANC_IPAC_PDCH: case NM_CHANC_IPAC_TCHFull_PDCH: - case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + case NM_CHANC_OSMO_DYN: if (ts->trx->nr == 0) return 0; else { @@ -2104,12 +2169,13 @@ int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb) } int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1, - uint8_t i2, uint8_t i3, int nack, uint8_t *attr, int att_len) + uint8_t i2, uint8_t i3, int nack, + const uint8_t *attr, unsigned int attr_len) { struct abis_om_hdr *oh; struct msgb *msg = nm_msgb_alloc(); uint8_t msgtype = NM_MT_SW_ACT_REQ_ACK; - uint8_t len = att_len; + uint8_t len = attr_len; if (nack) { len += 2; @@ -2117,12 +2183,10 @@ int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1, } oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, att_len, msgtype, obj_class, i1, i2, i3); + fill_om_fom_hdr(oh, len, msgtype, obj_class, i1, i2, i3); - if (attr) { - uint8_t *ptr = msgb_put(msg, att_len); - memcpy(ptr, attr, att_len); - } + if (attr != NULL && attr_len > 0) + memcpy(msgb_put(msg, attr_len), attr, attr_len); if (nack) msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP); @@ -2596,8 +2660,8 @@ struct file_list_entry *fl_dequeue(struct llist_head *queue) static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw) { + struct file_list_entry *fle; char linebuf[255]; - struct llist_head *lh, *lh2; FILE *swl; int rc = 0; @@ -2606,10 +2670,8 @@ static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw) return -ENODEV; /* zero the stale file list, if any */ - llist_for_each_safe(lh, lh2, &bs11_sw->file_list) { - llist_del(lh); - talloc_free(lh); - } + while ((fle = fl_dequeue(&bs11_sw->file_list))) + talloc_free(fle); while (fgets(linebuf, sizeof(linebuf), swl)) { char file_id[12+1]; @@ -2816,6 +2878,7 @@ static int abis_nm_rx_ipacc(struct msgb *msg) struct tlv_parsed tp; struct ipacc_ack_signal_data signal; struct e1inp_sign_link *sign_link = msg->dst; + struct gsm_bts *bts = sign_link->trx->bts; struct gsm_bts_trx *trx; foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen); @@ -2825,11 +2888,14 @@ static int abis_nm_rx_ipacc(struct msgb *msg) return -EINVAL; } - abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } /* The message might be received over the main OML link, so we cannot * just use sign_link->trx. Resolve it by number from the FOM header. */ - trx = gsm_bts_trx_num(sign_link->trx->bts, foh->obj_inst.trx_nr); + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); DEBUGPFOH(DNM, foh, "Rx IPACCESS(0x%02x): %s\n", foh->msg_type, osmo_hexdump(foh->data, oh->length - sizeof(*foh))); @@ -2909,17 +2975,15 @@ static int abis_nm_rx_ipacc(struct msgb *msg) case NM_MT_IPACC_RSL_CONNECT_NACK: case NM_MT_IPACC_SET_NVATTR_NACK: case NM_MT_IPACC_GET_NVATTR_NACK: - if (!trx) - goto obj_inst_error; - signal.trx = trx; - signal.msg_type = foh->msg_type; + signal.bts = bts; + signal.foh = foh; osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal); break; + case NM_MT_IPACC_RSL_CONNECT_ACK: case NM_MT_IPACC_SET_NVATTR_ACK: - if (!trx) - goto obj_inst_error; - signal.trx = trx; - signal.msg_type = foh->msg_type; + case NM_MT_IPACC_SET_ATTR_ACK: + signal.bts = bts; + signal.foh = foh; osmo_signal_dispatch(SS_NM, S_NM_IPACC_ACK, &signal); break; default: @@ -2986,40 +3050,48 @@ static void rsl_connect_timeout(void *data) LOG_TRX(trx, DRSL, LOGL_NOTICE, "RSL connection request timed out\n"); /* Fake an RSL CONNECT NACK message from the BTS. */ - signal.trx = trx; - signal.msg_type = NM_MT_IPACC_RSL_CONNECT_NACK; + struct abis_om_fom_hdr foh = { + .msg_type = NM_MT_IPACC_RSL_CONNECT_NACK, + .obj_class = NM_OC_BASEB_TRANSC, + .obj_inst = { + .bts_nr = trx->bts->bts_nr, + .trx_nr = trx->nr, + .ts_nr = 0xff, + }, + }; + signal.foh = &foh; + signal.bts = trx->bts; osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal); } int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx, uint32_t ip, uint16_t port, uint8_t stream) { + struct msgb *attr; struct in_addr ia; - uint8_t attr[] = { NM_ATT_IPACC_STREAM_ID, 0, - NM_ATT_IPACC_DST_IP_PORT, 0, 0, - NM_ATT_IPACC_DST_IP, 0, 0, 0, 0 }; - - int attr_len = sizeof(attr); int error; osmo_timer_setup(&trx->rsl_connect_timeout, rsl_connect_timeout, trx); - ia.s_addr = htonl(ip); - attr[1] = stream; - attr[3] = port >> 8; - attr[4] = port & 0xff; - memcpy(attr + 6, &ia.s_addr, sizeof(uint32_t)); + attr = msgb_alloc(32, "RSL-connect-attr"); + msgb_tv_put(attr, NM_ATT_IPACC_STREAM_ID, stream); + msgb_tv16_put(attr, NM_ATT_IPACC_DST_IP_PORT, port); /* if ip == 0, we use the default IP */ - if (ip == 0) - attr_len -= 5; + if (ip != 0) { + ia.s_addr = htonl(ip); + msgb_tv_fixed_put(attr, NM_ATT_IPACC_DST_IP, 4, (void*)&ia.s_addr); + } else { + ia = (struct in_addr){}; + } LOG_TRX(trx, DNM, LOGL_INFO, "IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n", inet_ntoa(ia), port, stream); error = abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_RSL_CONNECT, NM_OC_BASEB_TRANSC, trx->bts->bts_nr, - trx->nr, 0xff, attr, attr_len); + trx->nr, 0xff, attr->data, attr->len); + msgb_free(attr); if (error == 0) osmo_timer_schedule(&trx->rsl_connect_timeout, 60, 0); @@ -3034,7 +3106,7 @@ int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx) oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); fill_om_fom_hdr(oh, 0, NM_MT_IPACC_RESTART, NM_OC_BASEB_TRANSC, - trx->bts->nr, trx->nr, 0xff); + trx->bts->bts_nr, trx->nr, 0xff); return abis_nm_sendmsg_direct(trx->bts, msg); } diff --git a/src/osmo-bsc/abis_nm_vty.c b/src/osmo-bsc/abis_nm_vty.c index fe467fad1..bbd2d157f 100644 --- a/src/osmo-bsc/abis_nm_vty.c +++ b/src/osmo-bsc/abis_nm_vty.c @@ -54,11 +54,6 @@ struct oml_node_state { uint8_t obj_inst[3]; }; -static int dummy_config_write(struct vty *v) -{ - return CMD_SUCCESS; -} - /* FIXME: auto-generate those strings from the value_string lists */ #define NM_OBJCLASS_VTY "(site-manager|bts|radio-carrier|baseband-transceiver|channel|adjc|handover|power-contorl|btse|rack|test|envabtse|bport|gprs-nse|gprs-cell|gprs-nsvc|siemenshw)" #define NM_OBJCLASS_VTY_HELP "Site Manager Object\n" \ diff --git a/src/osmo-bsc/abis_om2000.c b/src/osmo-bsc/abis_om2000.c index a3f689add..69a86b54d 100644 --- a/src/osmo-bsc/abis_om2000.c +++ b/src/osmo-bsc/abis_om2000.c @@ -44,9 +44,22 @@ #include <osmocom/bsc/abis_om2000.h> #include <osmocom/bsc/signal.h> #include <osmocom/bsc/timeslot_fsm.h> +#include <osmocom/bsc/nm_common_fsm.h> #include <osmocom/bsc/bts.h> #include <osmocom/abis/e1_input.h> +static inline void abis_om2000_fsm_transc_becomes_enabled(struct gsm_bts_trx *trx) +{ + nm_obj_fsm_becomes_enabled_disabled(trx->bts, trx, NM_OC_RADIO_CARRIER, true); + nm_obj_fsm_becomes_enabled_disabled(trx->bts, &trx->bb_transc, NM_OC_BASEB_TRANSC, true); +} + +static inline void abis_om2000_fsm_transc_becomes_disabled(struct gsm_bts_trx *trx) +{ + nm_obj_fsm_becomes_enabled_disabled(trx->bts, trx, NM_OC_RADIO_CARRIER, false); + nm_obj_fsm_becomes_enabled_disabled(trx->bts, &trx->bb_transc, NM_OC_BASEB_TRANSC, false); +} + /* FIXME: move to libosmocore */ struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm, struct osmo_fsm_inst *parent, @@ -55,20 +68,11 @@ struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm, { struct osmo_fsm_inst *fi; - fi = osmo_fsm_inst_alloc(fsm, parent, NULL, parent->log_level, - id ? id : parent->id); - if (!fi) { - /* indicate immediate termination to caller */ - osmo_fsm_inst_dispatch(parent, parent_term_event, NULL); + fi = osmo_fsm_inst_alloc_child(fsm, parent, parent_term_event); + if (!fi) return NULL; - } - - LOGPFSM(fi, "is child of %s\n", osmo_fsm_inst_name(parent)); - - fi->proc.parent = parent; - fi->proc.parent_term_event = parent_term_event; - llist_add(&fi->proc.child, &parent->proc.children); - + if (id) + osmo_fsm_inst_update_id_f_sanitize(fi, '-', id); return fi; } @@ -310,6 +314,7 @@ enum abis_om2k_dei { OM2K_DEI_MAX_ALLOWED_POWER = 0xa9, OM2K_DEI_MAX_ALLOWED_NUM_TRXCS = 0xaa, OM2K_DEI_MCTR_FEAT_STATUS_BMAP = 0xab, + OM2K_DEI_SEEN_UNKNOWN_D2 = 0xd2, }; enum abis_om2k_mostate { @@ -388,6 +393,7 @@ const struct tlv_definition om2k_att_tlvdef = { [OM2K_DEI_FS_OFFSET] = { TLV_TYPE_FIXED, 5 }, [OM2K_DEI_EXT_COND_MAP_2_EXT] = { TLV_TYPE_FIXED, 4 }, [OM2K_DEI_TSS_MO_STATE] = { TLV_TYPE_FIXED, 4 }, + [OM2K_DEI_SEEN_UNKNOWN_D2] = { TLV_TYPE_FIXED, 6 }, }, }; @@ -776,6 +782,9 @@ get_om2k_mo(struct gsm_bts *bts, const struct abis_om2k_mo *abis_mo) struct gsm_bts_trx *trx; switch (abis_mo->class) { + case OM2K_MO_CLS_DP: + mo = &bts->rbs2000.dp.om2k_mo; + break; case OM2K_MO_CLS_CF: mo = &bts->rbs2000.cf.om2k_mo; break; @@ -849,7 +858,7 @@ static int om2k_decode_msg(struct om2k_decoded_msg *odm, struct msgb *msg) return abis_om2k_msg_tlv_parse(&odm->tp, o2h); } -static char *om2k_mo_name(const struct abis_om2k_mo *mo) +const char *abis_om2k_mo_name(const struct abis_om2k_mo *mo) { static char mo_buf[64]; @@ -861,8 +870,7 @@ static char *om2k_mo_name(const struct abis_om2k_mo *mo) } /* resolve the gsm_nm_state data structure for a given MO */ -static struct gsm_nm_state * -mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +static struct gsm_nm_state *mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo) { struct gsm_bts_trx *trx; struct gsm_nm_state *nm_state = NULL; @@ -917,7 +925,7 @@ mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo) return nm_state; } -static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo) +static void *mo2obj(struct gsm_bts *bts, const struct abis_om2k_mo *mo) { struct gsm_bts_trx *trx; @@ -945,17 +953,11 @@ static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo) return NULL; } -static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo, - uint8_t mo_state) +/* Derive an OML Availability state from an OM2000 MO state */ +static enum abis_nm_avail_state abis_nm_av_state_from_om2k_av_state(struct abis_om2k_mo *mo, uint8_t mo_state) { - struct gsm_nm_state *nm_state = mo2nm_state(bts, mo); - struct gsm_nm_state new_state; - struct nm_statechg_signal_data nsd; bool has_enabled_state; - if (!nm_state) - return; - switch (mo->class) { case OM2K_MO_CLS_CF: case OM2K_MO_CLS_TRXC: @@ -966,61 +968,132 @@ static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo, break; } - new_state = *nm_state; switch (mo_state) { case OM2K_MOSTATE_RESET: - new_state.availability = NM_AVSTATE_POWER_OFF; - break; + return NM_AVSTATE_POWER_OFF; case OM2K_MOSTATE_STARTED: - new_state.availability = has_enabled_state ? NM_AVSTATE_OFF_LINE : NM_AVSTATE_OK; - break; + return has_enabled_state ? NM_AVSTATE_OFF_LINE : NM_AVSTATE_OK; case OM2K_MOSTATE_ENABLED: - new_state.availability = NM_AVSTATE_OK; - break; + return NM_AVSTATE_OK; case OM2K_MOSTATE_DISABLED: - new_state.availability = NM_AVSTATE_POWER_OFF; - break; + return NM_AVSTATE_POWER_OFF; default: - new_state.availability = NM_AVSTATE_DEGRADED; - break; + return NM_AVSTATE_DEGRADED; } +} + +/* The OM2000 -> 12.21 mapping we do doesn't have a separate bb_transc MO, + * for compatibility reasons we pretend to have it anyway. */ +static void update_bb_trxc_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo, uint8_t mo_state) +{ + struct nm_statechg_signal_data nsd; + struct gsm_bts_trx *trx; + + trx = gsm_bts_trx_num(bts, mo->inst); + if (!trx) + return; memset(&nsd, 0, sizeof(nsd)); nsd.bts = bts; nsd.obj = mo2obj(bts, mo); - nsd.old_state = nm_state; - nsd.new_state = &new_state; + nsd.old_state = trx->bb_transc.mo.nm_state; + nsd.new_state = trx->bb_transc.mo.nm_state; nsd.om2k_mo = mo; - osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd); - - nm_state->availability = new_state.availability; + nsd.new_state.availability = abis_nm_av_state_from_om2k_av_state(mo, mo_state); + trx->bb_transc.mo.nm_state.availability = nsd.new_state.availability; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd); } -static void update_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo, - uint8_t op_state) +static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo, uint8_t mo_state) { struct gsm_nm_state *nm_state = mo2nm_state(bts, mo); - struct gsm_nm_state new_state; + struct nm_statechg_signal_data nsd; if (!nm_state) return; - new_state = *nm_state; + memset(&nsd, 0, sizeof(nsd)); + + nsd.bts = bts; + nsd.obj = mo2obj(bts, mo); + nsd.old_state = *nm_state; + nsd.new_state = *nm_state; + nsd.om2k_mo = mo; + + nsd.new_state.availability = abis_nm_av_state_from_om2k_av_state(mo, mo_state); + + /* Update current state before emitting signal: */ + nm_state->availability = nsd.new_state.availability; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd); + + /* When the TRXC MO is updated, also update the BB TRXC accordingly. */ + if (mo->class == OM2K_MO_CLS_TRXC) + update_bb_trxc_mo_state(bts, mo, mo_state); +} + +/* Derive an OML Operational state from an OM2000 OP state */ +static enum abis_nm_op_state abis_nm_op_state_from_om2k_op_state(uint8_t op_state) +{ switch (op_state) { case 1: - new_state.operational = NM_OPSTATE_ENABLED; - break; + return NM_OPSTATE_ENABLED; case 0: - new_state.operational = NM_OPSTATE_DISABLED; - break; + return NM_OPSTATE_DISABLED; default: - new_state.operational = NM_OPSTATE_NULL; - break; + return NM_OPSTATE_NULL; } +} + +/* (see comment in update_bb_trxc_mo_state() above) */ +static void update_bb_trxc_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo, uint8_t op_state) +{ + struct nm_statechg_signal_data nsd; + struct gsm_bts_trx *trx; + + trx = gsm_bts_trx_num(bts, mo->inst); + if (!trx) + return; - nm_state->operational = new_state.operational; + memset(&nsd, 0, sizeof(nsd)); + + nsd.bts = bts; + nsd.obj = mo2obj(bts, mo); + nsd.old_state = trx->bb_transc.mo.nm_state; + nsd.new_state = trx->bb_transc.mo.nm_state; + nsd.om2k_mo = mo; + + nsd.new_state.operational = abis_nm_op_state_from_om2k_op_state(op_state); + trx->bb_transc.mo.nm_state.operational = nsd.new_state.operational; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd); +} + +static void update_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo, uint8_t op_state) +{ + struct gsm_nm_state *nm_state = mo2nm_state(bts, mo); + struct nm_statechg_signal_data nsd; + + if (!nm_state) + return; + + memset(&nsd, 0, sizeof(nsd)); + + nsd.bts = bts; + nsd.obj = mo2obj(bts, mo); + nsd.old_state = *nm_state; + nsd.new_state = *nm_state; + nsd.om2k_mo = mo; + + nsd.new_state.operational = abis_nm_op_state_from_om2k_op_state(op_state); + + /* Update current state before emitting signal: */ + nm_state->operational = nsd.new_state.operational; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd); + + /* When the TRXC MO is updated, also update the BB TRXC accordingly. */ + if (mo->class == OM2K_MO_CLS_TRXC) + update_bb_trxc_op_state(bts, mo, op_state); } static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg) @@ -1041,8 +1114,8 @@ static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg) /* Route through per-TRX OML Link to the appropriate TRX */ trx = gsm_bts_trx_num(bts, o2h->mo.inst); if (!trx) { - LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to " - "non-existing TRX\n", om2k_mo_name(&o2h->mo)); + LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to non-existing TRX\n", + abis_om2k_mo_name(&o2h->mo)); return -ENODEV; } msg->dst = trx->oml_link; @@ -1051,8 +1124,8 @@ static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg) /* Route through per-TRX OML Link to the appropriate TRX */ trx = gsm_bts_trx_num(bts, o2h->mo.assoc_so); if (!trx) { - LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to " - "non-existing TRX\n", om2k_mo_name(&o2h->mo)); + LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to non-existing TRX\n", + abis_om2k_mo_name(&o2h->mo)); return -ENODEV; } msg->dst = trx->oml_link; @@ -1066,8 +1139,7 @@ static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg) return _abis_nm_sendmsg(msg); } -static void fill_om2k_hdr(struct abis_om2k_hdr *o2h, const struct abis_om2k_mo *mo, - uint16_t msg_type) +static void fill_om2k_hdr(struct abis_om2k_hdr *o2h, const struct abis_om2k_mo *mo, uint16_t msg_type) { o2h->om.mdisc = ABIS_OM_MDISC_FOM; o2h->om.placement = ABIS_OM_PLACEMENT_ONLY; @@ -1085,8 +1157,7 @@ static int abis_om2k_cal_time_resp(struct gsm_bts *bts) struct tm *tm; o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, &bts->rbs2000.cf.om2k_mo.addr, - OM2K_MSGT_CAL_TIME_RESP); + fill_om2k_hdr(o2k, &bts->rbs2000.cf.om2k_mo.addr, OM2K_MSGT_CAL_TIME_RESP); tm_t = time(NULL); tm = localtime(&tm_t); @@ -1102,8 +1173,7 @@ static int abis_om2k_cal_time_resp(struct gsm_bts *bts) return abis_om2k_sendmsg(bts, msg); } -static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo, - uint16_t msg_type) +static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo, uint16_t msg_type) { struct msgb *msg = om2k_msgb_alloc(); struct abis_om2k_hdr *o2k; @@ -1111,8 +1181,7 @@ static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *m o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); fill_om2k_hdr(o2k, mo, msg_type); - DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), - get_value_string(om2k_msgcode_vals, msg_type)); + DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(mo), get_value_string(om2k_msgcode_vals, msg_type)); return abis_om2k_sendmsg(bts, msg); } @@ -1157,8 +1226,7 @@ int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISABLE_REQ); } -int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo, - uint8_t operational) +int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo, uint8_t operational) { struct msgb *msg = om2k_msgb_alloc(); struct abis_om2k_hdr *o2k; @@ -1168,7 +1236,7 @@ int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo, msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational); - DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(mo), get_value_string(om2k_msgcode_vals, OM2K_MSGT_OP_INFO)); /* we update the state here... and send the signal at ACK */ @@ -1228,19 +1296,16 @@ int abis_om2k_tx_is_conf_req(struct gsm_bts *bts) om2k_fill_is_conn_grp(&cg[i++], grp->icp1, grp->icp2, grp->ci); o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, &bts->rbs2000.is.om2k_mo.addr, - OM2K_MSGT_IS_CONF_REQ); + fill_om2k_hdr(o2k, &bts->rbs2000.is.om2k_mo.addr, OM2K_MSGT_IS_CONF_REQ); msgb_tv_put(msg, OM2K_DEI_LIST_NR, 1); msgb_tv_put(msg, OM2K_DEI_END_LIST_NR, 1); - msgb_tlv_put(msg, OM2K_DEI_IS_CONN_LIST, - num_grps * sizeof(*cg), (uint8_t *)cg); + msgb_tlv_put(msg, OM2K_DEI_IS_CONN_LIST, num_grps * sizeof(*cg), (uint8_t *)cg); talloc_free(cg); - DEBUGP(DNM, "Tx MO=%s %s\n", - om2k_mo_name(&bts->rbs2000.is.om2k_mo.addr), + DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(&bts->rbs2000.is.om2k_mo.addr), get_value_string(om2k_msgcode_vals, OM2K_MSGT_IS_CONF_REQ)); return abis_om2k_sendmsg(bts, msg); @@ -1286,11 +1351,9 @@ int abis_om2k_tx_con_conf_req(struct gsm_bts *bts) /* pre-pend the OM2K header */ o2k = (struct abis_om2k_hdr *) msgb_push(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, &bts->rbs2000.con.om2k_mo.addr, - OM2K_MSGT_CON_CONF_REQ); + fill_om2k_hdr(o2k, &bts->rbs2000.con.om2k_mo.addr, OM2K_MSGT_CON_CONF_REQ); - DEBUGP(DNM, "Tx MO=%s %s\n", - om2k_mo_name(&bts->rbs2000.con.om2k_mo.addr), + DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(&bts->rbs2000.con.om2k_mo.addr), get_value_string(om2k_msgcode_vals, OM2K_MSGT_CON_CONF_REQ)); return abis_om2k_sendmsg(bts, msg); @@ -1316,17 +1379,14 @@ int abis_om2k_tx_mctr_conf_req(struct gsm_bts *bts) /* pre-pend the OM2K header */ o2k = (struct abis_om2k_hdr *) msgb_push(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, &bts->rbs2000.mctr.om2k_mo.addr, - OM2K_MSGT_MCTR_CONF_REQ); - DEBUGP(DNM, "Tx MO=%s %s\n", - om2k_mo_name(&bts->rbs2000.mctr.om2k_mo.addr), + fill_om2k_hdr(o2k, &bts->rbs2000.mctr.om2k_mo.addr, OM2K_MSGT_MCTR_CONF_REQ); + DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(&bts->rbs2000.mctr.om2k_mo.addr), get_value_string(om2k_msgcode_vals, OM2K_MSGT_MCTR_CONF_REQ)); return abis_om2k_sendmsg(bts, msg); } -static void om2k_trx_to_mo(struct abis_om2k_mo *mo, - const struct gsm_bts_trx *trx, +static void om2k_trx_to_mo(struct abis_om2k_mo *mo, const struct gsm_bts_trx *trx, enum abis_om2k_mo_cls cls) { mo->class = cls; @@ -1335,8 +1395,7 @@ static void om2k_trx_to_mo(struct abis_om2k_mo *mo, mo->assoc_so = 255; } -static void om2k_ts_to_mo(struct abis_om2k_mo *mo, - const struct gsm_bts_trx_ts *ts) +static void om2k_ts_to_mo(struct abis_om2k_mo *mo, const struct gsm_bts_trx_ts *ts) { mo->class = OM2K_MO_CLS_TS; mo->bts = 0; @@ -1358,7 +1417,7 @@ int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx) /* OM2K_DEI_FREQ_SPEC_RX: Using trx_nr as "RX address" only works for single MCTR case */ msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, 0x8000 | ((uint16_t)trx->nr << 10)); - msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */ + msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, trx->rbs2000.rx_diversity); return abis_om2k_sendmsg(trx->bts, msg); } @@ -1400,16 +1459,13 @@ int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts) struct abis_om2k_hdr *o2k; o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, &bts->rbs2000.tf.om2k_mo.addr, - OM2K_MSGT_TF_CONF_REQ); + fill_om2k_hdr(o2k, &bts->rbs2000.tf.om2k_mo.addr, OM2K_MSGT_TF_CONF_REQ); msgb_tv_put(msg, OM2K_DEI_TF_MODE, OM2K_TF_MODE_STANDALONE); - msgb_tv_put(msg, OM2K_DEI_TF_SYNC_SRC, 0x00); - msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET, - sizeof(fs_offset_undef), fs_offset_undef); + msgb_tv_put(msg, OM2K_DEI_TF_SYNC_SRC, bts->rbs2000.sync_src); + msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET, sizeof(fs_offset_undef), fs_offset_undef); - DEBUGP(DNM, "Tx MO=%s %s\n", - om2k_mo_name(&bts->rbs2000.tf.om2k_mo.addr), + DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(&bts->rbs2000.tf.om2k_mo.addr), get_value_string(om2k_msgcode_vals, OM2K_MSGT_TF_CONF_REQ)); return abis_om2k_sendmsg(bts, msg); @@ -1428,7 +1484,7 @@ static uint8_t pchan2comb(enum gsm_phys_chan_config pchan) case GSM_PCHAN_TCH_H: case GSM_PCHAN_PDCH: case GSM_PCHAN_TCH_F_PDCH: - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: return 8; default: return 0; @@ -1438,11 +1494,9 @@ static uint8_t pchan2comb(enum gsm_phys_chan_config pchan) static uint8_t ts2comb(struct gsm_bts_trx_ts *ts) { if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH) { - LOGP(DNM, LOGL_ERROR, "%s pchan %s not intended for use" - " with OM2000, use %s instead\n", - gsm_ts_and_pchan_name(ts), - gsm_pchan_name(GSM_PCHAN_TCH_F_PDCH), - gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH)); + LOGP(DNM, LOGL_ERROR, "%s pchan %s not intended for use with OM2000, use %s instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(GSM_PCHAN_TCH_F_PDCH), + gsm_pchan_name(GSM_PCHAN_OSMO_DYN)); /* If we allowed initialization of TCH/F_PDCH, it would fail * when we try to send the ip.access specific RSL PDCH Act * message for it. Rather fail completely right now: */ @@ -1537,7 +1591,7 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts) msgb_tv_put(msg, OM2K_DEI_HSN, ts->hopping.hsn); msgb_tv_put(msg, OM2K_DEI_MAIO, ts->hopping.maio); msgb_tv_put(msg, OM2K_DEI_BSIC, ts->trx->bts->bsic); - msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */ + msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, ts->trx->rbs2000.rx_diversity); msgb_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0); msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */ /* Optional: Interference Rejection Combining */ @@ -1606,7 +1660,7 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts) } DEBUGP(DNM, "Tx MO=%s %s\n", - om2k_mo_name(&mo), + abis_om2k_mo_name(&mo), get_value_string(om2k_msgcode_vals, OM2K_MSGT_TS_CONF_REQ)); return abis_om2k_sendmsg(ts->trx->bts, msg); @@ -1620,7 +1674,9 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts) #define S(x) (1 << (x)) enum om2k_event_name { + OM2K_MO_EVT_RESET, OM2K_MO_EVT_START, + OM2K_MO_EVT_CHILD_TERM, OM2K_MO_EVT_RX_CONN_COMPL, OM2K_MO_EVT_RX_RESET_COMPL, OM2K_MO_EVT_RX_START_REQ_ACCEPT, @@ -1633,7 +1689,9 @@ enum om2k_event_name { }; static const struct value_string om2k_event_names[] = { + { OM2K_MO_EVT_RESET, "RESET" }, { OM2K_MO_EVT_START, "START" }, + { OM2K_MO_EVT_CHILD_TERM, "CHILD-TERM" }, { OM2K_MO_EVT_RX_CONN_COMPL, "RX-CONN-COMPL" }, { OM2K_MO_EVT_RX_RESET_COMPL, "RX-RESET-COMPL" }, { OM2K_MO_EVT_RX_START_REQ_ACCEPT, "RX-RESET-REQ-ACCEPT" }, @@ -1665,6 +1723,7 @@ struct om2k_mo_fsm_priv { struct gsm_bts_trx *trx; struct om2k_mo *mo; uint8_t ts_nr; + uint32_t done_event; }; static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -1676,20 +1735,17 @@ static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data switch (omfp->mo->addr.class) { case OM2K_MO_CLS_CF: /* no Connect required, is always connected */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, OM2K_TIMEOUT, 0); abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr); break; case OM2K_MO_CLS_TRXC: /* no Connect required, start with Reset */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL, OM2K_TIMEOUT, 0); abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr); break; default: /* start with Connect */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CONN_COMPL, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CONN_COMPL, OM2K_TIMEOUT, 0); abis_om2k_tx_connect_cmd(omfp->trx->bts, &omfp->mo->addr); break; } @@ -1703,14 +1759,12 @@ static void om2k_mo_st_wait_conn_compl(struct osmo_fsm_inst *fi, uint32_t event, #if 0 case OM2K_MO_CLS_TF: /* skip the reset, hope that helps */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, OM2K_TIMEOUT, 0); abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr); break; #endif default: - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL, OM2K_TIMEOUT, 0); abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr); break; } @@ -1720,8 +1774,7 @@ static void om2k_mo_st_wait_res_compl(struct osmo_fsm_inst *fi, uint32_t event, { struct om2k_mo_fsm_priv *omfp = fi->priv; - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, OM2K_TIMEOUT, 0); abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr); } @@ -1731,8 +1784,7 @@ static void om2k_mo_st_wait_start_accept(struct osmo_fsm_inst *fi, uint32_t even switch (omd->msg_type) { case OM2K_MSGT_START_REQ_ACK: - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_RES, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_RES, OM2K_TIMEOUT, 0); break; case OM2K_MSGT_START_REQ_REJ: osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); @@ -1749,21 +1801,18 @@ static void om2k_mo_st_wait_start_res(struct osmo_fsm_inst *fi, uint32_t event, case OM2K_MO_CLS_CF: case OM2K_MO_CLS_TRXC: /* Transition directly to Operational Info */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT, OM2K_TIMEOUT, 0); abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1); return; case OM2K_MO_CLS_DP: /* Transition directory to WAIT_ENABLE_ACCEPT */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, OM2K_TIMEOUT, 0); abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr); return; #if 0 case OM2K_MO_CLS_TF: /* skip the config, hope that helps speeding things up */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, OM2K_TIMEOUT, 0); abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr); return; #endif @@ -1826,8 +1875,7 @@ static void om2k_mo_st_wait_cfg_res(struct osmo_fsm_inst *fi, uint32_t event, vo return; } - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, OM2K_TIMEOUT, 0); abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr); } @@ -1841,11 +1889,9 @@ static void om2k_mo_st_wait_enable_accept(struct osmo_fsm_inst *fi, uint32_t eve osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); break; case OM2K_MSGT_ENABLE_REQ_ACK: - if (omfp->mo->addr.class == OM2K_MO_CLS_IS && - omfp->trx->bts->rbs2000.use_superchannel) + if (omfp->mo->addr.class == OM2K_MO_CLS_IS && omfp->trx->bts->rbs2000.use_superchannel) e1inp_ericsson_set_altc(omfp->trx->bts->oml_link->ts->line, 1); - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_RES, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_RES, OM2K_TIMEOUT, 0); } } @@ -1855,8 +1901,7 @@ static void om2k_mo_st_wait_enable_res(struct osmo_fsm_inst *fi, uint32_t event, //struct om2k_decoded_msg *omd = data; /* TODO: check if state is actually enabled now? */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT, - OM2K_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT, OM2K_TIMEOUT, 0); abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1); } @@ -1868,8 +1913,9 @@ static void om2k_mo_st_wait_opinfo_accept(struct osmo_fsm_inst *fi, uint32_t eve static void om2k_mo_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct om2k_mo_fsm_priv *omfp = fi->priv; - omfp->mo->fsm = NULL; - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + + if (fi->proc.parent) + osmo_fsm_inst_dispatch(fi->proc.parent, omfp->done_event, NULL); } static void om2k_mo_s_error_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) @@ -1880,11 +1926,24 @@ static void om2k_mo_s_error_onenter(struct osmo_fsm_inst *fi, uint32_t prev_stat osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); } +static void om2k_mo_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case OM2K_MO_EVT_RESET: + osmo_fsm_inst_broadcast_children(fi, event, data); + osmo_fsm_inst_state_chg(fi, OM2K_ST_INIT, 0, 0); + break; + default: + OSMO_ASSERT(0); + } +} + static const struct osmo_fsm_state om2k_is_states[] = { [OM2K_ST_INIT] = { .name = "INIT", .in_event_mask = S(OM2K_MO_EVT_START), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_CONN_COMPL) | S(OM2K_ST_WAIT_START_ACCEPT) | @@ -1895,6 +1954,7 @@ static const struct osmo_fsm_state om2k_is_states[] = { .name = "WAIT-CONN-COMPL", .in_event_mask = S(OM2K_MO_EVT_RX_CONN_COMPL), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_START_ACCEPT) | S(OM2K_ST_WAIT_RES_COMPL), @@ -1904,6 +1964,7 @@ static const struct osmo_fsm_state om2k_is_states[] = { .name = "WAIT-RES-COMPL", .in_event_mask = S(OM2K_MO_EVT_RX_RESET_COMPL), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_START_ACCEPT), .action = om2k_mo_st_wait_res_compl, @@ -1912,6 +1973,7 @@ static const struct osmo_fsm_state om2k_is_states[] = { .name = "WAIT-START-ACCEPT", .in_event_mask = S(OM2K_MO_EVT_RX_START_REQ_ACCEPT), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_START_RES), .action =om2k_mo_st_wait_start_accept, @@ -1920,15 +1982,18 @@ static const struct osmo_fsm_state om2k_is_states[] = { .name = "WAIT-START-RES", .in_event_mask = S(OM2K_MO_EVT_RX_START_RES), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_CFG_ACCEPT) | - S(OM2K_ST_WAIT_OPINFO_ACCEPT), + S(OM2K_ST_WAIT_OPINFO_ACCEPT) | + S(OM2K_ST_WAIT_ENABLE_ACCEPT), .action = om2k_mo_st_wait_start_res, }, [OM2K_ST_WAIT_CFG_ACCEPT] = { .name = "WAIT-CFG-ACCEPT", .in_event_mask = S(OM2K_MO_EVT_RX_CFG_REQ_ACCEPT), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_CFG_RES), .action = om2k_mo_st_wait_cfg_accept, @@ -1937,6 +2002,7 @@ static const struct osmo_fsm_state om2k_is_states[] = { .name = "WAIT-CFG-RES", .in_event_mask = S(OM2K_MO_EVT_RX_CFG_RES), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_ENABLE_ACCEPT), .action = om2k_mo_st_wait_cfg_res, @@ -1945,6 +2011,7 @@ static const struct osmo_fsm_state om2k_is_states[] = { .name = "WAIT-ENABLE-ACCEPT", .in_event_mask = S(OM2K_MO_EVT_RX_ENA_REQ_ACCEPT), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_ENABLE_RES), .action = om2k_mo_st_wait_enable_accept, @@ -1953,6 +2020,7 @@ static const struct osmo_fsm_state om2k_is_states[] = { .name = "WAIT-ENABLE-RES", .in_event_mask = S(OM2K_MO_EVT_RX_ENA_RES), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR) | S(OM2K_ST_WAIT_OPINFO_ACCEPT), .action = om2k_mo_st_wait_enable_res, @@ -1961,19 +2029,20 @@ static const struct osmo_fsm_state om2k_is_states[] = { .name = "WAIT-OPINFO-ACCEPT", .in_event_mask = S(OM2K_MO_EVT_RX_OPINFO_ACC), .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_INIT) | S(OM2K_ST_ERROR), .action = om2k_mo_st_wait_opinfo_accept, }, [OM2K_ST_DONE] = { .name = "DONE", .in_event_mask = 0, - .out_state_mask = 0, + .out_state_mask = S(OM2K_ST_INIT), .onenter = om2k_mo_s_done_onenter, }, [OM2K_ST_ERROR] = { .name = "ERROR", .in_event_mask = 0, - .out_state_mask = 0, + .out_state_mask = S(OM2K_ST_INIT), .onenter = om2k_mo_s_error_onenter, }, @@ -1990,13 +2059,14 @@ static struct osmo_fsm om2k_mo_fsm = { .states = om2k_is_states, .num_states = ARRAY_SIZE(om2k_is_states), .log_subsys = DNM, + .allstate_event_mask = S(OM2K_MO_EVT_RESET), + .allstate_action = om2k_mo_allstate, .event_names = om2k_event_names, .timer_cb = om2k_mo_timer_cb, }; -struct osmo_fsm_inst *om2k_mo_fsm_start(struct osmo_fsm_inst *parent, - uint32_t term_event, - struct gsm_bts_trx *trx, struct om2k_mo *mo) +static struct osmo_fsm_inst *om2k_mo_fsm_alloc(struct osmo_fsm_inst *parent, uint32_t done_event, + struct gsm_bts_trx *trx, struct om2k_mo *mo) { struct osmo_fsm_inst *fi; struct om2k_mo_fsm_priv *omfp; @@ -2006,8 +2076,7 @@ struct osmo_fsm_inst *om2k_mo_fsm_start(struct osmo_fsm_inst *parent, get_value_string(om2k_mo_class_short_vals, mo->addr.class), mo->addr.bts, mo->addr.assoc_so, mo->addr.inst); - fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent, - term_event, idbuf); + fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent, OM2K_MO_EVT_CHILD_TERM, idbuf); if (!fi) return NULL; @@ -2015,38 +2084,38 @@ struct osmo_fsm_inst *om2k_mo_fsm_start(struct osmo_fsm_inst *parent, omfp = talloc_zero(fi, struct om2k_mo_fsm_priv); omfp->mo = mo; omfp->trx = trx; + omfp->done_event = done_event; fi->priv = omfp; - osmo_fsm_inst_dispatch(fi, OM2K_MO_EVT_START, NULL); - return fi; } +static void om2k_mo_fsm_start(struct om2k_mo *mo) +{ + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_START, NULL); +} + int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo, struct om2k_decoded_msg *odm) { switch (odm->msg_type) { case OM2K_MSGT_CONNECT_COMPL: case OM2K_MSGT_CONNECT_REJ: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_CONN_COMPL, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_CONN_COMPL, odm); break; case OM2K_MSGT_RESET_COMPL: case OM2K_MSGT_RESET_REJ: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_RESET_COMPL, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_RESET_COMPL, odm); break; case OM2K_MSGT_START_REQ_ACK: case OM2K_MSGT_START_REQ_REJ: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_START_REQ_ACCEPT, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_START_REQ_ACCEPT, odm); break; case OM2K_MSGT_START_RES: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_START_RES, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_START_RES, odm); break; case OM2K_MSGT_CON_CONF_REQ_ACK: @@ -2056,8 +2125,7 @@ int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo, case OM2K_MSGT_TF_CONF_REQ_ACK: case OM2K_MSGT_TS_CONF_REQ_ACK: case OM2K_MSGT_TX_CONF_REQ_ACK: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, odm); break; case OM2K_MSGT_CON_CONF_RES: @@ -2067,24 +2135,20 @@ int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo, case OM2K_MSGT_TF_CONF_RES: case OM2K_MSGT_TS_CONF_RES: case OM2K_MSGT_TX_CONF_RES: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_CFG_RES, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_CFG_RES, odm); break; case OM2K_MSGT_ENABLE_REQ_ACK: case OM2K_MSGT_ENABLE_REQ_REJ: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, odm); break; case OM2K_MSGT_ENABLE_RES: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_ENA_RES, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_ENA_RES, odm); break; case OM2K_MSGT_OP_INFO_ACK: case OM2K_MSGT_OP_INFO_REJ: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_OPINFO_ACC, odm); + osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_OPINFO_ACC, odm); break; default: return -1; @@ -2098,7 +2162,9 @@ int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo, ***********************************************************************/ enum om2k_trx_event { - OM2K_TRX_EVT_START, + OM2K_TRX_EVT_RESET = OM2K_MO_EVT_RESET, + OM2K_TRX_EVT_START = OM2K_MO_EVT_START, + OM2K_TRX_EVT_CHILD_TERM = OM2K_MO_EVT_CHILD_TERM, OM2K_TRX_EVT_TRXC_DONE, OM2K_TRX_EVT_TX_DONE, OM2K_TRX_EVT_RX_DONE, @@ -2107,7 +2173,9 @@ enum om2k_trx_event { }; static struct value_string om2k_trx_events[] = { + { OM2K_TRX_EVT_RESET, "RESET" }, { OM2K_TRX_EVT_START, "START" }, + { OM2K_TRX_EVT_CHILD_TERM, "CHILD-TERM" }, { OM2K_TRX_EVT_TRXC_DONE, "TRXC-DONE" }, { OM2K_TRX_EVT_TX_DONE, "TX-DONE" }, { OM2K_TRX_EVT_RX_DONE, "RX-DONE" }, @@ -2130,6 +2198,7 @@ enum om2k_trx_state { struct om2k_trx_fsm_priv { struct gsm_bts_trx *trx; uint8_t cur_ts_nr; + uint32_t done_event; }; static void om2k_trx_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2137,10 +2206,8 @@ static void om2k_trx_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data struct om2k_trx_fsm_priv *otfp = fi->priv; /* First initialize TRXC */ - osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TRXC, - TRX_FSM_TIMEOUT, 0); - om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TRXC_DONE, otfp->trx, - &otfp->trx->rbs2000.trxc.om2k_mo); + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TRXC, TRX_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(&otfp->trx->rbs2000.trxc.om2k_mo); } static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2148,10 +2215,8 @@ static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void struct om2k_trx_fsm_priv *otfp = fi->priv; /* Initialize TX after TRXC */ - osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TX, - TRX_FSM_TIMEOUT, 0); - om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TX_DONE, otfp->trx, - &otfp->trx->rbs2000.tx.om2k_mo); + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TX, TRX_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(&otfp->trx->rbs2000.tx.om2k_mo); } static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2159,10 +2224,8 @@ static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *d struct om2k_trx_fsm_priv *otfp = fi->priv; /* Initialize RX after TX */ - osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_RX, - TRX_FSM_TIMEOUT, 0); - om2k_mo_fsm_start(fi, OM2K_TRX_EVT_RX_DONE, otfp->trx, - &otfp->trx->rbs2000.rx.om2k_mo); + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_RX, TRX_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(&otfp->trx->rbs2000.rx.om2k_mo); } static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2171,12 +2234,10 @@ static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *d struct gsm_bts_trx_ts *ts; /* Initialize Timeslots after TX */ - osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TS, - TRX_FSM_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TS, TRX_FSM_TIMEOUT, 0); otfp->cur_ts_nr = 0; ts = &otfp->trx->ts[otfp->cur_ts_nr]; - om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx, - &ts->rbs2000.om2k_mo); + om2k_mo_fsm_start(&ts->rbs2000.om2k_mo); } static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2184,16 +2245,11 @@ static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *d struct om2k_trx_fsm_priv *otfp = fi->priv; struct gsm_bts_trx_ts *ts; - /* notify TS is ready */ - ts = &otfp->trx->ts[otfp->cur_ts_nr]; - osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL); - /* next ? */ if (++otfp->cur_ts_nr < 8) { /* iterate to the next timeslot */ ts = &otfp->trx->ts[otfp->cur_ts_nr]; - om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx, - &ts->rbs2000.om2k_mo); + om2k_mo_fsm_start(&ts->rbs2000.om2k_mo); } else { /* only after all 8 TS */ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_SEND_SI, 0, 0); @@ -2212,55 +2268,117 @@ static void om2k_trx_s_send_si(struct osmo_fsm_inst *fi, uint32_t prev_state) static void om2k_trx_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + struct om2k_trx_fsm_priv *otfp = fi->priv; + struct nm_statechg_signal_data nsd; + struct nm_statechg_signal_data nsd_bb_transc; + struct gsm_bts_trx *trx = otfp->trx; + unsigned int i; + + memset(&nsd, 0, sizeof(nsd)); + + nsd.bts = trx->bts; + nsd.obj = trx; + nsd.old_state = trx->mo.nm_state; + nsd.new_state = trx->mo.nm_state; + nsd.om2k_mo = &trx->rbs2000.trxc.om2k_mo.addr; + + /* See e1_config:bts_isdn_sign_link() / OS#4914 */ + nsd.new_state.administrative = NM_STATE_UNLOCKED; + trx->mo.nm_state.administrative = nsd.new_state.administrative; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd); + + /* The OM2000 -> 12.21 mapping we do doesn't have separate bb_transc MO, + * for compatibility reasons we pretend to have it anyway and mirror the + * mo state of the TRXC on it. */ + memset(&nsd_bb_transc, 0, sizeof(nsd)); + nsd_bb_transc.bts = trx->bts; + nsd_bb_transc.obj = &trx->bb_transc; + nsd_bb_transc.old_state = trx->bb_transc.mo.nm_state; + nsd_bb_transc.new_state = trx->bb_transc.mo.nm_state; + nsd_bb_transc.om2k_mo = &trx->rbs2000.trxc.om2k_mo.addr; + nsd_bb_transc.new_state.administrative = NM_STATE_UNLOCKED; + trx->bb_transc.mo.nm_state.administrative = nsd_bb_transc.new_state.administrative; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd_bb_transc); + + abis_om2000_fsm_transc_becomes_enabled(trx); + + if (fi->proc.parent) + osmo_fsm_inst_dispatch(fi->proc.parent, otfp->done_event, NULL); + + /* Notify the timeslot FSM that all TRX initialization steps are done. */ + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) + osmo_fsm_inst_dispatch(trx->ts[i].fi, TS_EV_OML_READY, NULL); +} + +static void om2k_trx_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + + switch (event) { + case OM2K_TRX_EVT_RESET: + abis_om2000_fsm_transc_becomes_disabled(otfp->trx); + osmo_fsm_inst_broadcast_children(fi, event, data); + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_INIT, 0, 0); + break; + default: + OSMO_ASSERT(0); + } } static const struct osmo_fsm_state om2k_trx_states[] = { [OM2K_TRX_S_INIT] = { .in_event_mask = S(OM2K_TRX_EVT_START), - .out_state_mask = S(OM2K_TRX_S_WAIT_TRXC), + .out_state_mask = S(OM2K_TRX_S_WAIT_TRXC) | + S(OM2K_TRX_S_INIT), .name = "INIT", .action = om2k_trx_s_init, }, [OM2K_TRX_S_WAIT_TRXC] = { .in_event_mask = S(OM2K_TRX_EVT_TRXC_DONE), .out_state_mask = S(OM2K_TRX_S_ERROR) | - S(OM2K_TRX_S_WAIT_TX), + S(OM2K_TRX_S_WAIT_TX) | + S(OM2K_TRX_S_INIT), .name = "WAIT-TRXC", .action = om2k_trx_s_wait_trxc, }, [OM2K_TRX_S_WAIT_TX] = { .in_event_mask = S(OM2K_TRX_EVT_TX_DONE), .out_state_mask = S(OM2K_TRX_S_ERROR) | - S(OM2K_TRX_S_WAIT_RX), + S(OM2K_TRX_S_WAIT_RX) | + S(OM2K_TRX_S_INIT), .name = "WAIT-TX", .action = om2k_trx_s_wait_tx, }, [OM2K_TRX_S_WAIT_RX] = { .in_event_mask = S(OM2K_TRX_EVT_RX_DONE), .out_state_mask = S(OM2K_TRX_S_ERROR) | - S(OM2K_TRX_S_WAIT_TS), + S(OM2K_TRX_S_WAIT_TS) | + S(OM2K_TRX_S_INIT), .name = "WAIT-RX", .action = om2k_trx_s_wait_rx, }, [OM2K_TRX_S_WAIT_TS] = { .in_event_mask = S(OM2K_TRX_EVT_TS_DONE), .out_state_mask = S(OM2K_TRX_S_ERROR) | - S(OM2K_TRX_S_SEND_SI), + S(OM2K_TRX_S_SEND_SI) | + S(OM2K_TRX_S_INIT), .name = "WAIT-TS", .action = om2k_trx_s_wait_ts, }, [OM2K_TRX_S_SEND_SI] = { .out_state_mask = S(OM2K_TRX_S_ERROR) | - S(OM2K_TRX_S_DONE), + S(OM2K_TRX_S_DONE) | + S(OM2K_TRX_S_INIT), .name = "SEND-SI", .onenter = om2k_trx_s_send_si, }, [OM2K_TRX_S_DONE] = { + .out_state_mask = S(OM2K_TRX_S_INIT), .name = "DONE", .onenter = om2k_trx_s_done_onenter, }, [OM2K_TRX_S_ERROR] = { + .out_state_mask = S(OM2K_TRX_S_INIT), .name = "ERROR", }, }; @@ -2276,41 +2394,68 @@ static struct osmo_fsm om2k_trx_fsm = { .states = om2k_trx_states, .num_states = ARRAY_SIZE(om2k_trx_states), .log_subsys = DNM, + .allstate_event_mask = S(OM2K_TRX_EVT_RESET), + .allstate_action = om2k_trx_allstate, .event_names = om2k_trx_events, .timer_cb = om2k_trx_timer_cb, }; -struct osmo_fsm_inst *om2k_trx_fsm_start(struct osmo_fsm_inst *parent, - struct gsm_bts_trx *trx, - uint32_t term_event) +static struct osmo_fsm_inst *om2k_trx_fsm_alloc(struct osmo_fsm_inst *parent, + struct gsm_bts_trx *trx, uint32_t done_event) { struct osmo_fsm_inst *fi; struct om2k_trx_fsm_priv *otfp; char idbuf[32]; + OSMO_ASSERT(!trx->rbs2000.trx_fi); + snprintf(idbuf, sizeof(idbuf), "%u-%u", trx->bts->nr, trx->nr); - fi = osmo_fsm_inst_alloc_child_id(&om2k_trx_fsm, parent, term_event, - idbuf); + fi = osmo_fsm_inst_alloc_child_id(&om2k_trx_fsm, parent, OM2K_MO_EVT_CHILD_TERM, idbuf); if (!fi) return NULL; otfp = talloc_zero(fi, struct om2k_trx_fsm_priv); otfp->trx = trx; + otfp->done_event = done_event; fi->priv = otfp; - osmo_fsm_inst_dispatch(fi, OM2K_TRX_EVT_START, NULL); - return fi; } +void om2k_trx_fsm_start(struct gsm_bts_trx *trx) +{ + struct osmo_fsm_inst *bts_fi = trx->bts->rbs2000.bts_fi; + OSMO_ASSERT(trx->rbs2000.trx_fi); + + /* suppress if BTS is not yet brought up */ + if (bts_fi->state == OM2K_BTS_S_DONE || bts_fi->state == OM2K_BTS_S_WAIT_TRX) + return; + + osmo_fsm_inst_dispatch(trx->rbs2000.trx_fi, OM2K_TRX_EVT_START, NULL); +} + +void om2k_trx_fsm_reset(struct gsm_bts_trx *trx) +{ + struct osmo_fsm_inst *bts_fi = trx->bts->rbs2000.bts_fi; + OSMO_ASSERT(trx->rbs2000.trx_fi); + OSMO_ASSERT(trx->rbs2000.trx_fi); + + /* suppress if BTS is not yet brought up */ + if (bts_fi->state == OM2K_BTS_S_DONE || bts_fi->state == OM2K_BTS_S_WAIT_TRX) + return; + + osmo_fsm_inst_dispatch(trx->rbs2000.trx_fi, OM2K_TRX_EVT_RESET, NULL); +} /*********************************************************************** * OM2000 BTS Finite State Machine, initializes CF and all siblings ***********************************************************************/ enum om2k_bts_event { - OM2K_BTS_EVT_START, + OM2K_BTS_EVT_RESET = OM2K_MO_EVT_RESET, + OM2K_BTS_EVT_START = OM2K_MO_EVT_START, + OM2K_BTS_EVT_CHILD_TERM = OM2K_MO_EVT_CHILD_TERM, OM2K_BTS_EVT_CF_DONE, OM2K_BTS_EVT_IS_DONE, OM2K_BTS_EVT_CON_DONE, @@ -2318,11 +2463,14 @@ enum om2k_bts_event { OM2K_BTS_EVT_MCTR_DONE, OM2K_BTS_EVT_TRX_LAPD_UP, OM2K_BTS_EVT_TRX_DONE, + OM2K_BTS_EVT_TRX_TERM, OM2K_BTS_EVT_STOP, }; static const struct value_string om2k_bts_events[] = { + { OM2K_BTS_EVT_RESET, "RESET" }, { OM2K_BTS_EVT_START, "START" }, + { OM2K_BTS_EVT_CHILD_TERM, "CHILD-TERM" }, { OM2K_BTS_EVT_CF_DONE, "CF-DONE" }, { OM2K_BTS_EVT_IS_DONE, "IS-DONE" }, { OM2K_BTS_EVT_CON_DONE, "CON-DONE" }, @@ -2334,19 +2482,6 @@ static const struct value_string om2k_bts_events[] = { { 0, NULL } }; -enum om2k_bts_state { - OM2K_BTS_S_INIT, - OM2K_BTS_S_WAIT_CF, - OM2K_BTS_S_WAIT_IS, - OM2K_BTS_S_WAIT_CON, - OM2K_BTS_S_WAIT_TF, - OM2K_BTS_S_WAIT_MCTR, - OM2K_BTS_S_WAIT_TRX_LAPD, - OM2K_BTS_S_WAIT_TRX, - OM2K_BTS_S_DONE, - OM2K_BTS_S_ERROR, -}; - struct om2k_bts_fsm_priv { struct gsm_bts *bts; uint8_t next_trx_nr; @@ -2358,10 +2493,8 @@ static void om2k_bts_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data struct gsm_bts *bts = obfp->bts; OSMO_ASSERT(event == OM2K_BTS_EVT_START); - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CF, - BTS_FSM_TIMEOUT, 0); - om2k_mo_fsm_start(fi, OM2K_BTS_EVT_CF_DONE, bts->c0, - &bts->rbs2000.cf.om2k_mo); + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CF, BTS_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(&bts->rbs2000.cf.om2k_mo); } static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2372,8 +2505,7 @@ static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *d OSMO_ASSERT(event == OM2K_BTS_EVT_CF_DONE); /* TF can take a long time to initialize, wait for 10min */ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TF, 600, 0); - om2k_mo_fsm_start(fi, OM2K_BTS_EVT_TF_DONE, bts->c0, - &bts->rbs2000.tf.om2k_mo); + om2k_mo_fsm_start(&bts->rbs2000.tf.om2k_mo); } static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2383,10 +2515,14 @@ static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *d OSMO_ASSERT(event == OM2K_BTS_EVT_TF_DONE); - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CON, - BTS_FSM_TIMEOUT, 0); - om2k_mo_fsm_start(fi, OM2K_BTS_EVT_CON_DONE, bts->c0, - &bts->rbs2000.con.om2k_mo); + if (!llist_count(&bts->rbs2000.con.conn_groups)) { + /* skip CON object if we have no configuration for it */ + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_IS, BTS_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(&bts->rbs2000.is.om2k_mo); + } else { + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CON, BTS_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(&bts->rbs2000.con.om2k_mo); + } } static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2396,10 +2532,8 @@ static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void * OSMO_ASSERT(event == OM2K_BTS_EVT_CON_DONE); - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_IS, - BTS_FSM_TIMEOUT, 0); - om2k_mo_fsm_start(fi, OM2K_BTS_EVT_IS_DONE, bts->c0, - &bts->rbs2000.is.om2k_mo); + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_IS, BTS_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(&bts->rbs2000.is.om2k_mo); } static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2411,13 +2545,10 @@ static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *d /* If we're running OML >= G12R13, start MCTR, else skip directly to TRX */ if (bts->rbs2000.om2k_version[0].active >= 0x0c0d) { - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_MCTR, - BTS_FSM_TIMEOUT, 0); - om2k_mo_fsm_start(fi, OM2K_BTS_EVT_MCTR_DONE, bts->c0, - &bts->rbs2000.mctr.om2k_mo); + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_MCTR, BTS_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(&bts->rbs2000.mctr.om2k_mo); } else { - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX_LAPD, - TRX_LAPD_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX_LAPD, TRX_LAPD_TIMEOUT, 0); } } @@ -2425,8 +2556,7 @@ static void om2k_bts_s_wait_mctr(struct osmo_fsm_inst *fi, uint32_t event, void { OSMO_ASSERT(event == OM2K_BTS_EVT_MCTR_DONE); - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX_LAPD, - TRX_LAPD_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX_LAPD, TRX_LAPD_TIMEOUT, 0); } static void om2k_bts_s_wait_trx_lapd(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2436,11 +2566,10 @@ static void om2k_bts_s_wait_trx_lapd(struct osmo_fsm_inst *fi, uint32_t event, v OSMO_ASSERT(event == OM2K_BTS_EVT_TRX_LAPD_UP); - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX, - BTS_FSM_TIMEOUT, 0); + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX, BTS_FSM_TIMEOUT, 0); obfp->next_trx_nr = 0; trx = gsm_bts_trx_num(obfp->bts, obfp->next_trx_nr++); - om2k_trx_fsm_start(fi, trx, OM2K_BTS_EVT_TRX_DONE); + om2k_trx_fsm_start(trx); } static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -2452,7 +2581,7 @@ static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void * if (obfp->next_trx_nr < obfp->bts->num_trx) { struct gsm_bts_trx *trx; trx = gsm_bts_trx_num(obfp->bts, obfp->next_trx_nr++); - om2k_trx_fsm_start(fi, trx, OM2K_BTS_EVT_TRX_DONE); + om2k_trx_fsm_start(trx); } else { osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_DONE, 0, 0); } @@ -2460,34 +2589,50 @@ static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void * static void om2k_bts_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +static void om2k_bts_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case OM2K_BTS_EVT_RESET: + osmo_fsm_inst_broadcast_children(fi, event, data); + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_INIT, 0, 0); + break; + default: + OSMO_ASSERT(0); + } } static const struct osmo_fsm_state om2k_bts_states[] = { [OM2K_BTS_S_INIT] = { .in_event_mask = S(OM2K_BTS_EVT_START), - .out_state_mask = S(OM2K_BTS_S_WAIT_CF), + .out_state_mask = S(OM2K_BTS_S_WAIT_CF) | + S(OM2K_BTS_S_INIT), .name = "INIT", .action = om2k_bts_s_init, }, [OM2K_BTS_S_WAIT_CF] = { .in_event_mask = S(OM2K_BTS_EVT_CF_DONE), .out_state_mask = S(OM2K_BTS_S_ERROR) | - S(OM2K_BTS_S_WAIT_TF), + S(OM2K_BTS_S_WAIT_TF) | + S(OM2K_BTS_S_INIT), .name = "WAIT-CF", .action = om2k_bts_s_wait_cf, }, [OM2K_BTS_S_WAIT_TF] = { .in_event_mask = S(OM2K_BTS_EVT_TF_DONE), .out_state_mask = S(OM2K_BTS_S_ERROR) | - S(OM2K_BTS_S_WAIT_CON), + S(OM2K_BTS_S_WAIT_CON) | + S(OM2K_BTS_S_WAIT_IS) | + S(OM2K_BTS_S_INIT), .name = "WAIT-TF", .action = om2k_bts_s_wait_tf, }, [OM2K_BTS_S_WAIT_CON] = { .in_event_mask = S(OM2K_BTS_EVT_CON_DONE), .out_state_mask = S(OM2K_BTS_S_ERROR) | - S(OM2K_BTS_S_WAIT_IS), + S(OM2K_BTS_S_WAIT_IS) | + S(OM2K_BTS_S_INIT), .name = "WAIT-CON", .action = om2k_bts_s_wait_con, }, @@ -2495,35 +2640,41 @@ static const struct osmo_fsm_state om2k_bts_states[] = { .in_event_mask = S(OM2K_BTS_EVT_IS_DONE), .out_state_mask = S(OM2K_BTS_S_ERROR) | S(OM2K_BTS_S_WAIT_MCTR) | - S(OM2K_BTS_S_WAIT_TRX_LAPD), + S(OM2K_BTS_S_WAIT_TRX_LAPD) | + S(OM2K_BTS_S_INIT), .name = "WAIT-IS", .action = om2k_bts_s_wait_is, }, [OM2K_BTS_S_WAIT_MCTR] = { .in_event_mask = S(OM2K_BTS_EVT_MCTR_DONE), .out_state_mask = S(OM2K_BTS_S_ERROR) | - S(OM2K_BTS_S_WAIT_TRX_LAPD), + S(OM2K_BTS_S_WAIT_TRX_LAPD) | + S(OM2K_BTS_S_INIT), .name = "WAIT-MCTR", .action = om2k_bts_s_wait_mctr, }, [OM2K_BTS_S_WAIT_TRX_LAPD] = { .in_event_mask = S(OM2K_BTS_EVT_TRX_LAPD_UP), - .out_state_mask = S(OM2K_BTS_S_WAIT_TRX), + .out_state_mask = S(OM2K_BTS_S_WAIT_TRX) | + S(OM2K_BTS_S_INIT), .name = "WAIT-TRX-LAPD", .action = om2k_bts_s_wait_trx_lapd, }, [OM2K_BTS_S_WAIT_TRX] = { .in_event_mask = S(OM2K_BTS_EVT_TRX_DONE), .out_state_mask = S(OM2K_BTS_S_ERROR) | - S(OM2K_BTS_S_DONE), + S(OM2K_BTS_S_DONE) | + S(OM2K_BTS_S_INIT), .name = "WAIT-TRX", .action = om2k_bts_s_wait_trx, }, [OM2K_BTS_S_DONE] = { + .out_state_mask = S(OM2K_BTS_S_INIT), .name = "DONE", .onenter = om2k_bts_s_done_onenter, }, [OM2K_BTS_S_ERROR] = { + .out_state_mask = S(OM2K_BTS_S_INIT), .name = "ERROR", }, }; @@ -2546,31 +2697,44 @@ static struct osmo_fsm om2k_bts_fsm = { .states = om2k_bts_states, .num_states = ARRAY_SIZE(om2k_bts_states), .log_subsys = DNM, + .allstate_event_mask = S(OM2K_BTS_EVT_RESET), + .allstate_action = om2k_bts_allstate, .event_names = om2k_bts_events, .timer_cb = om2k_bts_timer_cb, }; -struct osmo_fsm_inst * -om2k_bts_fsm_start(struct gsm_bts *bts) +static struct osmo_fsm_inst * +om2k_bts_fsm_alloc(struct gsm_bts *bts) { struct osmo_fsm_inst *fi; struct om2k_bts_fsm_priv *obfp; char idbuf[16]; + OSMO_ASSERT(!bts->rbs2000.bts_fi); + snprintf(idbuf, sizeof(idbuf), "%u", bts->nr); - fi = osmo_fsm_inst_alloc(&om2k_bts_fsm, bts, NULL, - LOGL_DEBUG, idbuf); + fi = osmo_fsm_inst_alloc(&om2k_bts_fsm, bts, NULL, LOGL_DEBUG, idbuf); if (!fi) return NULL; + fi->priv = obfp = talloc_zero(fi, struct om2k_bts_fsm_priv); obfp->bts = bts; - osmo_fsm_inst_dispatch(fi, OM2K_BTS_EVT_START, NULL); - return fi; } +void om2k_bts_fsm_start(struct gsm_bts *bts) +{ + OSMO_ASSERT(bts->rbs2000.bts_fi); + osmo_fsm_inst_dispatch(bts->rbs2000.bts_fi, OM2K_BTS_EVT_START, NULL); +} + +void om2k_bts_fsm_reset(struct gsm_bts *bts) +{ + OSMO_ASSERT(bts->rbs2000.bts_fi); + osmo_fsm_inst_dispatch(bts->rbs2000.bts_fi, OM2K_BTS_EVT_RESET, NULL); +} /*********************************************************************** * OM2000 Negotiation @@ -2587,7 +2751,7 @@ static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2 msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data); - DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(mo), get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK)); return abis_om2k_sendmsg(bts, msg); @@ -2709,18 +2873,16 @@ static int om2k_rx_nack(struct msgb *msg) uint16_t msg_type = ntohs(o2h->msg_type); struct tlv_parsed tp; - LOGP(DNM, LOGL_ERROR, "Rx MO=%s %s", om2k_mo_name(&o2h->mo), + LOGP(DNM, LOGL_ERROR, "Rx MO=%s %s", abis_om2k_mo_name(&o2h->mo), get_value_string(om2k_msgcode_vals, msg_type)); abis_om2k_msg_tlv_parse(&tp, o2h); if (TLVP_PRESENT(&tp, OM2K_DEI_REASON_CODE)) - LOGPC(DNM, LOGL_ERROR, ", Reason 0x%02x", - *TLVP_VAL(&tp, OM2K_DEI_REASON_CODE)); + LOGPC(DNM, LOGL_ERROR, ", Reason 0x%02x", *TLVP_VAL(&tp, OM2K_DEI_REASON_CODE)); if (TLVP_PRESENT(&tp, OM2K_DEI_RESULT_CODE)) LOGPC(DNM, LOGL_ERROR, ", Result %s", - get_value_string(om2k_result_strings, - *TLVP_VAL(&tp, OM2K_DEI_RESULT_CODE))); + get_value_string(om2k_result_strings, *TLVP_VAL(&tp, OM2K_DEI_RESULT_CODE))); LOGPC(DNM, LOGL_ERROR, "\n"); return 0; @@ -2734,8 +2896,7 @@ static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm) return -EIO; mo_state = *TLVP_VAL(&odm->tp, OM2K_DEI_MO_STATE); - LOGP(DNM, LOGL_DEBUG, "Rx MO=%s %s, MO State: %s\n", - om2k_mo_name(&odm->o2h.mo), + LOGP(DNM, LOGL_DEBUG, "Rx MO=%s %s, MO State: %s\n", abis_om2k_mo_name(&odm->o2h.mo), get_value_string(om2k_msgcode_vals, odm->msg_type), get_value_string(om2k_mostate_vals, mo_state)); @@ -2743,10 +2904,8 @@ static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm) * not yield an enabled mo-state */ if (odm->msg_type == OM2K_MSGT_ENABLE_RES && mo_state != OM2K_MO_S_ENABLED) { - LOGP(DNM, LOGL_ERROR, - "Rx MO=%s %s Failed to enable MO State!\n", - om2k_mo_name(&odm->o2h.mo), - get_value_string(om2k_msgcode_vals, odm->msg_type)); + LOGP(DNM, LOGL_ERROR, "Rx MO=%s %s Failed to enable MO State!\n", + abis_om2k_mo_name(&odm->o2h.mo), get_value_string(om2k_msgcode_vals, odm->msg_type)); } update_mo_state(bts, &odm->o2h.mo, mo_state); @@ -2786,7 +2945,7 @@ static bool display_fault_bits(const uint8_t *vect, uint16_t len, } sprintf(string + strlen(string), ")\n"); - DEBUGP(DNM, "Rx MO=%s %s", om2k_mo_name(mo), string); + DEBUGP(DNM, "Rx MO=%s %s", abis_om2k_mo_name(mo), string); return true; } @@ -2816,8 +2975,7 @@ static void display_fault_maps(const uint8_t *src, unsigned int src_len, src++; src_len--; if (msg_code != OM2K_MSGT_FAULT_REP) { - LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault report: invalid message code!\n", - om2k_mo_name(mo)); + LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault report: invalid message code!\n", abis_om2k_mo_name(mo)); return; } @@ -2830,22 +2988,19 @@ static void display_fault_maps(const uint8_t *src, unsigned int src_len, /* Bail if an the maximum number of TLV fields * have been parsed */ - if (tlv_count >= 11) { - LOGP(DNM, LOGL_ERROR, - "Rx MO=%s Fault Report: too many tlv elements!\n", - om2k_mo_name(mo)); + if (tlv_count >= 20) { + LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault Report: too many tlv elements!\n", + abis_om2k_mo_name(mo)); return; } /* Parse TLV field */ - rc = tlv_parse_one(&tag, &tag_len, &val, &om2k_att_tlvdef, - src + src_pos, src_len - src_pos); + rc = tlv_parse_one(&tag, &tag_len, &val, &om2k_att_tlvdef, src + src_pos, src_len - src_pos); if (rc > 0) src_pos += rc; else { - LOGP(DNM, LOGL_ERROR, - "Rx MO=%s Fault Report: invalid tlv element!\n", - om2k_mo_name(mo)); + LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault Report: invalid tlv element!\n", + abis_om2k_mo_name(mo)); return; } @@ -2872,8 +3027,7 @@ static void display_fault_maps(const uint8_t *src, unsigned int src_len, } if (!faults_present) { - DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n", - om2k_mo_name(mo)); + DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n", abis_om2k_mo_name(mo)); } } @@ -2890,28 +3044,24 @@ int abis_om2k_rcvmsg(struct msgb *msg) /* Various consistency checks */ if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { - LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", - oh->placement); + LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", oh->placement); if (oh->placement != ABIS_OM_PLACEMENT_FIRST) return -EINVAL; } if (oh->sequence != 0) { - LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", - oh->sequence); + LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", oh->sequence); return -EINVAL; } msg->l3h = (unsigned char *)o2h + sizeof(*o2h); if (oh->mdisc != ABIS_OM_MDISC_FOM) { - LOGP(DNM, LOGL_ERROR, "unknown ABIS OM2000 message discriminator 0x%x\n", - oh->mdisc); + LOGP(DNM, LOGL_ERROR, "unknown ABIS OM2000 message discriminator 0x%x\n", oh->mdisc); return -EINVAL; } - DEBUGP(DNM, "Rx MO=%s %s (%s)\n", om2k_mo_name(&o2h->mo), - get_value_string(om2k_msgcode_vals, msg_type), - osmo_hexdump(msg->l2h, msgb_l2len(msg))); + DEBUGP(DNM, "Rx MO=%s %s (%s)\n", abis_om2k_mo_name(&o2h->mo), + get_value_string(om2k_msgcode_vals, msg_type), osmo_hexdump(msg->l2h, msgb_l2len(msg))); om2k_decode_msg(&odm, msg); @@ -2920,11 +3070,13 @@ int abis_om2k_rcvmsg(struct msgb *msg) switch (msg_type) { case OM2K_MSGT_CAL_TIME_REQ: rc = abis_om2k_cal_time_resp(bts); - break; + /* we receive this from MOs without FSM (https://osmocom.org/issues/4670) */ + goto no_mo; case OM2K_MSGT_FAULT_REP: display_fault_maps(msg->l2h, msgb_l2len(msg), &o2h->mo); rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_FAULT_REP_ACK); - break; + /* we receive this from MOs without FSM (https://osmocom.org/issues/4643) */ + goto no_mo; case OM2K_MSGT_NEGOT_REQ: rc = om2k_rx_negot_req(msg); break; @@ -2996,26 +3148,24 @@ int abis_om2k_rcvmsg(struct msgb *msg) mo = get_om2k_mo(bts, &o2h->mo); if (!mo) { LOGP(DNM, LOGL_ERROR, "Couldn't resolve MO for OM2K msg " - "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type), - msgb_hexdump(msg)); - return 0; + "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type), msgb_hexdump(msg)); + goto no_mo; } if (!mo->fsm) { LOGP(DNM, LOGL_ERROR, "MO object should not generate any message. fsm == NULL " - "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type), - msgb_hexdump(msg)); - return 0; + "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type), msgb_hexdump(msg)); + goto no_mo; } /* Dispatch message to that MO */ om2k_mo_fsm_recvmsg(bts, mo, &odm); +no_mo: msgb_free(msg); return rc; } -static void om2k_mo_init(struct om2k_mo *mo, uint8_t class, - uint8_t bts_nr, uint8_t assoc_so, uint8_t inst) +static void om2k_mo_init(struct om2k_mo *mo, uint8_t class, uint8_t bts_nr, uint8_t assoc_so, uint8_t inst) { mo->addr.class = class; mo->addr.bts = bts_nr; @@ -3027,21 +3177,28 @@ static void om2k_mo_init(struct om2k_mo *mo, uint8_t class, void abis_om2k_trx_init(struct gsm_bts_trx *trx) { struct gsm_bts *bts = trx->bts; + struct osmo_fsm_inst *trx_fi; unsigned int i; OSMO_ASSERT(bts->type == GSM_BTS_TYPE_RBS2000); - om2k_mo_init(&trx->rbs2000.trxc.om2k_mo, OM2K_MO_CLS_TRXC, - bts->nr, 255, trx->nr); - om2k_mo_init(&trx->rbs2000.tx.om2k_mo, OM2K_MO_CLS_TX, - bts->nr, 255, trx->nr); - om2k_mo_init(&trx->rbs2000.rx.om2k_mo, OM2K_MO_CLS_RX, - bts->nr, 255, trx->nr); + trx_fi = om2k_trx_fsm_alloc(trx->bts->rbs2000.bts_fi, trx, OM2K_BTS_EVT_TRX_DONE); + trx->rbs2000.trx_fi = trx_fi; + trx->rbs2000.rx_diversity = OM2K_RX_DIVERSITY_A; + + om2k_mo_init(&trx->rbs2000.trxc.om2k_mo, OM2K_MO_CLS_TRXC, bts->nr, 255, trx->nr); + om2k_mo_fsm_alloc(trx_fi, OM2K_TRX_EVT_TRXC_DONE, trx, &trx->rbs2000.trxc.om2k_mo); + + om2k_mo_init(&trx->rbs2000.tx.om2k_mo, OM2K_MO_CLS_TX, bts->nr, 255, trx->nr); + om2k_mo_fsm_alloc(trx_fi, OM2K_TRX_EVT_TX_DONE, trx, &trx->rbs2000.tx.om2k_mo); + + om2k_mo_init(&trx->rbs2000.rx.om2k_mo, OM2K_MO_CLS_RX, bts->nr, 255, trx->nr); + om2k_mo_fsm_alloc(trx_fi, OM2K_TRX_EVT_RX_DONE, trx, &trx->rbs2000.rx.om2k_mo); for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { struct gsm_bts_trx_ts *ts = &trx->ts[i]; - om2k_mo_init(&ts->rbs2000.om2k_mo, OM2K_MO_CLS_TS, - bts->nr, trx->nr, i); + om2k_mo_init(&ts->rbs2000.om2k_mo, OM2K_MO_CLS_TS, bts->nr, trx->nr, i); + om2k_mo_fsm_alloc(trx_fi, OM2K_TRX_EVT_TS_DONE, trx, &ts->rbs2000.om2k_mo); OSMO_ASSERT(ts->fi); } } @@ -3049,20 +3206,31 @@ void abis_om2k_trx_init(struct gsm_bts_trx *trx) /* initialize the OM2K_MO members of gsm_bts */ void abis_om2k_bts_init(struct gsm_bts *bts) { + struct osmo_fsm_inst *bts_fi; + OSMO_ASSERT(bts->type == GSM_BTS_TYPE_RBS2000); - om2k_mo_init(&bts->rbs2000.cf.om2k_mo, OM2K_MO_CLS_CF, - bts->nr, 0xFF, 0); - om2k_mo_init(&bts->rbs2000.is.om2k_mo, OM2K_MO_CLS_IS, - bts->nr, 0xFF, 0); - om2k_mo_init(&bts->rbs2000.con.om2k_mo, OM2K_MO_CLS_CON, - bts->nr, 0xFF, 0); - om2k_mo_init(&bts->rbs2000.dp.om2k_mo, OM2K_MO_CLS_DP, - bts->nr, 0xFF, 0); - om2k_mo_init(&bts->rbs2000.tf.om2k_mo, OM2K_MO_CLS_TF, - bts->nr, 0xFF, 0); - om2k_mo_init(&bts->rbs2000.mctr.om2k_mo, OM2K_MO_CLS_MCTR, - bts->nr, 0xFF, 0); // FIXME: There can be multiple MCTRs ... + bts_fi = om2k_bts_fsm_alloc(bts); + bts->rbs2000.bts_fi = bts_fi; + bts->rbs2000.sync_src = OM2K_SYNC_SRC_INTERNAL; + + om2k_mo_init(&bts->rbs2000.cf.om2k_mo, OM2K_MO_CLS_CF, bts->nr, 0xFF, 0); + om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_CF_DONE, bts->c0, &bts->rbs2000.cf.om2k_mo); + + om2k_mo_init(&bts->rbs2000.is.om2k_mo, OM2K_MO_CLS_IS, bts->nr, 0xFF, 0); + om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_IS_DONE, bts->c0, &bts->rbs2000.is.om2k_mo); + + om2k_mo_init(&bts->rbs2000.con.om2k_mo, OM2K_MO_CLS_CON, bts->nr, 0xFF, 0); + om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_CON_DONE, bts->c0, &bts->rbs2000.con.om2k_mo); + + om2k_mo_init(&bts->rbs2000.dp.om2k_mo, OM2K_MO_CLS_DP, bts->nr, 0xFF, 0); + + om2k_mo_init(&bts->rbs2000.tf.om2k_mo, OM2K_MO_CLS_TF, bts->nr, 0xFF, 0); + om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_TF_DONE, bts->c0, &bts->rbs2000.tf.om2k_mo); + + om2k_mo_init(&bts->rbs2000.mctr.om2k_mo, OM2K_MO_CLS_MCTR, bts->nr, 0xFF, 0); + om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_MCTR_DONE, bts->c0, &bts->rbs2000.mctr.om2k_mo); + // FIXME: There can be multiple MCTRs ... } static __attribute__((constructor)) void abis_om2k_init(void) diff --git a/src/osmo-bsc/abis_om2000_vty.c b/src/osmo-bsc/abis_om2000_vty.c index 206fd6b9e..76048071a 100644 --- a/src/osmo-bsc/abis_om2000_vty.c +++ b/src/osmo-bsc/abis_om2000_vty.c @@ -63,11 +63,6 @@ struct oml_node_state { struct con_group *cg; }; -static int dummy_config_write(struct vty *v) -{ - return CMD_SUCCESS; -} - /* FIXME: auto-generate those strings from the value_string lists */ #define OM2K_OBJCLASS_VTY "(trxc|tg|ts|tf|is|con|dp|mctr|cf|tx|rx)" #define OM2K_OBJCLASS_VTY_HELP "TRX Controller\n" \ @@ -81,6 +76,7 @@ static int dummy_config_write(struct vty *v) "Central Function\n" \ "Transmitter\n" \ "Receiver\n" +#define OM2K_VTY_HELP "Configure OM2K specific parameters\n" DEFUN(om2k_class_inst, om2k_class_inst_cmd, "bts <0-255> om2000 class " OM2K_OBJCLASS_VTY @@ -475,10 +471,31 @@ DEFUN_USRATTR(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd, return CMD_SUCCESS; } +DEFUN_USRATTR(cfg_bts_om2k_sync, cfg_bts_om2k_sync_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "om2000 sync-source (internal|external)", + OM2K_VTY_HELP + "TF Synchronization Source\n" + "Use Internal (E1)\n" + "USe External (GPS)\n") +{ + struct gsm_bts *bts = vty->index; + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% Command only works for RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + if (!strcmp(argv[0], "internal")) + bts->rbs2000.sync_src = OM2K_SYNC_SRC_INTERNAL; + else if (!strcmp(argv[0], "external")) + bts->rbs2000.sync_src = OM2K_SYNC_SRC_EXTERNAL; + return CMD_SUCCESS; +} + DEFUN_USRATTR(cfg_bts_om2k_version_limit, cfg_bts_om2k_version_limit_cmd, X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), "om2000 version-limit (oml|rsl) gen <0-99> rev <0-99>", - "Configure OM2K specific parameters\n" + OM2K_VTY_HELP "Configure optional maximum protocol version to negotiate\n" "Limit OML IWD version\n" "Limit RSL IWD version\n" "Generation limit\n" @@ -552,6 +569,33 @@ DEFUN_USRATTR(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd, return CMD_SUCCESS; } +DEFUN_USRATTR(cfg_trx_om2k_rx_diversity, + cfg_trx_om2k_rx_diversity_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "om2000 rx-diversity-mode (a|ab|b)", + OM2K_VTY_HELP + "RX Diversity\n" + "Antenna TX/RX (A)\n" + "Both Antennas\n" + "Antenna RX (B)\n") + +{ + struct gsm_bts_trx *trx = vty->index; + + if (trx->bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% Command only works for RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "a")) + trx->rbs2000.rx_diversity = OM2K_RX_DIVERSITY_A; + else if (!strcmp(argv[0], "ab")) + trx->rbs2000.rx_diversity = OM2K_RX_DIVERSITY_AB; + else if (!strcmp(argv[0], "b")) + trx->rbs2000.rx_diversity = OM2K_RX_DIVERSITY_B; + return CMD_SUCCESS; +} DEFUN(om2k_conf_req, om2k_conf_req_cmd, "configuration-request", @@ -631,6 +675,24 @@ static void dump_con_group(struct vty *vty, struct con_group *cg) } } +static const struct value_string om2k_rx_diversity_names[4] = { + { OM2K_RX_DIVERSITY_A, "a" }, + { OM2K_RX_DIVERSITY_AB, "ab" }, + { OM2K_RX_DIVERSITY_B, "b" }, + { 0, NULL } +}; + +static const char *rx_diversity2str(enum om2k_rx_diversity type) +{ + return get_value_string(om2k_rx_diversity_names, type); +} + +void abis_om2k_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + vty_out(vty, " om2000 rx-diversity-mode %s%s", + rx_diversity2str(trx->rbs2000.rx_diversity), VTY_NEWLINE); +} + void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts) { struct is_conn_group *igrp; @@ -656,10 +718,86 @@ void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts) (bts->rbs2000.om2k_version[i].limit >> 8), (bts->rbs2000.om2k_version[i].limit & 0xff), VTY_NEWLINE); + vty_out(vty, " om2000 sync-source %s%s", + bts->rbs2000.sync_src != OM2K_SYNC_SRC_EXTERNAL + ? "internal" : "external", + VTY_NEWLINE); +} + +static void vty_dump_om2k_mo(struct vty *vty, const struct om2k_mo *mo, const char *pfx) +{ + unsigned int pfx_len = strlen(pfx); + const char *mo_name = abis_om2k_mo_name(&mo->addr); + unsigned int pfx_mo_len = pfx_len + strlen(mo_name); + unsigned int pfx2_len; + char pfx2[23]; + int i; + + /* generate padding after MO class to align the state names in the same column */ + if (pfx_mo_len > sizeof(pfx2)-1) + pfx2_len = 0; + else + pfx2_len = sizeof(pfx2)-1 - pfx_mo_len; + for (i = 0; i < pfx2_len; i++) + pfx2[i] = ' '; + pfx2[pfx2_len] = '\0'; + + vty_out(vty, "%s%s%s %s%s", pfx, mo_name, pfx2, + mo->fsm ? osmo_fsm_inst_state_name(mo->fsm) : "[NULL]", + VTY_NEWLINE); +} + +DEFUN(show_om2k_mo, show_om2k_mo_cmd, + "show bts <0-255> om2k-mo", + SHOW_STR "Display information about a BTS\n" + "BTS number\n" "OM2000 Managed Object information\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int bts_nr = atoi(argv[0]); + struct gsm_bts *bts = gsm_bts_num(net, bts_nr); + struct gsm_bts_trx *trx; + + if (!bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% BTS is not using OM2000%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "BTS %3u OM2K-FSM state %s%s", bts->nr, + osmo_fsm_inst_state_name(bts->rbs2000.bts_fi), VTY_NEWLINE); + vty_dump_om2k_mo(vty, &bts->rbs2000.cf.om2k_mo, " "); + vty_dump_om2k_mo(vty, &bts->rbs2000.con.om2k_mo, " "); + vty_dump_om2k_mo(vty, &bts->rbs2000.is.om2k_mo, " "); + vty_dump_om2k_mo(vty, &bts->rbs2000.dp.om2k_mo, " "); + vty_dump_om2k_mo(vty, &bts->rbs2000.tf.om2k_mo, " "); + vty_dump_om2k_mo(vty, &bts->rbs2000.mctr.om2k_mo, " "); + + + llist_for_each_entry(trx, &bts->trx_list, list) { + int tn; + + vty_out(vty, " TRX %u OM2K-FSM state %s%s", trx->nr, + osmo_fsm_inst_state_name(trx->rbs2000.trx_fi), VTY_NEWLINE); + vty_dump_om2k_mo(vty, &trx->rbs2000.trxc.om2k_mo, " "); + vty_dump_om2k_mo(vty, &trx->rbs2000.rx.om2k_mo, " "); + vty_dump_om2k_mo(vty, &trx->rbs2000.tx.om2k_mo, " "); + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + vty_dump_om2k_mo(vty, &ts->rbs2000.om2k_mo, " "); + } + } + + return CMD_SUCCESS; } int abis_om2k_vty_init(void) { + install_element_ve(&show_om2k_mo_cmd); install_element(ENABLE_NODE, &om2k_class_inst_cmd); install_element(ENABLE_NODE, &om2k_classnum_inst_cmd); install_node(&om2k_node, dummy_config_write); @@ -683,9 +821,12 @@ int abis_om2k_vty_init(void) install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd); install_element(BTS_NODE, &cfg_bts_alt_mode_cmd); + install_element(BTS_NODE, &cfg_bts_om2k_sync_cmd); install_element(BTS_NODE, &cfg_bts_om2k_version_limit_cmd); install_element(BTS_NODE, &cfg_om2k_con_group_cmd); install_element(BTS_NODE, &del_om2k_con_group_cmd); + install_element(TRX_NODE, &cfg_trx_om2k_rx_diversity_cmd); + return 0; } diff --git a/src/osmo-bsc/abis_osmo.c b/src/osmo-bsc/abis_osmo.c new file mode 100644 index 000000000..48774b4e8 --- /dev/null +++ b/src/osmo-bsc/abis_osmo.c @@ -0,0 +1,226 @@ +/* Osmocom specific protocols over Abis (IPA) */ + +/* (C) 2021 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <osmocom/core/logging.h> + +#include <osmocom/core/msgb.h> + +#include <osmocom/gsm/protocol/ipaccess.h> +#include <osmocom/gsm/ipa.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmocom/bsc/abis_osmo.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/pcuif_proto.h> +#include <osmocom/bsc/neighbor_ident.h> + +#define OM_HEADROOM_SIZE 128 + +//////////////////////////////////////// +// OSMO ABIS extensions (PCU) +/////////////////////////////////////// +#define PCUIF_HDR_SIZE ( sizeof(struct gsm_pcu_if) - sizeof(((struct gsm_pcu_if *)0)->u) ) + +static struct msgb *abis_osmo_pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr, size_t extra_size) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + msg = msgb_alloc_headroom(OM_HEADROOM_SIZE + sizeof(struct gsm_pcu_if) + extra_size, + OM_HEADROOM_SIZE, "IPA/ABIS/OSMO"); + /* Only header is filled, caller is responible for reserving + filling + * message type specific contents: */ + msgb_put(msg, PCUIF_HDR_SIZE); + pcu_prim = (struct gsm_pcu_if *) msgb_data(msg); + pcu_prim->msg_type = msg_type; + pcu_prim->bts_nr = bts_nr; + return msg; +} + +/* Send a OML NM Message from BSC to BTS */ +static int abis_osmo_pcu_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_PCU); + return abis_osmo_sendmsg(bts, msg); +} + +static int abis_osmo_pcu_tx_neigh_addr_cnf(struct gsm_bts *bts, const struct gsm_pcu_if_neigh_addr_req *naddr_req, + uint8_t err_code, const struct osmo_cell_global_id_ps *cgi_ps) +{ + struct msgb *msg = abis_osmo_pcu_msgb_alloc(PCU_IF_MSG_CONTAINER, bts->bts_nr, sizeof(struct gsm_pcu_if_neigh_addr_cnf)); + struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msgb_data(msg); + struct gsm_pcu_if_neigh_addr_cnf *naddr_cnf = (struct gsm_pcu_if_neigh_addr_cnf *)&pcu_prim->u.container.data[0]; + + msgb_put(msg, sizeof(pcu_prim->u.container) + sizeof(struct gsm_pcu_if_neigh_addr_cnf)); + pcu_prim->u.container.msg_type = PCU_IF_MSG_NEIGH_ADDR_CNF; + osmo_store16be(sizeof(struct gsm_pcu_if_neigh_addr_cnf), &pcu_prim->u.container.length); + + naddr_cnf->orig_req = *naddr_req; + naddr_cnf->err_code = err_code; + if (err_code == 0) { + osmo_store16be(cgi_ps->rai.lac.plmn.mcc, &naddr_cnf->cgi_ps.mcc); + osmo_store16be(cgi_ps->rai.lac.plmn.mnc, &naddr_cnf->cgi_ps.mnc); + naddr_cnf->cgi_ps.mnc_3_digits = cgi_ps->rai.lac.plmn.mnc_3_digits; + osmo_store16be(cgi_ps->rai.lac.lac, &naddr_cnf->cgi_ps.lac); + naddr_cnf->cgi_ps.rac = cgi_ps->rai.rac; + osmo_store16be(cgi_ps->cell_identity, &naddr_cnf->cgi_ps.cell_identity); + } + + return abis_osmo_pcu_sendmsg(bts, msg); +} + +static int rcvmsg_pcu_neigh_addr_req(struct gsm_bts *bts, const struct gsm_pcu_if_neigh_addr_req *naddr_req) +{ + + struct cell_ab ab; + uint16_t local_lac, local_ci; + struct osmo_cell_global_id_ps cgi_ps; + int rc; + + local_lac = osmo_load16be(&naddr_req->local_lac); + local_ci = osmo_load16be(&naddr_req->local_ci); + ab = (struct cell_ab){ + .arfcn = osmo_load16be(&naddr_req->tgt_arfcn), + .bsic = naddr_req->tgt_bsic, + }; + + LOGP(DNM, LOGL_INFO, "(bts=%d) Rx Neighbor Address Resolution Req (ARFCN=%u,BSIC=%u) from (LAC=%u,CI=%u)\n", + bts->nr, ab.arfcn, ab.bsic, local_lac, local_ci); + + if (!cell_ab_valid(&ab)) { + rc = 2; + goto do_fail; + } + + if (neighbor_address_resolution(bts->network, &ab, local_lac, local_ci, &cgi_ps) < 0) { + rc = 1; + goto do_fail; + } + return abis_osmo_pcu_tx_neigh_addr_cnf(bts, naddr_req, 0, &cgi_ps); + +do_fail: + return abis_osmo_pcu_tx_neigh_addr_cnf(bts, naddr_req, rc, NULL); +} + + +static int rcvmsg_pcu_container(struct gsm_bts *bts, struct gsm_pcu_if_container *container, size_t container_len) +{ + int rc; + uint16_t data_length = osmo_load16be(&container->length); + + if (container_len < sizeof(*container) + data_length) { + LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU CONTAINER message inside (%d) too short\n", + container->msg_type); + return -EINVAL; + } + + LOGP(DNM, LOGL_INFO, "(bts=%d) Rx ABIS_OSMO_PCU CONTAINER msg type %u\n", + bts->nr, container->msg_type); + + switch (container->msg_type) { + case PCU_IF_MSG_NEIGH_ADDR_REQ: + if (data_length < sizeof(struct gsm_pcu_if_neigh_addr_req)) { + LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU CONTAINER ANR_CNF message too short\n"); + return -EINVAL; + } + rc = rcvmsg_pcu_neigh_addr_req(bts, (struct gsm_pcu_if_neigh_addr_req *)&container->data); + break; + default: + LOGP(DNM, LOGL_NOTICE, "(bts=%d) Rx ABIS_OSMO_PCU unexpected msg type (%u) inside container!\n", + bts->nr, container->msg_type); + rc = -1; + } + + return rc; +} + +static int rcvmsg_pcu(struct gsm_bts *bts, struct msgb *msg) +{ + struct gsm_pcu_if *pcu_prim; + int rc; + + if (msgb_l2len(msg) < PCUIF_HDR_SIZE) { + LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU message too short\n"); + return -EIO; + } + + pcu_prim = msgb_l2(msg); + LOGP(DNM, LOGL_INFO, "(bts=%d) Rx ABIS_OSMO_PCU msg type %u\n", + pcu_prim->bts_nr, pcu_prim->msg_type); + + switch (pcu_prim->msg_type) { + case PCU_IF_MSG_CONTAINER: + if (msgb_l2len(msg) < PCUIF_HDR_SIZE + sizeof(pcu_prim->u.container)) { + LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU CONTAINER message too short\n"); + rc = -EINVAL; + } else { + rc = rcvmsg_pcu_container(bts, &pcu_prim->u.container, msgb_l2len(msg) - PCUIF_HDR_SIZE); + } + break; + default: + LOGP(DNM, LOGL_NOTICE, "(bts=%d) Rx ABIS_OSMO_PCU unexpected msg type %u!\n", + pcu_prim->bts_nr, pcu_prim->msg_type); + rc = -1; + } + + return rc; +} + +//////////////////////////////////////// +// OSMO ABIS extensions (generic code) +/////////////////////////////////////// + +/* High-Level API */ +/* Entry-point where L2 OSMO from BTS enters the NM code */ +int abis_osmo_rcvmsg(struct msgb *msg) +{ + int rc; + struct e1inp_sign_link *link = msg->dst; + struct gsm_bts *bts = link->trx->bts; + uint8_t *osmo_type = msgb_l2(msg); + msg->l2h = osmo_type + 1; + + switch (*osmo_type) { + case IPAC_PROTO_EXT_PCU: + rc = rcvmsg_pcu(bts, msg); + break; + default: + LOGP(DNM, LOGL_ERROR, "IPAC_PROTO_EXT 0x%x not supported!\n", + *osmo_type); + rc = -EINVAL; + } + + msgb_free(msg); + return rc; +} + + +/* Send a OML NM Message from BSC to BTS */ +int abis_osmo_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + msg->dst = bts->osmo_link; + + msg->l2h = msg->data; + + return abis_sendmsg(msg); + +} diff --git a/src/osmo-bsc/abis_rsl.c b/src/osmo-bsc/abis_rsl.c index 858c683e1..49e8b52c3 100644 --- a/src/osmo-bsc/abis_rsl.c +++ b/src/osmo-bsc/abis_rsl.c @@ -44,6 +44,7 @@ #include <osmocom/gsm/rsl.h> #include <osmocom/core/talloc.h> #include <osmocom/bsc/pcu_if.h> +#include <osmocom/bsc/pcuif_proto.h> #include <osmocom/bsc/gsm_08_08.h> #include <osmocom/netif/rtp.h> #include <osmocom/core/tdef.h> @@ -55,8 +56,10 @@ #include <osmocom/bsc/handover_fsm.h> #include <osmocom/bsc/smscb.h> #include <osmocom/bsc/bts.h> -#define RSL_ALLOC_SIZE 1024 -#define RSL_ALLOC_HEADROOM 128 +#include <osmocom/bsc/power_control.h> +#include <osmocom/bsc/chan_counts.h> +#include <osmocom/bsc/lchan.h> +#include <osmocom/bsc/vgcs_fsm.h> static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan, struct gsm_meas_rep *resp) @@ -72,26 +75,26 @@ static void count_codecs(struct gsm_bts *bts, struct gsm_lchan *lchan) OSMO_ASSERT(bts); if (lchan->type == GSM_LCHAN_TCH_H) { - switch (lchan->tch_mode) { + switch (gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode)) { case GSM48_CMODE_SPEECH_AMR: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_H]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_AMR_H)); break; case GSM48_CMODE_SPEECH_V1: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_HR]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_V1_HR)); break; default: break; } } else if (lchan->type == GSM_LCHAN_TCH_F) { - switch (lchan->tch_mode) { + switch (gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode)) { case GSM48_CMODE_SPEECH_AMR: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_F]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_AMR_F)); break; case GSM48_CMODE_SPEECH_V1: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_FR]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_V1_FR)); break; case GSM48_CMODE_SPEECH_EFR: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_EFR]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_EFR)); break; default: break; @@ -146,12 +149,6 @@ static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, return lchan; } -static struct msgb *rsl_msgb_alloc(void) -{ - return msgb_alloc_headroom(RSL_ALLOC_SIZE, RSL_ALLOC_HEADROOM, - "RSL"); -} - static void pad_macblock(uint8_t *out, const uint8_t *in, int len) { memcpy(out, in, len); @@ -160,13 +157,44 @@ static void pad_macblock(uint8_t *out, const uint8_t *in, int len) memset(out+len, 0x2b, GSM_MACBLOCK_LEN - len); } -/* Chapter 9.3.7: Encryption Information */ +/* Chapter 9.3.7: Encryption Information + * Return negative on error, number of bytes written to 'out' on success. + * 'out' must provide room for 17 bytes. */ static int build_encr_info(uint8_t *out, struct gsm_lchan *lchan) { - *out++ = lchan->encr.alg_id & 0xff; - if (lchan->encr.key_len) - memcpy(out, lchan->encr.key, lchan->encr.key_len); - return lchan->encr.key_len + 1; + out[0] = ALG_A5_NR_TO_RSL(lchan->encr.alg_a5_n); + switch (out[0]) { + case GSM0808_ALG_ID_A5_1: + case GSM0808_ALG_ID_A5_2: + case GSM0808_ALG_ID_A5_3: + if (!lchan->encr.key_len) { + LOG_LCHAN(lchan, LOGL_ERROR, "A5/%d encryption chosen, but missing Kc\n", lchan->encr.alg_a5_n); + return -EINVAL; + } + /* fall through */ + case GSM0808_ALG_ID_A5_0: + /* When A5/0 is chosen, no encryption is active, so technically, no key is needed. However, 3GPP TS + * 48.058 9.3.7 Encryption Information stays quite silent about presence or absence of a key for A5/0. + * The only thing specified is how to indicate the length of the key; the possibility that the key may + * be zero length is not explicitly mentioned. So it seems that we should always send the key along, + * even for A5/0. Currently our ttcn3 test suite does expect the key to be present also for A5/0, see + * f_cipher_mode() in bsc/MSC_ConnectionHandler.ttcn. */ + if (lchan->encr.key_len) + memcpy(&out[1], lchan->encr.key, lchan->encr.key_len); + return 1 + lchan->encr.key_len; + + case GSM0808_ALG_ID_A5_4: + if (!lchan->encr.kc128_present) { + LOG_LCHAN(lchan, LOGL_ERROR, "A5/4 encryption chosen, but missing Kc128\n"); + return -EINVAL; + } + memcpy(&out[1], lchan->encr.kc128, sizeof(lchan->encr.kc128)); + return 1 + sizeof(lchan->encr.kc128); + + default: + LOG_LCHAN(lchan, LOGL_ERROR, "A5/%d encryption not supported\n", lchan->encr.alg_a5_n); + return -EINVAL; + } } /* If the TLV contain an RSL Cause IE, return pointer to the cause value. If there is no Cause IE, return @@ -193,8 +221,52 @@ static const char *rsl_cause_name(struct tlv_parsed *tp) return ""; } +static void add_power_control_params(struct msgb *msg, enum abis_rsl_ie iei, + const struct gsm_lchan *lchan) +{ + const struct gsm_bts *bts = lchan->ts->trx->bts; + const struct gsm_power_ctrl_params *cp; + + /* Since {MS,BS}_POWER_PARAM IE content is operator dependent, it's not + * known how different BTS models will interpret an empty IE, so let's + * better skip sending it unless we know for sure what each expects. */ + if (bts->model->power_ctrl_enc_rsl_params == NULL) + return; + + if (iei == RSL_IE_MS_POWER_PARAM) + cp = &bts->ms_power_ctrl; + else + cp = &bts->bs_power_ctrl; + + /* These parameters are only valid for dynamic mode */ + if (cp->mode != GSM_PWR_CTRL_MODE_DYN_BTS) + return; + + /* No dynamic BS power control if the maximum is 0 dB */ + if (cp->dir == GSM_PWR_CTRL_DIR_DL) { + if (lchan->bs_power_db == 0) + return; + } + + /* Put tag first, length will be updated later */ + uint8_t *ie_len = msgb_tl_put(msg, iei); + uint8_t msg_len = msgb_length(msg); + + if (bts->model->power_ctrl_enc_rsl_params(msg, cp) != 0) { + LOGP(DRSL, LOGL_ERROR, "Failed to encode MS/BS Power Control " + "parameters, omitting this IE (tag 0x%02x)\n", iei); + msgb_get(msg, msg_len - 2); + return; + } + + /* Update length part of the containing IE */ + *ie_len = msgb_length(msg) - msg_len; +} + /* Send a BCCH_INFO message as per Chapter 8.5.1 */ -int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) +/* Allow test to overwrite it */ +__attribute__((weak)) int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, + const uint8_t *data, int len) { struct abis_rsl_dchan_hdr *dh; const struct gsm_bts *bts = trx->bts; @@ -223,12 +295,13 @@ int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data); } - msg->dst = trx->rsl_link; + msg->dst = trx->rsl_link_primary; return abis_rsl_sendmsg(msg); } -int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, +/* Allow test to overwrite it */ +__attribute__((weak)) int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, const uint8_t *data, int len) { struct abis_rsl_common_hdr *ch; @@ -242,7 +315,7 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, if (data) msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); - msg->dst = trx->rsl_link; + msg->dst = trx->rsl_link_primary; return abis_rsl_sendmsg(msg); } @@ -251,8 +324,12 @@ int rsl_sacch_info_modify(struct gsm_lchan *lchan, uint8_t type, const uint8_t *data, int len) { struct abis_rsl_dchan_hdr *dh; - struct msgb *msg = rsl_msgb_alloc(); - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + struct msgb *msg; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; + + msg = rsl_msgb_alloc(); dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY); @@ -262,7 +339,7 @@ int rsl_sacch_info_modify(struct gsm_lchan *lchan, uint8_t type, if (data) msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); return abis_rsl_sendmsg(msg); } @@ -271,7 +348,10 @@ int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db) { struct abis_rsl_dchan_hdr *dh; struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t bs_power_enc; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; db = abs(db); if (db > 30) @@ -279,28 +359,31 @@ int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db) msg = rsl_msgb_alloc(); - lchan->bs_power = db/2; + bs_power_enc = db / 2; if (fpc) - lchan->bs_power |= 0x10; + bs_power_enc |= 0x10; dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_BS_POWER_CONTROL); dh->chan_nr = chan_nr; - msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power); + msgb_tv_put(msg, RSL_IE_BS_POWER, bs_power_enc); - msg->dst = lchan->ts->trx->rsl_link; + /* BS Power Control Parameters (if supported by BTS model) */ + add_power_control_params(msg, RSL_IE_BS_POWER_PARAM, lchan); + + msg->dst = rsl_chan_link(lchan); return abis_rsl_sendmsg(msg); } int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan) { - struct gsm_bts_trx *trx = lchan->ts->trx; - struct gsm_bts *bts = trx->bts; struct abis_rsl_dchan_hdr *dh; struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; LOG_LCHAN(lchan, LOGL_DEBUG, "Tx MS POWER CONTROL (ms_power_lvl=%" PRIu8 ")\n", lchan->ms_power); @@ -312,24 +395,23 @@ int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan) dh->chan_nr = chan_nr; msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); - /* indicate MS power control to be performed by BTS: */ - if (bts->type == GSM_BTS_TYPE_OSMOBTS) - msgb_tl_put(msg, RSL_IE_MS_POWER_PARAM); - /* else: Since IE MS_POWER_PARAM content is operator dependent, it's not - known if non-osmocom BTS models will support an empty IE, so let's - better skip sending it unless we know for sure what each expects. */ - msg->dst = trx->rsl_link; + /* MS Power Control Parameters (if supported by BTS model) */ + add_power_control_params(msg, RSL_IE_MS_POWER_PARAM, lchan); + + msg->dst = rsl_chan_link(lchan); return abis_rsl_sendmsg(msg); } static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm, - struct gsm_lchan *lchan) + struct gsm_lchan *lchan, + const struct channel_mode_and_rate *ch_mode_rate, + enum lchan_type_for type_for) { + int rc; memset(cm, 0, sizeof(*cm)); - /* FIXME: what to do with data calls ? */ cm->dtx_dtu = 0; if (lchan->ts->trx->bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED) cm->dtx_dtu |= RSL_CMOD_DTXu; @@ -337,33 +419,57 @@ static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm, cm->dtx_dtu |= RSL_CMOD_DTXd; /* set TCH Speech/Data */ - cm->spd_ind = lchan->rsl_cmode; - - if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN && - lchan->tch_mode != GSM48_CMODE_SIGN) - LOGP(DRSL, LOGL_ERROR, "unsupported: rsl_mode == signalling, " - "but tch_mode != signalling\n"); + rc = chan_mode_to_rsl_cmod_spd(ch_mode_rate->chan_mode); + if (rc < 0) { + LOGP(DRSL, LOGL_ERROR, "unsupported: chan_mode = 0x%02x\n", ch_mode_rate->chan_mode); + return rc; + } + cm->spd_ind = rc; switch (lchan->type) { case GSM_LCHAN_SDCCH: cm->chan_rt = RSL_CMOD_CRT_SDCCH; break; case GSM_LCHAN_TCH_F: - cm->chan_rt = RSL_CMOD_CRT_TCH_Bm; + switch (type_for) { + case LCHAN_TYPE_FOR_VAMOS: + cm->chan_rt = RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm; + break; + case LCHAN_TYPE_FOR_VGCS: + cm->chan_rt = RSL_CMOD_CRT_TCH_GROUP_Bm; + break; + case LCHAN_TYPE_FOR_VBS: + cm->chan_rt = RSL_CMOD_CRT_TCH_BCAST_Bm; + break; + default: + cm->chan_rt = RSL_CMOD_CRT_TCH_Bm; + } break; case GSM_LCHAN_TCH_H: - cm->chan_rt = RSL_CMOD_CRT_TCH_Lm; + switch (type_for) { + case LCHAN_TYPE_FOR_VAMOS: + cm->chan_rt = RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm; + break; + case LCHAN_TYPE_FOR_VGCS: + cm->chan_rt = RSL_CMOD_CRT_TCH_GROUP_Lm; + break; + case LCHAN_TYPE_FOR_VBS: + cm->chan_rt = RSL_CMOD_CRT_TCH_BCAST_Lm; + break; + default: + cm->chan_rt = RSL_CMOD_CRT_TCH_Lm; + } break; case GSM_LCHAN_NONE: case GSM_LCHAN_UNKNOWN: default: LOGP(DRSL, LOGL_ERROR, "unsupported activation lchan->type %u %s\n", - lchan->type, gsm_lchant_name(lchan->type)); + lchan->type, gsm_chan_t_name(lchan->type)); return -EINVAL; } - switch (lchan->tch_mode) { + switch (gsm48_chan_mode_to_non_vamos(ch_mode_rate->chan_mode)) { case GSM48_CMODE_SIGN: cm->chan_rate = 0; break; @@ -379,82 +485,96 @@ static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm, case GSM48_CMODE_DATA_14k5: case GSM48_CMODE_DATA_12k0: case GSM48_CMODE_DATA_6k0: - switch (lchan->csd_mode) { - case LCHAN_CSD_M_NT: - /* non-transparent CSD with RLP */ - switch (lchan->tch_mode) { - case GSM48_CMODE_DATA_14k5: - cm->chan_rate = RSL_CMOD_SP_NT_14k5; - break; - case GSM48_CMODE_DATA_12k0: - cm->chan_rate = RSL_CMOD_SP_NT_12k0; - break; - case GSM48_CMODE_DATA_6k0: - cm->chan_rate = RSL_CMOD_SP_NT_6k0; - break; - default: - LOGP(DRSL, LOGL_ERROR, - "unsupported lchan->tch_mode %u\n", - lchan->tch_mode); - return -EINVAL; - } - break; - /* transparent data services below */ - case LCHAN_CSD_M_T_1200_75: - cm->chan_rate = RSL_CMOD_CSD_T_1200_75; - break; - case LCHAN_CSD_M_T_600: - cm->chan_rate = RSL_CMOD_CSD_T_600; - break; - case LCHAN_CSD_M_T_1200: - cm->chan_rate = RSL_CMOD_CSD_T_1200; - break; - case LCHAN_CSD_M_T_2400: - cm->chan_rate = RSL_CMOD_CSD_T_2400; - break; - case LCHAN_CSD_M_T_9600: - cm->chan_rate = RSL_CMOD_CSD_T_9600; - break; - case LCHAN_CSD_M_T_14400: - cm->chan_rate = RSL_CMOD_CSD_T_14400; - break; - case LCHAN_CSD_M_T_29000: - cm->chan_rate = RSL_CMOD_CSD_T_29000; - break; - case LCHAN_CSD_M_T_32000: - cm->chan_rate = RSL_CMOD_CSD_T_32000; - break; - default: - LOGP(DRSL, LOGL_ERROR, - "unsupported lchan->csd_mode %u\n", - lchan->csd_mode); - return -EINVAL; + case GSM48_CMODE_DATA_3k6: + /* 3GPP TS 48.058 § 9.3.6 Channel Mode octet 6 */ + if (ch_mode_rate->data_transparent) { + cm->chan_rate = ch_mode_rate->data_rate.t; + } else { + cm->chan_rate = ch_mode_rate->data_rate.nt; + cm->chan_rate |= 0x40; } break; default: - LOGP(DRSL, LOGL_ERROR, - "unsupported lchan->tch_mode %u\n", - lchan->tch_mode); + LOGP(DRSL, LOGL_ERROR, "unsupported channel mode %u\n", ch_mode_rate->chan_mode); return -EINVAL; } return 0; } -static void mr_config_for_bts(struct gsm_lchan *lchan, struct msgb *msg) +static int put_mr_config_for_bts(struct msgb *msg, const struct gsm48_multi_rate_conf *mr_conf_filtered, + const struct amr_multirate_conf *mr_modes) { - uint8_t len; + msgb_put_u8(msg, RSL_IE_MR_CONFIG); + return gsm48_multirate_config(msg, mr_conf_filtered, mr_modes->bts_mode, mr_modes->num_modes); +} - if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) +/* indicate FACCH/SACCH Repetition to be performed by BTS, + * see also: 3GPP TS 44.006, section 10 and 11 */ +static void put_rep_acch_cap_ie(const struct gsm_lchan *lchan, + struct msgb *msg) +{ + struct abis_rsl_osmo_rep_acch_cap *cap; + const struct gsm_bts *bts = lchan->ts->trx->bts; + + /* The RSL_IE_OSMO_REP_ACCH_CAP IE is a proprietary IE, that can only + * be used with osmo-bts type BTSs */ + if (!(bts->model->type == GSM_BTS_TYPE_OSMOBTS + && osmo_bts_has_feature(&bts->features, BTS_FEAT_ACCH_REP))) return; - len = lchan->mr_bts_lv[0]; - if (!len) { - LOG_LCHAN(lchan, LOGL_ERROR, "Missing Multirate Config (len is zero)\n"); + cap = (struct abis_rsl_osmo_rep_acch_cap*) msg->tail; + msgb_tlv_put(msg, RSL_IE_OSMO_REP_ACCH_CAP, sizeof(*cap), + (uint8_t *)&bts->rep_acch_cap); + + if (!(lchan->conn && lchan->conn->cm3_valid + && lchan->conn->cm3.repeated_acch_capability)) { + /* MS supports only FACCH repetition for command frames, so + * we mask out all other features, even when they are enabled + * on this BTS. */ + cap->dl_facch_all = 0; + cap->dl_sacch = 0; + cap->ul_sacch = 0; + } +} + +/* indicate Temporary overpower of SACCH and FACCH channels */ +static void put_top_acch_cap_ie(const struct gsm_lchan *lchan, + const struct rsl_ie_chan_mode *cm, + struct msgb *msg) +{ + const struct gsm_bts *bts = lchan->ts->trx->bts; + + /* The BTS_FEAT_ACCH_TEMP_OVP IE is a proprietary IE, that can only be used with osmo-bts type BTSs */ + if (!(bts->model->type == GSM_BTS_TYPE_OSMOBTS && osmo_bts_has_feature(&bts->features, BTS_FEAT_ACCH_TEMP_OVP))) return; + + /* Check if TOP is permitted for the given Channel Mode */ + switch (bts->top_acch_chan_mode) { + case TOP_ACCH_CHAN_MODE_SPEECH_V3: + if (cm->spd_ind != RSL_CMOD_SPD_SPEECH) + return; + if (cm->chan_rate != RSL_CMOD_SP_GSM3) + return; + break; + case TOP_ACCH_CHAN_MODE_ANY: + break; } - msgb_tlv_put(msg, RSL_IE_MR_CONFIG, lchan->mr_bts_lv[0], - lchan->mr_bts_lv + 1); + + msgb_tlv_put(msg, RSL_IE_OSMO_TEMP_OVP_ACCH_CAP, + sizeof(bts->top_acch_cap), + (void *)&bts->top_acch_cap); +} + +/* Write RSL_IE_OSMO_TRAINING_SEQUENCE to msgb. The tsc_set argument's range is 1-4, tsc argument range is 0-7. */ +static void put_osmo_training_sequence_ie(struct msgb *msg, uint8_t tsc_set, uint8_t tsc) +{ + uint8_t *len = msgb_tl_put(msg, RSL_IE_OSMO_TRAINING_SEQUENCE); + *len = 2; + /* Convert from spec conforming "human readable" TSC Set 1-4 to 0-3 on the wire */ + msgb_put_u8(msg, tsc_set - 1); + /* TSC is 0-7 both on the wire and in spec descriptions */ + msgb_put_u8(msg, tsc); } /* Chapter 8.4.1 */ @@ -466,10 +586,12 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref) struct msgb *msg; int rc; uint8_t *len; - uint8_t ta; struct rsl_ie_chan_mode cm; struct gsm48_chan_desc cd; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; DEBUGP(DRSL, "%s Tx RSL Channel Activate with act_type=%s\n", gsm_ts_and_pchan_name(lchan->ts), @@ -478,7 +600,7 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref) /* PDCH activation is a job for rsl_tx_dyn_ts_pdch_act_deact(); */ OSMO_ASSERT(act_type != RSL_ACT_OSMO_PDCH); - rc = channel_mode_from_lchan(&cm, lchan); + rc = channel_mode_from_lchan(&cm, lchan, &lchan->activate.ch_mode_rate, lchan->activate.info.type_for); if (rc < 0) { LOGP(DRSL, LOGL_ERROR, "%s Cannot find channel mode from lchan type\n", @@ -486,20 +608,18 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref) return rc; } - ta = lchan->rqd_ta; - - /* BS11 requires TA shifted by 2 bits */ - if (bts->type == GSM_BTS_TYPE_BS11) - ta <<= 2; - memset(&cd, 0, sizeof(cd)); - gsm48_lchan2chan_desc(&cd, lchan); + rc = gsm48_lchan2chan_desc(&cd, lchan, lchan->activate.tsc, true); + if (rc) { + LOG_LCHAN(lchan, LOGL_ERROR, "Error encoding Channel Number\n"); + return rc; + } msg = rsl_msgb_alloc(); dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV); - dh->chan_nr = gsm_lchan2chan_nr(lchan); + dh->chan_nr = chan_nr; msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type); msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), @@ -525,11 +645,15 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref) msg->l3h = len + 1; *len = msgb_l3len(msg); - if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) { + if (lchan->encr.alg_a5_n > 0) { uint8_t encr_info[MAX_A5_KEY_LEN+2]; rc = build_encr_info(encr_info, lchan); if (rc > 0) msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info); + if (rc < 0) { + msgb_free(msg); + return rc; + } } switch (act_type) { @@ -541,21 +665,60 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref) break; } - msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power); - msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); - msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta); - /* indicate MS power control to be performed by BTS: */ - if (bts->type == GSM_BTS_TYPE_OSMOBTS) - msgb_tl_put(msg, RSL_IE_MS_POWER_PARAM); - /* else: Since IE MS_POWER_PARAM content is operator dependent, it's not - known if non-osmocom BTS models will support an empty IE, so let's - better skip sending it unless we know for sure what each expects. */ + if (bts->bs_power_ctrl.mode != GSM_PWR_CTRL_MODE_NONE) + msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power_db / 2); + if (bts->ms_power_ctrl.mode != GSM_PWR_CTRL_MODE_NONE) + msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); + + if (lchan->activate.info.ta_known) { + uint8_t ta = lchan->activate.info.ta; + /* BS11 requires TA shifted by 2 bits */ + if (bts->type == GSM_BTS_TYPE_BS11) + ta <<= 2; + msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta); + } else if ((act_type & 0x06) == 0x00) { + /* Note '4)' in section 8.4.1: The Timing Advance element must be + * included if activation type is intra cell channel change. */ + LOG_LCHAN(lchan, LOGL_NOTICE, "Timing Advance IE shall be present, " + "but the actual value is not known => assuming 0\n"); + msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, 0); + } + + /* BS/MS Power Control Parameters (if supported by BTS model) */ + add_power_control_params(msg, RSL_IE_BS_POWER_PARAM, lchan); + add_power_control_params(msg, RSL_IE_MS_POWER_PARAM, lchan); + + if (cm.chan_rate == RSL_CMOD_SP_GSM3) { + rc = put_mr_config_for_bts(msg, &lchan->activate.mr_conf_filtered, + (lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half); + if (rc) { + LOG_LCHAN(lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n"); + msgb_free(msg); + return rc; + } + } - mr_config_for_bts(lchan, msg); + put_rep_acch_cap_ie(lchan, msg); + put_top_acch_cap_ie(lchan, &cm, msg); - msg->dst = trx->rsl_link; + /* Selecting a specific TSC Set is only applicable to VAMOS mode */ + if (lchan->activate.info.type_for == LCHAN_TYPE_FOR_VAMOS && lchan->activate.tsc_set >= 1) + put_osmo_training_sequence_ie(msg, lchan->activate.tsc_set, lchan->activate.tsc); - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_TOTAL]); + msg->dst = rsl_chan_link(lchan); + + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_TOTAL)); + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_SDCCH)); + break; + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_TCH_F: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_TCH)); + break; + default: + break; + } return abis_rsl_sendmsg(msg); } @@ -567,10 +730,14 @@ int rsl_chan_mode_modify_req(struct gsm_lchan *lchan) struct msgb *msg; int rc; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); struct rsl_ie_chan_mode cm; + struct gsm_bts *bts = lchan->ts->trx->bts; - rc = channel_mode_from_lchan(&cm, lchan); + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; + + rc = channel_mode_from_lchan(&cm, lchan, &lchan->modify.ch_mode_rate, lchan->modify.info.type_for); if (rc < 0) return rc; @@ -582,16 +749,37 @@ int rsl_chan_mode_modify_req(struct gsm_lchan *lchan) msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), (uint8_t *) &cm); - if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) { + if (lchan->encr.alg_a5_n > 0) { uint8_t encr_info[MAX_A5_KEY_LEN+2]; rc = build_encr_info(encr_info, lchan); if (rc > 0) msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info); + if (rc < 0) { + msgb_free(msg); + return rc; + } } - mr_config_for_bts(lchan, msg); + if (cm.chan_rate == RSL_CMOD_SP_GSM3) { + rc = put_mr_config_for_bts(msg, &lchan->modify.mr_conf_filtered, + (lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half); + if (rc) { + LOG_LCHAN(lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n"); + msgb_free(msg); + return rc; + } + } + + put_rep_acch_cap_ie(lchan, msg); + put_top_acch_cap_ie(lchan, &cm, msg); + + /* Selecting a specific TSC Set is only applicable to VAMOS mode. Send this Osmocom specific IE only to OsmoBTS + * types. */ + if (lchan->modify.info.type_for == LCHAN_TYPE_FOR_VAMOS && lchan->modify.tsc_set >= 1 && + bts->model->type == GSM_BTS_TYPE_OSMOBTS) + put_osmo_training_sequence_ie(msg, lchan->modify.tsc_set, lchan->modify.tsc); - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); return abis_rsl_sendmsg(msg); } @@ -601,11 +789,14 @@ int rsl_encryption_cmd(struct msgb *msg) { struct abis_rsl_dchan_hdr *dh; struct gsm_lchan *lchan = msg->lchan; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); uint8_t encr_info[MAX_A5_KEY_LEN+2]; uint8_t l3_len = msg->len; int rc; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; + /* First push the L3 IE tag and length */ msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); @@ -623,7 +814,7 @@ int rsl_encryption_cmd(struct msgb *msg) init_dchan_hdr(dh, RSL_MT_ENCR_CMD); dh->chan_nr = chan_nr; - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); return abis_rsl_sendmsg(msg); } @@ -634,12 +825,16 @@ int rsl_deact_sacch(struct gsm_lchan *lchan) struct abis_rsl_dchan_hdr *dh; struct msgb *msg = rsl_msgb_alloc(); + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_DEACTIVATE_SACCH); - dh->chan_nr = gsm_lchan2chan_nr(lchan); + dh->chan_nr = chan_nr; msg->lchan = lchan; - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan)); @@ -652,13 +847,17 @@ int rsl_tx_rf_chan_release(struct gsm_lchan *lchan) struct abis_rsl_dchan_hdr *dh; struct msgb *msg; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; + msg = rsl_msgb_alloc(); dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_RF_CHAN_REL); - dh->chan_nr = gsm_lchan2chan_nr(lchan); + dh->chan_nr = chan_nr; msg->lchan = lchan; - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); return abis_rsl_sendmsg(msg); } @@ -667,14 +866,14 @@ int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, const struct osmo_mobile_identity *mi, uint8_t chan_needed, bool is_gprs) { - struct abis_rsl_dchan_hdr *dh; + struct abis_rsl_cchan_hdr *cch; struct msgb *msg = rsl_msgb_alloc(); uint8_t *l; int rc; - dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); - init_dchan_hdr(dh, RSL_MT_PAGING_CMD); - dh->chan_nr = RSL_CHAN_PCH_AGCH; + cch = (struct abis_rsl_cchan_hdr *) msgb_put(msg, sizeof(*cch)); + rsl_init_cchan_hdr(cch, RSL_MT_PAGING_CMD); + cch->chan_nr = RSL_CHAN_PCH_AGCH; msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group); @@ -693,8 +892,47 @@ int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, if (bts->type == GSM_BTS_TYPE_RBS2000 && is_gprs) msgb_tv_put(msg, RSL_IE_ERIC_PACKET_PAG_IND, 0); - msg->dst = bts->c0->rsl_link; + msg->dst = bts->c0->rsl_link_primary; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.5.10: NOTIFICATION COMMAND */ +int rsl_notification_cmd(struct gsm_bts *bts, struct gsm_lchan *lchan, struct gsm0808_group_callref *gc, uint8_t *drx) +{ + struct abis_rsl_cchan_hdr *cch; + struct msgb *msg = rsl_msgb_alloc(); + struct gsm48_chan_desc cd; + uint8_t sti = (lchan) ? RSL_CMD_INDICATOR_START : RSL_CMD_INDICATOR_STOP; + uint8_t *t; + int rc; + + cch = (struct abis_rsl_cchan_hdr *) msgb_put(msg, sizeof(*cch)); + rsl_init_cchan_hdr(cch, RSL_MT_NOT_CMD); + cch->chan_nr = RSL_CHAN_PCH_AGCH; + + msgb_tlv_put(msg, RSL_IE_CMD_INDICATOR, 1, &sti); + + /* Use TLV encoding from TS 08.08. Change different IE type. */ + t = msg->tail; + gsm0808_enc_group_callref(msg, gc); + *t = RSL_IE_GROUP_CALL_REF; + + if (lchan) { + memset(&cd, 0, sizeof(cd)); + rc = gsm48_lchan2chan_desc(&cd, lchan, lchan->activate.tsc, true); + if (rc) { + LOG_LCHAN(lchan, LOGL_ERROR, "Error encoding Channel Number\n"); + msgb_free(msg); + return rc; + } + msgb_tlv_put(msg, RSL_IE_CHAN_DESC, sizeof(cd), (const uint8_t *)&cd); + if (drx) + msgb_tlv_put(msg, RSL_IE_NCH_DRX_INFO, 1, drx); + } + + msg->dst = bts->c0->rsl_link_primary; return abis_rsl_sendmsg(msg); } @@ -713,30 +951,21 @@ int rsl_forward_layer3_info(struct gsm_lchan *lchan, const uint8_t *l3_info, uin return rsl_data_request(msg, 0); } -int imsi_str2bcd(uint8_t *bcd_out, const char *str_in) -{ - int i, len = strlen(str_in); - - for (i = 0; i < len; i++) { - int num = str_in[i] - 0x30; - if (num < 0 || num > 9) - return -1; - if (i % 2 == 0) - bcd_out[i/2] = num; - else - bcd_out[i/2] |= (num << 4); - } - - return 0; -} - /* Chapter 8.5.6 */ -struct msgb *rsl_imm_assign_cmd_common(struct gsm_bts *bts, uint8_t len, uint8_t *val) +struct msgb *rsl_imm_assign_cmd_common(const struct gsm_bts *bts, uint8_t len, const uint8_t *val) { - struct msgb *msg = rsl_msgb_alloc(); + struct msgb *msg; struct abis_rsl_dchan_hdr *dh; uint8_t buf[GSM_MACBLOCK_LEN]; + if (len > sizeof(buf)) { + LOGP(DRSL, LOGL_ERROR, + "Cannot send IMMEDIATE ASSIGNMENT message with excessive length (%u)\n", len); + return NULL; + } + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_IMMEDIATE_ASSIGN_CMD); dh->chan_nr = RSL_CHAN_PCH_AGCH; @@ -753,12 +982,12 @@ struct msgb *rsl_imm_assign_cmd_common(struct gsm_bts *bts, uint8_t len, uint8_t break; } - msg->dst = bts->c0->rsl_link; + msg->dst = bts->c0->rsl_link_primary; return msg; } /* Chapter 8.5.6 */ -int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val) +int rsl_imm_assign_cmd(const struct gsm_bts *bts, uint8_t len, const uint8_t *val) { struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val); if (!msg) @@ -766,17 +995,24 @@ int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val) return abis_rsl_sendmsg(msg); } -/* Chapter 8.5.6 */ -int rsl_ericsson_imm_assign_cmd(struct gsm_bts *bts, uint32_t tlli, uint8_t len, uint8_t *val) +/* Chapter 8.5.6 Immediate Assignment Command (with Ericcson vendor specific RSL extension) */ +int rsl_ericsson_imm_assign_cmd(const struct gsm_bts *bts, uint32_t msg_id, uint8_t len, + const uint8_t *val, uint8_t pag_grp, bool confirm) { struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val); if (!msg) return 1; + /* Append ericsson proprietary paging group IE, this will instruct the BTS to + * send this immediate assignment through PCH instead of AGCH. */ + msgb_tv_put(msg, RSL_IE_ERIC_PAGING_GROUP, pag_grp); + /* ericsson can handle a reference at the end of the message which is used in * the confirm message. The confirm message is only sent if the trailer is present */ - msgb_put_u8(msg, RSL_IE_ERIC_MOBILE_ID); - msgb_put_u32(msg, tlli); + if (confirm) { + msgb_put_u8(msg, RSL_IE_ERIC_MOBILE_ID); + msgb_put_u32(msg, msg_id); + } return abis_rsl_sendmsg(msg); } @@ -784,37 +1020,178 @@ int rsl_ericsson_imm_assign_cmd(struct gsm_bts *bts, uint32_t tlli, uint8_t len, /* Send Siemens specific MS RF Power Capability Indication */ int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci) { - struct msgb *msg = rsl_msgb_alloc(); + struct msgb *msg; struct abis_rsl_dchan_hdr *dh; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_SIEMENS_MRPCI); dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; - dh->chan_nr = gsm_lchan2chan_nr(lchan); + dh->chan_nr = chan_nr; msgb_tv_put(msg, RSL_IE_SIEMENS_MRPCI, *(uint8_t *)mrpci); DEBUGP(DRSL, "%s TX Siemens MRPCI 0x%02x\n", gsm_lchan_name(lchan), *(uint8_t *)mrpci); - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); return abis_rsl_sendmsg(msg); } +/* For 3GPP TS 52.402 unsuccReqsForService, we need to decode the DTAP and count CM Service Reject messages. */ +static void count_unsucc_reqs_for_service(const struct msgb *msg) +{ + struct gsm_bts *bts = msg->lchan->ts->trx->bts; + const struct gsm48_hdr *gh; + uint8_t pdisc, mtype; + uint8_t cause; + + if (msgb_l3len(msg) < sizeof(*gh)) + return; + + gh = msgb_l3(msg); + pdisc = gsm48_hdr_pdisc(gh); + mtype = gsm48_hdr_msg_type(gh); + + if (pdisc != GSM48_PDISC_MM || mtype != GSM48_MT_MM_CM_SERV_REJ) + return; + + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ)); + + cause = gh->data[0]; + switch (cause) { + case GSM48_REJECT_IMSI_UNKNOWN_IN_HLR: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_IMSI_UNKNOWN_IN_HLR)); + break; + case GSM48_REJECT_ILLEGAL_MS: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_ILLEGAL_MS)); + break; + case GSM48_REJECT_IMSI_UNKNOWN_IN_VLR: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_IMSI_UNKNOWN_IN_VLR)); + break; + case GSM48_REJECT_IMEI_NOT_ACCEPTED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_IMEI_NOT_ACCEPTED)); + break; + case GSM48_REJECT_ILLEGAL_ME: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_ILLEGAL_ME)); + break; + case GSM48_REJECT_PLMN_NOT_ALLOWED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_PLMN_NOT_ALLOWED)); + break; + case GSM48_REJECT_LOC_NOT_ALLOWED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_LOC_NOT_ALLOWED)); + break; + case GSM48_REJECT_ROAMING_NOT_ALLOWED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_ROAMING_NOT_ALLOWED)); + break; + case GSM48_REJECT_NETWORK_FAILURE: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_NETWORK_FAILURE)); + break; + case GSM48_REJECT_SYNCH_FAILURE: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_SYNCH_FAILURE)); + break; + case GSM48_REJECT_CONGESTION: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_CONGESTION)); + break; + case GSM48_REJECT_SRV_OPT_NOT_SUPPORTED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_SRV_OPT_NOT_SUPPORTED)); + break; + case GSM48_REJECT_RQD_SRV_OPT_NOT_SUPPORTED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_RQD_SRV_OPT_NOT_SUPPORTED)); + break; + case GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_SRV_OPT_TMP_OUT_OF_ORDER)); + break; + case GSM48_REJECT_CALL_CAN_NOT_BE_IDENTIFIED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_CALL_CAN_NOT_BE_IDENTIFIED)); + break; + case GSM48_REJECT_INCORRECT_MESSAGE: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_INCORRECT_MESSAGE)); + break; + case GSM48_REJECT_INVALID_MANDANTORY_INF: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_INVALID_MANDANTORY_INF)); + break; + case GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_MSG_TYPE_NOT_IMPLEMENTED)); + break; + case GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_MSG_TYPE_NOT_COMPATIBLE)); + break; + case GSM48_REJECT_INF_ELEME_NOT_IMPLEMENTED: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_INF_ELEME_NOT_IMPLEMENTED)); + break; + case GSM48_REJECT_CONDTIONAL_IE_ERROR: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_CONDTIONAL_IE_ERROR)); + break; + case GSM48_REJECT_MSG_NOT_COMPATIBLE: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_MSG_NOT_COMPATIBLE)); + break; + default: + if (cause >= 48 && cause <= 63) { + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_RETRY_IN_NEW_CELL)); + break; + } + /* else fall thru */ + case GSM48_REJECT_PROTOCOL_ERROR: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_PROTOCOL_ERROR)); + break; + } +} + /* Send "DATA REQUEST" message with given L3 Info payload */ /* Chapter 8.3.1 */ int rsl_data_request(struct msgb *msg, uint8_t link_id) { + int chan_nr; + if (msg->lchan == NULL) { LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n"); + msgb_free(msg); return -EINVAL; } - rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, gsm_lchan2chan_nr(msg->lchan), - link_id, 1); + count_unsucc_reqs_for_service(msg); - msg->dst = msg->lchan->ts->trx->rsl_link; + chan_nr = gsm_lchan2chan_nr(msg->lchan, true); + if (chan_nr < 0) { + msgb_free(msg); + return chan_nr; + } + + rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, chan_nr, link_id, 1); + + msg->dst = rsl_chan_link(msg->lchan); + + return abis_rsl_sendmsg(msg); +} + +/* Send "UNIT DATA REQUEST" message with given L3 Info payload */ +/* Chapter 8.3.11 */ +int rsl_unit_data_request(struct msgb *msg, uint8_t link_id) +{ + int chan_nr; + + if (msg->lchan == NULL) { + LOGP(DRSL, LOGL_ERROR, "cannot send UNIT DATA REQUEST to unknown lchan\n"); + msgb_free(msg); + return -EINVAL; + } + + chan_nr = gsm_lchan2chan_nr(msg->lchan, true); + if (chan_nr < 0) { + msgb_free(msg); + return chan_nr; + } + + rsl_rll_push_l3(msg, RSL_MT_UNIT_DATA_REQ, chan_nr, link_id, 1); + + msg->dst = rsl_chan_link(msg->lchan); return abis_rsl_sendmsg(msg); } @@ -824,10 +1201,12 @@ int rsl_data_request(struct msgb *msg, uint8_t link_id) int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id) { struct msgb *msg; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; - msg = rsl_rll_simple(RSL_MT_EST_REQ, gsm_lchan2chan_nr(lchan), - link_id, 0); - msg->dst = lchan->ts->trx->rsl_link; + msg = rsl_rll_simple(RSL_MT_EST_REQ, chan_nr, link_id, 0); + msg->dst = rsl_chan_link(lchan); DEBUGP(DRLL, "%s RSL RLL ESTABLISH REQ (link_id=0x%02x)\n", gsm_lchan_name(lchan), link_id); @@ -845,13 +1224,15 @@ int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id, { struct msgb *msg; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; - msg = rsl_rll_simple(RSL_MT_REL_REQ, gsm_lchan2chan_nr(lchan), - link_id, 0); + msg = rsl_rll_simple(RSL_MT_REL_REQ, chan_nr, link_id, 0); /* 0 is normal release, 1 is local end */ msgb_tv_put(msg, RSL_IE_RELEASE_MODE, release_mode); - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); DEBUGP(DRLL, "%s RSL RLL RELEASE REQ (link_id=0x%02x, reason=%u)\n", gsm_lchan_name(lchan), link_id, release_mode); @@ -864,7 +1245,7 @@ int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id, static bool msg_for_osmocom_dyn_ts(struct msgb *msg) { struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); - if (msg->lchan->ts->pchan_on_init != GSM_PCHAN_TCH_F_TCH_H_PDCH) + if (msg->lchan->ts->pchan_on_init != GSM_PCHAN_OSMO_DYN) return false; /* dyn TS messages always come in on the first lchan of a timeslot */ if (msg->lchan->nr != 0) @@ -880,7 +1261,7 @@ static int rsl_rx_chan_act_nack(struct msgb *msg) struct gsm_lchan *lchan = msg->lchan; const uint8_t *cause_p; - rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(msg->lchan->ts->trx->bts->bts_ctrs, BTS_CTR_CHAN_ACT_NACK)); if (dh->ie_chan != RSL_IE_CHAN_NR) { LOG_LCHAN(msg->lchan, LOGL_ERROR, "Invalid IE: expected CHAN_NR IE (0x%x), got 0x%x\n", @@ -888,7 +1269,12 @@ static int rsl_rx_chan_act_nack(struct msgb *msg) return -EINVAL; } - rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } + cause_p = rsl_cause(&tp); LOG_LCHAN(lchan, LOGL_ERROR, "CHANNEL ACTIVATE NACK%s\n", rsl_cause_name(&tp)); @@ -904,86 +1290,131 @@ static int rsl_rx_conn_fail(struct msgb *msg) { struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); struct gsm_lchan *lchan = msg->lchan; + struct rate_ctr_group *bts_ctrs = lchan->ts->trx->bts->bts_ctrs; struct tlv_parsed tp; const uint8_t *cause_p; - rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } + cause_p = rsl_cause(&tp); LOG_LCHAN(lchan, LOGL_ERROR, "CONNECTION FAIL%s\n", rsl_cause_name(&tp)); - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_CHAN_RF_FAIL)); + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_CHAN_RF_FAIL_SDCCH)); + break; + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_TCH_F: + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_CHAN_RF_FAIL_TCH)); + break; + default: + break; + } + + /* Report to VGCS FSM */ + if (lchan_is_asci(lchan)) { + if (lchan->conn && lchan->conn->vgcs_chan.fi) { + uint8_t cause = GSM0808_CAUSE_RADIO_INTERFACE_FAILURE; + osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_FAIL, &cause); + } + return 0; + } /* If the lchan is associated with a conn, we shall notify the MSC of the RSL Conn Failure, and * the connection will presumably be torn down and lead to an lchan release. During initial * Channel Request from the MS, an lchan has no conn yet, so in that case release now. */ if (!lchan->conn) - lchan_release(lchan, false, true, *cause_p); + lchan_release(lchan, false, true, *cause_p, NULL); else osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_RSL_CONN_FAIL, (void*)cause_p); return 0; } -static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru, - const char *prefix) +static void print_meas_rep_uni(struct osmo_strbuf *sb, + const struct gsm_meas_rep_unidir *mru, + const char *prefix) { - DEBUGPC(DMEAS, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ", - prefix, rxlev2dbm(mru->full.rx_lev), - prefix, rxlev2dbm(mru->sub.rx_lev)); - DEBUGPC(DMEAS, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ", - prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual); + OSMO_STRBUF_PRINTF(*sb, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ", + prefix, rxlev2dbm(mru->full.rx_lev), + prefix, rxlev2dbm(mru->sub.rx_lev)); + OSMO_STRBUF_PRINTF(*sb, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ", + prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual); } -static void print_meas_rep(struct gsm_lchan *lchan, struct gsm_meas_rep *mr) +static int print_meas_rep_buf(char *buf, size_t len, const struct gsm_meas_rep *mr) { - int i; - const char *name = ""; - struct bsc_subscr *bsub = NULL; + struct osmo_strbuf sb = { .buf = buf, .len = len }; - if (lchan && lchan->conn) { - bsub = lchan->conn->bsub; - if (bsub) { - log_set_context(LOG_CTX_BSC_SUBSCR, bsub); - name = bsc_subscr_name(bsub); - } else - name = lchan->name; - } - - DEBUGP(DMEAS, "[%s] MEASUREMENT RESULT NR=%d ", name, mr->nr); + OSMO_STRBUF_PRINTF(sb, "MEASUREMENT RESULT NR=%d ", mr->nr); if (mr->flags & MEAS_REP_F_DL_DTX) - DEBUGPC(DMEAS, "DTXd "); + OSMO_STRBUF_PRINTF(sb, "DTXd "); - print_meas_rep_uni(&mr->ul, "ul"); - DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power); + print_meas_rep_uni(&sb, &mr->ul, "ul"); + OSMO_STRBUF_PRINTF(sb, "BS_POWER=%ddB ", mr->bs_power_db); if (mr->flags & MEAS_REP_F_MS_TO) - DEBUGPC(DMEAS, "MS_TO=%d ", mr->ms_timing_offset); + OSMO_STRBUF_PRINTF(sb, "MS_TO=%d ", mr->ms_timing_offset); if (mr->flags & MEAS_REP_F_MS_L1) { - DEBUGPC(DMEAS, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr); - DEBUGPC(DMEAS, "L1_FPC=%u ", - mr->flags & MEAS_REP_F_FPC ? 1 : 0); - DEBUGPC(DMEAS, "L1_TA=%u ", mr->ms_l1.ta); + OSMO_STRBUF_PRINTF(sb, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr); + OSMO_STRBUF_PRINTF(sb, "L1_FPC=%u ", mr->flags & MEAS_REP_F_FPC ? 1 : 0); + OSMO_STRBUF_PRINTF(sb, "L1_TA=%u ", mr->ms_l1.ta); } if (mr->flags & MEAS_REP_F_UL_DTX) - DEBUGPC(DMEAS, "DTXu "); + OSMO_STRBUF_PRINTF(sb, "DTXu "); if (mr->flags & MEAS_REP_F_BA1) - DEBUGPC(DMEAS, "BA1 "); + OSMO_STRBUF_PRINTF(sb, "BA1 "); if (!(mr->flags & MEAS_REP_F_DL_VALID)) - DEBUGPC(DMEAS, "NOT VALID "); + OSMO_STRBUF_PRINTF(sb, "NOT VALID "); else - print_meas_rep_uni(&mr->dl, "dl"); + print_meas_rep_uni(&sb, &mr->dl, "dl"); - DEBUGPC(DMEAS, "NUM_NEIGH=%u\n", mr->num_cell); - if (mr->num_cell == 7) - return; - for (i = 0; i < mr->num_cell; i++) { - struct gsm_meas_rep_cell *mrc = &mr->cell[i]; - DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u => %d dBm\n", - mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev)); + OSMO_STRBUF_PRINTF(sb, "NUM_NEIGH=%u", mr->num_cell); + + return sb.chars_needed; +} + +static char *print_meas_rep_c(void *ctx, const struct gsm_meas_rep *mr) +{ + /* A naive count of required characters gets me to ~200, so 256 should be safe to get a large enough buffer on + * the first time. */ + OSMO_NAME_C_IMPL(ctx, 256, "ERROR", print_meas_rep_buf, mr) +} + +static void print_meas_rep(struct gsm_lchan *lchan, const struct gsm_meas_rep *mr) +{ + int i; + const char *name = ""; + struct bsc_subscr *bsub = NULL; + + if (lchan && lchan->conn) { + bsub = lchan->conn->bsub; + if (bsub) { + log_set_context(LOG_CTX_BSC_SUBSCR, bsub); + name = bsc_subscr_name(bsub); + } else { + name = lchan->name; + } + } + + DEBUGP(DMEAS, "[%s] %s\n", name, print_meas_rep_c(OTC_SELECT, mr)); + + if (mr->num_cell != 7 + && log_check_level(DMEAS, LOGL_DEBUG)) { + for (i = 0; i < mr->num_cell; i++) { + const struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u RXLEV=%ddBm\n", + mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev)); + } } if (bsub) @@ -1011,6 +1442,7 @@ static int rsl_rx_meas_res(struct msgb *msg) uint8_t len; const uint8_t *val; int rc; + uint8_t bs_power_enc; if (!lchan_may_receive_data(msg->lchan)) { LOG_LCHAN(msg->lchan, LOGL_DEBUG, "MEAS RES for inactive channel\n"); @@ -1020,7 +1452,11 @@ static int rsl_rx_meas_res(struct msgb *msg) memset(mr, 0, sizeof(*mr)); mr->lchan = msg->lchan; - rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) || !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) || @@ -1044,7 +1480,8 @@ static int rsl_rx_meas_res(struct msgb *msg) mr->ul.sub.rx_qual = val[2] & 0x7; } - mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + bs_power_enc = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + mr->bs_power_db = (bs_power_enc & 0x0f) * 2; /* Optional Parts */ if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET)) { @@ -1062,12 +1499,13 @@ static int rsl_rx_meas_res(struct msgb *msg) if (val[0] & 0x04) mr->flags |= MEAS_REP_F_FPC; mr->ms_l1.ta = val[1]; - /* BS11 and Nokia reports TA shifted by 2 bits */ + /* BS11, Nokia and RBS report TA shifted by 2 bits */ if (msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11 - || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) + || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE + || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_RBS2000) mr->ms_l1.ta >>= 2; - /* store TA for next assignment/handover */ - mr->lchan->rqd_ta = mr->ms_l1.ta; + /* store TA for handover decision, and for intra-cell re-assignment */ + mr->lchan->last_ta = mr->ms_l1.ta; } if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO); @@ -1083,6 +1521,8 @@ static int rsl_rx_meas_res(struct msgb *msg) print_meas_rep(msg->lchan, mr); + lchan_ms_pwr_ctrl(msg->lchan, mr); + send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr); return 0; @@ -1097,7 +1537,11 @@ static int rsl_rx_hando_det(struct msgb *msg) .msg = msg, }; - rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY)) d.access_delay = TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY); @@ -1113,6 +1557,66 @@ static int rsl_rx_hando_det(struct msgb *msg) return 0; } +/* Chapter 8.4.21: TALKER DETECTION */ +static int rsl_rx_talker_det(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + /* We use this struct, because it has same data. */ + struct handover_rr_detect_data d = { + .msg = msg, + }; + + if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } + + if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY)) + d.access_delay = TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY); + + if (!msg->lchan->conn || !msg->lchan->conn->vgcs_chan.fi) { + LOGP(DRSL, LOGL_ERROR, "%s TALKER DETECTION but no VGCS channel\n", + gsm_lchan_name(msg->lchan)); + return 0; + } + + osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_DET, &d); + + return 0; +} + +/* Chapter 8.4.22: LISTENER DETECTION */ +static int rsl_rx_listener_det(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + /* We use this struct, because it has same data. */ + struct handover_rr_detect_data d = { + .msg = msg, + }; + + if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } + + if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY)) + d.access_delay = TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY); + + if (!msg->lchan->conn || !msg->lchan->conn->vgcs_chan.fi) { + LOGP(DRSL, LOGL_ERROR, "%s LISTENER DETECTION but no VGCS channel\n", + gsm_lchan_name(msg->lchan)); + return 0; + } + + osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_LISTENER_DET, &d); + + return 0; +} + static int rsl_rx_ipacc_pdch(struct msgb *msg, char *name, uint32_t ts_ev) { struct gsm_bts_trx_ts *ts = msg->lchan->ts; @@ -1133,6 +1637,9 @@ static int abis_rsl_rx_dchan(struct msgb *msg) int rc = 0; struct e1inp_sign_link *sign_link = msg->dst; + if (msgb_l2len(msg) < sizeof(*rslh)) + return -EINVAL; + if (rslh->ie_chan != RSL_IE_CHAN_NR) { LOGP(DRSL, LOGL_ERROR, "Rx RSL DCHAN: invalid RSL header, expecting Channel Number IE tag, got 0x%x\n", @@ -1158,6 +1665,10 @@ static int abis_rsl_rx_dchan(struct msgb *msg) switch (rslh->c.msg_type) { case RSL_MT_CHAN_ACTIV_ACK: + /* Ignore acknowlegement of channel reactivation, if a VGCS/VBS channel was reactivated to assign + * the calling subscriber to it. */ + if (msg->lchan->conn && msg->lchan->conn->assignment.req.vgcs) + break; if (msg_for_osmocom_dyn_ts(msg)) osmo_fsm_inst_dispatch(msg->lchan->ts->fi, TS_EV_PDCH_ACT_ACK, NULL); else { @@ -1177,6 +1688,12 @@ static int abis_rsl_rx_dchan(struct msgb *msg) case RSL_MT_HANDO_DET: rc = rsl_rx_hando_det(msg); break; + case RSL_MT_TALKER_DET: + rc = rsl_rx_talker_det(msg); + break; + case RSL_MT_LISTENER_DET: + rc = rsl_rx_listener_det(msg); + break; case RSL_MT_RF_CHAN_REL_ACK: if (msg_for_osmocom_dyn_ts(msg)) osmo_fsm_inst_dispatch(msg->lchan->ts->fi, TS_EV_PDCH_DEACT_ACK, NULL); @@ -1190,7 +1707,7 @@ static int abis_rsl_rx_dchan(struct msgb *msg) break; case RSL_MT_MODE_MODIFY_NACK: LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CHANNEL MODE MODIFY NACK\n"); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_MODE_MODIFY_NACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_MODE_MODIFY_NACK)); osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK, NULL); break; case RSL_MT_IPAC_PDCH_ACT_ACK: @@ -1207,20 +1724,18 @@ static int abis_rsl_rx_dchan(struct msgb *msg) break; case RSL_MT_PHY_CONTEXT_CONF: case RSL_MT_PREPROC_MEAS_RES: - case RSL_MT_TALKER_DET: - case RSL_MT_LISTENER_DET: case RSL_MT_REMOTE_CODEC_CONF_REP: case RSL_MT_MR_CODEC_MOD_ACK: case RSL_MT_MR_CODEC_MOD_NACK: case RSL_MT_MR_CODEC_MOD_PER: LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unimplemented Abis RSL DChan msg 0x%02x\n", rslh->c.msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN)); break; default: LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown Abis RSL DChan msg 0x%02x\n", rslh->c.msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN)); return -EINVAL; } @@ -1233,7 +1748,14 @@ static int rsl_rx_error_rep(struct msgb *msg) struct tlv_parsed tp; struct e1inp_sign_link *sign_link = msg->dst; - rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh)); + if (msgb_l2len(msg) < sizeof(*rslh)) + return -EINVAL; + + if (rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh)) < 0) { + LOGP(DRSL, LOGL_ERROR, "%s Failed to parse RSL %s\n", + gsm_trx_name(sign_link->trx), rsl_or_ipac_msg_name(rslh->msg_type)); + return -EINVAL; + } LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT%s\n", gsm_trx_name(sign_link->trx), rsl_cause_name(&tp)); @@ -1241,6 +1763,81 @@ static int rsl_rx_error_rep(struct msgb *msg) return 0; } +static int rsl_rx_resource_indication(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + struct tlv_parsed tp; + struct e1inp_sign_link *sign_link = msg->dst; + struct tlv_p_entry *res_info_ie; + struct gsm_bts_trx *trx = sign_link->trx; + struct gsm_lchan *lchan; + int ts_nr; + int i; + + LOGP(DRSL, LOGL_DEBUG, "%s Rx Resource Indication\n", gsm_trx_name(trx)); + + /* First clear out all ratings, because only the last resource indication counts. If we can't parse the message, + * then there are no ratings. */ + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { + lchan->interf_dbm = INTERF_DBM_UNKNOWN; + lchan->interf_band = INTERF_BAND_UNKNOWN; + } + } + + if (rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh)) < 0) { + LOGP(DRSL, LOGL_ERROR, "%s Failed to parse RSL %s\n", + gsm_trx_name(trx), rsl_or_ipac_msg_name(rslh->msg_type)); + return -EINVAL; + } + + res_info_ie = TLVP_GET(&tp, RSL_IE_RESOURCE_INFO); + if (!res_info_ie) { + LOGP(DRSL, LOGL_ERROR, "Rx Resource Indication: missing Resource Info IE\n"); + return -EINVAL; + } + + /* The IE value is defined in 3GPP TS 48.058 9.3.21 Resource Information: + * one octet channel nr, one octet interference level, channel nr, interference level, ... + * Where channel nr is cbits + tn (as usual), + * and interference level is a 3bit value in the most significant bits of the octet. + * Evaluate each pair and update interference ratings for all lchans in this trx. */ + + /* There must be an even amount of octets in the value */ + if (res_info_ie->len & 1) { + LOGP(DRSL, LOGL_ERROR, "Rx Resource Indication: Resource Info IE has odd length\n"); + return -EINVAL; + } + + /* Now iterate the reported levels and update corresponding lchans. + * Note that an empty res_info_ie can also make sense, if no lchans are idle and no interference ratings are + * present. The practical effect of the message then is to invalidate previous interference ratings. */ + for (i = 0; i < res_info_ie->len; i += 2) { + struct gsm_bts *bts = trx->bts; + uint8_t chan_nr = res_info_ie->val[i]; + uint8_t interf_band = res_info_ie->val[i + 1] >> 5; + + lchan = lchan_lookup(trx, chan_nr, "Abis RSL Rx Resource Indication: "); + if (!lchan) + continue; + + /* Store the actual received index */ + lchan->interf_band = interf_band; + /* Clamp the index to 5 before accessing array of interference band bounds */ + interf_band = OSMO_MIN(interf_band, ARRAY_SIZE(bts->interf_meas_params_used.bounds_dbm)-1); + /* FIXME: when testing with ip.access nanoBTS, we observe a value range of 1..6. According to spec, it + * seems like values 0..5 are intended: 3GPP TS 48.058 9.3.21 Resource Information says: + * "The Interf Band field (bits 6-8) indicates in binary the interference level expressed as one of five + * possible interference level bands as defined by O&M." + * and 3GPP TS 52.021 9.4.25 "Interference level Boundaries" (OML) defines values 0, X1, X2, X3, X4, X5. + * If nanoBTS sends 6, the above code clamps it to 5, so that we lose one band in accuracy. */ + lchan->interf_dbm = -((int16_t)bts->interf_meas_params_used.bounds_dbm[interf_band]); + } + + return 0; +} + static int abis_rsl_rx_trx(struct msgb *msg) { struct abis_rsl_common_hdr *rslh = msgb_l2(msg); @@ -1253,7 +1850,7 @@ static int abis_rsl_rx_trx(struct msgb *msg) break; case RSL_MT_RF_RES_IND: /* interference on idle channels of TRX */ - //DEBUGP(DRSL, "%s RF Resource Indication\n", gsm_trx_name(sign_link->trx)); + rc = rsl_rx_resource_indication(msg); break; case RSL_MT_OVERLOAD: /* indicate CCCH / ACCH / processor overload */ @@ -1269,7 +1866,7 @@ static int abis_rsl_rx_trx(struct msgb *msg) default: LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message " "type 0x%02x\n", gsm_trx_name(sign_link->trx), rslh->msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN)); return -EINVAL; } return rc; @@ -1344,25 +1941,29 @@ struct chan_rqd { /* Handle packet channel rach requests */ static int rsl_rx_pchan_rqd(struct chan_rqd *rqd) { - uint8_t t1, t2, t3; uint32_t fn; uint8_t rqd_ta; uint8_t is_11bit; + struct gsm_time gsm_time; /* Process rach request and forward contained information to PCU */ if (rqd->ref.ra == 0x7F) { is_11bit = 1; /* FIXME: Also handle 11 bit rach requests */ - LOGP(DRSL, LOGL_ERROR, "BTS %d eleven bit access burst not supported yet!\n",rqd->bts->nr); + LOGP(DRSL, LOGL_ERROR, "BTS %d eleven bit access burst not supported yet!\n", rqd->bts->nr); return -EINVAL; } else { is_11bit = 0; - t1 = rqd->ref.t1; - t2 = rqd->ref.t2; - t3 = rqd->ref.t3_low | (rqd->ref.t3_high << 3); - fn = (51 * ((t3-t2) % 26) + t3 + 51 * 26 * t1); rqd_ta = rqd->ta; + + gsm_time.t1 = rqd->ref.t1; + gsm_time.t2 = rqd->ref.t2; + gsm_time.t3 = rqd->ref.t3_low | (rqd->ref.t3_high << 3); + fn = gsm_gsmtime2fn(&gsm_time); + + LOG_BTS(rqd->bts, DRSL, LOGL_INFO, "CHAN RQD: fn(t1=%u,t3=%u,t2=%u) = %u\n", + gsm_time.t1, gsm_time.t3, gsm_time.t2, fn); } return pcu_tx_rach_ind(rqd->bts, rqd_ta, rqd->ref.ra, fn, is_11bit, @@ -1374,7 +1975,6 @@ static int rsl_rx_pchan_rqd(struct chan_rqd *rqd) * requests from the queue to prevent the queue from growing indefinetly. */ static void reduce_rach_dos(struct gsm_bts *bts) { - int rlt = gsm_bts_get_radio_link_timeout(bts); time_t timestamp_current = time(NULL); struct chan_rqd *rqd; struct chan_rqd *rqd_tmp; @@ -1382,9 +1982,9 @@ static void reduce_rach_dos(struct gsm_bts *bts) /* Drop all expired channel requests in the list */ llist_for_each_entry_safe(rqd, rqd_tmp, &bts->chan_rqd_queue, entry) { - /* If the channel request is older than the radio link timeout we drop it. This also means that the + /* If the channel request is older than the rach expiry timeout we drop it. This also means that the * queue is under its overflow limit again. */ - if (timestamp_current - rqd->timestamp > rlt) { + if (timestamp_current - rqd->timestamp > bts->rach_expiry_timeout) { LOG_BTS(bts, DRSL, LOGL_INFO, "CHAN RQD: tossing expired channel request" "(ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n", rqd->ref.ra, bts->network->neci, rqd->reason); @@ -1446,13 +2046,56 @@ static int rsl_rx_chan_rqd(struct msgb *msg) return -EINVAL; } rqd->ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2]; + if (rqd->ta > bts->rach_max_delay) { + LOG_BTS(bts, DRSL, LOGL_INFO, "Ignoring CHAN RQD: Access Delay(%d) greater than %u\n", + rqd->ta, bts->rach_max_delay); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_MAX_DELAY_EXCEEDED)); + talloc_free(rqd); + return -EINVAL; + } /* Determine channel request cause code */ rqd->reason = get_reason_by_chreq(rqd->ref.ra, bts->network->neci); - LOG_BTS(bts, DRSL, LOGL_INFO, "CHAN RQD: reason: %s (ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n", - get_value_string(gsm_chreq_descs, rqd->reason), rqd->ref.ra, bts->network->neci, rqd->reason); + LOG_BTS(bts, DRSL, LOGL_INFO, "CHAN RQD: reason: %s (ra=0x%02x, t1=%d, t3=%d, t2=%d, neci=0x%02x, chreq_reason=0x%02x)\n", + get_value_string(gsm_chreq_descs, rqd->reason), rqd->ref.ra, + rqd->ref.t1, rqd->ref.t3_high << 3 | rqd->ref.t3_low, rqd->ref.t2, + bts->network->neci, rqd->reason); - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_TOTAL)); + switch (rqd->reason) { + case GSM_CHREQ_REASON_EMERG: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_EMERG)); + break; + case GSM_CHREQ_REASON_CALL: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_CALL)); + break; + case GSM_CHREQ_REASON_LOCATION_UPD: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_LOCATION_UPD)); + break; + case GSM_CHREQ_REASON_PAG: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_PAG)); + break; + case GSM_CHREQ_REASON_PDCH: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_PDCH)); + break; + case GSM_CHREQ_REASON_OTHER: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_OTHER)); + break; + default: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_UNKNOWN)); + break; + } + + /* Block emergency calls if we explicitly disable them via sysinfo. */ + if (rqd->reason == GSM_CHREQ_REASON_EMERG) { + if (bts->si_common.rach_control.t2 & 0x4) { + LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: MS attempts EMERGENCY CALL although EMERGENCY CALLS " + "are not allowed in sysinfo (cfg: network / bts / rach emergency call allowed 0)\n"); + rsl_tx_imm_ass_rej(bts, &rqd->ref); + talloc_free(rqd); + return 0; + } + } /* Enqueue request */ llist_add_tail(&rqd->entry, &bts->chan_rqd_queue); @@ -1479,21 +2122,14 @@ static struct gsm_lchan *get_any_lchan(struct gsm_bts *bts) trx = gsm_bts_trx_num(bts, trx_nr); for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { ts = &trx->ts[ts_nr]; - ts_for_each_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) { if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H) { - if (bts->chan_alloc_reverse) { - if (lchan->fi->state == LCHAN_ST_ESTABLISHED) + if (lchan->fi->state == LCHAN_ST_ESTABLISHED) { + if (!lchan_est || bts->chan_alloc_chan_req_reverse) lchan_est = lchan; - else - lchan_any = lchan; } else { - if (lchan->fi->state == LCHAN_ST_ESTABLISHED) { - if (!lchan_est) - lchan_est = lchan; - } else { - if (!lchan_any) - lchan_any = lchan; - } + if (!lchan_any || bts->chan_alloc_chan_req_reverse) + lchan_any = lchan; } } } @@ -1518,25 +2154,26 @@ static bool force_free_lchan_for_emergency(struct chan_rqd *rqd) /* First check the situation on the BTS, if we have TCH/H or TCH/F resources available for another (EMERGENCY) * call. If yes, then no (further) action has to be carried out. */ - if (lchan_avail_by_type(rqd->bts, GSM_LCHAN_TCH_F)) { + if (lchan_avail_by_type(rqd->bts, GSM_LCHAN_TCH_F, SELECT_FOR_MS_CHAN_REQ, NULL, true)) { LOG_BTS(rqd->bts, DRSL, LOGL_NOTICE, "CHAN RQD/EMERGENCY-PRIORITY: at least one TCH/F is (now) available!\n"); return false; } - if (lchan_avail_by_type(rqd->bts, GSM_LCHAN_TCH_H)) { + if (lchan_avail_by_type(rqd->bts, GSM_LCHAN_TCH_H, SELECT_FOR_MS_CHAN_REQ, NULL, true)) { LOG_BTS(rqd->bts, DRSL, LOGL_NOTICE, "CHAN RQD/EMERGENCY-PRIORITY: at least one TCH/H is (now) available!\n"); return false; } - /* No free TCH/F or TCH/H was found, we now select one of the busy lchans and initate a release on that lchan. - * This will take a short amount of time. We need to come back and check regulary to see if we managed to + /* No free TCH/F or TCH/H was found, we now select one of the busy lchans and initiate a release on that lchan. + * This will take a short amount of time. We need to come back and check regularly to see if we managed to * free up another lchan. */ if (!rqd->release_lchan) { - /* Pick any busy TCH/F or TCH/H lchan and inititate a channel + struct gsm_lchan *release_lchan; + /* Pick any busy TCH/F or TCH/H lchan and initiate a channel * release to make room for the incoming emergency call */ - rqd->release_lchan = get_any_lchan(rqd->bts); - if (!rqd->release_lchan) { + rqd->release_lchan = release_lchan = get_any_lchan(rqd->bts); + if (!release_lchan) { /* It can not happen that we first find out that there * is no TCH/H or TCH/F available and at the same time * we ware unable to find any busy TCH/H or TCH/F. In @@ -1549,11 +2186,23 @@ static bool force_free_lchan_for_emergency(struct chan_rqd *rqd) LOG_BTS(rqd->bts, DRSL, LOGL_NOTICE, "CHAN RQD/EMERGENCY-PRIORITY: inducing termination of lchan %s (state:%s) in favor of incoming EMERGENCY CALL!\n", - gsm_lchan_name(rqd->release_lchan), osmo_fsm_inst_state_name(rqd->release_lchan->fi)); - - lchan_release(rqd->release_lchan, !!(rqd->release_lchan->conn), true, 0); + gsm_lchan_name(release_lchan), osmo_fsm_inst_state_name(release_lchan->fi)); + + /* Make sure the Clear Request to the MSC has the proper cause */ + if (release_lchan->conn) + gscon_bssmap_clear(release_lchan->conn, GSM0808_CAUSE_PREEMPTION); + /* The gscon FSM would only release the lchan after the MSC responds with a Clear Command. + * But we need it released right now. Also with the right RR cause. */ + lchan_release(release_lchan, !!(release_lchan->conn), true, GSM48_RR_CAUSE_PREMPTIVE_REL, + gscon_last_eutran_plmn(release_lchan->conn)); + + /* Also release any overlapping VAMOS multiplexes on this lchan */ + release_lchan = gsm_lchan_primary_to_vamos(release_lchan); + if (release_lchan) + lchan_release(release_lchan, !!(release_lchan->conn), true, GSM48_RR_CAUSE_PREMPTIVE_REL, + gscon_last_eutran_plmn(release_lchan->conn)); } else { - /* BTS is shutting down, give up... */ + /* if BTS has shut down, give up... */ if (rqd->release_lchan->ts->fi->state == TS_ST_NOT_INITIALIZED) return false; @@ -1575,6 +2224,62 @@ static bool force_free_lchan_for_emergency(struct chan_rqd *rqd) return true; } +struct gsm_lchan *_select_sdcch_for_call(struct gsm_bts *bts, const struct chan_rqd *rqd, enum gsm_chan_t lctype) +{ + struct gsm_lchan *lchan = NULL; + int free_tchf, free_tchh; + bool needs_dyn_switch; + + lchan = lchan_avail_by_type(bts, GSM_LCHAN_SDCCH, SELECT_FOR_MS_CHAN_REQ, NULL, false); + if (!lchan) + return NULL; + + needs_dyn_switch = lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN && + lchan->ts->pchan_is != GSM_PCHAN_SDCCH8_SACCH8C; + + free_tchf = bts->chan_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]; + free_tchh = bts->chan_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]; + if (free_tchf == 0 && free_tchh == 0) { + LOG_BTS(bts, DRSL, LOGL_INFO, + "CHAN RQD: 0x%x Requesting %s reason=call but no TCH available\n", + rqd->ref.ra, gsm_chan_t_name(lctype)); + return NULL; + } + + /* There's a TCH available and we'll not switch any dyn ts, so we are + * fine (we can switch one of them to SDCCH8 and still have one left) */ + if (!needs_dyn_switch) + goto select_lchan; + + /* We need to switch, but there's at least 2 TCH TS available so we are fine: */ + if (free_tchf > 1 || free_tchh > 2) + goto select_lchan; + + /* At this point (needs_dyn_switch==true), following cases are possible: + * [A] H=0, F=1 + * [B] H=1, F=0 + * [B] H=1, F=1 + * [C] H=2, F=1 + * If condition [C] is met, it means there's 1 dynamic TS (because a dyn + * TS is counted both as 1 free TCH/F and 2 free TCH/H at the same time) + * and it's the same as the dynamic TS available for SDCCH requiring + * switch, so selecting it would basically leave us without free TCH, so + * avoid selecting it. Regarding the other conditions, it basically + * results in them being different TS than the one we want to switch, so + * we are fine selecting the TS for SDCCH */ + if (free_tchf == 1 && free_tchh == 2) { + LOG_BTS(bts, DRSL, LOGL_INFO, + "CHAN RQD: 0x%x Requesting %s reason=call but dyn TS switch to " + "SDCCH would starve the single available TCH timeslot\n", + rqd->ref.ra, gsm_chan_t_name(lctype)); + return NULL; + } + +select_lchan: + lchan_select_set_type(lchan, GSM_LCHAN_SDCCH); + return lchan; +} + void abis_rsl_chan_rqd_queue_poll(struct gsm_bts *bts) { struct lchan_activate_info info; @@ -1588,8 +2293,8 @@ void abis_rsl_chan_rqd_queue_poll(struct gsm_bts *bts) /* Handle PDCH related rach requests (in case of BSC-co-located-PCU) */ if (rqd->reason == GSM_CHREQ_REASON_PDCH) { - rsl_rx_pchan_rqd(rqd); - return; + if (rsl_rx_pchan_rqd(rqd) == 0) + goto leave; } /* Ensure that emergency calls will get priority over regular calls, however releasing @@ -1610,36 +2315,44 @@ void abis_rsl_chan_rqd_queue_poll(struct gsm_bts *bts) * - If there is still no channel available, try a TCH/F. * */ - if (rqd->reason == GSM_CHREQ_REASON_EMERG) { - if (bts->si_common.rach_control.t2 & 0x4) { - LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: MS attempts EMERGENCY CALL although EMERGENCY CALLS " - "are not allowed in sysinfo (spec violation by MS!)\n"); - rsl_tx_imm_ass_rej(bts, &rqd->ref); - llist_del(&rqd->entry); - talloc_free(rqd); - return; - } - } - - /* Emergency calls will be put on a free TCH/H or TCH/F directly in the code below, all other channel requests - * will get an SDCCH first (if possible). */ - if (rqd->reason != GSM_CHREQ_REASON_EMERG) - lchan = lchan_select_by_type(bts, GSM_LCHAN_SDCCH); - if (!lchan) { - LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: no resources for %s 0x%x, retrying with %s\n", - gsm_lchant_name(GSM_LCHAN_SDCCH), rqd->ref.ra, gsm_lchant_name(GSM_LCHAN_TCH_H)); - lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_H); - } - if (!lchan) { - LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: no resources for %s 0x%x, retrying with %s\n", - gsm_lchant_name(GSM_LCHAN_SDCCH), rqd->ref.ra, gsm_lchant_name(GSM_LCHAN_TCH_F)); - lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_F); + if (rqd->reason == GSM_CHREQ_REASON_CALL) { + lchan = _select_sdcch_for_call(bts, rqd, lctype); + } else if (rqd->reason != GSM_CHREQ_REASON_EMERG) { + lchan = lchan_select_by_type(bts, GSM_LCHAN_SDCCH, + SELECT_FOR_MS_CHAN_REQ, + NULL); + } + /* else: Emergency calls will be put on a free TCH/H or TCH/F directly + * in the code below, all other channel requests will get an SDCCH first + * (if possible). */ + + if (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_ALWAYS || + (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_VOICE && + gsm_chreq_reason_is_voicecall(rqd->reason)) || + (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_EMERG && + rqd->reason == GSM_CHREQ_REASON_EMERG)) { + if (!lchan) { + LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD[%s]: no resources for %s 0x%x, retrying with %s\n", + get_value_string(gsm_chreq_descs, rqd->reason), gsm_chan_t_name(GSM_LCHAN_SDCCH), + rqd->ref.ra, gsm_chan_t_name(GSM_LCHAN_TCH_H)); + lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_H, + SELECT_FOR_MS_CHAN_REQ, + NULL); + } + if (!lchan) { + LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD[%s]: no resources for %s 0x%x, retrying with %s\n", + get_value_string(gsm_chreq_descs, rqd->reason), gsm_chan_t_name(GSM_LCHAN_SDCCH), + rqd->ref.ra, gsm_chan_t_name(GSM_LCHAN_TCH_F)); + lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_F, + SELECT_FOR_MS_CHAN_REQ, + NULL); + } } if (!lchan) { - LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: no resources for %s 0x%x\n", - gsm_lchant_name(lctype), rqd->ref.ra); - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL]); + LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD[%s]: no resources for %s 0x%x\n", + get_value_string(gsm_chreq_descs, rqd->reason), gsm_chan_t_name(lctype), rqd->ref.ra); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_NO_CHANNEL)); rsl_tx_imm_ass_rej(bts, &rqd->ref); llist_del(&rqd->entry); talloc_free(rqd); @@ -1651,27 +2364,65 @@ void abis_rsl_chan_rqd_queue_poll(struct gsm_bts *bts) OSMO_ASSERT(lchan->rqd_ref); *(lchan->rqd_ref) = rqd->ref; - lchan->rqd_ta = rqd->ta; LOG_LCHAN(lchan, LOGL_DEBUG, "MS: Channel Request: reason=%s ra=0x%02x ta=%d\n", gsm_chreq_name(rqd->reason), rqd->ref.ra, rqd->ta); info = (struct lchan_activate_info){ - .activ_for = FOR_MS_CHANNEL_REQUEST, - .chan_mode = GSM48_CMODE_SIGN, + .activ_for = ACTIVATE_FOR_MS_CHANNEL_REQUEST, + .chreq_reason = rqd->reason, + .ch_mode_rate = { + .chan_mode = GSM48_CMODE_SIGN, + .chan_rate = CH_RATE_SDCCH, + }, + .ta = rqd->ta, + .ta_known = true, + .imm_ass_time = bts->imm_ass_time, }; lchan_activate(lchan, &info); + +leave: llist_del(&rqd->entry); talloc_free(rqd); return; } +static void imm_ass_rate_ctr(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL)); + switch (lchan->activate.info.chreq_reason) { + case GSM_CHREQ_REASON_EMERG: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_EMERG)); + break; + case GSM_CHREQ_REASON_CALL: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_CALL)); + break; + case GSM_CHREQ_REASON_LOCATION_UPD: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_LOCATION_UPD)); + break; + case GSM_CHREQ_REASON_PAG: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_PAG)); + break; + case GSM_CHREQ_REASON_PDCH: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_PDCH)); + break; + case GSM_CHREQ_REASON_OTHER: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_OTHER)); + break; + default: + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_UNKNOWN)); + break; + } +} + int rsl_tx_imm_assignment(struct gsm_lchan *lchan) { int rc; struct gsm_bts *bts = lchan->ts->trx->bts; uint8_t buf[GSM_MACBLOCK_LEN]; struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf; + enum gsm_phys_chan_config pchan; /* create IMMEDIATE ASSIGN 04.08 message */ memset(ia, 0, sizeof(*ia)); @@ -1679,11 +2430,23 @@ int rsl_tx_imm_assignment(struct gsm_lchan *lchan) ia->proto_discr = GSM48_PDISC_RR; ia->msg_type = GSM48_MT_RR_IMM_ASS; ia->page_mode = GSM48_PM_SAME; - gsm48_lchan2chan_desc(&ia->chan_desc, lchan); + + /* In case the dyn TS is not ready yet, ts->pchan_is still reflects the previous pchan type; so get the pchan + * kind from lchan->type, which already reflects the target type. This only happens for dynamic timeslots. + * gsm_pchan_by_lchan_type() isn't always exact, which is fine for dyn TS with their limited pchan kinds. */ + if (lchan_state_is(lchan, LCHAN_ST_WAIT_TS_READY)) + pchan = gsm_pchan_by_lchan_type(lchan->type); + else + pchan = lchan->ts->pchan_is; + rc = gsm48_lchan_and_pchan2chan_desc(&ia->chan_desc, lchan, pchan, lchan->tsc, true); + if (rc) { + LOG_LCHAN(lchan, LOGL_ERROR, "Error encoding Channel Number\n"); + return rc; + } /* use request reference extracted from CHAN_RQD */ memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref)); - ia->timing_advance = lchan->rqd_ta; + ia->timing_advance = lchan->last_ta; if (!lchan->ts->hopping.enabled) { ia->mob_alloc_len = 0; } else { @@ -1697,12 +2460,12 @@ int rsl_tx_imm_assignment(struct gsm_lchan *lchan) rc = rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia); if (!rc) - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_SUCCESSFUL]); + imm_ass_rate_ctr(lchan); return rc; } -/* current load on the CCCH */ +/* 5.4 and 8.5.2 Rx CCCH Load Ind */ static int rsl_rx_ccch_load(struct msgb *msg) { struct e1inp_sign_link *sign_link = msg->dst; @@ -1710,22 +2473,20 @@ static int rsl_rx_ccch_load(struct msgb *msg) struct ccch_signal_data sd; sd.bts = sign_link->trx->bts; - sd.rach_slot_count = -1; - sd.rach_busy_count = -1; - sd.rach_access_count = -1; + sd.rach_slot_count = UINT16_MAX; + sd.rach_busy_count = UINT16_MAX; + sd.rach_access_count = UINT16_MAX; switch (rslh->data[0]) { case RSL_IE_PAGING_LOAD: sd.pg_buf_space = rslh->data[1] << 8 | rslh->data[2]; - if (is_ipaccess_bts(sign_link->trx->bts) && sd.pg_buf_space == 0xffff) { - /* paging load below configured threshold, use 50 as default */ - sd.pg_buf_space = 50; + if (is_ipa_abisip_bts(sd.bts) && sd.pg_buf_space == UINT16_MAX) { + sd.pg_buf_space = paging_estimate_available_slots(sd.bts, sd.bts->ccch_load_ind_period); } - paging_update_buffer_space(sign_link->trx->bts, sd.pg_buf_space); osmo_signal_dispatch(SS_CCCH, S_CCCH_PAGING_LOAD, &sd); break; case RSL_IE_RACH_LOAD: - if (msg->data_len >= 7) { + if (msgb_length(msg) >= 7) { int32_t busy_percent, access_percent; /* build data for signal */ sd.rach_slot_count = rslh->data[2] << 8 | rslh->data[3]; @@ -1740,8 +2501,8 @@ static int rsl_rx_ccch_load(struct msgb *msg) busy_percent = 100; } - osmo_stat_item_set(sd.bts->bts_statg->items[BTS_STAT_RACH_BUSY], busy_percent); - osmo_stat_item_set(sd.bts->bts_statg->items[BTS_STAT_RACH_ACCESS], access_percent); + osmo_stat_item_set(osmo_stat_item_group_get_item(sd.bts->bts_statg, BTS_STAT_RACH_BUSY), busy_percent); + osmo_stat_item_set(osmo_stat_item_group_get_item(sd.bts->bts_statg, BTS_STAT_RACH_ACCESS), access_percent); /* dispatch signal */ osmo_signal_dispatch(SS_CCCH, S_CCCH_RACH_LOAD, &sd); } @@ -1765,7 +2526,12 @@ static int rsl_rx_cbch_load(struct msgb *msg) struct tlv_parsed tp; uint8_t slot_count; - rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh)); + if (rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh)) < 0) { + LOGP(DRSL, LOGL_ERROR, "%s Failed to parse RSL %s\n", + gsm_trx_name(sign_link->trx), rsl_or_ipac_msg_name(rslh->c.msg_type)); + return -EINVAL; + } + if (!TLVP_PRESENT(&tp, RSL_IE_CBCH_LOAD_INFO)) { LOG_BTS(bts, DRSL, LOGL_ERROR, "CBCH LOAD IND without mandatory CBCH Load Info IE\n"); return -1; @@ -1788,7 +2554,7 @@ static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg) { struct e1inp_sign_link *sign_link = msg->dst; struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); - uint32_t tlli; + uint32_t msg_id; LOGP(DRSL, LOGL_INFO, "IMM.ass sent\n"); msgb_pull(msg, sizeof(*dh)); @@ -1800,8 +2566,8 @@ static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg) LOGP(DRSL, LOGL_ERROR, "unsupported IMM.ass message format! (please fix)\n"); else { msgb_pull(msg, 1); /* drop previous data to use msg_pull_u32 */ - tlli = msgb_pull_u32(msg); - pcu_tx_imm_ass_sent(sign_link->trx->bts, tlli); + msg_id = msgb_pull_u32(msg); + pcu_tx_data_cnf(sign_link->trx->bts, msg_id, PCU_IF_SAPI_PCH_2); } return 0; } @@ -1810,8 +2576,12 @@ static int abis_rsl_rx_cchan(struct msgb *msg) { struct e1inp_sign_link *sign_link = msg->dst; struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + struct rate_ctr_group *bts_ctrs = sign_link->trx->bts->bts_ctrs; int rc = 0; + if (msgb_l2len(msg) < sizeof(*rslh)) + return -EINVAL; + msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr, "Abis RSL rx CCHAN: "); @@ -1827,7 +2597,7 @@ static int abis_rsl_rx_cchan(struct msgb *msg) case RSL_MT_DELETE_IND: /* CCCH overloaded, IMM_ASSIGN was dropped */ LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "DELETE INDICATION (Downlink CCCH overload)\n"); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_DELETE_IND]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_RSL_DELETE_IND)); break; case RSL_MT_CBCH_LOAD_IND: /* current load on the CBCH */ @@ -1839,7 +2609,7 @@ static int abis_rsl_rx_cchan(struct msgb *msg) default: LOGP(DRSL, LOGL_NOTICE, "Unknown Abis RSL TRX message type " "0x%02x\n", rslh->c.msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_RSL_UNKNOWN)); return -EINVAL; } @@ -1852,9 +2622,14 @@ static int rsl_rx_rll_err_ind(struct msgb *msg) struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); uint8_t rlm_cause; - rsl_tlv_parse(&tp, rllh->data, msgb_l2len(msg) - sizeof(*rllh)); + if (rsl_tlv_parse(&tp, rllh->data, msgb_l2len(msg) - sizeof(*rllh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(rllh->c.msg_type)); + return -EINVAL; + } + if (!TLVP_PRESENT(&tp, RSL_IE_RLM_CAUSE)) { - LOG_LCHAN(msg->lchan, LOGL_ERROR, "ERROR INDICATION without mandantory cause.\n"); + LOG_LCHAN(msg->lchan, LOGL_ERROR, "ERROR INDICATION without mandatory cause.\n"); return -1; } @@ -1863,10 +2638,17 @@ static int rsl_rx_rll_err_ind(struct msgb *msg) rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND); - rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR]); + rate_ctr_inc(rate_ctr_group_get_ctr(msg->lchan->ts->trx->bts->bts_ctrs, BTS_CTR_CHAN_RLL_ERR)); osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_ERR_IND, &rlm_cause); + /* Report to VGCS FSM */ + if (lchan_is_asci(msg->lchan)) { + if (msg->lchan->conn && msg->lchan->conn->vgcs_chan.fi) { + uint8_t cause = GSM0808_CAUSE_RADIO_INTERFACE_FAILURE; + osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_FAIL, &cause); + } + } return 0; } @@ -1881,17 +2663,30 @@ static int abis_rsl_rx_rll(struct msgb *msg) struct e1inp_sign_link *sign_link = msg->dst; struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); int rc = 0; - uint8_t sapi = rllh->link_id & 0x7; + uint8_t sapi; + + if (msgb_l2len(msg) < sizeof(*rllh)) + return -1; + sapi = rllh->link_id & 0x7; msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, "Abis RSL rx RLL: "); + if (OSMO_UNLIKELY(msg->lchan == NULL)) + return -1; switch (rllh->c.msg_type) { case RSL_MT_DATA_IND: LOG_LCHAN(msg->lchan, LOGL_DEBUG, "SAPI=%u DATA INDICATION\n", sapi); - if (msgb_l2len(msg) > - sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && + + if (msgb_l2len(msg) > (sizeof(*rllh) + 3) && rllh->data[0] == RSL_IE_L3_INFO) { msg->l3h = &rllh->data[3]; + /* Data message on a VGCS channel is handled by VGCS FSM only. */ + if (lchan_is_asci(msg->lchan)) { + if (msg->lchan->conn && msg->lchan->conn->vgcs_chan.fi) + osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_DATA, + msg); + return 0; + } return gsm0408_rcvmsg(msg, rllh->link_id); } break; @@ -1931,8 +2726,14 @@ static int abis_rsl_rx_rll(struct msgb *msg) msg->lchan->sapis[sapi] = LCHAN_SAPI_MS; osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_ESTABLISH_IND, msg); - if (msgb_l2len(msg) > - sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && + /* Establishment message on a VGCS channel is handled by VGCS FSM only. */ + if (lchan_is_asci(msg->lchan)) { + if (msg->lchan->conn && msg->lchan->conn->vgcs_chan.fi) + osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_EST, msg); + break; + } + + if (msgb_l2len(msg) > (sizeof(*rllh) + 3) && rllh->data[0] == RSL_IE_L3_INFO) { msg->l3h = &rllh->data[3]; return gsm0408_rcvmsg(msg, rllh->link_id); @@ -1947,6 +2748,14 @@ static int abis_rsl_rx_rll(struct msgb *msg) case RSL_MT_REL_IND: /* BTS informs us of having received DISC from MS */ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_REL_IND, &rllh->link_id); + + /* Report to VGCS FSM */ + if (lchan_is_asci(msg->lchan)) { + if (msg->lchan->conn && msg->lchan->conn->vgcs_chan.fi) { + uint8_t cause = GSM0808_CAUSE_CALL_CONTROL; + osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_REL, &cause); + } + } break; case RSL_MT_REL_CONF: /* BTS informs us of having received UA from MS, @@ -1964,15 +2773,77 @@ static int abis_rsl_rx_rll(struct msgb *msg) default: LOG_LCHAN(msg->lchan, LOGL_NOTICE, "SAPI=%u Unknown Abis RLL message type 0x%02x\n", sapi, rllh->c.msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN)); } return rc; } +/* Return an ip.access RTP CSD FMT value (uint8_t) or negative on error. */ +int ipacc_rtp_csd_fmt_transp(const struct channel_mode_and_rate *ch_mode_rate, + const enum rsl_ipac_rtp_csd_format_d format_d) +{ + uint8_t ret = format_d; + + switch (ch_mode_rate->data_rate.t) { + case RSL_CMOD_CSD_T_32k0: + case RSL_CMOD_CSD_T_29k0: + ret |= RSL_IPAC_RTP_CSD_IR_32k << 4; + break; + case RSL_CMOD_CSD_T_14k4: + case RSL_CMOD_CSD_T_9k6: + ret |= RSL_IPAC_RTP_CSD_IR_16k << 4; + break; + case RSL_CMOD_CSD_T_4k8: + case RSL_CMOD_CSD_T_2k4: + case RSL_CMOD_CSD_T_1k2: + case RSL_CMOD_CSD_T_600: + case RSL_CMOD_CSD_T_1200_75: + ret |= RSL_IPAC_RTP_CSD_IR_8k << 4; + break; + default: + return -EINVAL; + } + + return ret; +} + +/* Return an ip.access RTP CSD FMT value (uint8_t) or negative on error. */ +int ipacc_rtp_csd_fmt_non_transp(const struct channel_mode_and_rate *ch_mode_rate, + const enum rsl_ipac_rtp_csd_format_d format_d) +{ + uint8_t ret = format_d; + + switch (ch_mode_rate->data_rate.nt) { + case RSL_CMOD_CSD_NTA_43k5_14k5: + case RSL_CMOD_CSD_NTA_43k5_29k0: + case RSL_CMOD_CSD_NTA_14k5_43k5: + case RSL_CMOD_CSD_NTA_29k0_43k5: + case RSL_CMOD_CSD_NT_43k5: + ret |= RSL_IPAC_RTP_CSD_IR_64k << 4; + break; + case RSL_CMOD_CSD_NTA_29k0_14k5: + case RSL_CMOD_CSD_NTA_14k5_29k0: + case RSL_CMOD_CSD_NT_28k8: + ret |= RSL_IPAC_RTP_CSD_IR_32k << 4; + break; + case RSL_CMOD_CSD_NT_14k5: + case RSL_CMOD_CSD_NT_12k0: + ret |= RSL_IPAC_RTP_CSD_IR_16k << 4; + break; + case RSL_CMOD_CSD_NT_6k0: + ret |= RSL_IPAC_RTP_CSD_IR_8k << 4; + break; + default: + return -EINVAL; + } + + return ret; +} + /* Return an ip.access BTS speech mode value (uint8_t) or negative on error. */ int ipacc_speech_mode(enum gsm48_chan_mode tch_mode, enum gsm_chan_t type) { - switch (tch_mode) { + switch (gsm48_chan_mode_to_non_vamos(tch_mode)) { case GSM48_CMODE_SPEECH_V1: switch (type) { case GSM_LCHAN_TCH_F: @@ -2020,7 +2891,12 @@ void ipacc_speech_mode_set_direction(uint8_t *speech_mode, bool send) /* Return an ip.access BTS payload type value (uint8_t) or negative on error. */ int ipacc_payload_type(enum gsm48_chan_mode tch_mode, enum gsm_chan_t type) { - switch (tch_mode) { + switch (gsm48_chan_mode_to_non_vamos(tch_mode)) { + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + return RTP_PT_CSDATA; case GSM48_CMODE_SPEECH_V1: switch (type) { case GSM_LCHAN_TCH_F: @@ -2105,11 +2981,16 @@ static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv, const port = ntohs(port); lchan->abis_ip.connect_port = port; } + if (TLVP_PRESENT(tv, RSL_IE_OSMO_OSMUX_CID)) { + lchan->abis_ip.osmux.remote_cid_present = true; + lchan->abis_ip.osmux.remote_cid = tlvp_val8(tv, RSL_IE_OSMO_OSMUX_CID, 0); + } LOG_LCHAN(lchan, LOGL_DEBUG, "Rx IPACC %s ACK:" - " BTS=%s:%u conn_id=%u rtp_payload2=0x%02x speech_mode=0x%02x\n", + " BTS=%s:%u conn_id=%u rtp_payload2=0x%02x speech_mode=0x%02x osmux_use=%d osmux_loc_cid=%d\n", label, ip_to_a(lchan->abis_ip.bound_ip), lchan->abis_ip.bound_port, - lchan->abis_ip.conn_id, lchan->abis_ip.rtp_payload2, lchan->abis_ip.speech_mode); + lchan->abis_ip.conn_id, lchan->abis_ip.rtp_payload2, lchan->abis_ip.speech_mode, + lchan->abis_ip.osmux.use, lchan->abis_ip.osmux.local_cid); } /*! Send Issue IPA RSL CRCX to configure the RTP port of the BTS. @@ -2117,22 +2998,43 @@ static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv, const */ int rsl_tx_ipacc_crcx(const struct gsm_lchan *lchan) { - struct msgb *msg = rsl_msgb_alloc(); + struct msgb *msg; struct abis_rsl_dchan_hdr *dh; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return chan_nr; + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_IPAC_CRCX); dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS; - dh->chan_nr = gsm_lchan2chan_nr(lchan); + dh->chan_nr = chan_nr; + + if (lchan->current_ch_indctr == GSM0808_CHAN_DATA) { + msgb_tv_put(msg, RSL_IE_IPAC_RTP_CSD_FMT, lchan->abis_ip.rtp_csd_fmt); + + LOG_LCHAN(lchan, LOGL_DEBUG, + "Sending IPACC CRCX to BTS: rtp_csd_fmt=0x%02x RTP_PAYLOAD=%d (CSD) osmux_use=%d osmux_loc_cid=%d\n", + lchan->abis_ip.rtp_csd_fmt, lchan->abis_ip.rtp_payload, + lchan->abis_ip.osmux.use, lchan->abis_ip.osmux.local_cid); + } else { + /* 0x1- == receive-only, 0x-1 == EFR codec */ + msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode); + + LOG_LCHAN(lchan, LOGL_DEBUG, + "Sending IPACC CRCX to BTS: speech_mode=0x%02x RTP_PAYLOAD=%d osmux_use=%d osmux_loc_cid=%d\n", + lchan->abis_ip.speech_mode, lchan->abis_ip.rtp_payload, + lchan->abis_ip.osmux.use, lchan->abis_ip.osmux.local_cid); + } - /* 0x1- == receive-only, 0x-1 == EFR codec */ - msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode); msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload); + if (lchan->abis_ip.osmux.use) + msgb_tlv_put(msg, RSL_IE_OSMO_OSMUX_CID, 1, &lchan->abis_ip.osmux.local_cid); - LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC CRCX to BTS: speech_mode=0x%02x RTP_PAYLOAD=%d\n", - lchan->abis_ip.speech_mode, lchan->abis_ip.rtp_payload); - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); return abis_rsl_sendmsg(msg); } @@ -2144,26 +3046,39 @@ int rsl_tx_ipacc_crcx(const struct gsm_lchan *lchan) */ struct msgb *rsl_make_ipacc_mdcx(const struct gsm_lchan *lchan, uint32_t dest_ip, uint16_t dest_port) { - struct msgb *msg = rsl_msgb_alloc(); + struct msgb *msg; struct abis_rsl_dchan_hdr *dh; uint32_t *att_ip; + int chan_nr = gsm_lchan2chan_nr(lchan, true); + if (chan_nr < 0) + return NULL; + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_IPAC_MDCX); dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS; - dh->chan_nr = gsm_lchan2chan_nr(lchan); + dh->chan_nr = chan_nr; msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id); msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP); att_ip = (uint32_t *)msgb_put(msg, sizeof(uint32_t)); *att_ip = htonl(dest_ip); msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, dest_port); - msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode); + + if (lchan->current_ch_indctr == GSM0808_CHAN_DATA) + msgb_tv_put(msg, RSL_IE_IPAC_RTP_CSD_FMT, lchan->abis_ip.rtp_csd_fmt); + else + msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode); + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload); if (lchan->abis_ip.rtp_payload2) msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, lchan->abis_ip.rtp_payload2); + if (lchan->abis_ip.osmux.use) + msgb_tlv_put(msg, RSL_IE_OSMO_OSMUX_CID, 1, &lchan->abis_ip.osmux.local_cid); - msg->dst = lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(lchan); return msg; } @@ -2176,14 +3091,27 @@ int rsl_tx_ipacc_mdcx(const struct gsm_lchan *lchan) { struct msgb *msg = rsl_make_ipacc_mdcx(lchan, lchan->abis_ip.connect_ip, lchan->abis_ip.connect_port); - LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC MDCX to BTS:" - " %s:%u rtp_payload=%u rtp_payload2=%u conn_id=%u speech_mode=0x%02x\n", - ip_to_a(lchan->abis_ip.connect_ip), - lchan->abis_ip.connect_port, - lchan->abis_ip.rtp_payload, - lchan->abis_ip.rtp_payload2, - lchan->abis_ip.conn_id, - lchan->abis_ip.speech_mode); + if (!msg) + return -EINVAL; + + if (lchan->current_ch_indctr == GSM0808_CHAN_DATA) + LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC MDCX to BTS:" + " %s:%u rtp_payload=%u (CSD) rtp_payload2=%u conn_id=%u rtp_csd_fmt=0x%02x\n", + ip_to_a(lchan->abis_ip.connect_ip), + lchan->abis_ip.connect_port, + lchan->abis_ip.rtp_payload, + lchan->abis_ip.rtp_payload2, + lchan->abis_ip.conn_id, + lchan->abis_ip.rtp_csd_fmt); + else + LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC MDCX to BTS:" + " %s:%u rtp_payload=%u rtp_payload2=%u conn_id=%u speech_mode=0x%02x\n", + ip_to_a(lchan->abis_ip.connect_ip), + lchan->abis_ip.connect_port, + lchan->abis_ip.rtp_payload, + lchan->abis_ip.rtp_payload2, + lchan->abis_ip.conn_id, + lchan->abis_ip.speech_mode); return abis_rsl_sendmsg(msg); } @@ -2203,7 +3131,12 @@ static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg) * address and port number to which it has bound the given logical * channel */ - rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } + if (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) || !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) || !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) { @@ -2211,6 +3144,15 @@ static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg) return -EINVAL; } + if (!lchan->abis_ip.osmux.use && TLVP_PRESENT(&tv, RSL_IE_OSMO_OSMUX_CID)) { + LOGP(DRSL, LOGL_NOTICE, "Received unexpected IE Osmux CID\n"); + return -EINVAL; + } + if (lchan->abis_ip.osmux.use && !TLVP_PRESENT(&tv, RSL_IE_OSMO_OSMUX_CID)) { + LOGP(DRSL, LOGL_NOTICE, "Mandatory IE Osmux CID missing\n"); + return -EINVAL; + } + ipac_parse_rtp(lchan, &tv, "CRCX"); osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_IPACC_CRCX_ACK, 0); @@ -2223,7 +3165,7 @@ static int abis_rsl_rx_ipacc_crcx_nack(struct msgb *msg) struct e1inp_sign_link *sign_link = msg->dst; struct gsm_lchan *lchan = msg->lchan; - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_IPA_NACK)); if (!lchan->fi_rtp) { LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX NACK message for unconfigured lchan\n"); @@ -2248,7 +3190,12 @@ static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg) * it now tells us the IP address and port number to which it has * connected the given logical channel */ - rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } + ipac_parse_rtp(lchan, &tv, "MDCX"); osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_IPACC_MDCX_ACK, 0); @@ -2261,7 +3208,7 @@ static int abis_rsl_rx_ipacc_mdcx_nack(struct msgb *msg) struct e1inp_sign_link *sign_link = msg->dst; struct gsm_lchan *lchan = msg->lchan; - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_IPA_NACK)); if (!lchan->fi_rtp) { LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX NACK message for unconfigured lchan\n"); @@ -2276,7 +3223,12 @@ static int abis_rsl_rx_ipacc_dlcx_ind(struct msgb *msg) struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); struct tlv_parsed tv; - rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n", + rsl_or_ipac_msg_name(dh->c.msg_type)); + return -EINVAL; + } + LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Rx IPACC DLCX IND%s\n", rsl_cause_name(&tv)); @@ -2289,6 +3241,9 @@ static int abis_rsl_rx_ipacc(struct msgb *msg) struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); int rc = 0; + if (msgb_l2len(msg) < sizeof(*rllh)) + return -EINVAL; + msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, "Abis RSL rx IPACC: "); @@ -2330,7 +3285,7 @@ static int abis_rsl_rx_ipacc(struct msgb *msg) default: LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n", rllh->c.msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN)); break; } @@ -2360,22 +3315,28 @@ static int send_osmocom_style_pdch_chan_act(struct gsm_bts_trx_ts *ts, bool acti } } - msg->dst = ts->trx->rsl_link; + msg->dst = ts->trx->rsl_link_primary; return abis_rsl_sendmsg(msg); } /*! Tx simplified channel (de-)activation message for non-standard ip.access dyn TS PDCH type. */ static int send_ipacc_style_pdch_act(struct gsm_bts_trx_ts *ts, bool activate) { - struct msgb *msg = rsl_msgb_alloc(); + struct msgb *msg; struct abis_rsl_dchan_hdr *dh; + int chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0, false); + if (chan_nr < 0) + return chan_nr; + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, activate ? RSL_MT_IPAC_PDCH_ACT : RSL_MT_IPAC_PDCH_DEACT); dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; - dh->chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0); + dh->chan_nr = chan_nr; - msg->dst = ts->trx->rsl_link; + msg->dst = ts->trx->rsl_link_primary; return abis_rsl_sendmsg(msg); } @@ -2386,7 +3347,7 @@ int rsl_tx_dyn_ts_pdch_act_deact(struct gsm_bts_trx_ts *ts, bool activate) const char *act; switch (ts->pchan_on_init) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: what = "Osmocom dyn TS"; act = activate? "PDCH Chan Activ" : "PDCH Chan RF Release"; @@ -2459,7 +3420,7 @@ int abis_rsl_rcvmsg(struct msgb *msg) default: LOGP(DRSL, LOGL_NOTICE, "unknown RSL message discriminator " "0x%02x\n", rslh->msg_discr); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN)); rc = -EINVAL; } msgb_free(msg); @@ -2480,7 +3441,7 @@ int rsl_etws_pn_command(struct gsm_bts *bts, uint8_t chan_nr, const uint8_t *dat msgb_tlv_put(msg, RSL_IE_SMSCB_MSG, len, data); - msg->dst = bts->c0->rsl_link; + msg->dst = bts->c0->rsl_link_primary; return abis_rsl_sendmsg(msg); } @@ -2506,7 +3467,7 @@ int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number, if (use_extended_cbch) msgb_tv_put(cb_cmd, RSL_IE_SMSCB_CHAN_INDICATOR, 0x01); - cb_cmd->dst = bts->c0->rsl_link; + cb_cmd->dst = bts->c0->rsl_link_primary; return abis_rsl_sendmsg(cb_cmd); } @@ -2520,7 +3481,7 @@ int rsl_nokia_si_begin(struct gsm_bts_trx *trx) ch->msg_discr = ABIS_RSL_MDISC_TRX; ch->msg_type = 0x40; /* Nokia SI Begin */ - msg->dst = trx->rsl_link; + msg->dst = trx->rsl_link_primary; return abis_rsl_sendmsg(msg); } @@ -2536,7 +3497,7 @@ int rsl_nokia_si_end(struct gsm_bts_trx *trx) msgb_tv_put(msg, 0xFD, 0x00); /* Nokia Pagemode Info, No paging reorganisation required */ - msg->dst = trx->rsl_link; + msg->dst = trx->rsl_link_primary; return abis_rsl_sendmsg(msg); } @@ -2553,7 +3514,12 @@ int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduc msgb_tv_put(msg, RSL_IE_CHAN_NR, channel); msgb_tv_put(msg, RSL_IE_BS_POWER, reduction); /* reduction in 2dB steps */ - msg->dst = trx->rsl_link; + msg->dst = trx->rsl_link_primary; return abis_rsl_sendmsg(msg); } + +struct e1inp_sign_link *rsl_chan_link(const struct gsm_lchan *lchan) +{ + return lchan->ts->trx->rsl_link_primary; +} diff --git a/src/osmo-bsc/acc.c b/src/osmo-bsc/acc.c index 06f96c625..80a35766c 100644 --- a/src/osmo-bsc/acc.c +++ b/src/osmo-bsc/acc.c @@ -73,9 +73,8 @@ static void acc_mgr_enable_rotation_cond(struct acc_mgr *acc_mgr) if (!osmo_timer_pending(&acc_mgr->rotate_timer)) osmo_timer_schedule(&acc_mgr->rotate_timer, acc_mgr->rotation_time_sec, 0); } else { - /* No rotation needed, disable rotation timer */ - if (osmo_timer_pending(&acc_mgr->rotate_timer)) - osmo_timer_del(&acc_mgr->rotate_timer); + /* No rotation needed, disable rotation timer (if pending) */ + osmo_timer_del(&acc_mgr->rotate_timer); } } @@ -411,122 +410,47 @@ static void do_acc_ramping_step(void *data) } /* Implements osmo_signal_cbfn() -- trigger or abort ACC ramping upon changes RF lock state. */ -static int acc_ramp_nm_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) +static int acc_ramp_nm_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) { - struct nm_statechg_signal_data *nsd = signal_data; - struct acc_ramp *acc_ramp = handler_data; - struct gsm_bts_trx *trx = NULL; - bool trigger_ramping = false, abort_ramping = false; - - /* Handled signals map to an Administrative State Change ACK, or a State Changed Event Report. */ - if (signal != S_NM_STATECHG_ADM && signal != S_NM_STATECHG_OPER) + struct nm_running_chg_signal_data *nsd; + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + if (signal != S_NM_RUNNING_CHG) return 0; - - if (nsd->obj_class != NM_OC_RADIO_CARRIER) + nsd = signal_data; + bts = nsd->bts; + switch (nsd->obj_class) { + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *)nsd->obj; + break; + case NM_OC_BASEB_TRANSC: + trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj); + break; + default: return 0; - - trx = nsd->obj; - - LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: administrative state %s -> %s\n", - get_value_string(abis_nm_adm_state_names, nsd->old_state->administrative), - get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative)); - LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: operational state %s -> %s\n", - abis_nm_opstate_name(nsd->old_state->operational), - abis_nm_opstate_name(nsd->new_state->operational)); + } /* We only care about state changes of the first TRX. */ - if (trx->nr != 0) + if (trx != trx->bts->c0) return 0; - /* RSL must already be up. We cannot send RACH system information to the BTS otherwise. */ - if (trx->rsl_link == NULL) { - LOG_TRX(trx, DRSL, LOGL_DEBUG, - "ACC RAMP: ignoring state change because RSL link is down\n"); - return 0; - } - - /* Trigger or abort ACC ramping based on the new state of this TRX. */ - if (nsd->old_state->administrative != nsd->new_state->administrative) { - switch (nsd->new_state->administrative) { - case NM_STATE_UNLOCKED: - if (nsd->old_state->operational != nsd->new_state->operational) { - /* - * Administrative and operational state have both changed. - * Trigger ramping only if TRX 0 will be both enabled and unlocked. - */ - if (nsd->new_state->operational == NM_OPSTATE_ENABLED) - trigger_ramping = true; - else - LOG_TRX(trx, DRSL, LOGL_DEBUG, - "ACC RAMP: ignoring state change because TRX is " - "transitioning into operational state '%s'\n", - abis_nm_opstate_name(nsd->new_state->operational)); - } else { - /* - * Operational state has not changed. - * Trigger ramping only if TRX 0 is already usable. - */ - if (trx_is_usable(trx)) - trigger_ramping = true; - else - LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: ignoring state change " - "because TRX is not usable\n"); - } - break; - case NM_STATE_LOCKED: - case NM_STATE_SHUTDOWN: - abort_ramping = true; - break; - case NM_STATE_NULL: - default: - LOG_TRX(trx, DRSL, LOGL_ERROR, "ACC RAMP: unrecognized administrative state '0x%x' " - "reported for TRX 0\n", nsd->new_state->administrative); - break; - } - } - if (nsd->old_state->operational != nsd->new_state->operational) { - switch (nsd->new_state->operational) { - case NM_OPSTATE_ENABLED: - if (nsd->old_state->administrative != nsd->new_state->administrative) { - /* - * Administrative and operational state have both changed. - * Trigger ramping only if TRX 0 will be both enabled and unlocked. - */ - if (nsd->new_state->administrative == NM_STATE_UNLOCKED) - trigger_ramping = true; - else - LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: ignoring state change " - "because TRX is transitioning into administrative state '%s'\n", - get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative)); - } else { - /* - * Administrative state has not changed. - * Trigger ramping only if TRX 0 is already unlocked. - */ - if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) - trigger_ramping = true; - else - LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: ignoring state change " - "because TRX is in administrative state '%s'\n", - get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative)); - } - break; - case NM_OPSTATE_DISABLED: - abort_ramping = true; - break; - case NM_OPSTATE_NULL: - default: - LOG_TRX(trx, DRSL, LOGL_ERROR, "ACC RAMP: unrecognized operational state '0x%x' " - "reported for TRX 0\n", nsd->new_state->administrative); - break; + LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: nm_obj=%s running=%u\n", + get_value_string(abis_nm_obj_class_names, nsd->obj_class), nsd->running); + if (nsd->running) { + /* Trigger ramping only if TRX 0 is already usable. That usually + * means RCARRIER+BBTRANSC NM objects are running (op=enabled + * adm=unlocked) */ + if (trx_is_usable(trx)) { + LOG_BTS(bts, DPAG, LOGL_INFO, "ACC RAMP: C0 becomes available\n"); + acc_ramp_trigger(&trx->bts->acc_ramp); + } else { + LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: ignoring state change " + "because TRX is not usable\n"); } + } else { + acc_ramp_abort(&trx->bts->acc_ramp); } - - if (trigger_ramping) - acc_ramp_trigger(acc_ramp); - else if (abort_ramping) - acc_ramp_abort(acc_ramp); - return 0; } @@ -548,7 +472,6 @@ void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts) acc_ramp->chan_load_lower_threshold = ACC_RAMP_CHAN_LOAD_THRESHOLD_LOW; acc_ramp->chan_load_upper_threshold = ACC_RAMP_CHAN_LOAD_THRESHOLD_UP; osmo_timer_setup(&acc_ramp->step_timer, do_acc_ramping_step, acc_ramp); - osmo_signal_register_handler(SS_NM, acc_ramp_nm_sig_cb, acc_ramp); } /*! @@ -641,8 +564,12 @@ void acc_ramp_trigger(struct acc_ramp *acc_ramp) */ void acc_ramp_abort(struct acc_ramp *acc_ramp) { - if (osmo_timer_pending(&acc_ramp->step_timer)) - osmo_timer_del(&acc_ramp->step_timer); + osmo_timer_del(&acc_ramp->step_timer); acc_mgr_set_len_allowed_ramp(&acc_ramp->bts->acc_mgr, 10); } + +void acc_ramp_global_init(void) +{ + osmo_signal_register_handler(SS_NM, acc_ramp_nm_sig_cb, NULL); +} diff --git a/src/osmo-bsc/arfcn_range_encode.c b/src/osmo-bsc/arfcn_range_encode.c deleted file mode 100644 index 54d98a967..000000000 --- a/src/osmo-bsc/arfcn_range_encode.c +++ /dev/null @@ -1,340 +0,0 @@ -/* gsm 04.08 system information (si) encoding and decoding - * 3gpp ts 04.08 version 7.21.0 release 1998 / etsi ts 100 940 v7.21.0 */ - -/* - * (C) 2012 Holger Hans Peter Freyther - * (C) 2012 by On-Waves - * 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 <http://www.gnu.org/licenses/>. - */ - -#include <osmocom/bsc/arfcn_range_encode.h> -#include <osmocom/bsc/debug.h> - -#include <osmocom/gsm/protocol/gsm_04_08.h> - -#include <osmocom/core/utils.h> - -#include <errno.h> - -static inline int greatest_power_of_2_lesser_or_equal_to(int index) -{ - int power_of_2 = 1; - - do { - power_of_2 *= 2; - } while (power_of_2 <= index); - - /* now go back one step */ - return power_of_2 / 2; -} - -static inline int mod(int data, int range) -{ - int res = data % range; - while (res < 0) - res += range; - return res; -} - -/** - * Determine at which index to split the ARFCNs to create an - * equally size partition for the given range. Return -1 if - * no such partition exists. - */ -int range_enc_find_index(enum gsm48_range range, const int *freqs, const int size) -{ - int i, j, n; - - const int RANGE_DELTA = (range - 1) / 2; - - for (i = 0; i < size; ++i) { - n = 0; - for (j = 0; j < size; ++j) { - if (mod(freqs[j] - freqs[i], range) <= RANGE_DELTA) - n += 1; - } - - if (n - 1 == (size - 1) / 2) - return i; - } - - return -1; -} - -/* Worker for range_enc_arfcns(), do not call directly. */ -int _range_enc_arfcns(enum gsm48_range range, - const int *arfcns, int size, int *out, - const int index) -{ - int split_at; - int i; - - /* - * The below is a GNU extension and we can remove it when - * we move to a quicksort like in-situ swap with the pivot. - */ - int arfcns_left[size / 2]; - int arfcns_right[size / 2]; - int l_size; - int r_size; - int l_origin; - int r_origin; - - /* Now do the processing */ - split_at = range_enc_find_index(range, arfcns, size); - if (split_at < 0) - return -EINVAL; - - /* we now know where to split */ - out[index] = 1 + arfcns[split_at]; - - /* calculate the work that needs to be done for the leafs */ - l_origin = mod(arfcns[split_at] + ((range - 1) / 2) + 1, range); - r_origin = mod(arfcns[split_at] + 1, range); - for (i = 0, l_size = 0, r_size = 0; i < size; ++i) { - if (mod(arfcns[i] - l_origin, range) < range / 2) - arfcns_left[l_size++] = mod(arfcns[i] - l_origin, range); - if (mod(arfcns[i] - r_origin, range) < range / 2) - arfcns_right[r_size++] = mod(arfcns[i] - r_origin, range); - } - - /* - * Now recurse and we need to make this iterative... but as the - * tree is balanced the stack will not be too deep. - */ - if (l_size) - range_enc_arfcns(range / 2, arfcns_left, l_size, - out, index + greatest_power_of_2_lesser_or_equal_to(index + 1)); - if (r_size) - range_enc_arfcns((range - 1) / 2, arfcns_right, r_size, - out, index + (2 * greatest_power_of_2_lesser_or_equal_to(index + 1))); - return 0; -} - -/** - * Range encode the ARFCN list. - * \param range The range to use. - * \param arfcns The list of ARFCNs - * \param size The size of the list of ARFCNs - * \param out Place to store the W(i) output. - */ -int range_enc_arfcns(enum gsm48_range range, - const int *arfcns, int size, int *out, - const int index) -{ - if (size <= 0) - return 0; - - if (size == 1) { - out[index] = 1 + arfcns[0]; - return 0; - } - - return _range_enc_arfcns(range, arfcns, size, out, index); -} - -/* - * The easiest is to use f0 == arfcns[0]. This means that under certain - * circumstances we can encode less ARFCNs than possible with an optimal f0. - * - * TODO: Solve the optimisation problem and pick f0 so that the max distance - * is the smallest. Taking into account the modulo operation. I think picking - * size/2 will be the optimal arfcn. - */ -/** - * This implements the range determination as described in GSM 04.08 J4. The - * result will be a base frequency f0 and the range to use. Note that for range - * 1024 encoding f0 always refers to ARFCN 0 even if it is not an element of - * the arfcns list. - * - * \param[in] arfcns The input frequencies, they must be sorted, lowest number first - * \param[in] size The length of the array - * \param[out] f0 The selected F0 base frequency. It might not be inside the list - */ -int range_enc_determine_range(const int *arfcns, const int size, int *f0) -{ - int max = 0; - - /* don't dereference arfcns[] array if size is 0 */ - if (size == 0) - return ARFCN_RANGE_128; - - /* - * Go for the easiest. And pick arfcns[0] == f0. - */ - max = arfcns[size - 1] - arfcns[0]; - *f0 = arfcns[0]; - - if (max < 128 && size <= 29) - return ARFCN_RANGE_128; - if (max < 256 && size <= 22) - return ARFCN_RANGE_256; - if (max < 512 && size <= 18) - return ARFCN_RANGE_512; - if (max < 1024 && size <= 17) { - *f0 = 0; - return ARFCN_RANGE_1024; - } - - return ARFCN_RANGE_INVALID; -} - -static void write_orig_arfcn(uint8_t *chan_list, int f0) -{ - chan_list[0] |= (f0 >> 9) & 1; - chan_list[1] = (f0 >> 1); - chan_list[2] = (f0 & 1) << 7; -} - -static void write_all_wn(uint8_t *chan_list, int bit_offs, - int *w, int w_size, int w1_len) -{ - int octet_offs = 0; /* offset into chan_list */ - int wk_len = w1_len; /* encoding size in bits of w[k] */ - int k; /* 1 based */ - int level = 0; /* tree level, top level = 0 */ - int lvl_left = 1; /* nodes per tree level */ - - /* W(2^i) to W(2^(i+1)-1) are on w1_len-i bits when present */ - - for (k = 1; k <= w_size; k++) { - int wk_left = wk_len; - DEBUGP(DRR, - "k=%d, wk_len=%d, offs=%d:%d, level=%d, " - "lvl_left=%d\n", - k, wk_len, octet_offs, bit_offs, level, lvl_left); - - while (wk_left > 0) { - int cur_bits = 8 - bit_offs; - int cur_mask; - int wk_slice; - - if (cur_bits > wk_left) - cur_bits = wk_left; - - cur_mask = ((1 << cur_bits) - 1); - - DEBUGP(DRR, - " wk_left=%d, cur_bits=%d, offs=%d:%d\n", - wk_left, cur_bits, octet_offs, bit_offs); - - /* advance */ - wk_left -= cur_bits; - bit_offs += cur_bits; - - /* right aligned wk data for current out octet */ - wk_slice = (w[k-1] >> wk_left) & cur_mask; - - /* cur_bits now contains the number of bits - * that are to be copied from wk to the chan_list. - * wk_left is set to the number of bits that must - * not yet be copied. - * bit_offs points after the bit area that is going to - * be overwritten: - * - * wk_left - * | - * v - * wk: WWWWWWWWWWW - * |||||<-- wk_slice, cur_bits=5 - * --WWWWW- - * ^ - * | - * bit_offs - */ - - DEBUGP(DRR, - " wk=%02x, slice=%02x/%02x, cl=%02x\n", - w[k-1], wk_slice, cur_mask, wk_slice << (8 - bit_offs)); - - chan_list[octet_offs] &= ~(cur_mask << (8 - bit_offs)); - chan_list[octet_offs] |= wk_slice << (8 - bit_offs); - - /* adjust output */ - if (bit_offs == 8) { - bit_offs = 0; - octet_offs += 1; - } - } - - /* adjust bit sizes */ - lvl_left -= 1; - if (!lvl_left) { - /* completed tree level, advance to next */ - level += 1; - lvl_left = 1 << level; - wk_len -= 1; - } - } -} - -int range_enc_range128(uint8_t *chan_list, int f0, int *w) -{ - chan_list[0] = 0x8C; - write_orig_arfcn(chan_list, f0); - - write_all_wn(&chan_list[2], 1, w, 28, 7); - return 0; -} - -int range_enc_range256(uint8_t *chan_list, int f0, int *w) -{ - chan_list[0] = 0x8A; - write_orig_arfcn(chan_list, f0); - - write_all_wn(&chan_list[2], 1, w, 21, 8); - return 0; -} - -int range_enc_range512(uint8_t *chan_list, int f0, int *w) -{ - chan_list[0] = 0x88; - write_orig_arfcn(chan_list, f0); - - write_all_wn(&chan_list[2], 1, w, 17, 9); - return 0; -} - -int range_enc_range1024(uint8_t *chan_list, int f0, int f0_included, int *w) -{ - chan_list[0] = 0x80 | (f0_included << 2); - - write_all_wn(&chan_list[0], 6, w, 16, 10); - return 0; -} - -int range_enc_filter_arfcns(int *arfcns, - const int size, const int f0, int *f0_included) -{ - int i, j = 0; - *f0_included = 0; - - for (i = 0; i < size; ++i) { - /* - * Appendix J.4 says the following: - * All frequencies except F(0), minus F(0) + 1. - * I assume we need to exclude it here. - */ - if (arfcns[i] == f0) { - *f0_included = 1; - continue; - } - - arfcns[j++] = mod(arfcns[i] - (f0 + 1), 1024); - } - - return j; -} diff --git a/src/osmo-bsc/assignment_fsm.c b/src/osmo-bsc/assignment_fsm.c index fde028ed9..5e98a28e8 100644 --- a/src/osmo-bsc/assignment_fsm.c +++ b/src/osmo-bsc/assignment_fsm.c @@ -32,15 +32,17 @@ #include <osmocom/bsc/osmo_bsc_lcls.h> #include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/gsm_08_08.h> +#include <osmocom/bsc/gsm_04_08_rr.h> #include <osmocom/bsc/lchan_select.h> #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/bts.h> - +#include <osmocom/bsc/bsc_stats.h> +#include <osmocom/bsc/lchan.h> #include <osmocom/bsc/assignment_fsm.h> static struct osmo_fsm assignment_fsm; -struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi) +static struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi) { OSMO_ASSERT(fi); OSMO_ASSERT(fi->fsm == &assignment_fsm); @@ -49,10 +51,10 @@ struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi) } static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = { - [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = { .T=10 }, - [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { .keep_timer=true }, - [ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED] = { .keep_timer=true }, - [ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T=23042 }, + [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = { .T = 10 }, + [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { .keep_timer = true }, + [ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED] = { .keep_timer = true }, + [ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = -9 }, }; /* Transition to a state, using the T timer defined in assignment_fsm_timeouts. @@ -72,7 +74,7 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = { osmo_fsm_inst_state_name(fi), gsm0808_cause_name(cause), ## args); \ assignment_count_result(CTR_ASSIGNMENT_ERROR); \ on_assignment_failure(_conn); \ - } while(0) + } while (0) /* Assume presence of local var 'conn' as struct gsm_subscriber_connection */ #define assignment_count(counter) do { \ @@ -80,10 +82,31 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = { LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: %s %s\n", \ bsc_ctr_description[BSC_##counter].name, \ bsc_ctr_description[BSC_##counter].description); \ - rate_ctr_inc(&conn->network->bsc_ctrs->ctr[BSC_##counter]); \ - if (bts) \ - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_##counter]); \ - } while(0) + rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bsc_ctrs, BSC_##counter)); \ + if (bts) { \ + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter)); \ + switch (gsm48_chan_mode_to_non_vamos(conn->assignment.req.ch_mode_rate_list[0].chan_mode)) { \ + case GSM48_CMODE_SIGN: \ + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter##_SIGN)); \ + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: bts%u %s %s\n", \ + bts->nr, \ + bts_ctr_description[BTS_##counter##_SIGN].name, \ + bts_ctr_description[BTS_##counter##_SIGN].description); \ + break; \ + case GSM48_CMODE_SPEECH_V1: \ + case GSM48_CMODE_SPEECH_EFR: \ + case GSM48_CMODE_SPEECH_AMR: \ + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter##_SPEECH)); \ + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: bts%u %s %s\n", \ + bts->nr, \ + bts_ctr_description[BTS_##counter##_SPEECH].name, \ + bts_ctr_description[BTS_##counter##_SPEECH].description); \ + break; \ + default: \ + break; \ + } \ + } \ + } while (0) #define assignment_count_result(counter) do { \ if (!conn->assignment.result_rate_ctr_done) { \ @@ -94,21 +117,24 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = { "result rate counter already recorded, NOT counting as: %s %s\n", \ bsc_ctr_description[BSC_##counter].name, \ bsc_ctr_description[BSC_##counter].description); \ - } while(0) + } while (0) void assignment_reset(struct gsm_subscriber_connection *conn) { if (conn->assignment.new_lchan) { struct gsm_lchan *lchan = conn->assignment.new_lchan; conn->assignment.new_lchan = NULL; - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); } if (conn->assignment.created_ci_for_msc) { - gscon_forget_mgw_endpoint_ci(conn, conn->assignment.created_ci_for_msc); + /* Store ci pointer locally, because gscon_forget_mgw_endpoint_ci() NULLs + * conn->assignment.created_ci_for_msc. */ + struct osmo_mgcpc_ep_ci *ci = conn->assignment.created_ci_for_msc; + gscon_forget_mgw_endpoint_ci(conn, ci); /* If this is the last endpoint released, the mgw_endpoint_fsm will terminate and tell * the gscon about it. */ - osmo_mgcpc_ep_ci_dlcx(conn->assignment.created_ci_for_msc); + osmo_mgcpc_ep_ci_dlcx(ci); } conn->assignment = (struct assignment_fsm_data){ @@ -118,13 +144,18 @@ void assignment_reset(struct gsm_subscriber_connection *conn) static void on_assignment_failure(struct gsm_subscriber_connection *conn) { - struct msgb *resp = gsm0808_create_assignment_failure(conn->assignment.failure_cause, NULL); - - if (!resp) { - LOG_ASSIGNMENT(conn, LOGL_ERROR, "Unable to compose BSSMAP Assignment Failure message\n"); - } else { - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_FAILURE]); - gscon_sigtran_send(conn, resp); + /* Send Assignment Failure to MSC only when the assignment was requested via BSSAP. Do not send anything to the + * MSC if re-assignment was requested for congestion resolution, for VAMOS multiplexing, or by VTY. */ + if (conn->assignment.req.assign_for == ASSIGN_FOR_BSSMAP_REQ) { + struct msgb *resp = gsm0808_create_assignment_failure(conn->assignment.failure_cause, NULL); + + if (!resp) { + LOG_ASSIGNMENT(conn, LOGL_ERROR, "Unable to compose BSSMAP Assignment Failure message\n"); + } else { + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, + MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_FAILURE)); + gscon_sigtran_send(conn, resp); + } } /* If assignment failed as early as in assignment_fsm_start(), there may not be an fi yet. */ @@ -134,7 +165,7 @@ static void on_assignment_failure(struct gsm_subscriber_connection *conn) } } -static void _gsm0808_ass_compl_extend_osmux(struct msgb *msg, uint8_t cid) +void bssap_extend_osmux(struct msgb *msg, uint8_t cid) { OSMO_ASSERT(msg->l3h[1] == msgb_l3len(msg) - 2); /*TL not in len */ msgb_tv_put(msg, GSM0808_IE_OSMO_OSMUX_CID, cid); @@ -155,30 +186,40 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) struct gsm_lchan *lchan = conn->lchan; struct osmo_fsm_inst *fi = conn->fi; - chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode); + if (!lchan) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, "Assignment interrupted: primary lchan lost"); + return; + } + + chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode); if (!chosen_channel) { assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, "Unable to compose Chosen Channel for mode=%s type=%s", - get_value_string(gsm48_chan_mode_names, lchan->tch_mode), - gsm_lchant_name(lchan->type)); + get_value_string(gsm48_chan_mode_names, lchan->current_ch_mode_rate.chan_mode), + gsm_chan_t_name(lchan->type)); return; } - /* Generate voice related fields */ - if (conn->assignment.requires_voice_stream) { - perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode); - - if (gscon_is_aoip(conn)) { - if (!osmo_mgcpc_ep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc, - &addr_local)) { - assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, - "Unable to compose RTP address of MGW -> MSC"); - return; - } - addr_local_p = &addr_local; + if (gscon_is_aoip(conn) && bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr)) { + if (!osmo_mgcpc_ep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc, + &addr_local)) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to compose RTP address of MGW -> MSC"); + return; } + addr_local_p = &addr_local; + } + + /* Generate rtp related fields */ + switch (conn->assignment.ch_indctr) { + case GSM0808_CHAN_SPEECH: + perm_spch = gsm0808_permitted_speech(lchan->type, lchan->current_ch_mode_rate.chan_mode); - if (gscon_is_aoip(conn) && conn->assignment.req.use_osmux) { + /* below is AoIP specific logic */ + if (!gscon_is_aoip(conn)) + break; + + if (conn->assignment.req.use_osmux) { if (!osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(conn->user_plane.mgw_endpoint_ci_msc, &osmux_cid)) { assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, @@ -187,19 +228,33 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) } } - /* Only AoIP networks include a speech codec (choosen) in the - * assignment complete message. */ - if (gscon_is_aoip(conn)) { - /* Extrapolate speech codec from speech mode */ - gsm0808_speech_codec_from_chan_type(&sc, perm_spch); - sc.cfg = conn->lchan->activate.info.s15_s0; - sc_ptr = ≻ - } + /* Extrapolate speech codec from speech mode */ + gsm0808_speech_codec_from_chan_type(&sc, perm_spch); + sc.cfg = conn->lchan->current_ch_mode_rate.s15_s0; + sc_ptr = ≻ + break; + case GSM0808_CHAN_DATA: + /* below is AoIP specific logic */ + if (!gscon_is_aoip(conn)) + break; + + /* The coding of Speech Codec Element for the CSData Codec Type + * is defined in 3GPP TS 48.008 section 3.2.2.103 */ + sc = (struct gsm0808_speech_codec) { + .pi = true, /* PI indicates CSDoIP support */ + .pt = false, /* PT indicates CSDoTDM support */ + .type = GSM0808_SCT_CSD, + .cfg = 0, /* R2/R3 not set (redundancy not supported) */ + }; + sc_ptr = ≻ + break; + default: + break; } resp = gsm0808_create_ass_compl2(lchan->abis_ip.ass_compl.rr_cause, chosen_channel, - lchan->encr.alg_id, perm_spch, + ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n), perm_spch, addr_local_p, sc_ptr, NULL, lcls_get_status(conn)); if (!resp) { @@ -208,11 +263,11 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) return; } - if (gscon_is_aoip(conn) && conn->assignment.requires_voice_stream && + if (gscon_is_aoip(conn) && bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr) && conn->assignment.req.use_osmux) - _gsm0808_ass_compl_extend_osmux(resp, osmux_cid); + bssap_extend_osmux(resp, osmux_cid); - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_COMPLETE]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_COMPLETE)); rc = gscon_sigtran_send(conn, resp); if (rc) { assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, @@ -224,55 +279,77 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) static void assignment_success(struct gsm_subscriber_connection *conn) { - /* Take on the new lchan */ - gscon_change_primary_lchan(conn, conn->assignment.new_lchan); - conn->assignment.new_lchan = NULL; + struct gsm_bts *bts; + bool lchan_changed = (conn->assignment.new_lchan != NULL && !conn->assignment.req.vgcs); + + /* Take on the new lchan. If there only was a Channel Mode Modify, then there is no new lchan to take on. + * In case of VGCS/VBS channel, the assignment is handled by its state machine. This subscriber connection will + * be released by MSC. */ + if (lchan_changed) { + gscon_change_primary_lchan(conn, conn->assignment.new_lchan); + + OSMO_ASSERT((bts = conn_get_bts(conn)) != NULL); + if (is_siemens_bts(bts) && ts_is_tch(conn->lchan->ts)) { + /* HACK: store the actual Classmark 2 LV from the subscriber and use it here! */ + uint8_t cm2_lv[] = { 0x02, 0x00, 0x00 }; + send_siemens_mrpci(conn->lchan, cm2_lv); + } - /* apply LCLS configuration (if any) */ - lcls_apply_config(conn); + /* apply LCLS configuration (if any) */ + lcls_apply_config(conn); + } + conn->assignment.new_lchan = NULL; - send_assignment_complete(conn); - /* If something went wrong during send_assignment_complete(), the fi will be gone from - * error handling in there. Almost a success, but then again the whole thing failed. */ - if (!conn->assignment.fi) { - /* The lchan was ready, and we failed to tell the MSC about it. By releasing this lchan, - * the conn will notice that its primary lchan is gone and should clean itself up. */ - lchan_release(conn->lchan, true, true, RSL_ERR_EQUIPMENT_FAIL); - return; + if (conn->assignment.req.assign_for == ASSIGN_FOR_BSSMAP_REQ) { + send_assignment_complete(conn); + /* If something went wrong during send_assignment_complete(), the fi will be gone from + * error handling in there. Almost a success, but then again the whole thing failed. */ + if (!conn->assignment.fi) { + /* The lchan was ready, and we failed to tell the MSC about it. By releasing this lchan, + * the conn will notice that its primary lchan is gone and should clean itself up. */ + lchan_release(conn->lchan, true, true, RSL_ERR_EQUIPMENT_FAIL, + gscon_last_eutran_plmn(conn)); + return; + } } /* Rembered this only for error handling: should assignment fail, assignment_reset() will release * the MGW endpoint right away. If successful, the conn continues to use the endpoint. */ conn->assignment.created_ci_for_msc = NULL; - /* New RTP information is now accepted */ + /* New RTP information is now accepted. If there is no RTP stream, this information is zero / empty. Either way + * store the result of this assignment. */ conn->user_plane.msc_assigned_cic = conn->assignment.req.msc_assigned_cic; osmo_strlcpy(conn->user_plane.msc_assigned_rtp_addr, conn->assignment.req.msc_rtp_addr, sizeof(conn->user_plane.msc_assigned_rtp_addr)); conn->user_plane.msc_assigned_rtp_port = conn->assignment.req.msc_rtp_port; + assignment_count_result(CTR_ASSIGNMENT_COMPLETED); + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Assignment successful\n"); osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_REGULAR, 0); - - assignment_count_result(CTR_ASSIGNMENT_COMPLETED); } -static void assignment_fsm_update_id(struct gsm_subscriber_connection *conn) +void assignment_fsm_update_id(struct gsm_subscriber_connection *conn) { - struct gsm_lchan *new_lchan = conn->assignment.new_lchan; + /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan. + * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */ + struct gsm_lchan *new_lchan = (conn->assignment.new_lchan ? : conn->lchan); if (!new_lchan) { osmo_fsm_inst_update_id(conn->assignment.fi, conn->fi->id); return; } - osmo_fsm_inst_update_id_f(conn->assignment.fi, "%s_%u-%u-%u-%s%s%s-%u", - conn->fi->id, - new_lchan->ts->trx->bts->nr, new_lchan->ts->trx->nr, new_lchan->ts->nr, - gsm_pchan_id(new_lchan->ts->pchan_on_init), - (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is)? "" : "as", - (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is)? "" - : gsm_pchan_id(new_lchan->ts->pchan_is), - new_lchan->nr); + osmo_fsm_inst_update_id_f_sanitize(conn->assignment.fi, '_', "%s_%u-%u-%u-%s%s%s-%s%u", + conn->fi->id, + new_lchan->ts->trx->bts->nr, new_lchan->ts->trx->nr, new_lchan->ts->nr, + gsm_pchan_name(new_lchan->ts->pchan_on_init), + (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is) ? "" : "as", + (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is) ? + "" : gsm_pchan_name(new_lchan->ts->pchan_is), + new_lchan->vamos.is_secondary ? "shadow" : "", + new_lchan->nr - (new_lchan->vamos.is_secondary ? + new_lchan->ts->max_primary_lchans : 0)); } static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct channel_mode_and_rate *ch_mode_rate) @@ -280,7 +357,7 @@ static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct chann enum gsm48_chan_mode chan_mode = ch_mode_rate->chan_mode; enum channel_rate chan_rate = ch_mode_rate->chan_rate; - switch (chan_mode) { + switch (gsm48_chan_mode_to_non_vamos(chan_mode)) { case GSM48_CMODE_SIGN: switch (type) { case GSM_LCHAN_TCH_F: return chan_rate == CH_RATE_FULL; @@ -315,67 +392,58 @@ static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct chann } } -void assignment_fsm_init() +static __attribute__((constructor)) void assignment_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&assignment_fsm) == 0); } -static int check_requires_voice(bool *requires_voice, enum gsm48_chan_mode chan_mode) +static int chan_mode_to_ch_indctr(enum gsm48_chan_mode chan_mode) { - *requires_voice = false; - - switch (chan_mode) { + switch (gsm48_chan_mode_to_non_vamos(chan_mode)) { + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + return GSM0808_CHAN_DATA; case GSM48_CMODE_SPEECH_V1: case GSM48_CMODE_SPEECH_EFR: case GSM48_CMODE_SPEECH_AMR: - *requires_voice = true; - break; + return GSM0808_CHAN_SPEECH; case GSM48_CMODE_SIGN: - *requires_voice = false; - break; + return GSM0808_CHAN_SIGN; default: return -EINVAL; } - - return 0; } -/* Check if the incoming assignment requests requires a voice stream or not, - * we will look at the preferred and the alternate channel mode and also make - * sure that both are consistent. */ -static int check_requires_voice_stream(struct gsm_subscriber_connection *conn) +/* Check if the incoming assignment request has a channel mode that is + * inconsistent with ch_indctr, e.g. GSM48_CMODE_DATA_14k5 and + * GSM0808_CHAN_SPEECH */ +static int check_chan_mode_rate_against_ch_indctr(struct gsm_subscriber_connection *conn) { - bool requires_voice_pref = false, requires_voice_alt; struct assignment_request *req = &conn->assignment.req; struct osmo_fsm_inst *fi = conn->fi; - int i, rc; - - /* When the assignment request indicates that there is an alternate - * rate available (e.g. "Full or Half rate channel, Half rate - * preferred..."), then both must be either voice or either signalling, - * a mismatch is not permitted */ + int i; + int rc; for (i = 0; i < req->n_ch_mode_rate; i++) { - rc = check_requires_voice(&requires_voice_alt, req->ch_mode_rate[i].chan_mode); + rc = chan_mode_to_ch_indctr(req->ch_mode_rate_list[i].chan_mode); if (rc < 0) { assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, "Channel mode not supported (prev level %d): %s", i, - gsm48_chan_mode_name(req->ch_mode_rate[i].chan_mode)); + gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode)); return -EINVAL; } - if (i==0) - requires_voice_pref = requires_voice_alt; - else if (requires_voice_alt != requires_voice_pref) { + if (rc != req->ch_indctr) { assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, - "Inconsistent channel modes: %s != %s", - gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode), - gsm48_chan_mode_name(req->ch_mode_rate[i].chan_mode)); + "Channel mode %s has ch_indctr %d, channel type has ch_indctr %d", + gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode), + rc, req->ch_indctr); return -EINVAL; } } - conn->assignment.requires_voice_stream = requires_voice_pref; return 0; } @@ -391,15 +459,69 @@ static bool reuse_existing_lchan(struct gsm_subscriber_connection *conn) /* Check if the currently existing lchan is compatible with the * preferred rate/codec. */ - for (i = 0; i < req->n_ch_mode_rate; i++) - if (lchan_type_compat_with_mode(conn->lchan->type, &req->ch_mode_rate[i])) { - conn->lchan->ch_mode_rate = req->ch_mode_rate[i]; - return true; - } + for (i = 0; i < req->n_ch_mode_rate; i++) { + if (!lchan_type_compat_with_mode(conn->lchan->type, &req->ch_mode_rate_list[i])) + continue; + conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i]; + return true; + } return false; } +static int _reassignment_request(enum assign_for assign_for, struct gsm_lchan *lchan, struct gsm_lchan *to_lchan, + enum gsm_chan_t new_lchan_type, int tsc_set, int tsc) +{ + struct gsm_subscriber_connection *conn = lchan->conn; + struct assignment_request req = { + .assign_for = assign_for, + .aoip = gscon_is_aoip(conn), + .msc_assigned_cic = conn->user_plane.msc_assigned_cic, + .msc_rtp_port = conn->user_plane.msc_assigned_rtp_port, + .n_ch_mode_rate = 1, + .ch_mode_rate_list = { lchan->current_ch_mode_rate }, + .target_lchan = to_lchan, + .tsc_set = { + .present = (tsc_set >= 0), + .val = tsc_set, + }, + .tsc = { + .present = (tsc >= 0), + .val = tsc, + }, + + .ch_indctr = chan_mode_to_ch_indctr(lchan->current_ch_mode_rate.chan_mode), + }; + + if (to_lchan) + new_lchan_type = to_lchan->type; + req.ch_mode_rate_list[0].chan_rate = chan_t_to_chan_rate(new_lchan_type); + /* lchan activation will automatically convert chan_mode to a VAMOS equivalent if required. + * So rather always pass the plain non-VAMOS mode. */ + req.ch_mode_rate_list[0].chan_mode = gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode); + + OSMO_STRLCPY_ARRAY(req.msc_rtp_addr, conn->user_plane.msc_assigned_rtp_addr); + + if (conn->user_plane.mgw_endpoint_ci_msc) { + req.use_osmux = osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(conn->user_plane.mgw_endpoint_ci_msc, + &req.osmux_cid); + } + + return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req); +} + +int reassignment_request_to_lchan(enum assign_for assign_for, struct gsm_lchan *lchan, struct gsm_lchan *to_lchan, + int tsc_set, int tsc) +{ + return _reassignment_request(assign_for, lchan, to_lchan, 0, tsc_set, tsc); +} + +int reassignment_request_to_chan_type(enum assign_for assign_for, struct gsm_lchan *lchan, + enum gsm_chan_t new_lchan_type) +{ + return _reassignment_request(assign_for, lchan, NULL, new_lchan_type, -1, -1); +} + void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, struct assignment_request *req) { @@ -409,7 +531,6 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts [CH_RATE_FULL] = "FR", }; struct osmo_fsm_inst *fi; - struct lchan_activate_info info; int i; OSMO_ASSERT(conn); @@ -417,8 +538,6 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts OSMO_ASSERT(!conn->assignment.fi); OSMO_ASSERT(!conn->assignment.new_lchan); - assignment_count(CTR_ASSIGNMENT_ATTEMPTED); - fi = osmo_fsm_inst_alloc_child(&assignment_fsm, conn->fi, GSCON_EV_ASSIGNMENT_END); OSMO_ASSERT(fi); conn->assignment.fi = fi; @@ -428,26 +547,28 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts conn->assignment.req = *req; req = &conn->assignment.req; - /* Check if we need a voice stream. If yes, set the appropriate struct - * members in conn */ - if (check_requires_voice_stream(conn) < 0) + assignment_count(CTR_ASSIGNMENT_ATTEMPTED); + + if (check_chan_mode_rate_against_ch_indctr(conn) < 0) return; + conn->assignment.ch_indctr = req->ch_indctr; - /* There may be an already existing lchan, if yes, try to work with - * the existing lchan. */ - if (reuse_existing_lchan(conn)) { + if (!req->target_lchan && reuse_existing_lchan(conn)) { + /* The already existing lchan is suitable for this mode */ + conn->assignment.new_lchan = NULL; /* If the requested mode and the current TCH mode matches up, just send the * assignment complete directly and be done with the assignment procedure. */ - if (conn->lchan->tch_mode == conn->lchan->ch_mode_rate.chan_mode) { + if (conn->lchan->current_ch_mode_rate.chan_mode == conn->assignment.selected_ch_mode_rate.chan_mode) { LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Current lchan mode is compatible with requested chan_mode," " sending BSSMAP Assignment Complete directly." " requested chan_mode=%s; current lchan is %s\n", - gsm48_chan_mode_name(conn->lchan->ch_mode_rate.chan_mode), + gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode), gsm_lchan_name(conn->lchan)); - send_assignment_complete(conn); + if (req->assign_for == ASSIGN_FOR_BSSMAP_REQ) + send_assignment_complete(conn); /* If something went wrong during send_assignment_complete(), * the fi will be gone from error handling in there. */ if (conn->assignment.fi) { @@ -462,39 +583,63 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Current lchan mode is not compatible with requested chan_mode," " so we will modify it. requested chan_mode=%s; current lchan is %s\n", - gsm48_chan_mode_name(conn->lchan->ch_mode_rate.chan_mode), + gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode), gsm_lchan_name(conn->lchan)); - info = (struct lchan_activate_info){ - .activ_for = FOR_ASSIGNMENT, - .for_conn = conn, - .chan_mode = conn->lchan->ch_mode_rate.chan_mode, - .encr = conn->lchan->encr, - .s15_s0 = conn->lchan->ch_mode_rate.s15_s0, - .requires_voice_stream = conn->assignment.requires_voice_stream, - .msc_assigned_cic = req->msc_assigned_cic, - .re_use_mgw_endpoint_from_lchan = conn->lchan, - }; - - osmo_fsm_inst_dispatch(conn->lchan->fi, LCHAN_EV_REQUEST_MODE_MODIFY, &info); - - /* Since we opted not to allocate a new lchan, the new lchan is still the old lchan. */ - conn->assignment.new_lchan = conn->lchan; - - /* Also we need to skip the RR assignment, so we jump forward and wait for the lchan_fsm until it - * reaches the established state again. */ - assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED); - + assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED); return; } - /* Try to allocate a new lchan in order of preference */ - for (i = 0; i < req->n_ch_mode_rate; i++) { - conn->assignment.new_lchan = lchan_select_by_chan_mode(bts, - req->ch_mode_rate[i].chan_mode, req->ch_mode_rate[i].chan_rate); - conn->lchan->ch_mode_rate = req->ch_mode_rate[i]; - if (conn->assignment.new_lchan) + if (req->vgcs) { + /* When assigning to a VGCS/VBS, the target lchan is already defined. */ + conn->assignment.new_lchan = req->target_lchan; + } else if (req->target_lchan) { + bool matching_mode; + + /* The caller already picked a target lchan to assign to. No need to try re-using the current lchan or + * picking a new one. */ + if (!lchan_state_is(req->target_lchan, LCHAN_ST_UNUSED)) { + assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + "Assignment to lchan %s requested, but lchan is already in use (state=%s)\n", + gsm_lchan_name(req->target_lchan), + osmo_fsm_inst_state_name(req->target_lchan->fi)); + return; + } + + conn->assignment.new_lchan = req->target_lchan; + matching_mode = false; + for (i = 0; i < req->n_ch_mode_rate; i++) { + if (!lchan_type_compat_with_mode(conn->assignment.new_lchan->type, &req->ch_mode_rate_list[i])) + continue; + conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i]; + matching_mode = true; + } + if (!matching_mode) { + OSMO_ASSERT(conn->lchan); + assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + "Assignment of lchan %s to %s type %s requested, but lchan is not compatible", + gsm_lchan_name(conn->lchan), + gsm_lchan_name(req->target_lchan), + gsm_chan_t_name(conn->assignment.new_lchan->type)); + return; + } + } else { + /* Try to allocate a new lchan in order of preference */ + for (i = 0; i < req->n_ch_mode_rate; i++) { + conn->assignment.new_lchan = lchan_select_by_chan_mode(bts, + req->ch_mode_rate_list[i].chan_mode, + req->ch_mode_rate_list[i].chan_rate, + SELECT_FOR_ASSIGNMENT, conn->lchan); + if (!conn->assignment.new_lchan) + continue; + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "selected new lchan %s for mode[%d] = %s channel_rate=%d\n", + gsm_lchan_name(conn->assignment.new_lchan), + i, gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode), + req->ch_mode_rate_list[i].chan_rate); + + conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i]; break; + } } /* Check whether the lchan allocation was successful or not and tear @@ -503,13 +648,13 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts assignment_count_result(CTR_ASSIGNMENT_NO_CHANNEL); assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, "BSSMAP Assignment Command:" - " No lchan available for: pref=%s:%s / alt1=%s:%s / alt2=%s:%s\n", - gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode), - rate_names[req->ch_mode_rate[0].chan_rate], - req->n_ch_mode_rate >= 1 ? gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode) : "", - req->n_ch_mode_rate >= 1 ? rate_names[req->ch_mode_rate[0].chan_rate] : "", - req->n_ch_mode_rate >= 2 ? gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode) : "", - req->n_ch_mode_rate >= 2 ? rate_names[req->ch_mode_rate[0].chan_rate] : "" + " No lchan available for: pref=%s:%s / alt1=%s:%s / alt2=%s:%s", + gsm48_chan_mode_name(req->ch_mode_rate_list[0].chan_mode), + rate_names[req->ch_mode_rate_list[0].chan_rate], + req->n_ch_mode_rate > 1 ? gsm48_chan_mode_name(req->ch_mode_rate_list[1].chan_mode) : "", + req->n_ch_mode_rate > 1 ? rate_names[req->ch_mode_rate_list[1].chan_rate] : "", + req->n_ch_mode_rate > 2 ? gsm48_chan_mode_name(req->ch_mode_rate_list[2].chan_mode) : "", + req->n_ch_mode_rate > 2 ? rate_names[req->ch_mode_rate_list[2].chan_rate] : "" ); return; } @@ -517,33 +662,49 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts assignment_fsm_update_id(conn); LOG_ASSIGNMENT(conn, LOGL_INFO, "Starting Assignment: chan_mode=%s, chan_type=%s," " aoip=%s MSC-rtp=%s:%u (osmux=%s)\n", - gsm48_chan_mode_name(conn->lchan->ch_mode_rate.chan_mode), - rate_names[conn->lchan->ch_mode_rate.chan_rate], + gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode), + rate_names[conn->assignment.selected_ch_mode_rate.chan_rate], req->aoip ? "yes" : "no", req->msc_rtp_addr, req->msc_rtp_port, req->use_osmux ? "yes" : "no"); - assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE); - info = (struct lchan_activate_info){ - .activ_for = FOR_ASSIGNMENT, + /* Wait for lchan to become active before send assignment. In case of VGCS/VBS directly send assignment, + * because the channel is already active. */ + assignment_fsm_state_chg(req->vgcs ? ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE : ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE); +} + +static void assignment_fsm_wait_lchan_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + struct assignment_request *req = &conn->assignment.req; + struct lchan_activate_info activ_info = { + .activ_for = ACTIVATE_FOR_ASSIGNMENT, .for_conn = conn, - .chan_mode = conn->lchan->ch_mode_rate.chan_mode, + .ch_mode_rate = conn->assignment.selected_ch_mode_rate, .encr = conn->lchan->encr, - .s15_s0 = conn->lchan->ch_mode_rate.s15_s0, - .requires_voice_stream = conn->assignment.requires_voice_stream, + .ch_indctr = conn->assignment.ch_indctr, .msc_assigned_cic = req->msc_assigned_cic, .re_use_mgw_endpoint_from_lchan = conn->lchan, + .ta = conn->lchan->last_ta, + .ta_known = true, + .tsc_set = req->tsc_set, + .tsc = req->tsc, }; - lchan_activate(conn->assignment.new_lchan, &info); + if (conn->assignment.new_lchan->vamos.is_secondary) + activ_info.type_for = LCHAN_TYPE_FOR_VAMOS; + lchan_activate(conn->assignment.new_lchan, &activ_info); } -static void assignment_fsm_wait_lchan(struct osmo_fsm_inst *fi, uint32_t event, void *data) +static void assignment_fsm_wait_lchan_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); switch (event) { case ASSIGNMENT_EV_LCHAN_ACTIVE: - if (data != conn->assignment.new_lchan) + if (data != conn->assignment.new_lchan) { + LOG_ASSIGNMENT(conn, LOGL_ERROR, "Some unrelated lchan was activated, ignoring: %s\n", + gsm_lchan_name(data)); return; + } /* The TS may have changed its pchan_is */ assignment_fsm_update_id(conn); @@ -561,6 +722,15 @@ static void assignment_fsm_wait_rr_ass_complete_onenter(struct osmo_fsm_inst *fi int rc; struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + /* There may be situations where the SDCCH gets released while the TCH is still being activated. We will then + * receive ChanActivAck message from the BTS when the TCH is ready. Since the SDCCH is already released by + * then conn->lchan will be NULL in this case. */ + if (!conn->lchan) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to send RR Assignment Command: conn without lchan"); + return; + } + rc = gsm48_send_rr_ass_cmd(conn->lchan, conn->assignment.new_lchan, conn->lchan->ms_power); @@ -628,7 +798,7 @@ static void assignment_fsm_wait_lchan_established(struct osmo_fsm_inst *fi, uint static void assignment_fsm_post_lchan_established(struct osmo_fsm_inst *fi) { struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); - if (conn->assignment.requires_voice_stream) + if (bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr)) assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC); else assignment_success(conn); @@ -638,15 +808,17 @@ static void assignment_fsm_wait_mgw_endpoint_to_msc_onenter(struct osmo_fsm_inst { struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); - OSMO_ASSERT(conn->assignment.requires_voice_stream); + OSMO_ASSERT(bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr)); LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Connecting MGW endpoint to the MSC's RTP port: %s:%u\n", conn->assignment.req.msc_rtp_addr, conn->assignment.req.msc_rtp_port); + /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan. + * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */ if (!gscon_connect_mgw_to_msc(conn, - conn->assignment.new_lchan, + conn->assignment.new_lchan ? : conn->lchan, conn->assignment.req.msc_rtp_addr, conn->assignment.req.msc_rtp_port, fi, @@ -692,19 +864,57 @@ static void assignment_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, ui } } +static void assignment_fsm_wait_lchan_modified_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + struct gsm_lchan *lchan = conn->lchan; + struct assignment_request *req = &conn->assignment.req; + struct lchan_modify_info modif_info = { + .modify_for = MODIFY_FOR_ASSIGNMENT, + .ch_mode_rate = conn->assignment.selected_ch_mode_rate, + .ch_indctr = conn->assignment.ch_indctr, + .msc_assigned_cic = req->msc_assigned_cic, + /* keep previous training sequence code. TSC is always present, TSC Set may or may not be an explicit + * value. */ + .tsc_set = { + .present = (lchan->tsc_set >= 0), + .val = lchan->tsc_set, + }, + .tsc = { + .present = true, + .val = lchan->tsc, + }, + }; + lchan_mode_modify(lchan, &modif_info); +} + +static void assignment_fsm_wait_lchan_modified(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + + case ASSIGNMENT_EV_LCHAN_MODIFIED: + assignment_fsm_post_lchan_established(fi); + return; + + default: + OSMO_ASSERT(false); + } +} + #define S(x) (1 << (x)) static const struct osmo_fsm_state assignment_fsm_states[] = { [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = { .name = "WAIT_LCHAN_ACTIVE", - .action = assignment_fsm_wait_lchan, + .onenter = assignment_fsm_wait_lchan_active_onenter, + .action = assignment_fsm_wait_lchan_active, .in_event_mask = 0 | S(ASSIGNMENT_EV_LCHAN_ACTIVE) , .out_state_mask = 0 | S(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE) | S(ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE) - | S(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED) /* MODE MODIFY */ + | S(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED) , }, [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { @@ -740,11 +950,23 @@ static const struct osmo_fsm_state assignment_fsm_states[] = { | S(ASSIGNMENT_EV_MSC_MGW_FAIL) , }, + [ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED] = { + .name = "WAIT_LCHAN_MODIFIED", + .onenter = assignment_fsm_wait_lchan_modified_onenter, + .action = assignment_fsm_wait_lchan_modified, + .in_event_mask = 0 + | S(ASSIGNMENT_EV_LCHAN_MODIFIED) + , + .out_state_mask = 0 + | S(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC) + , + }, }; static const struct value_string assignment_fsm_event_names[] = { OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ACTIVE), OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ESTABLISHED), + OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_MODIFIED), OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ERROR), OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_OK), OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_FAIL), @@ -754,9 +976,14 @@ static const struct value_string assignment_fsm_event_names[] = { {} }; -void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +static void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + + /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan. + * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */ + struct gsm_lchan *new_lchan = conn->assignment.new_lchan ? : conn->lchan; + switch (event) { case ASSIGNMENT_EV_CONN_RELEASING: @@ -765,11 +992,12 @@ void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, vo return; case ASSIGNMENT_EV_LCHAN_ERROR: - if (data != conn->assignment.new_lchan) + if (data != new_lchan) return; - assignment_fail(conn->assignment.new_lchan->activate.gsm0808_error_cause, - "Failed to activate lchan %s", - gsm_lchan_name(conn->assignment.new_lchan)); + assignment_fail(new_lchan->activate.gsm0808_error_cause, + "Failed to %s lchan %s", + conn->assignment.new_lchan ? "activate" : "modify", + gsm_lchan_name(new_lchan)); return; default: @@ -777,7 +1005,7 @@ void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, vo } } -int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi) +static int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi) { struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); assignment_count_result(CTR_ASSIGNMENT_TIMEOUT); @@ -785,7 +1013,7 @@ int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi) return 0; } -void assignment_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +static void assignment_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); assignment_reset(conn); diff --git a/src/osmo-bsc/osmo_bsc_ctrl.c b/src/osmo-bsc/bsc_ctrl.c index 05bc86c2b..aff1d83e6 100644 --- a/src/osmo-bsc/osmo_bsc_ctrl.c +++ b/src/osmo-bsc/bsc_ctrl.c @@ -1,6 +1,9 @@ -/* (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de> - * (C) 2011 by Holger Hans Peter Freyther +/* + * (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de> * (C) 2011 by On-Waves + * (C) 2011-2015 by Holger Hans Peter Freyther + * (C) 2013-2015 by sysmocom s.f.m.c. GmbH + * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify @@ -17,235 +20,461 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ +#include <errno.h> +#include <time.h> + +#include <osmocom/gsm/ipa.h> #include <osmocom/ctrl/control_cmd.h> -#include <osmocom/bsc/ctrl.h> -#include <osmocom/bsc/debug.h> +#include <osmocom/ctrl/control_if.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmocom/gsm/gsm48.h> +#include <osmocom/bsc/ipaccess.h> #include <osmocom/bsc/gsm_data.h> -#include <osmocom/bsc/osmo_bsc.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/chan_alloc.h> #include <osmocom/bsc/osmo_bsc_rf.h> #include <osmocom/bsc/bsc_msc_data.h> -#include <osmocom/bsc/signal.h> -#include <osmocom/bsc/a_reset.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/a_reset.h> +#include <osmocom/bsc/ctrl.h> +#include <osmocom/bsc/handover_ctrl.h> +#include <osmocom/bsc/neighbor_ident.h> -#include <osmocom/core/linuxlist.h> -#include <osmocom/core/signal.h> +static int verify_net_apply_config_file(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + FILE *cfile; -#include <osmocom/ctrl/control_if.h> + if (!cmd->value || cmd->value[0] == '\0') + return -1; -#include <osmocom/gsm/protocol/ipaccess.h> -#include <osmocom/gsm/ipa.h> + cfile = fopen(cmd->value, "r"); + if (!cfile) + return -1; -#include <stdio.h> -#include <stdlib.h> -#include <time.h> -#include <unistd.h> + fclose(cfile); -/* Obtain SS7 application server currently handling given MSC (DPC) */ -static struct osmo_ss7_as *msc_get_ss7_as(struct bsc_msc_data *msc) -{ - struct osmo_ss7_route *rt; - struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(msc->a.sccp); - rt = osmo_ss7_route_lookup(ss7, msc->a.msc_addr.pc); - if (!rt) - return NULL; - return rt->dest.as; + return 0; } - -static int _ss7_as_send(struct osmo_ss7_as *as, struct msgb *msg) +static int set_net_apply_config_file(struct ctrl_cmd *cmd, void *_data) { - struct osmo_ss7_asp *asp; - unsigned int i; + int rc; + FILE *cfile; + unsigned cmd_ret = CTRL_CMD_ERROR; + + LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s...\n", cmd->value); + cfile = fopen(cmd->value, "r"); + if (!cfile) { + LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s: fopen() failed: %d\n", + cmd->value, errno); + cmd->reply = "NoFile"; + return cmd_ret; + } - /* FIXME: unify with xua_as_transmit_msg() and perform proper ASP lookup */ - for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { - asp = as->cfg.asps[i]; - if (!asp) - continue; - /* FIXME: deal with multiple ASPs per AS */ - return osmo_ss7_asp_send(asp, msg); + rc = vty_read_config_filep(cfile, NULL); + LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s returned %d\n", cmd->value, rc); + if (rc) { + cmd->reply = talloc_asprintf(cmd, "ParseError=%d", rc); + if (!cmd->reply) + cmd->reply = "OOM"; + goto close_ret; } - msgb_free(msg); - return -1; + + rc = neighbors_check_cfg(); + if (rc) { + cmd->reply = talloc_asprintf(cmd, "Errors in neighbor configuration"); + if (!cmd->reply) + cmd->reply = "OOM"; + goto close_ret; + } + + cmd->reply = "OK"; + cmd_ret = CTRL_CMD_REPLY; +close_ret: + fclose(cfile); + return cmd_ret; } +CTRL_CMD_DEFINE_WO(net_apply_config_file, "apply-config-file"); -int bsc_sccplite_msc_send(struct bsc_msc_data *msc, struct msgb *msg) +static int verify_net_write_config_file(struct ctrl_cmd *cmd, const char *value, void *_data) { - struct osmo_ss7_as *as; + return 0; +} +static int set_net_write_config_file(struct ctrl_cmd *cmd, void *_data) +{ + const char *cfile_name; + unsigned cmd_ret = CTRL_CMD_ERROR; - as = msc_get_ss7_as(msc); - if (!as) { - msgb_free(msg); + if (strcmp(cmd->value, "overwrite")) + host_config_set(cmd->value); + + cfile_name = host_config_file(); + + LOGP(DCTRL, LOGL_NOTICE, "Writing VTY config to file %s...\n", cfile_name); + if (osmo_vty_write_config_file(cfile_name) < 0) + goto ret; + + cmd->reply = "OK"; + cmd_ret = CTRL_CMD_REPLY; +ret: + return cmd_ret; +} +CTRL_CMD_DEFINE_WO(net_write_config_file, "write-config-file"); + +CTRL_CMD_DEFINE(net_mcc, "mcc"); +static int get_net_mcc(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + cmd->reply = talloc_asprintf(cmd, "%s", osmo_mcc_name(net->plmn.mcc)); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + return CTRL_CMD_REPLY; +} +static int set_net_mcc(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + uint16_t mcc; + if (osmo_mcc_from_str(cmd->value, &mcc)) return -1; + net->plmn.mcc = mcc; + return get_net_mcc(cmd, _data); +} +static int verify_net_mcc(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (osmo_mcc_from_str(value, NULL)) + return -1; + return 0; +} + +CTRL_CMD_DEFINE(net_mnc, "mnc"); +static int get_net_mnc(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + cmd->reply = talloc_asprintf(cmd, "%s", osmo_mnc_name(net->plmn.mnc, net->plmn.mnc_3_digits)); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + return CTRL_CMD_REPLY; +} +static int set_net_mnc(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + struct osmo_plmn_id plmn = net->plmn; + if (osmo_mnc_from_str(cmd->value, &plmn.mnc, &plmn.mnc_3_digits)) { + cmd->reply = "Error while decoding MNC"; + return CTRL_CMD_ERROR; } + net->plmn = plmn; + return get_net_mnc(cmd, _data); +} +static int verify_net_mnc(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (osmo_mnc_from_str(value, NULL, NULL)) + return -1; + return 0; +} - /* don't attempt to send CTRL on a non-SCCPlite AS */ - if (as->cfg.proto != OSMO_SS7_ASP_PROT_IPA) - return 0; +static int set_net_apply_config(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_network *net = cmd->node; + struct gsm_bts *bts; - return _ss7_as_send(as, msg); + llist_for_each_entry(bts, &net->bts_list, list) { + if (!is_ipa_abisip_bts(bts)) + continue; + + /* + * The ip.access nanoBTS seems to be unreliable on BSSGP + * so let's us just reboot it. For the sysmoBTS we can just + * restart the process as all state is gone. + */ + if (!is_osmobts(bts) && strcmp(cmd->value, "restart") == 0) { + struct gsm_bts_trx *trx; + llist_for_each_entry_reverse(trx, &bts->trx_list, list) + abis_nm_ipaccess_restart(trx); + } else + ipaccess_drop_oml(bts, "ctrl net.apply-configuration"); + } + + cmd->reply = "Tried to drop the BTS"; + return CTRL_CMD_REPLY; } -/* Encode a CTRL command and send it to the given ASP - * \param[in] asp ASP through which we shall send the encoded message - * \param[in] cmd decoded CTRL command to be encoded and sent. Ownership is *NOT* - * transferred, to permit caller to send the same CMD to several ASPs. - * Caller must hence free 'cmd' itself. - * \returns 0 on success; negative on error */ -static int sccplite_asp_ctrl_cmd_send(struct osmo_ss7_asp *asp, struct ctrl_cmd *cmd) +CTRL_CMD_DEFINE_WO_NOVRF(net_apply_config, "apply-configuration"); + +static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d) { - /* this is basically like libosmoctrl:ctrl_cmd_send(), not for a dedicated - * CTRL connection but for the CTRL piggy-back on the IPA/SCCPlite link */ - struct msgb *msg; + char *tmp, *saveptr, *mcc, *mnc; + int rc = 0; - /* don't attempt to send CTRL on a non-SCCPlite ASP */ - if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA) - return 0; + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; - msg = ctrl_cmd_make(cmd); - if (!msg) - return -1; + mcc = strtok_r(tmp, ",", &saveptr); + mnc = strtok_r(NULL, ",", &saveptr); - ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); - ipa_prepend_header(msg, IPAC_PROTO_OSMO); + if (osmo_mcc_from_str(mcc, NULL) || osmo_mnc_from_str(mnc, NULL, NULL)) + rc = -1; - return osmo_ss7_asp_send(asp, msg); + talloc_free(tmp); + return rc; } -/* Ownership of 'cmd' is *NOT* transferred, to permit caller to send the same CMD to several ASPs. - * Caller must hence free 'cmd' itself. */ -static int sccplite_msc_ctrl_cmd_send(struct bsc_msc_data *msc, struct ctrl_cmd *cmd) +static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data) { - struct msgb *msg; + struct gsm_network *net = cmd->node; + char *tmp, *saveptr, *mcc_str, *mnc_str; + struct osmo_plmn_id plmn; - msg = ctrl_cmd_make(cmd); - if (!msg) - return -1; + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) + goto oom; - ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); - ipa_prepend_header(msg, IPAC_PROTO_OSMO); + mcc_str = strtok_r(tmp, ",", &saveptr); + mnc_str = strtok_r(NULL, ",", &saveptr); - return bsc_sccplite_msc_send(msc, msg); + if (osmo_mcc_from_str(mcc_str, &plmn.mcc)) { + cmd->reply = "Error while decoding MCC"; + talloc_free(tmp); + return CTRL_CMD_ERROR; + } + + if (osmo_mnc_from_str(mnc_str, &plmn.mnc, &plmn.mnc_3_digits)) { + cmd->reply = "Error while decoding MNC"; + talloc_free(tmp); + return CTRL_CMD_ERROR; + } + + talloc_free(tmp); + + if (!osmo_plmn_cmp(&net->plmn, &plmn)) { + cmd->reply = "Nothing changed"; + return CTRL_CMD_REPLY; + } + + net->plmn = plmn; + + return set_net_apply_config(cmd, data); + +oom: + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; } +CTRL_CMD_DEFINE_WO(net_mcc_mnc_apply, "mcc-mnc-apply"); -/* receive + process a CTRL command from the piggy-back on the IPA/SCCPlite link. - * Transfers msg ownership. */ -int bsc_sccplite_rx_ctrl(struct osmo_ss7_asp *asp, struct msgb *msg) +static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data) { - struct ctrl_cmd *cmd; - bool parse_failed; - int rc; + struct gsm_network *net = cmd->node; + struct gsm_bts *bts; + const char *policy_name; - /* caller has already ensured ipaccess_head + ipaccess_head_ext */ - OSMO_ASSERT(msg->l2h); + policy_name = osmo_bsc_rf_get_policy_name(net->rf_ctrl->policy); - /* prase raw (ASCII) CTRL command into ctrl_cmd */ - cmd = ctrl_cmd_parse3(asp, msg, &parse_failed); - OSMO_ASSERT(cmd); - msgb_free(msg); - if (cmd->type == CTRL_TYPE_ERROR && parse_failed) - goto send_reply; + llist_for_each_entry(bts, &net->bts_list, list) { + struct gsm_bts_trx *trx; - /* handle the CTRL command */ - ctrl_cmd_handle(bsc_gsmnet->ctrl, cmd, bsc_gsmnet); + /* Exclude the BTS from the global lock */ + if (bts->excl_from_rf_lock) + continue; -send_reply: - rc = sccplite_asp_ctrl_cmd_send(asp, cmd); - talloc_free(cmd); - return rc; + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.availability == NM_AVSTATE_OK && + trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) { + cmd->reply = talloc_asprintf(cmd, + "state=on,policy=%s,bts=%u,trx=%u", + policy_name, bts->nr, trx->nr); + return CTRL_CMD_REPLY; + } + } + } + + cmd->reply = talloc_asprintf(cmd, "state=off,policy=%s", + policy_name); + return CTRL_CMD_REPLY; } +#define TIME_FORMAT_RFC2822 "%a, %d %b %Y %T %z" -void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_data *msc_data) +static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data) { - struct ctrl_cmd *trap; - struct ctrl_handle *ctrl; + int locked = atoi(cmd->value); + struct gsm_network *net = cmd->node; + time_t now = time(NULL); + char now_buf[64]; + struct osmo_bsc_rf *rf; + + if (!net) { + cmd->reply = "net not found."; + return CTRL_CMD_ERROR; + } - ctrl = msc_data->network->ctrl; + rf = net->rf_ctrl; - trap = ctrl_cmd_trap(cmd); - if (!trap) { + if (!rf) { + cmd->reply = "RF Ctrl is not enabled in the BSC Configuration"; + return CTRL_CMD_ERROR; + } - LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n"); - return; + talloc_free(rf->last_rf_lock_ctrl_command); + strftime(now_buf, sizeof(now_buf), TIME_FORMAT_RFC2822, gmtime(&now)); + rf->last_rf_lock_ctrl_command = + talloc_asprintf(rf, "rf_locked %u (%s)", locked, now_buf); + + osmo_bsc_rf_schedule_lock(rf, locked == 1 ? '0' : '1'); + + cmd->reply = talloc_asprintf(cmd, "%u", locked); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; } - ctrl_cmd_send_to_all(ctrl, trap); - sccplite_msc_ctrl_cmd_send(msc_data, trap); + return CTRL_CMD_REPLY; +} - talloc_free(trap); +static int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data) +{ + int locked = atoi(cmd->value); + + if ((locked != 0) && (locked != 1)) + return 1; + + return 0; } +CTRL_CMD_DEFINE(net_rf_lock, "rf_locked"); -CTRL_CMD_DEFINE_RO(msc_connection_status, "connection_status"); -static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data) +static int get_net_bts_num(struct ctrl_cmd *cmd, void *data) { - struct bsc_msc_data *msc = (struct bsc_msc_data *)cmd->node; + struct gsm_network *net = cmd->node; - if (msc == NULL) { - cmd->reply = "msc not found"; + cmd->reply = talloc_asprintf(cmd, "%u", net->num_bts); + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(net_bts_num, "number-of-bts"); + +/* Return a list of the states of each TRX for all BTS: + * <bts_nr>,<trx_nr>,<opstate>,<adminstate>,<rf_policy>,<rsl_status>;<bts_nr>,<trx_nr>,...;...; + * For details on the string, see bsc_rf_states_c(); + */ +static int get_net_rf_states(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = bsc_rf_states_c(cmd); + if (!cmd->reply) { + cmd->reply = "OOM"; return CTRL_CMD_ERROR; } - if (a_reset_conn_ready(msc)) - cmd->reply = "connected"; - else - cmd->reply = "disconnected"; return CTRL_CMD_REPLY; } +CTRL_CMD_DEFINE_RO(net_rf_states, "rf_states"); -/* Backwards compat. */ -CTRL_CMD_DEFINE_RO(msc0_connection_status, "msc_connection_status"); - -static int get_msc0_connection_status(struct ctrl_cmd *cmd, void *data) +CTRL_CMD_DEFINE(net_timezone, "timezone"); +static int get_net_timezone(struct ctrl_cmd *cmd, void *data) { - struct bsc_msc_data *msc = osmo_msc_data_find(bsc_gsmnet, 0); - void *old_node = cmd->node; - int rc; + struct gsm_network *net = (struct gsm_network *)cmd->node; - cmd->node = msc; - rc = get_msc_connection_status(cmd, data); - cmd->node = old_node; + struct gsm_tz *tz = &net->tz; + if (tz->override) + cmd->reply = talloc_asprintf(cmd, "%d,%d,%d", + tz->hr, tz->mn, tz->dst); + else + cmd->reply = talloc_asprintf(cmd, "off"); - return rc; + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; } -static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) +static int set_net_timezone(struct ctrl_cmd *cmd, void *data) { - struct ctrl_cmd *cmd; - struct gsm_network *gsmnet = (struct gsm_network *)handler_data; - struct bsc_msc_data *msc = (struct bsc_msc_data *)signal_data; + char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0; + int override = 0; + struct gsm_network *net = (struct gsm_network *)cmd->node; + struct gsm_tz *tz = &net->tz; - if (signal == S_MSC_LOST) { - LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n"); - } else if (signal == S_MSC_CONNECTED) { - LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n"); - } else { - return 0; - } + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) + goto oom; - cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); - if (!cmd) { - LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n"); - return 0; + hourstr = strtok_r(tmp, ",", &saveptr); + minstr = strtok_r(NULL, ",", &saveptr); + dststr = strtok_r(NULL, ",", &saveptr); + + if (hourstr != NULL) { + override = strcasecmp(hourstr, "off") != 0; + if (override) { + tz->hr = atol(hourstr); + tz->mn = minstr ? atol(minstr) : 0; + tz->dst = dststr ? atol(dststr) : 0; + } } - cmd->id = "0"; - cmd->variable = talloc_asprintf(cmd, "msc.%d.connection_status", msc->nr); - cmd->node = msc; + tz->override = override; - get_msc_connection_status(cmd, NULL); - ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); + talloc_free(tmp); + tmp = NULL; - if (msc->nr == 0) { - /* Backwards compat. */ - cmd->variable = "msc_connection_status"; - ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); + return get_net_timezone(cmd, data); + +oom: + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; +} + +static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data) +{ + char *saveptr, *hourstr, *minstr, *dststr, *tmp; + int override, tz_hours, tz_mins, tz_dst; + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + hourstr = strtok_r(tmp, ",", &saveptr); + minstr = strtok_r(NULL, ",", &saveptr); + dststr = strtok_r(NULL, ",", &saveptr); + + if (hourstr == NULL) + goto err; + + override = strcasecmp(hourstr, "off") != 0; + + if (!override) { + talloc_free(tmp); + return 0; } - talloc_free(cmd); + if (minstr == NULL || dststr == NULL) + goto err; + + tz_hours = atol(hourstr); + tz_mins = atol(minstr); + tz_dst = atol(dststr); + + talloc_free(tmp); + tmp = NULL; + + if ((tz_hours < -19) || (tz_hours > 19) || + (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) || + (tz_dst < 0) || (tz_dst > 2)) + goto err; return 0; + +err: + talloc_free(tmp); + cmd->reply = talloc_strdup(cmd, "The format is <hours>,<mins>,<dst> or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2"); + return 1; } CTRL_CMD_DEFINE_RO(bts_connection_status, "bts_connection_status"); @@ -307,309 +536,231 @@ static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signa return 0; } -static int get_bts_loc(struct ctrl_cmd *cmd, void *data); - -static void generate_location_state_trap(struct gsm_bts *bts, struct bsc_msc_data *msc) +CTRL_CMD_DEFINE_RO(msc_connection_status, "connection_status"); +static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data) { - struct ctrl_cmd *cmd; - const char *oper, *admin, *policy; + struct bsc_msc_data *msc = (struct bsc_msc_data *)cmd->node; - cmd = ctrl_cmd_create(msc, CTRL_TYPE_TRAP); - if (!cmd) { - LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n"); - return; + if (msc == NULL) { + cmd->reply = "msc not found"; + return CTRL_CMD_ERROR; } - - cmd->id = "0"; - cmd->variable = talloc_asprintf(cmd, "bts.%i.location-state", bts->nr); - - /* Prepare the location reply */ - cmd->node = bts; - get_bts_loc(cmd, NULL); - - oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts)); - admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts)); - policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts)); - - cmd->reply = talloc_asprintf_append(cmd->reply, - ",%s,%s,%s,%s,%s", - oper, admin, policy, - osmo_mcc_name(bts->network->plmn.mcc), - osmo_mnc_name(bts->network->plmn.mnc, - bts->network->plmn.mnc_3_digits)); - - osmo_bsc_send_trap(cmd, msc); - talloc_free(cmd); + if (a_reset_conn_ready(msc)) + cmd->reply = "connected"; + else + cmd->reply = "disconnected"; + return CTRL_CMD_REPLY; } -void bsc_gen_location_state_trap(struct gsm_bts *bts) -{ - struct bsc_msc_data *msc; - - llist_for_each_entry(msc, &bts->network->mscs, entry) - generate_location_state_trap(bts, msc); -} +/* Backwards compat. */ +CTRL_CMD_DEFINE_RO(msc0_connection_status, "msc_connection_status"); -static int location_equal(struct bts_location *a, struct bts_location *b) +static int get_msc0_connection_status(struct ctrl_cmd *cmd, void *data) { - return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) && - (a->lon == b->lon) && (a->height == b->height)); -} + struct bsc_msc_data *msc = osmo_msc_data_find(bsc_gsmnet, 0); + void *old_node = cmd->node; + int rc; -static void cleanup_locations(struct llist_head *locations) -{ - struct bts_location *myloc, *tmp; - int invalpos = 0, i = 0; + cmd->node = msc; + rc = get_msc_connection_status(cmd, data); + cmd->node = old_node; - LOGP(DCTRL, LOGL_DEBUG, "Checking position list.\n"); - llist_for_each_entry_safe(myloc, tmp, locations, list) { - i++; - if (i > 3) { - LOGP(DCTRL, LOGL_DEBUG, "Deleting old position.\n"); - llist_del(&myloc->list); - talloc_free(myloc); - } else if (myloc->valid == BTS_LOC_FIX_INVALID) { - /* Only capture the newest of subsequent invalid positions */ - invalpos++; - if (invalpos > 1) { - LOGP(DCTRL, LOGL_DEBUG, "Deleting subsequent invalid position.\n"); - invalpos--; - i--; - llist_del(&myloc->list); - talloc_free(myloc); - } - } else { - invalpos = 0; - } - } - LOGP(DCTRL, LOGL_DEBUG, "Found %i positions.\n", i); + return rc; } -CTRL_CMD_DEFINE(bts_loc, "location"); -static int get_bts_loc(struct ctrl_cmd *cmd, void *data) +static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { - struct bts_location *curloc; - struct gsm_bts *bts = (struct gsm_bts *) cmd->node; - if (!bts) { - cmd->reply = "bts not found."; - return CTRL_CMD_ERROR; - } + struct ctrl_cmd *cmd; + struct gsm_network *gsmnet = (struct gsm_network *)handler_data; + struct bsc_msc_data *msc = (struct bsc_msc_data *)signal_data; - if (llist_empty(&bts->loc_list)) { - cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0"); - return CTRL_CMD_REPLY; + if (signal == S_MSC_LOST) { + LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n"); + } else if (signal == S_MSC_CONNECTED) { + LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n"); } else { - curloc = llist_entry(bts->loc_list.next, struct bts_location, list); + return 0; } - cmd->reply = talloc_asprintf(cmd, "%lu,%s,%f,%f,%f", curloc->tstamp, - get_value_string(bts_loc_fix_names, curloc->valid), curloc->lat, curloc->lon, curloc->height); - if (!cmd->reply) { - cmd->reply = "OOM"; - return CTRL_CMD_ERROR; + cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); + if (!cmd) { + LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n"); + return 0; } - return CTRL_CMD_REPLY; -} + cmd->id = "0"; + cmd->variable = talloc_asprintf(cmd, "msc.%d.connection_status", msc->nr); + cmd->node = msc; -static int set_bts_loc(struct ctrl_cmd *cmd, void *data) -{ - char *saveptr, *lat, *lon, *height, *tstamp, *valid, *tmp; - struct bts_location *curloc, *lastloc; - int ret; - struct gsm_bts *bts = (struct gsm_bts *) cmd->node; - if (!bts) { - cmd->reply = "bts not found."; - return CTRL_CMD_ERROR; - } + get_msc_connection_status(cmd, NULL); - tmp = talloc_strdup(cmd, cmd->value); - if (!tmp) - goto oom; + ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); - curloc = talloc_zero(tall_bsc_ctx, struct bts_location); - if (!curloc) { - talloc_free(tmp); - goto oom; + if (msc->nr == 0) { + /* Backwards compat. */ + cmd->variable = "msc_connection_status"; + ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); } - INIT_LLIST_HEAD(&curloc->list); - - - tstamp = strtok_r(tmp, ",", &saveptr); - valid = strtok_r(NULL, ",", &saveptr); - lat = strtok_r(NULL, ",", &saveptr); - lon = strtok_r(NULL, ",", &saveptr); - height = strtok_r(NULL, "\0", &saveptr); - curloc->tstamp = atol(tstamp); - curloc->valid = get_string_value(bts_loc_fix_names, valid); - curloc->lat = atof(lat); - curloc->lon = atof(lon); - curloc->height = atof(height); - talloc_free(tmp); - - lastloc = llist_entry(bts->loc_list.next, struct bts_location, list); + talloc_free(cmd); - /* Add location to the end of the list */ - llist_add(&curloc->list, &bts->loc_list); + return 0; +} - ret = get_bts_loc(cmd, data); +static int msc_signal_handler(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct msc_signal_data *msc; + struct gsm_network *net; + struct gsm_bts *bts; - if (!location_equal(curloc, lastloc)) - bsc_gen_location_state_trap(bts); + if (subsys != SS_MSC) + return 0; + if (signal != S_MSC_AUTHENTICATED) + return 0; - cleanup_locations(&bts->loc_list); + msc = signal_data; - return ret; + net = msc->data->network; + llist_for_each_entry(bts, &net->bts_list, list) + ctrl_generate_bts_location_state_trap(bts, msc->data); -oom: - cmd->reply = "OOM"; - return CTRL_CMD_ERROR; + return 0; } -static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data) +/* Obtain SS7 application server currently handling given MSC (DPC) */ +static struct osmo_ss7_as *msc_get_ss7_as(struct bsc_msc_data *msc) { - char *saveptr, *latstr, *lonstr, *heightstr, *tstampstr, *validstr, *tmp; - time_t tstamp; - int valid; - double lat, lon, height __attribute__((unused)); - - tmp = talloc_strdup(cmd, value); - if (!tmp) - return 1; - - tstampstr = strtok_r(tmp, ",", &saveptr); - validstr = strtok_r(NULL, ",", &saveptr); - latstr = strtok_r(NULL, ",", &saveptr); - lonstr = strtok_r(NULL, ",", &saveptr); - heightstr = strtok_r(NULL, "\0", &saveptr); - - if ((tstampstr == NULL) || (validstr == NULL) || (latstr == NULL) || - (lonstr == NULL) || (heightstr == NULL)) - goto err; + struct osmo_ss7_route *rt; + struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(msc->a.sccp); + rt = osmo_ss7_route_lookup(ss7, msc->a.msc_addr.pc); + if (!rt) + return NULL; + return rt->dest.as; +} - tstamp = atol(tstampstr); - valid = get_string_value(bts_loc_fix_names, validstr); - lat = atof(latstr); - lon = atof(lonstr); - height = atof(heightstr); - talloc_free(tmp); - tmp = NULL; +static int _ss7_as_send(struct osmo_ss7_as *as, struct msgb *msg) +{ + struct osmo_ss7_asp *asp; + unsigned int i; - if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) || - (lon < -180) || (lon > 180) || (valid < 0)) { - goto err; + /* FIXME: unify with xua_as_transmit_msg() and perform proper ASP lookup */ + for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) { + asp = as->cfg.asps[i]; + if (!asp) + continue; + /* FIXME: deal with multiple ASPs per AS */ + return osmo_ss7_asp_send(asp, msg); } - - return 0; - -err: - talloc_free(tmp); - cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>"); - return 1; + msgb_free(msg); + return -1; } -CTRL_CMD_DEFINE(net_timezone, "timezone"); -static int get_net_timezone(struct ctrl_cmd *cmd, void *data) +int bsc_sccplite_msc_send(struct bsc_msc_data *msc, struct msgb *msg) { - struct gsm_network *net = (struct gsm_network*)cmd->node; - - struct gsm_tz *tz = &net->tz; - if (tz->override) - cmd->reply = talloc_asprintf(cmd, "%d,%d,%d", - tz->hr, tz->mn, tz->dst); - else - cmd->reply = talloc_asprintf(cmd, "off"); + struct osmo_ss7_as *as; - if (!cmd->reply) { - cmd->reply = "OOM"; - return CTRL_CMD_ERROR; + as = msc_get_ss7_as(msc); + if (!as) { + msgb_free(msg); + return -1; } - return CTRL_CMD_REPLY; + /* don't attempt to send CTRL on a non-SCCPlite AS */ + if (as->cfg.proto != OSMO_SS7_ASP_PROT_IPA) + return 0; + + return _ss7_as_send(as, msg); } -static int set_net_timezone(struct ctrl_cmd *cmd, void *data) +/* Encode a CTRL command and send it to the given ASP + * \param[in] asp ASP through which we shall send the encoded message + * \param[in] cmd decoded CTRL command to be encoded and sent. Ownership is *NOT* + * transferred, to permit caller to send the same CMD to several ASPs. + * Caller must hence free 'cmd' itself. + * \returns 0 on success; negative on error */ +static int sccplite_asp_ctrl_cmd_send(struct osmo_ss7_asp *asp, struct ctrl_cmd *cmd) { - char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0; - int override = 0; - struct gsm_network *net = (struct gsm_network*)cmd->node; - struct gsm_tz *tz = &net->tz; + /* this is basically like libosmoctrl:ctrl_cmd_send(), not for a dedicated + * CTRL connection but for the CTRL piggy-back on the IPA/SCCPlite link */ + struct msgb *msg; - tmp = talloc_strdup(cmd, cmd->value); - if (!tmp) - goto oom; + /* don't attempt to send CTRL on a non-SCCPlite ASP */ + if (osmo_ss7_asp_get_proto(asp) != OSMO_SS7_ASP_PROT_IPA) + return 0; - hourstr = strtok_r(tmp, ",", &saveptr); - minstr = strtok_r(NULL, ",", &saveptr); - dststr = strtok_r(NULL, ",", &saveptr); + msg = ctrl_cmd_make(cmd); + if (!msg) + return -1; - if (hourstr != NULL) { - override = strcasecmp(hourstr, "off") != 0; - if (override) { - tz->hr = atol(hourstr); - tz->mn = minstr ? atol(minstr) : 0; - tz->dst = dststr ? atol(dststr) : 0; - } - } + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); + ipa_prepend_header(msg, IPAC_PROTO_OSMO); - tz->override = override; + return osmo_ss7_asp_send(asp, msg); +} +/* Ownership of 'cmd' is *NOT* transferred, to permit caller to send the same CMD to several ASPs. + * Caller must hence free 'cmd' itself. */ +static int sccplite_msc_ctrl_cmd_send(struct bsc_msc_data *msc, struct ctrl_cmd *cmd) +{ + struct msgb *msg; - talloc_free(tmp); - tmp = NULL; + msg = ctrl_cmd_make(cmd); + if (!msg) + return -1; - return get_net_timezone(cmd, data); + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); + ipa_prepend_header(msg, IPAC_PROTO_OSMO); -oom: - cmd->reply = "OOM"; - return CTRL_CMD_ERROR; + return bsc_sccplite_msc_send(msc, msg); } -static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data) +/* receive + process a CTRL command from the piggy-back on the IPA/SCCPlite link. + * Transfers msg ownership. */ +int bsc_sccplite_rx_ctrl(struct osmo_ss7_asp *asp, struct msgb *msg) { - char *saveptr, *hourstr, *minstr, *dststr, *tmp; - int override, tz_hours, tz_mins, tz_dst; + struct ctrl_cmd *cmd; + bool parse_failed; + int rc; - tmp = talloc_strdup(cmd, value); - if (!tmp) - return 1; + /* caller has already ensured ipaccess_head + ipaccess_head_ext */ + OSMO_ASSERT(msg->l2h); - hourstr = strtok_r(tmp, ",", &saveptr); - minstr = strtok_r(NULL, ",", &saveptr); - dststr = strtok_r(NULL, ",", &saveptr); + /* prase raw (ASCII) CTRL command into ctrl_cmd */ + cmd = ctrl_cmd_parse3(asp, msg, &parse_failed); + OSMO_ASSERT(cmd); + msgb_free(msg); + if (cmd->type == CTRL_TYPE_ERROR && parse_failed) + goto send_reply; - if (hourstr == NULL) - goto err; + /* handle the CTRL command */ + ctrl_cmd_handle(bsc_gsmnet->ctrl, cmd, bsc_gsmnet); - override = strcasecmp(hourstr, "off") != 0; +send_reply: + rc = sccplite_asp_ctrl_cmd_send(asp, cmd); + talloc_free(cmd); + return rc; +} - if (!override) { - talloc_free(tmp); - return 0; - } - if (minstr == NULL || dststr == NULL) - goto err; +void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_data *msc_data) +{ + struct ctrl_cmd *trap; + struct ctrl_handle *ctrl; - tz_hours = atol(hourstr); - tz_mins = atol(minstr); - tz_dst = atol(dststr); + ctrl = msc_data->network->ctrl; - talloc_free(tmp); - tmp = NULL; + trap = ctrl_cmd_trap(cmd); + if (!trap) { - if ((tz_hours < -19) || (tz_hours > 19) || - (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) || - (tz_dst < 0) || (tz_dst > 2)) - goto err; + LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n"); + return; + } - return 0; + ctrl_cmd_send_to_all(ctrl, trap); + sccplite_msc_ctrl_cmd_send(msc_data, trap); -err: - talloc_free(tmp); - cmd->reply = talloc_strdup(cmd, "The format is <hours>,<mins>,<dst> or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2"); - return 1; + talloc_free(trap); } CTRL_CMD_DEFINE_WO_NOVRF(net_notification, "notification"); @@ -669,63 +820,87 @@ static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data) return CTRL_CMD_HANDLED; } -static int msc_signal_handler(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) +/* Return full information about all logical channels. + * format: show-lchan.full + * result format: New line delimited list of <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>, + * <ms power>,<interference dbm>, <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>, + * <ipa bound conn id>,<ipa conn ip>,<ipa conn port>,<ipa conn speech mode> + */ +static int get_net_show_lchan_full(struct ctrl_cmd *cmd, void *data) { - struct msc_signal_data *msc; - struct gsm_network *net; - struct gsm_bts *bts; + struct gsm_network *net = cmd->node; + int bts_nr; + bool first_bts = true; + char *bts_dump; - if (subsys != SS_MSC) - return 0; - if (signal != S_MSC_AUTHENTICATED) - return 0; + cmd->reply = talloc_strdup(cmd, ""); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } - msc = signal_data; + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts_dump = bts_lchan_dump_full_ctrl(cmd, gsm_bts_num(net, bts_nr)); + if (!bts_dump) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + if (!strlen(bts_dump)) + continue; + cmd->reply = talloc_asprintf_append(cmd->reply, first_bts ? "%s" : "\n%s", bts_dump); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + first_bts = false; + } - net = msc->data->network; - llist_for_each_entry(bts, &net->bts_list, list) - generate_location_state_trap(bts, msc->data); + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(net_show_lchan_full, "show-lchan full"); - return 0; +static int bsc_base_ctrl_cmds_install(struct gsm_network *net) +{ + int rc = 0; + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config_file); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_write_config_file); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc_mnc_apply); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_lock); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_bts_num); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_states); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc0_connection_status); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_show_lchan_full); + + rc |= ctrl_cmd_install(CTRL_NODE_MSC, &cmd_msc_connection_status); + + rc |= osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net); + rc |= osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net); + rc |= osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL); + + return rc; } + int bsc_ctrl_cmds_install(struct gsm_network *net) { int rc; - rc = bsc_base_ctrl_cmds_install(); - if (rc) - goto end; - rc = ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_loc); - if (rc) - goto end; - rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone); - if (rc) - goto end; - rc = ctrl_cmd_install(CTRL_NODE_MSC, &cmd_msc_connection_status); - if (rc) - goto end; - rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc0_connection_status); - if (rc) - goto end; - rc = osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net); + rc = bsc_base_ctrl_cmds_install(net); if (rc) goto end; - rc = osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL); + rc = bsc_ho_ctrl_cmds_install(net); if (rc) goto end; - rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status); + rc = bsc_bts_ctrl_cmds_install(); if (rc) goto end; - rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification); - if (rc) - goto end; - rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc); - if (rc) - goto end; - rc = osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net); - end: return rc; } diff --git a/src/osmo-bsc/bsc_ctrl_commands.c b/src/osmo-bsc/bsc_ctrl_commands.c deleted file mode 100644 index 9383167fb..000000000 --- a/src/osmo-bsc/bsc_ctrl_commands.c +++ /dev/null @@ -1,501 +0,0 @@ -/* - * (C) 2013-2015 by Holger Hans Peter Freyther - * (C) 2013-2015 by sysmocom s.f.m.c. GmbH - * - * 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 <http://www.gnu.org/licenses/>. - * - */ -#include <errno.h> -#include <time.h> - -#include <osmocom/ctrl/control_cmd.h> -#include <osmocom/gsm/gsm48.h> -#include <osmocom/bsc/ipaccess.h> -#include <osmocom/bsc/gsm_data.h> -#include <osmocom/bsc/abis_nm.h> -#include <osmocom/bsc/debug.h> -#include <osmocom/bsc/chan_alloc.h> -#include <osmocom/bsc/osmo_bsc_rf.h> -#include <osmocom/bsc/bsc_msc_data.h> -#include <osmocom/bsc/bts.h> - -CTRL_CMD_DEFINE(net_mcc, "mcc"); -static int get_net_mcc(struct ctrl_cmd *cmd, void *_data) -{ - struct gsm_network *net = cmd->node; - cmd->reply = talloc_asprintf(cmd, "%s", osmo_mcc_name(net->plmn.mcc)); - if (!cmd->reply) { - cmd->reply = "OOM"; - return CTRL_CMD_ERROR; - } - return CTRL_CMD_REPLY; -} -static int set_net_mcc(struct ctrl_cmd *cmd, void *_data) -{ - struct gsm_network *net = cmd->node; - uint16_t mcc; - if (osmo_mcc_from_str(cmd->value, &mcc)) - return -1; - net->plmn.mcc = mcc; - return get_net_mcc(cmd, _data); -} -static int verify_net_mcc(struct ctrl_cmd *cmd, const char *value, void *_data) -{ - if (osmo_mcc_from_str(value, NULL)) - return -1; - return 0; -} - -CTRL_CMD_DEFINE(net_mnc, "mnc"); -static int get_net_mnc(struct ctrl_cmd *cmd, void *_data) -{ - struct gsm_network *net = cmd->node; - cmd->reply = talloc_asprintf(cmd, "%s", osmo_mnc_name(net->plmn.mnc, net->plmn.mnc_3_digits)); - if (!cmd->reply) { - cmd->reply = "OOM"; - return CTRL_CMD_ERROR; - } - return CTRL_CMD_REPLY; -} -static int set_net_mnc(struct ctrl_cmd *cmd, void *_data) -{ - struct gsm_network *net = cmd->node; - struct osmo_plmn_id plmn = net->plmn; - if (osmo_mnc_from_str(cmd->value, &plmn.mnc, &plmn.mnc_3_digits)) { - cmd->reply = "Error while decoding MNC"; - return CTRL_CMD_ERROR; - } - net->plmn = plmn; - return get_net_mnc(cmd, _data); -} -static int verify_net_mnc(struct ctrl_cmd *cmd, const char *value, void *_data) -{ - if (osmo_mnc_from_str(value, NULL, NULL)) - return -1; - return 0; -} - -static int set_net_apply_config(struct ctrl_cmd *cmd, void *data) -{ - struct gsm_network *net = cmd->node; - struct gsm_bts *bts; - - llist_for_each_entry(bts, &net->bts_list, list) { - if (!is_ipaccess_bts(bts)) - continue; - - /* - * The ip.access nanoBTS seems to be unrelaible on BSSGP - * so let's us just reboot it. For the sysmoBTS we can just - * restart the process as all state is gone. - */ - if (!is_sysmobts_v2(bts) && strcmp(cmd->value, "restart") == 0) { - struct gsm_bts_trx *trx; - llist_for_each_entry_reverse(trx, &bts->trx_list, list) - abis_nm_ipaccess_restart(trx); - } else - ipaccess_drop_oml(bts, "ctrl net.apply-configuration"); - } - - cmd->reply = "Tried to drop the BTS"; - return CTRL_CMD_REPLY; -} - -CTRL_CMD_DEFINE_WO_NOVRF(net_apply_config, "apply-configuration"); - -static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d) -{ - char *tmp, *saveptr, *mcc, *mnc; - int rc = 0; - - tmp = talloc_strdup(cmd, value); - if (!tmp) - return 1; - - mcc = strtok_r(tmp, ",", &saveptr); - mnc = strtok_r(NULL, ",", &saveptr); - - if (osmo_mcc_from_str(mcc, NULL) || osmo_mnc_from_str(mnc, NULL, NULL)) - rc = -1; - - talloc_free(tmp); - return rc; -} - -static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data) -{ - struct gsm_network *net = cmd->node; - char *tmp, *saveptr, *mcc_str, *mnc_str; - struct osmo_plmn_id plmn; - - tmp = talloc_strdup(cmd, cmd->value); - if (!tmp) - goto oom; - - mcc_str = strtok_r(tmp, ",", &saveptr); - mnc_str = strtok_r(NULL, ",", &saveptr); - - if (osmo_mcc_from_str(mcc_str, &plmn.mcc)) { - cmd->reply = "Error while decoding MCC"; - talloc_free(tmp); - return CTRL_CMD_ERROR; - } - - if (osmo_mnc_from_str(mnc_str, &plmn.mnc, &plmn.mnc_3_digits)) { - cmd->reply = "Error while decoding MNC"; - talloc_free(tmp); - return CTRL_CMD_ERROR; - } - - talloc_free(tmp); - - if (!osmo_plmn_cmp(&net->plmn, &plmn)) { - cmd->reply = "Nothing changed"; - return CTRL_CMD_REPLY; - } - - net->plmn = plmn; - - return set_net_apply_config(cmd, data); - -oom: - cmd->reply = "OOM"; - return CTRL_CMD_ERROR; -} -CTRL_CMD_DEFINE_WO(net_mcc_mnc_apply, "mcc-mnc-apply"); - -/* BTS related commands below */ -CTRL_CMD_DEFINE_RANGE(bts_lac, "location-area-code", struct gsm_bts, location_area_code, 0, 65535); -CTRL_CMD_DEFINE_RANGE(bts_ci, "cell-identity", struct gsm_bts, cell_identity, 0, 65535); - -static int set_bts_apply_config(struct ctrl_cmd *cmd, void *data) -{ - struct gsm_bts *bts = cmd->node; - - if (!is_ipaccess_bts(bts)) { - cmd->reply = "BTS is not IP based"; - return CTRL_CMD_ERROR; - } - - ipaccess_drop_oml(bts, "ctrl bts.apply-configuration"); - cmd->reply = "Tried to drop the BTS"; - return CTRL_CMD_REPLY; -} - -CTRL_CMD_DEFINE_WO_NOVRF(bts_apply_config, "apply-configuration"); - -static int set_bts_si(struct ctrl_cmd *cmd, void *data) -{ - struct gsm_bts *bts = cmd->node; - int rc; - - rc = gsm_bts_set_system_infos(bts); - if (rc != 0) { - cmd->reply = "Failed to generate SI"; - return CTRL_CMD_ERROR; - } - - cmd->reply = "Generated new System Information"; - return CTRL_CMD_REPLY; -} -CTRL_CMD_DEFINE_WO_NOVRF(bts_si, "send-new-system-informations"); - -static int get_bts_chan_load(struct ctrl_cmd *cmd, void *data) -{ - int i; - struct pchan_load pl; - struct gsm_bts *bts; - const char *space = ""; - - bts = cmd->node; - memset(&pl, 0, sizeof(pl)); - bts_chan_load(&pl, bts); - - cmd->reply = talloc_strdup(cmd, ""); - - for (i = 0; i < ARRAY_SIZE(pl.pchan); ++i) { - const struct load_counter *lc = &pl.pchan[i]; - - /* These can never have user load */ - if (i == GSM_PCHAN_NONE) - continue; - if (i == GSM_PCHAN_CCCH) - continue; - if (i == GSM_PCHAN_PDCH) - continue; - if (i == GSM_PCHAN_UNKNOWN) - continue; - - cmd->reply = talloc_asprintf_append(cmd->reply, - "%s%s,%u,%u", - space, gsm_pchan_name(i), lc->used, lc->total); - if (!cmd->reply) - goto error; - space = " "; - } - - return CTRL_CMD_REPLY; - -error: - cmd->reply = "Memory allocation failure"; - return CTRL_CMD_ERROR; -} - -CTRL_CMD_DEFINE_RO(bts_chan_load, "channel-load"); - -static int get_bts_oml_conn(struct ctrl_cmd *cmd, void *data) -{ - const struct gsm_bts *bts = cmd->node; - - cmd->reply = get_model_oml_status(bts); - - return CTRL_CMD_REPLY; -} - -CTRL_CMD_DEFINE_RO(bts_oml_conn, "oml-connection-state"); - -static int get_bts_oml_up(struct ctrl_cmd *cmd, void *data) -{ - const struct gsm_bts *bts = cmd->node; - - cmd->reply = talloc_asprintf(cmd, "%llu", bts_uptime(bts)); - if (!cmd->reply) { - cmd->reply = "OOM"; - return CTRL_CMD_ERROR; - } - - return CTRL_CMD_REPLY; -} - -CTRL_CMD_DEFINE_RO(bts_oml_up, "oml-uptime"); - -static int verify_bts_gprs_mode(struct ctrl_cmd *cmd, const char *value, void *_data) -{ - int valid; - enum bts_gprs_mode mode; - struct gsm_bts *bts = cmd->node; - - mode = bts_gprs_mode_parse(value, &valid); - if (!valid) { - cmd->reply = "Mode is not known"; - return 1; - } - - if (!bts_gprs_mode_is_compat(bts, mode)) { - cmd->reply = "bts does not support this mode"; - return 1; - } - - return 0; -} - -static int get_bts_gprs_mode(struct ctrl_cmd *cmd, void *data) -{ - struct gsm_bts *bts = cmd->node; - - cmd->reply = talloc_strdup(cmd, bts_gprs_mode_name(bts->gprs.mode)); - return CTRL_CMD_REPLY; -} - -static int set_bts_gprs_mode(struct ctrl_cmd *cmd, void *data) -{ - struct gsm_bts *bts = cmd->node; - - bts->gprs.mode = bts_gprs_mode_parse(cmd->value, NULL); - return get_bts_gprs_mode(cmd, data); -} - -CTRL_CMD_DEFINE(bts_gprs_mode, "gprs-mode"); - -static int get_bts_rf_state(struct ctrl_cmd *cmd, void *data) -{ - const char *oper, *admin, *policy; - struct gsm_bts *bts = cmd->node; - - if (!bts) { - cmd->reply = "bts not found."; - return CTRL_CMD_ERROR; - } - - oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts)); - admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts)); - policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts)); - - cmd->reply = talloc_asprintf(cmd, "%s,%s,%s", oper, admin, policy); - if (!cmd->reply) { - cmd->reply = "OOM."; - return CTRL_CMD_ERROR; - } - - return CTRL_CMD_REPLY; -} -CTRL_CMD_DEFINE_RO(bts_rf_state, "rf_state"); - -static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data) -{ - struct gsm_network *net = cmd->node; - struct gsm_bts *bts; - const char *policy_name; - - policy_name = osmo_bsc_rf_get_policy_name(net->rf_ctrl->policy); - - llist_for_each_entry(bts, &net->bts_list, list) { - struct gsm_bts_trx *trx; - - /* Exclude the BTS from the global lock */ - if (bts->excl_from_rf_lock) - continue; - - llist_for_each_entry(trx, &bts->trx_list, list) { - if (trx->mo.nm_state.availability == NM_AVSTATE_OK && - trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) { - cmd->reply = talloc_asprintf(cmd, - "state=on,policy=%s,bts=%u,trx=%u", - policy_name, bts->nr, trx->nr); - return CTRL_CMD_REPLY; - } - } - } - - cmd->reply = talloc_asprintf(cmd, "state=off,policy=%s", - policy_name); - return CTRL_CMD_REPLY; -} - -#define TIME_FORMAT_RFC2822 "%a, %d %b %Y %T %z" - -static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data) -{ - int locked = atoi(cmd->value); - struct gsm_network *net = cmd->node; - time_t now = time(NULL); - char now_buf[64]; - struct osmo_bsc_rf *rf; - - if (!net) { - cmd->reply = "net not found."; - return CTRL_CMD_ERROR; - } - - rf = net->rf_ctrl; - - if (!rf) { - cmd->reply = "RF Ctrl is not enabled in the BSC Configuration"; - return CTRL_CMD_ERROR; - } - - talloc_free(rf->last_rf_lock_ctrl_command); - strftime(now_buf, sizeof(now_buf), TIME_FORMAT_RFC2822, gmtime(&now)); - rf->last_rf_lock_ctrl_command = - talloc_asprintf(rf, "rf_locked %u (%s)", locked, now_buf); - - osmo_bsc_rf_schedule_lock(rf, locked == 1 ? '0' : '1'); - - cmd->reply = talloc_asprintf(cmd, "%u", locked); - if (!cmd->reply) { - cmd->reply = "OOM."; - return CTRL_CMD_ERROR; - } - - return CTRL_CMD_REPLY; -} - -static int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data) -{ - int locked = atoi(cmd->value); - - if ((locked != 0) && (locked != 1)) - return 1; - - return 0; -} -CTRL_CMD_DEFINE(net_rf_lock, "rf_locked"); - -static int get_net_bts_num(struct ctrl_cmd *cmd, void *data) -{ - struct gsm_network *net = cmd->node; - - cmd->reply = talloc_asprintf(cmd, "%u", net->num_bts); - return CTRL_CMD_REPLY; -} -CTRL_CMD_DEFINE_RO(net_bts_num, "number-of-bts"); - -/* TRX related commands below here */ -CTRL_HELPER_GET_INT(trx_max_power, struct gsm_bts_trx, max_power_red); -static int verify_trx_max_power(struct ctrl_cmd *cmd, const char *value, void *_data) -{ - int tmp = atoi(value); - - if (tmp < 0 || tmp > 22) { - cmd->reply = "Value must be between 0 and 22"; - return -1; - } - - if (tmp & 1) { - cmd->reply = "Value must be even"; - return -1; - } - - return 0; -} -CTRL_CMD_DEFINE_RANGE(trx_arfcn, "arfcn", struct gsm_bts_trx, arfcn, 0, 1023); - -static int set_trx_max_power(struct ctrl_cmd *cmd, void *_data) -{ - struct gsm_bts_trx *trx = cmd->node; - int old_power; - - /* remember the old value, set the new one */ - old_power = trx->max_power_red; - trx->max_power_red = atoi(cmd->value); - - /* Maybe update the value */ - if (old_power != trx->max_power_red) { - LOGP(DCTRL, LOGL_NOTICE, - "%s updating max_pwr_red(%d)\n", - gsm_trx_name(trx), trx->max_power_red); - abis_nm_update_max_power_red(trx); - } - - return get_trx_max_power(cmd, _data); -} -CTRL_CMD_DEFINE(trx_max_power, "max-power-reduction"); - -int bsc_base_ctrl_cmds_install(void) -{ - int rc = 0; - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc); - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc); - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config); - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc_mnc_apply); - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_lock); - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_bts_num); - - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_lac); - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_ci); - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_apply_config); - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si); - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_chan_load); - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_conn); - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_up); - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_gprs_mode); - rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_state); - - rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_max_power); - rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_arfcn); - - return rc; -} diff --git a/src/osmo-bsc/bsc_ctrl_lookup.c b/src/osmo-bsc/bsc_ctrl_lookup.c index 4328d76eb..63d0c866c 100644 --- a/src/osmo-bsc/bsc_ctrl_lookup.c +++ b/src/osmo-bsc/bsc_ctrl_lookup.c @@ -6,18 +6,14 @@ * All Rights Reserved * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. * */ @@ -47,6 +43,7 @@ static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type, struct gsm_bts *bts = NULL; struct gsm_bts_trx *trx = NULL; struct gsm_bts_trx_ts *ts = NULL; + struct gsm_lchan *lchan = NULL; struct bsc_msc_data *msc = NULL; char *token = vector_slot(vline, *i); long num; @@ -93,6 +90,20 @@ static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type, goto err_missing; *node_data = ts; *node_type = CTRL_NODE_TS; + } else if (!strcmp(token, "lchan")) { + if (*node_type != CTRL_NODE_TS || !*node_data) + goto err_missing; + ts = *node_data; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + if ((num >= 0) && (num < TS_MAX_LCHAN)) + lchan = &ts->lchan[num]; + if (!lchan) + goto err_missing; + *node_data = lchan; + *node_type = CTRL_NODE_LCHAN; } else if (!strcmp(token, "msc")) { if (*node_type != CTRL_NODE_ROOT || !net) goto err_missing; @@ -115,10 +126,7 @@ err_index: return -ERANGE; } -struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net, - const char *bind_addr, uint16_t port) +struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net, uint16_t port) { - return ctrl_interface_setup_dynip2(net, bind_addr, port, - bsc_ctrl_node_lookup, - _LAST_CTRL_NODE_BSC); + return ctrl_interface_setup2(net, port, bsc_ctrl_node_lookup, _LAST_CTRL_NODE_BSC); } diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c index b959c9f4c..c6c3e79a5 100644 --- a/src/osmo-bsc/bsc_init.c +++ b/src/osmo-bsc/bsc_init.c @@ -38,6 +38,7 @@ #include <osmocom/bsc/neighbor_ident.h> #include <osmocom/bsc/bts.h> #include <osmocom/bsc/lb.h> +#include <osmocom/bsc/bsc_stats.h> #include <osmocom/bsc/smscb.h> #include <osmocom/gsm/protocol/gsm_48_049.h> @@ -46,17 +47,7 @@ #include <limits.h> #include <stdbool.h> -static const struct osmo_stat_item_desc bsc_stat_desc[] = { - { "num_bts:total", "Number of configured BTS for this BSC", "", 16, 0 }, -}; - -static const struct osmo_stat_item_group_desc bsc_statg_desc = { - .group_name_prefix = "bsc", - .group_description = "base station controller", - .class_id = OSMO_STATS_CLASS_GLOBAL, - .num_items = ARRAY_SIZE(bsc_stat_desc), - .item_desc = bsc_stat_desc, -}; +struct gsm_network *bsc_gsmnet; int bsc_shutdown_net(struct gsm_network *net) { @@ -85,6 +76,30 @@ static void update_t3122_chan_load_timer(void *data) osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0); } +static void bsc_store_bts_uptime(void *data) +{ + struct gsm_network *net = data; + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + bts_store_uptime(bts); + + /* Keep this timer ticking. */ + osmo_timer_schedule(&net->bts_store_uptime_timer, BTS_STORE_UPTIME_INTERVAL, 0); +} + +static void bsc_store_bts_lchan_durations(void *data) +{ + struct gsm_network *net = data; + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + bts_store_lchan_durations(bts); + + /* Keep this timer ticking. */ + osmo_timer_schedule(&net->bts_store_lchan_durations_timer, BTS_STORE_LCHAN_DURATIONS_INTERVAL, 0); +} + static struct gsm_network *bsc_network_init(void *ctx) { struct gsm_network *net = gsm_network_init(ctx); @@ -100,7 +115,6 @@ static struct gsm_network *bsc_network_init(void *ctx) net->ho = ho_cfg_init(net, NULL); net->hodec2.congestion_check_interval_s = HO_CFG_CONGESTION_CHECK_DEFAULT; - net->neighbor_bss_cells = neighbor_ident_init(net); /* init statistics */ net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0); @@ -118,6 +132,51 @@ static struct gsm_network *bsc_network_init(void *ctx) if (!net->bts_unknown_statg) goto err_free_all; + net->all_allocated.sdcch = (struct osmo_time_cc){ + .cfg = { + .gran_usec = 1*1000000, + .forget_sum_usec = 60*1000000, + .rate_ctr = rate_ctr_group_get_ctr(net->bsc_ctrs, BSC_CTR_ALL_ALLOCATED_SDCCH), + .T_gran = -16, + .T_round_threshold = -17, + .T_forget_sum = -18, + .T_defs = net->T_defs, + }, + }; + net->all_allocated.static_sdcch = (struct osmo_time_cc){ + .cfg = { + .gran_usec = 1*1000000, + .forget_sum_usec = 60*1000000, + .rate_ctr = rate_ctr_group_get_ctr(net->bsc_ctrs, BSC_CTR_ALL_ALLOCATED_STATIC_SDCCH), + .T_gran = -16, + .T_round_threshold = -17, + .T_forget_sum = -18, + .T_defs = net->T_defs, + }, + }; + net->all_allocated.tch = (struct osmo_time_cc){ + .cfg = { + .gran_usec = 1*1000000, + .forget_sum_usec = 60*1000000, + .rate_ctr = rate_ctr_group_get_ctr(net->bsc_ctrs, BSC_CTR_ALL_ALLOCATED_TCH), + .T_gran = -16, + .T_round_threshold = -17, + .T_forget_sum = -18, + .T_defs = net->T_defs, + }, + }; + net->all_allocated.static_tch = (struct osmo_time_cc){ + .cfg = { + .gran_usec = 1*1000000, + .forget_sum_usec = 60*1000000, + .rate_ctr = rate_ctr_group_get_ctr(net->bsc_ctrs, BSC_CTR_ALL_ALLOCATED_STATIC_TCH), + .T_gran = -16, + .T_round_threshold = -17, + .T_forget_sum = -18, + .T_defs = net->T_defs, + }, + }; + INIT_LLIST_HEAD(&net->bts_rejected); gsm_net_update_ctype(net); @@ -129,6 +188,14 @@ static struct gsm_network *bsc_network_init(void *ctx) osmo_timer_setup(&net->t3122_chan_load_timer, update_t3122_chan_load_timer, net); osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0); + /* Init uptime tracking timer. */ + osmo_timer_setup(&net->bts_store_uptime_timer, bsc_store_bts_uptime, net); + osmo_timer_schedule(&net->bts_store_uptime_timer, BTS_STORE_UPTIME_INTERVAL, 0); + + /* Init lchan duration tracking timer. */ + osmo_timer_setup(&net->bts_store_lchan_durations_timer, bsc_store_bts_lchan_durations, net); + osmo_timer_schedule(&net->bts_store_lchan_durations_timer, BTS_STORE_LCHAN_DURATIONS_INTERVAL, 0); + net->cbc->net = net; net->cbc->mode = BSC_CBC_LINK_MODE_DISABLED; net->cbc->server.local_addr = bsc_cbc_default_server_local_addr; @@ -137,6 +204,7 @@ static struct gsm_network *bsc_network_init(void *ctx) net->cbc->client.remote_addr = (struct osmo_sockaddr_str){ .port = CBSP_TCP_PORT, }; net->cbc->client.local_addr = (struct osmo_sockaddr_str){}; + net->pcu_sock_wqueue_len_max = BSC_PCU_SOCK_WQUEUE_LEN_DEFAULT; return net; err_free_all: diff --git a/src/osmo-bsc/bsc_rf_ctrl.c b/src/osmo-bsc/bsc_rf_ctrl.c index a845859be..108f7e9ce 100644 --- a/src/osmo-bsc/bsc_rf_ctrl.c +++ b/src/osmo-bsc/bsc_rf_ctrl.c @@ -124,6 +124,96 @@ enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts) } } +enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_trx(struct gsm_bts_trx *trx) +{ + if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED) + return OSMO_BSC_RF_OPSTATE_OPERATIONAL; + return OSMO_BSC_RF_OPSTATE_INOPERATIONAL; +} + +enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_trx(struct gsm_bts_trx *trx) +{ + if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + return OSMO_BSC_RF_ADMINSTATE_UNLOCKED; + return OSMO_BSC_RF_ADMINSTATE_LOCKED; +} + +/* Return a string listing the state of the given TRX. + * For details, see bsc_rf_states_c(). + */ +static int bsc_rf_state_of_trx_buf(char *buf, size_t buflen, struct gsm_bts_trx *trx) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + OSMO_STRBUF_PRINTF(sb, "%u,%u,%s,%s,%s,%s;", + trx->bts->nr, trx->nr, + osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_trx(trx)), + osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_trx(trx)), + osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(trx->bts)), + trx->rsl_link_primary ? "rsl-up" : "rsl-down"); + return sb.chars_needed; +} + +/* Same as bsc_rf_states_of_bts_c() but return result in a fixed-size buffer. + * For details, see bsc_rf_states_c(). + * Return the amount of characters that would be written to the buffer if it is large enough, like snprintf(). */ +static int bsc_rf_states_of_bts_buf(char *buf, size_t buflen, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + + llist_for_each_entry(trx, &bts->trx_list, list) { + OSMO_STRBUF_APPEND(sb, bsc_rf_state_of_trx_buf, trx); + } + return sb.chars_needed; +} + +/* Return a string listing the states of each TRX for the given BTS. + * For details, see bsc_rf_states_c(). + * + * \param ctx Talloc context to allocate the returned string from. + * \param bts BTS of which to list the TRX states. + * \return talloc allocated string. + */ +char *bsc_rf_states_of_bts_c(void *ctx, struct gsm_bts *bts) +{ + OSMO_NAME_C_IMPL(ctx, 256, "ERROR", bsc_rf_states_of_bts_buf, bts); +} + +/* Same as bsc_rf_states_c() but return result in a fixed-size buffer. + * For details, see bsc_rf_states_c(). + * Return the amount of characters that would be written to the buffer if it is large enough, like snprintf(). */ +static int bsc_rf_states_buf(char *buf, size_t buflen) +{ + struct gsm_bts *bts; + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + OSMO_STRBUF_APPEND(sb, bsc_rf_states_of_bts_buf, bts); + } + return sb.chars_needed; +} + +/* Return a string listing the states of all TRX of all BTS. + * The string has the form: + * <bts_nr>,<trx_nr>,<opstate>,<adminstate>,<rf_policy>,<rsl_status>;<bts_nr>,<trx_nr>,...;...; + * (always terminates in a semicolon). + * + * Meaning of the elements: + * - bts_nr: 0..255 -- BTS index. + * - trx_nr: 0..255 -- TRX index. + * - opstate: inoperational|operational -- whether RF is active. + * - adminstate: unlocked|locked -- whether the TRX is configured as RF-locked. + * - rf_policy: off|on|grace|unknown -- which state RF should be in according to user rf_lock requests. + * - rsl_status: rsl-up|rsl-down -- 'rsl-up' if an RSL link to the TRX is currently present. + * + * \param ctx Talloc context to allocate the returned string from. + * \return talloc allocated string. + */ +char *bsc_rf_states_c(void *ctx) +{ + OSMO_NAME_C_IMPL(ctx, 4096, "ERROR", bsc_rf_states_buf); +} + static int lock_each_trx(struct gsm_network *net, bool lock) { struct gsm_bts *bts; @@ -213,7 +303,7 @@ static void rf_check_cb(void *_data) struct gsm_bts_trx *trx; /* don't bother to check a booting or missing BTS */ - if (!bts->oml_link || !is_ipaccess_bts(bts)) + if (!bts->oml_link || !is_ipa_abisip_bts(bts)) continue; /* Exclude the BTS from the global lock */ diff --git a/src/osmo-bsc/bsc_sccp.c b/src/osmo-bsc/bsc_sccp.c index 0cd1dc9f3..323d7fd4f 100644 --- a/src/osmo-bsc/bsc_sccp.c +++ b/src/osmo-bsc/bsc_sccp.c @@ -21,44 +21,120 @@ * */ +#include <osmocom/core/utils.h> + #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/lb.h> -/* We need an unused SCCP conn_id across all SCCP users. */ -int bsc_sccp_inst_next_conn_id(struct osmo_sccp_instance *sccp) +void bscp_sccp_conn_node_init(struct bscp_sccp_conn_node *sccp_conn, struct gsm_subscriber_connection *gscon) +{ + sccp_conn->conn_id = SCCP_CONN_ID_UNSET; + sccp_conn->gscon = gscon; +} + +struct bsc_sccp_inst *bsc_sccp_inst_alloc(void *ctx) { - static uint32_t next_id = 1; - int i; - - /* This looks really suboptimal, but in most cases the static next_id should indicate exactly the next unused - * conn_id, and we only iterate all conns once to make super sure that it is not already in use. */ - - for (i = 0; i < 0xFFFFFF; i++) { - struct gsm_subscriber_connection *conn; - uint32_t conn_id = next_id; - bool conn_id_already_used = false; - next_id = (next_id + 1) & 0xffffff; - - llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) { - if (conn->sccp.msc && conn->sccp.msc->a.sccp == sccp) { - if (conn_id == conn->sccp.conn_id) { - conn_id_already_used = true; - break; - } - } - - if (bsc_gsmnet->smlc->sccp == sccp - && conn->lcs.lb.state != SUBSCR_SCCP_ST_NONE) { - if (conn_id == conn->lcs.lb.conn_id) { - conn_id_already_used = true; - break; - } - } + struct bsc_sccp_inst *bsc_sccp; + + bsc_sccp = talloc_zero(ctx, struct bsc_sccp_inst); + OSMO_ASSERT(bsc_sccp); + bsc_sccp->next_id = 1; + + return bsc_sccp; +} + +int bsc_sccp_inst_register_gscon(struct bsc_sccp_inst *bsc_sccp, struct bscp_sccp_conn_node *sccp_conn) +{ + struct rb_node **n = &(bsc_sccp->connections.rb_node); + struct rb_node *parent = NULL; + uint32_t conn_id = sccp_conn->conn_id; + + OSMO_ASSERT(conn_id != SCCP_CONN_ID_UNSET); + + while (*n) { + struct bscp_sccp_conn_node *it = container_of(*n, struct bscp_sccp_conn_node, node); + + parent = *n; + if (conn_id < it->conn_id) { + n = &((*n)->rb_left); + } else if (conn_id > it->conn_id) { + n = &((*n)->rb_right); + } else { + LOGP(DMSC, LOGL_ERROR, + "Trying to reserve already reserved conn_id %u\n", conn_id); + return -EEXIST; } + } + + rb_link_node(&sccp_conn->node, parent, n); + rb_insert_color(&sccp_conn->node, &bsc_sccp->connections); + return 0; +} + +void bsc_sccp_inst_unregister_gscon(struct bsc_sccp_inst *bsc_sccp, struct bscp_sccp_conn_node *sccp_conn) +{ + OSMO_ASSERT(sccp_conn->conn_id != SCCP_CONN_ID_UNSET); + rb_erase(&sccp_conn->node, &bsc_sccp->connections); +} + +/* Helper function to Check if the given connection id is already assigned */ +struct gsm_subscriber_connection *bsc_sccp_inst_get_gscon_by_conn_id(const struct bsc_sccp_inst *bsc_sccp, uint32_t conn_id) +{ + const struct rb_node *node = bsc_sccp->connections.rb_node; + + OSMO_ASSERT(conn_id != SCCP_CONN_ID_UNSET); + /* Range (0..SCCP_CONN_ID_MAX) expected, see bsc_sccp_inst_next_conn_id() */ + OSMO_ASSERT(conn_id <= SCCP_CONN_ID_MAX); - if (!conn_id_already_used) - return conn_id; + while (node) { + struct bscp_sccp_conn_node *sccp_conn = container_of(node, struct bscp_sccp_conn_node, node); + if (conn_id < sccp_conn->conn_id) + node = node->rb_left; + else if (conn_id > sccp_conn->conn_id) + node = node->rb_right; + else + return sccp_conn->gscon; } - return -1; + + return NULL; +} + +/* We need an unused SCCP conn_id across all SCCP users. */ +uint32_t bsc_sccp_inst_next_conn_id(struct bsc_sccp_inst *bsc_sccp) +{ + uint32_t first_id, test_id; + + first_id = test_id = bsc_sccp->next_id; + + /* SUA: RFC3868 sec 3.10.4: + * The source reference number is a 4 octet long integer. + * This is allocated by the source SUA instance. + * M3UA/SCCP: ITU-T Q.713 sec 3.3: + * The "source local reference" parameter field is a three-octet field containing a + * reference number which is generated and used by the local node to identify the + * connection section after the connection section is set up. + * The coding "all ones" is reserved for future use. + *Hence, as we currently use the connection ID also as local reference, + *let's simply use 24 bit ids to fit all link types (excluding 0x00ffffff). + */ + + while (bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, test_id)) { + /* Optimized modulo operation (% SCCP_CONN_ID_MAX) using bitwise AND plus CMP: */ + test_id = (test_id + 1) & 0x00FFFFFF; + if (OSMO_UNLIKELY(test_id == 0x00FFFFFF)) + test_id = 0; + + /* Did a whole loop, all used, fail */ + if (OSMO_UNLIKELY(test_id == first_id)) + return SCCP_CONN_ID_UNSET; + } + + bsc_sccp->next_id = test_id; + /* Optimized modulo operation (% SCCP_CONN_ID_MAX) using bitwise AND plus CMP: */ + bsc_sccp->next_id = (bsc_sccp->next_id + 1) & 0x00FFFFFF; + if (OSMO_UNLIKELY(bsc_sccp->next_id == 0x00FFFFFF)) + bsc_sccp->next_id = 0; + + return test_id; } diff --git a/src/osmo-bsc/bsc_stats.c b/src/osmo-bsc/bsc_stats.c new file mode 100644 index 000000000..ce307207c --- /dev/null +++ b/src/osmo-bsc/bsc_stats.c @@ -0,0 +1,236 @@ +/* osmo-bsc statistics */ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/bsc/bsc_stats.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/stats.h> +#include <osmocom/core/stat_item.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/chan_counts.h> + +const struct rate_ctr_desc bsc_ctr_description[] = { + [BSC_CTR_ASSIGNMENT_ATTEMPTED] = {"assignment:attempted", "Assignment attempts"}, + [BSC_CTR_ASSIGNMENT_COMPLETED] = {"assignment:completed", "Assignment completed"}, + [BSC_CTR_ASSIGNMENT_STOPPED] = {"assignment:stopped", "Connection ended during Assignment"}, + [BSC_CTR_ASSIGNMENT_NO_CHANNEL] = {"assignment:no_channel", "Failure to allocate lchan for Assignment"}, + [BSC_CTR_ASSIGNMENT_TIMEOUT] = {"assignment:timeout", "Assignment timed out"}, + [BSC_CTR_ASSIGNMENT_FAILED] = {"assignment:failed", "Received Assignment Failure message"}, + [BSC_CTR_ASSIGNMENT_ERROR] = {"assignment:error", "Assignment failed for other reason"}, + + [BSC_CTR_HANDOVER_ATTEMPTED] = {"handover:attempted", "Handover attempts"}, + [BSC_CTR_HANDOVER_COMPLETED] = {"handover:completed", "Handover completed"}, + [BSC_CTR_HANDOVER_STOPPED] = {"handover:stopped", "Connection ended during HO"}, + [BSC_CTR_HANDOVER_NO_CHANNEL] = {"handover:no_channel", "Failure to allocate lchan for HO"}, + [BSC_CTR_HANDOVER_TIMEOUT] = {"handover:timeout", "Handover timed out"}, + [BSC_CTR_HANDOVER_FAILED] = {"handover:failed", "Received Handover Fail messages"}, + [BSC_CTR_HANDOVER_ERROR] = {"handover:error", "Handover failed for other reason"}, + + [BSC_CTR_INTRA_CELL_HO_ATTEMPTED] = {"intra_cell_ho:attempted", "Intra-Cell handover attempts"}, + [BSC_CTR_INTRA_CELL_HO_COMPLETED] = {"intra_cell_ho:completed", "Intra-Cell handover completed"}, + [BSC_CTR_INTRA_CELL_HO_STOPPED] = {"intra_cell_ho:stopped", "Connection ended during HO"}, + [BSC_CTR_INTRA_CELL_HO_NO_CHANNEL] = {"intra_cell_ho:no_channel", "Failure to allocate lchan for HO"}, + [BSC_CTR_INTRA_CELL_HO_TIMEOUT] = {"intra_cell_ho:timeout", "Handover timed out"}, + [BSC_CTR_INTRA_CELL_HO_FAILED] = {"intra_cell_ho:failed", "Received Handover Fail messages"}, + [BSC_CTR_INTRA_CELL_HO_ERROR] = {"intra_cell_ho:error", "Intra-cell handover failed for other reason"}, + + [BSC_CTR_INTRA_BSC_HO_ATTEMPTED] = {"intra_bsc_ho:attempted", "Intra-BSC inter-cell handover attempts"}, + [BSC_CTR_INTRA_BSC_HO_COMPLETED] = {"intra_bsc_ho:completed", "Intra-BSC inter-cell handover completed"}, + [BSC_CTR_INTRA_BSC_HO_STOPPED] = {"intra_bsc_ho:stopped", "Connection ended during HO"}, + [BSC_CTR_INTRA_BSC_HO_NO_CHANNEL] = {"intra_bsc_ho:no_channel", "Failure to allocate lchan for HO"}, + [BSC_CTR_INTRA_BSC_HO_TIMEOUT] = {"intra_bsc_ho:timeout", "Handover timed out"}, + [BSC_CTR_INTRA_BSC_HO_FAILED] = {"intra_bsc_ho:failed", "Received Handover Fail messages"}, + [BSC_CTR_INTRA_BSC_HO_ERROR] = {"intra_bsc_ho:error", "Intra-BSC inter-cell HO failed for other reason"}, + + [BSC_CTR_INTER_BSC_HO_OUT_ATTEMPTED] = {"interbsc_ho_out:attempted", + "Attempts to handover to remote BSS"}, + [BSC_CTR_INTER_BSC_HO_OUT_COMPLETED] = {"interbsc_ho_out:completed", + "Handover to remote BSS completed"}, + [BSC_CTR_INTER_BSC_HO_OUT_STOPPED] = {"interbsc_ho_out:stopped", "Connection ended during HO"}, + [BSC_CTR_INTER_BSC_HO_OUT_TIMEOUT] = {"interbsc_ho_out:timeout", "Handover timed out"}, + [BSC_CTR_INTER_BSC_HO_OUT_FAILED] = {"interbsc_ho_out:failed", "Received Handover Fail message"}, + [BSC_CTR_INTER_BSC_HO_OUT_ERROR] = {"interbsc_ho_out:error", + "Handover to remote BSS failed for other reason"}, + + [BSC_CTR_INTER_BSC_HO_IN_ATTEMPTED] = {"interbsc_ho_in:attempted", + "Attempts to handover from remote BSS"}, + [BSC_CTR_INTER_BSC_HO_IN_COMPLETED] = {"interbsc_ho_in:completed", + "Handover from remote BSS completed"}, + [BSC_CTR_INTER_BSC_HO_IN_STOPPED] = {"interbsc_ho_in:stopped", "Connection ended during HO"}, + [BSC_CTR_INTER_BSC_HO_IN_NO_CHANNEL] = {"interbsc_ho_in:no_channel", + "Failure to allocate lchan for HO"}, + [BSC_CTR_INTER_BSC_HO_IN_TIMEOUT] = {"interbsc_ho_in:timeout", "Handover from remote BSS timed out"}, + [BSC_CTR_INTER_BSC_HO_IN_FAILED] = {"interbsc_ho_in:failed", "Received Handover Fail message"}, + [BSC_CTR_INTER_BSC_HO_IN_ERROR] = {"interbsc_ho_in:error", + "Handover from remote BSS failed for other reason"}, + + [BSC_CTR_SRVCC_ATTEMPTED] = {"srvcc:attempted", "Intra-BSC SRVCC attempts"}, + [BSC_CTR_SRVCC_COMPLETED] = {"srvcc:completed", "Intra-BSC SRVCC completed"}, + [BSC_CTR_SRVCC_STOPPED] = {"srvcc:stopped", "Connection ended during HO"}, + [BSC_CTR_SRVCC_NO_CHANNEL] = {"srvcc:no_channel", "Failure to allocate lchan for HO"}, + [BSC_CTR_SRVCC_TIMEOUT] = {"srvcc:timeout", "SRVCC timed out"}, + [BSC_CTR_SRVCC_FAILED] = {"srvcc:failed", "Received SRVCC Fail messages"}, + [BSC_CTR_SRVCC_ERROR] = {"srvcc:error", "Re-assignment failed for other reason"}, + + [BSC_CTR_PAGING_ATTEMPTED] = {"paging:attempted", "Paging attempts for a subscriber"}, + [BSC_CTR_PAGING_DETACHED] = {"paging:detached", "Paging request send failures because no responsible BTS was found"}, + [BSC_CTR_PAGING_RESPONDED] = {"paging:responded", "Paging attempts with successful response"}, + [BSC_CTR_PAGING_EXPIRED] = {"paging:expired", "Paging Request expired because of timeout T3113"}, + [BSC_CTR_PAGING_NO_ACTIVE_PAGING] = {"paging:no_active_paging", "Paging response without an active paging request (arrived after paging expiration?)"}, + + [BSC_CTR_UNKNOWN_UNIT_ID] = {"abis:unknown_unit_id", "Connection attempts from unknown IPA CCM Unit ID"}, + + [BSC_CTR_MSCPOOL_SUBSCR_NO_MSC] = {"mscpool:subscr:no_msc", + "Complete Layer 3 requests lost because no connected MSC is found available"}, + [BSC_CTR_MSCPOOL_EMERG_FORWARDED] = {"mscpool:emerg:forwarded", + "Emergency call requests forwarded to an MSC (see also per-MSC counters"}, + [BSC_CTR_MSCPOOL_EMERG_LOST] = {"mscpool:emerg:lost", + "Emergency call requests lost because no MSC was found available"}, + [BSC_CTR_ALL_ALLOCATED_SDCCH] = {"all_allocated:sdcch", "Cumulative counter of seconds where all SDCCH channels were allocated"}, + [BSC_CTR_ALL_ALLOCATED_STATIC_SDCCH] = {"all_allocated:static_sdcch", + "Cumulative counter of seconds where all non-dynamic SDCCH channels were allocated"}, + [BSC_CTR_ALL_ALLOCATED_TCH] = {"all_allocated:tch", "Cumulative counter of seconds where all TCH channels were allocated"}, + [BSC_CTR_ALL_ALLOCATED_STATIC_TCH] = {"all_allocated:static_tch", + "Cumulative counter of seconds where all non-dynamic TCH channels were allocated"}, +}; + +const struct rate_ctr_group_desc bsc_ctrg_desc = { + "bsc", + "base station controller", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(bsc_ctr_description), + bsc_ctr_description, +}; + +static const struct osmo_stat_item_desc bsc_stat_desc[] = { + [BSC_STAT_NUM_BTS_OML_CONNECTED] = { "num_bts:oml_connected", "Number of BTS for this BSC where OML is up", "", 16, 0 }, + [BSC_STAT_NUM_BTS_ALL_TRX_RSL_CONNECTED] = { "num_bts:all_trx_rsl_connected", "Number of BTS for this BSC where RSL is up for all TRX", "", 16, 0 }, + [BSC_STAT_NUM_BTS_TOTAL] = { "num_bts:total", "Number of configured BTS for this BSC", "", 16, 0 }, + [BSC_STAT_NUM_TRX_RSL_CONNECTED] = { "num_trx:rsl_connected", "Number of TRX where RSL is up, total sum across all BTS", "", 16, 0 }, + [BSC_STAT_NUM_TRX_TOTAL] = { "num_trx:total", "Number of configured TRX, total sum across all BTS", "", 1, 0 }, + [BSC_STAT_NUM_MSC_CONNECTED] = { "num_msc:connected", "Number of actively connected MSCs", "", 16, 0 }, + [BSC_STAT_NUM_MSC_TOTAL] = { "num_msc:total", "Number of configured MSCs, not necessarily connected", "", 1, 0 }, +}; + +const struct osmo_stat_item_group_desc bsc_statg_desc = { + .group_name_prefix = "bsc", + .group_description = "base station controller", + .class_id = OSMO_STATS_CLASS_GLOBAL, + .num_items = ARRAY_SIZE(bsc_stat_desc), + .item_desc = bsc_stat_desc, +}; + +/* Count all BTS and TRX OML and RSL stati and update stat items */ +void bsc_update_connection_stats(struct gsm_network *net) +{ + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + + /* Nr of configured BTS and total sum of configured TRX across all BTS */ + int num_bts = 0; + int num_trx_total = 0; + /* Nr of BTS where OML is up */ + int bts_oml_connected = 0; + /* Nr of TRX across all BTS where RSL is up */ + int trx_rsl_connected_total = 0; + /* Nr of BTS that have all TRX RSL up */ + int bts_rsl_all_trx_connected = 0; + + llist_for_each_entry(bts, &net->bts_list, list) { + bool oml_connected = false; + int num_trx = 0; + int trx_rsl_connected = 0; + + llist_for_each_entry(trx, &bts->trx_list, list) { + /* If any one trx is usable, it means OML for this BTS is connected */ + if (trx_is_usable(trx)) + oml_connected = true; + + /* Count nr of TRX for this BTS */ + num_trx++; + if (trx->ts[0].is_rsl_ready) + trx_rsl_connected++; + } + + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_NUM_TRX_RSL_CONNECTED), + trx_rsl_connected); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_NUM_TRX_TOTAL), + num_trx); + + num_trx_total += num_trx; + trx_rsl_connected_total += trx_rsl_connected; + + num_bts++; + if (oml_connected) + bts_oml_connected++; + if (trx_rsl_connected == num_trx) + bts_rsl_all_trx_connected++; + + all_allocated_update_bts(bts); + } + + osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_BTS_OML_CONNECTED), + bts_oml_connected); + osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_BTS_ALL_TRX_RSL_CONNECTED), + bts_rsl_all_trx_connected); + osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_BTS_TOTAL), num_bts); + osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_TRX_RSL_CONNECTED), + trx_rsl_connected_total); + osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_TRX_TOTAL), num_trx_total); + + /* This is optional, just running this to catch bugs in chan_counts accounting. If there is a bug, there will be + * a DLGLOBAL ERROR logged, and the error gets fixed. */ + chan_counts_bsc_verify(); +} + +static void all_allocated_update(struct all_allocated *all_allocated, const struct chan_counts *c) +{ + osmo_time_cc_set_flag(&all_allocated->sdcch, + c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH] + && !c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]); + + osmo_time_cc_set_flag(&all_allocated->static_sdcch, + c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH] + && !c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]); + + osmo_time_cc_set_flag(&all_allocated->tch, + (c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F] + + c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H]) + && !(c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F] + + c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H])); + + osmo_time_cc_set_flag(&all_allocated->static_tch, + (c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F] + + c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H]) + && !(c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F] + + c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H])); +} + +void all_allocated_update_bts(struct gsm_bts *bts) +{ + all_allocated_update(&bts->all_allocated, &bts->chan_counts); +} + +void all_allocated_update_bsc(void) +{ + struct gsm_network *net = bsc_gsmnet; + all_allocated_update(&net->all_allocated, &net->chan_counts); +} diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c index ed08e86ad..f1f48bc35 100644 --- a/src/osmo-bsc/bsc_subscr_conn_fsm.c +++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c @@ -32,6 +32,7 @@ #include <osmocom/bsc/handover_fsm.h> #include <osmocom/bsc/lchan_fsm.h> #include <osmocom/bsc/lchan_rtp_fsm.h> +#include <osmocom/bsc/lchan.h> #include <osmocom/bsc/bsc_subscriber.h> #include <osmocom/bsc/osmo_bsc_sigtran.h> #include <osmocom/bsc/osmo_bsc_lcls.h> @@ -45,9 +46,11 @@ #include <osmocom/bsc/assignment_fsm.h> #include <osmocom/bsc/codec_pref.h> #include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> +#include <osmocom/mgcp_client/mgcp_client_pool.h> #include <osmocom/core/byteswap.h> #include <osmocom/bsc/lb.h> #include <osmocom/bsc/lcs_loc_req.h> +#include <osmocom/bsc/vgcs_fsm.h> #define S(x) (1 << (x)) @@ -59,6 +62,8 @@ enum gscon_fsm_states { ST_INIT, + /* wait for initial BSSMAP after the MSC opened a new SCCP connection */ + ST_WAIT_INITIAL_USER_DATA, /* waiting for CC from MSC */ ST_WAIT_CC, /* active connection */ @@ -66,15 +71,17 @@ enum gscon_fsm_states { ST_ASSIGNMENT, ST_HANDOVER, /* BSSMAP CLEAR has been received */ - ST_CLEARING, + ST_WAIT_CLEAR_CMD, + ST_WAIT_SCCP_RLSD, }; static const struct value_string gscon_fsm_event_names[] = { {GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"}, + {GSCON_EV_A_INITIAL_USER_DATA, "A_INITIAL_USER_DATA"}, {GSCON_EV_MO_COMPL_L3, "MO_COMPL_L3"}, {GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"}, {GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"}, - {GSCON_EV_A_DISC_IND, "DISCONNET.ind"}, + {GSCON_EV_A_DISC_IND, "DISCONNECT.ind"}, {GSCON_EV_A_COMMON_ID_IND, "COMMON_ID.ind"}, {GSCON_EV_ASSIGNMENT_START, "ASSIGNMENT_START"}, {GSCON_EV_ASSIGNMENT_END, "ASSIGNMENT_END"}, @@ -93,8 +100,10 @@ static const struct value_string gscon_fsm_event_names[] = { }; struct osmo_tdef_state_timeout conn_fsm_timeouts[32] = { + [ST_WAIT_INITIAL_USER_DATA] = { .T = -25 }, [ST_WAIT_CC] = { .T = -3210 }, - [ST_CLEARING] = { .T = -4 }, + [ST_WAIT_CLEAR_CMD] = { .T = -4 }, + [ST_WAIT_SCCP_RLSD] = { .T = -4 }, }; /* Transition to a state, using the T timer defined in conn_fsm_timeouts. @@ -137,34 +146,85 @@ int gscon_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg) return rc; } -static void gscon_bssmap_clear(struct gsm_subscriber_connection *conn, - enum gsm0808_cause cause) +void gscon_bssmap_clear(struct gsm_subscriber_connection *conn, enum gsm0808_cause cause) { + /* already clearing? */ + switch (conn->fi->state) { + case ST_WAIT_CLEAR_CMD: + case ST_WAIT_SCCP_RLSD: + return; + default: + break; + } + + conn->clear_cause = cause; + conn_fsm_state_chg(ST_WAIT_CLEAR_CMD); +} +static void gscon_fsm_wait_clear_cmd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ struct msgb *resp; int rc; - - if (conn->rx_clear_command) { - LOGPFSML(conn->fi, LOGL_DEBUG, "Not sending BSSMAP CLEAR REQUEST, already got CLEAR COMMAND from MSC\n"); - return; - } + struct gsm_subscriber_connection *conn = fi->priv; + enum gsm0808_cause cause = conn->clear_cause; if (!conn->sccp.msc) { - LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message, no MSC for this conn\n"); - return; + LOGPFSML(fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message, no MSC for this conn\n"); + goto nothing_sent; } - LOGPFSML(conn->fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST(%s) to MSC\n", gsm0808_cause_name(cause)); + LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST(%s) to MSC\n", gsm0808_cause_name(cause)); resp = gsm0808_create_clear_rqst(cause); if (!resp) { - LOGPFSML(conn->fi, LOGL_ERROR, "Unable to compose BSSMAP Clear Request message\n"); - return; + LOGPFSML(fi, LOGL_ERROR, "Unable to compose BSSMAP Clear Request message\n"); + goto nothing_sent; } - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST)); rc = osmo_bsc_sigtran_send(conn, resp); - if (rc < 0) + if (rc < 0) { LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message\n"); + goto nothing_sent; + } + return; + +nothing_sent: + /* Normally, we request a CLEAR from the MSC and terminate as soon as the CLEAR COMMAND has been issued by the + * MSC. But if we are trying to clear without being able to send anything to the MSC, we might as well shut down + * the conn right away now. */ + conn_fsm_state_chg(ST_WAIT_SCCP_RLSD); +} + +void gscon_fsm_wait_sccp_rlsd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* According to 3GPP 48.008 3.1.9.1. "The BSS need not wait for the radio channel + * release to be completed or for the guard timer to expire before returning the + * CLEAR COMPLETE message" */ + if (!gscon_sigtran_send(conn, gsm0808_create_clear_complete())) + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE)); + + /* Give the handover_fsm a chance to book this as handover success before tearing down everything, + * making it look like a sudden death failure. */ + if (conn->ho.fi) + osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL); + + if (conn->lcs.loc_req) + osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL); + + if (conn->vgcs_call.fi) + osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_CLEANUP, NULL); + + if (conn->vgcs_chan.fi) + osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_CLEANUP, NULL); + + gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(conn->clear_cause)); + osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint); + + /* If there is no SCCP connection at all, then no need to wait for an SCCP RLSD. */ + if (!conn->sccp.msc || conn->sccp.state != SUBSCR_SCCP_ST_CONNECTED) + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } /* forward MO DTAP from RSL side to BSSAP side */ @@ -176,7 +236,7 @@ static void forward_dtap(struct gsm_subscriber_connection *conn, struct msgb *ms OSMO_ASSERT(conn); resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg)); - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_DTAP]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_DTAP)); gscon_sigtran_send(conn, resp); } @@ -193,9 +253,12 @@ static void gscon_release_lchan(struct gsm_subscriber_connection *conn, struct g conn->lchan = NULL; if (conn->ho.fi && conn->ho.new_lchan == lchan) conn->ho.new_lchan = NULL; + if (conn->vgcs_chan.new_lchan == lchan) + conn->vgcs_chan.new_lchan = NULL; if (conn->assignment.new_lchan == lchan) conn->assignment.new_lchan = NULL; - lchan_release(lchan, do_rr_release, err, cause_rr); + lchan_release(lchan, do_rr_release, err, cause_rr, + gscon_last_eutran_plmn(conn)); } void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_release, enum gsm48_rr_cause cause_rr) @@ -208,89 +271,149 @@ void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_rel gscon_release_lchan(conn, conn->lchan, do_rr_release, false, cause_rr); } -static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim) +static int validate_initial_user_data(struct osmo_fsm_inst *fi, struct msgb *msg) { - struct gsm_subscriber_connection *conn = fi->priv; - struct msgb *msg = scu_prim->oph.msg; struct bssmap_header *bs; - uint8_t bssmap_type; + enum BSS_MAP_MSG_TYPE bssmap_type; msg->l3h = msgb_l2(msg); if (!msgb_l3(msg)) { LOGPFSML(fi, LOGL_ERROR, "internal error: no l3 in msg\n"); - goto refuse; + return -EINVAL; } if (msgb_l3len(msg) < sizeof(*bs)) { - LOGPFSML(fi, LOGL_NOTICE, "message too short for BSSMAP header (%u < %zu)\n", + LOGPFSML(fi, LOGL_ERROR, "message too short for BSSMAP header (%u < %zu)\n", msgb_l3len(msg), sizeof(*bs)); - goto refuse; + return -EINVAL; } bs = (struct bssmap_header*)msgb_l3(msg); if (msgb_l3len(msg) < (bs->length + sizeof(*bs))) { - LOGPFSML(fi, LOGL_NOTICE, + LOGPFSML(fi, LOGL_ERROR, "message too short for length indicated in BSSMAP header (%u < %u)\n", msgb_l3len(msg), bs->length); - goto refuse; + return -EINVAL; } switch (bs->type) { case BSSAP_MSG_BSS_MANAGEMENT: break; default: - LOGPFSML(fi, LOGL_NOTICE, - "message type not allowed for N-CONNECT: %s\n", gsm0808_bssap_name(bs->type)); - goto refuse; + LOGPFSML(fi, LOGL_ERROR, + "message type not allowed for initial BSSMAP: %s\n", gsm0808_bssap_name(bs->type)); + return -EINVAL; } msg->l4h = &msg->l3h[sizeof(*bs)]; - bssmap_type = msg->l4h[0]; - - LOGPFSML(fi, LOGL_DEBUG, "Rx N-CONNECT: %s: %s\n", gsm0808_bssap_name(bs->type), - gsm0808_bssmap_name(bssmap_type)); + /* Validate initial message type. See also BSC_Tests.TC_outbound_connect. */ + bssmap_type = msg->l4h[0]; switch (bssmap_type) { case BSS_MAP_MSG_HANDOVER_RQST: case BSS_MAP_MSG_PERFORM_LOCATION_RQST: - break; + case BSS_MAP_MSG_VGCS_VBS_SETUP: + case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST: + return 0; default: - LOGPFSML(fi, LOGL_NOTICE, "No support for N-CONNECT: %s: %s\n", + LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n", gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type)); - goto refuse; + return -EINVAL; } +} - /* First off, accept the new conn. */ - if (osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, - &scu_prim->u.connect.called_addr, NULL, 0)) { - LOGPFSML(fi, LOGL_ERROR, "Cannot send SCCP CONN RESP\n"); - goto refuse; - } +static void handle_initial_user_data(struct osmo_fsm_inst *fi, struct msgb *msg) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct bssmap_header *bs; + enum BSS_MAP_MSG_TYPE bssmap_type; - /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */ - conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; + /* validate_initial_user_data() must be called before this */ + OSMO_ASSERT(msgb_l4(msg)); + + bs = msgb_l3(msg); + bssmap_type = msg->l4h[0]; + + /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() (OS#2969) */ + + LOGPFSML(fi, LOGL_DEBUG, "Rx initial BSSMAP: %s: %s\n", gsm0808_bssap_name(bs->type), + gsm0808_bssmap_name(bssmap_type)); switch (bssmap_type) { case BSS_MAP_MSG_HANDOVER_RQST: - /* Inter-BSC MT Handover Request, another BSS is handovering to us. */ + rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST]); + /* Inter-BSC incoming Handover Request, another BSS is handovering to us. */ handover_start_inter_bsc_in(conn, msg); return; case BSS_MAP_MSG_PERFORM_LOCATION_RQST: + rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST]); /* Location Services: MSC asks for location of an IDLE subscriber */ conn_fsm_state_chg(ST_ACTIVE); lcs_loc_req_start(conn, msg); return; + case BSS_MAP_MSG_VGCS_VBS_SETUP: + rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_VGCS_VBS_SETUP]); + /* VGCS: MSC asks vor voice group/bcast call. */ + conn_fsm_state_chg(ST_ACTIVE); + vgcs_vbs_call_start(conn, msg); + return; + + case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST: + rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_VGCS_VBS_ASSIGN_RQST]); + /* VGCS: MSC asks vor resource (channel) for voice group/bcast call. */ + conn_fsm_state_chg(ST_ACTIVE); + vgcs_vbs_chan_start(conn, msg); + return; + default: - OSMO_ASSERT(false); + LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n", + gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type)); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } +} + +static void handle_sccp_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *msg = scu_prim->oph.msg; + + /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */ + conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; + + msg->l3h = msgb_l2(msg); + + /* If (BSSMAP) user data is included, validate it before accepting the connection */ + if (msgb_l3(msg) && msgb_l3len(msg)) { + if (validate_initial_user_data(fi, msg)) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } } -refuse: - osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, - &scu_prim->u.connect.called_addr, 0); - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + /* accept the new conn. */ + if (osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, NULL, 0)) { + LOGPFSML(fi, LOGL_ERROR, "Cannot send SCCP CONN RESP\n"); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } + + /* The initial user data may already be included in this N-Connect, or it may come later in a separate message. + * If it is already included, also go to ST_WAIT_INITIAL_USER_DATA now, so that we don't have to tend to two + * separate code paths doing the same thing (handling of HANDOVER_END). */ + OSMO_ASSERT(conn_fsm_state_chg(ST_WAIT_INITIAL_USER_DATA) == 0); + + /* It is usually a bad idea to continue using a fi after a state change, because the fi might terminate during + * the state change. In this case it is certain that the fi stays around for the initial user data. */ + if (msgb_l3(msg) && msgb_l3len(msg)) { + handle_initial_user_data(fi, msg); + } else { + LOGPFSML(fi, LOGL_DEBUG, "N-Connect does not contain user data (no BSSMAP message included)\n"); + } } static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -299,7 +422,6 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) struct osmo_scu_prim *scu_prim = NULL; struct msgb *msg = NULL; int rc; - enum handover_result ho_result; switch (event) { case GSCON_EV_MO_COMPL_L3: @@ -327,11 +449,28 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); return; } - /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() - * related: OS2969 (same as above) */ + handle_sccp_n_connect(fi, scu_prim); + break; + default: + OSMO_ASSERT(false); + } +} + +static void gscon_fsm_wait_initial_user_data(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *msg = data; + enum handover_result ho_result; - handle_bssap_n_connect(fi, scu_prim); + switch (event) { + case GSCON_EV_A_INITIAL_USER_DATA: + if (validate_initial_user_data(fi, msg)) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } + handle_initial_user_data(fi, msg); break; + case GSCON_EV_HANDOVER_END: ho_result = HO_RESULT_ERROR; if (data) @@ -348,12 +487,10 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) return; } LOG_HO(conn, LOGL_ERROR, - "Conn is in state %s, the only accepted handover kind is inter-BSC MT\n", + "Conn is in state %s, the only accepted handover kind is inter-BSC incoming handover\n", osmo_fsm_inst_state_name(conn->fi)); } gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); - if (conn->fi->state != ST_CLEARING) - osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, -4); return; default: OSMO_ASSERT(false); @@ -373,7 +510,6 @@ static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *da confirmed connection, then instead simply drop the connection */ LOGPFSML(fi, LOGL_INFO, "Connection confirmed but lchan was dropped previously, clearing conn\n"); - osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, -4); gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); break; } @@ -510,6 +646,62 @@ static bool same_mgw_info(const struct mgcp_conn_peer *a, const struct mgcp_conn return true; } +static struct mgcp_client *select_mgw(struct gsm_subscriber_connection *conn, struct gsm_lchan *for_lchan) +{ + struct mgcp_client_pool_member *mgcp_pmemb; + struct mgcp_client *mgcp_client; + struct gsm_bts *bts = for_lchan->ts->trx->bts; + + /* If BTS is not pinned to a given MGW, let regular allocation which + * spreads load over available MGWs: */ + if (bts->mgw_pool_target == -1) + goto pick_any; + + /* BTS is pinned to an MGW, retrieve pointer to it: */ + mgcp_pmemb = mgcp_client_pool_find_member_by_nr(conn->network->mgw.mgw_pool, bts->mgw_pool_target); + if (!mgcp_pmemb) { + if (!bts->mgw_pool_target_strict) { + LOGPFSML(conn->fi, LOGL_NOTICE, + "mgw pool-target %u not found! selecting another one.\n", bts->mgw_pool_target); + goto pick_any; + } else { + LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u not found!\n", bts->mgw_pool_target); + return NULL; + } + } + if (mgcp_client_pool_member_is_blocked(mgcp_pmemb)) { + if (!bts->mgw_pool_target_strict) { + LOGPFSML(conn->fi, LOGL_NOTICE, + "mgw pool-target %u is administratively blocked! selecting another one.\n", + bts->mgw_pool_target); + goto pick_any; + } else { + LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u is administratively blocked!\n", + bts->mgw_pool_target); + return NULL; + } + } + + mgcp_client = mgcp_client_pool_member_get(mgcp_pmemb); + if (!mgcp_client) { + if (!bts->mgw_pool_target_strict) { + LOGPFSML(conn->fi, LOGL_NOTICE, + "mgw pool-target %u is not connected! selecting another one.\n", + bts->mgw_pool_target); + goto pick_any; + } else { + LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u is not connected!\n", + bts->mgw_pool_target); + return NULL; + } + } + return mgcp_client; + +pick_any: + mgcp_client = mgcp_client_pool_get(conn->network->mgw.mgw_pool); + return mgcp_client; +} + /* Make sure a conn->user_plane.mgw_endpoint is allocated with the proper mgw endpoint name. For * SCCPlite, pass in msc_assigned_cic the CIC received upon BSSMAP Assignment Command or BSSMAP Handover * Request form the MSC (which is only stored in conn->user_plane after success). Ignored for AoIP. */ @@ -517,39 +709,57 @@ struct osmo_mgcpc_ep *gscon_ensure_mgw_endpoint(struct gsm_subscriber_connection uint16_t msc_assigned_cic, struct gsm_lchan *for_lchan) { const char *epname; + struct mgcp_client *mgcp_client = NULL; + + if (!conn) { + LOG_LCHAN(for_lchan, LOGL_ERROR, "no conn!\n"); + return NULL; + } if (conn->user_plane.mgw_endpoint) return conn->user_plane.mgw_endpoint; + if (gscon_is_sccplite(conn) || gscon_is_aoip(conn)) { + /* Get MGCP client from pool */ + mgcp_client = select_mgw(conn, for_lchan); + if (!mgcp_client) { + LOGPFSML(conn->fi, LOGL_ERROR, + "cannot ensure MGW endpoint -- no MGW configured, check configuration!\n"); + return NULL; + } + } + if (gscon_is_sccplite(conn)) { /* derive endpoint name from CIC on A interface side */ conn->user_plane.mgw_endpoint = osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, - conn->network->mgw.client, + mgcp_client, conn->network->mgw.tdefs, conn->fi->id, "%x@%s", msc_assigned_cic, - mgcp_client_endpoint_domain(conn->network->mgw.client)); + mgcp_client_endpoint_domain(mgcp_client)); LOGPFSML(conn->fi, LOGL_DEBUG, "MGW endpoint name derived from CIC 0x%x: %s\n", msc_assigned_cic, osmo_mgcpc_ep_name(conn->user_plane.mgw_endpoint)); } else if (gscon_is_aoip(conn)) { - - if (is_ipaccess_bts(for_lchan->ts->trx->bts)) + if (is_ipa_abisip_bts(for_lchan->ts->trx->bts)) /* use dynamic RTPBRIDGE endpoint allocation in MGW */ - epname = mgcp_client_rtpbridge_wildcard(conn->network->mgw.client); + epname = mgcp_client_rtpbridge_wildcard(mgcp_client); else { - epname = mgcp_client_e1_epname(conn, conn->network->mgw.client, for_lchan->ts->e1_link.e1_nr, + uint8_t i460_bit_offs; + if (for_lchan->ts->e1_link.e1_ts_ss == E1_SUBSLOT_FULL) + i460_bit_offs = 0; + else + i460_bit_offs = for_lchan->ts->e1_link.e1_ts_ss * 2; + + epname = mgcp_client_e1_epname(conn, mgcp_client, for_lchan->ts->e1_link.e1_nr, for_lchan->ts->e1_link.e1_ts, 16, - for_lchan->ts->e1_link.e1_ts_ss*2); + i460_bit_offs); } conn->user_plane.mgw_endpoint = - osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, - conn->network->mgw.client, - conn->network->mgw.tdefs, - conn->fi->id, - "%s", epname); + osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, mgcp_client, + conn->network->mgw.tdefs, conn->fi->id, "%s", epname); } else { LOGPFSML(conn->fi, LOGL_ERROR, "Conn is neither SCCPlite nor AoIP!?\n"); return NULL; @@ -584,7 +794,7 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, mgw_info = (struct mgcp_conn_peer){ .port = port, - .call_id = conn->sccp.conn_id, + .call_id = conn->sccp.conn.conn_id, .ptime = 20, .x_osmo_osmux_use = conn->assignment.req.use_osmux, .x_osmo_osmux_cid = conn->assignment.req.osmux_cid, @@ -599,7 +809,7 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, ci = conn->user_plane.mgw_endpoint_ci_msc; if (ci) { - const struct mgcp_conn_peer *prev_crcx_info = osmo_mgcpc_ep_ci_get_rtp_info(ci); + const struct mgcp_conn_peer *prev_crcx_info = osmo_mgcpc_ep_ci_get_remote_rtp_info(ci); if (!conn->user_plane.mgw_endpoint) { LOGPFSML(conn->fi, LOGL_ERROR, "Internal error: conn has a CI but no endpoint\n"); @@ -617,6 +827,8 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, LOGPFSML(conn->fi, LOGL_DEBUG, "MSC side MGW endpoint ci is already configured to %s\n", osmo_mgcpc_ep_ci_name(ci)); + /* Immediately dispatch the success event */ + osmo_fsm_inst_dispatch(notify, event_success, notify_data); return true; } @@ -651,15 +863,25 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, static const struct osmo_fsm_state gscon_fsm_states[] = { [ST_INIT] = { .name = "INIT", - .in_event_mask = S(GSCON_EV_MO_COMPL_L3) | S(GSCON_EV_A_CONN_IND) - | S(GSCON_EV_HANDOVER_END), - .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_CLEARING), + .in_event_mask = S(GSCON_EV_MO_COMPL_L3) | S(GSCON_EV_A_CONN_IND), + .out_state_mask = 0 + | S(ST_WAIT_INITIAL_USER_DATA) + | S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_init, + }, + [ST_WAIT_INITIAL_USER_DATA] = { + .name = "WAIT_INITIAL_USER_DATA", + .in_event_mask = 0 + | S(GSCON_EV_A_INITIAL_USER_DATA) + | S(GSCON_EV_HANDOVER_END) + , + .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), + .action = gscon_fsm_wait_initial_user_data, }, [ST_WAIT_CC] = { .name = "WAIT_CC", .in_event_mask = S(GSCON_EV_A_CONN_CFM), - .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_wait_cc, }, [ST_ACTIVE] = { @@ -669,26 +891,32 @@ static const struct osmo_fsm_state gscon_fsm_states[] = { | S(GSCON_EV_LCS_LOC_REQ_END) | S(GSCON_EV_MO_COMPL_L3) , - .out_state_mask = S(ST_CLEARING) | S(ST_ASSIGNMENT) | + .out_state_mask = S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD) | S(ST_ASSIGNMENT) | S(ST_HANDOVER), .action = gscon_fsm_active, }, [ST_ASSIGNMENT] = { .name = "ASSIGNMENT", .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_END), - .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_assignment, }, [ST_HANDOVER] = { .name = "HANDOVER", .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_HANDOVER_END), - .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_handover, }, - [ST_CLEARING] = { - .name = "CLEARING", - /* dead end state */ - }, + [ST_WAIT_CLEAR_CMD] = { + .name = "WAIT_CLEAR_CMD", + .onenter = gscon_fsm_wait_clear_cmd_onenter, + .out_state_mask = S(ST_WAIT_SCCP_RLSD), + }, + [ST_WAIT_SCCP_RLSD] = { + .name = "WAIT_SCCP_RLSD", + .onenter = gscon_fsm_wait_sccp_rlsd_onenter, + .in_event_mask = S(GSCON_EV_HANDOVER_END), + }, }; void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *new_lchan) @@ -696,9 +924,19 @@ void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct g /* On release, do not receive release events that look like the primary lchan is gone. */ struct gsm_lchan *old_lchan = conn->lchan; + OSMO_ASSERT(new_lchan); + if (old_lchan == new_lchan) return; + if (!old_lchan) + LOGPFSML(conn->fi, LOGL_DEBUG, "setting primary lchan for this conn to %s\n", + new_lchan->fi? osmo_fsm_inst_name(new_lchan->fi) : gsm_lchan_name(new_lchan)); + else + LOGPFSML(conn->fi, LOGL_DEBUG, "primary lchan for this conn changes from %s to %s\n", + old_lchan->fi? osmo_fsm_inst_name(old_lchan->fi) : gsm_lchan_name(old_lchan), + new_lchan->fi? osmo_fsm_inst_name(new_lchan->fi) : gsm_lchan_name(new_lchan)); + conn->lchan = new_lchan; conn->lchan->conn = conn; @@ -723,23 +961,23 @@ void gscon_lchan_releasing(struct gsm_subscriber_connection *conn, struct gsm_lc if (conn->ho.fi) osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_LCHAN_ERROR, lchan); } + if (conn->vgcs_chan.new_lchan == lchan) { + if (conn->vgcs_chan.fi) + osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_LCHAN_ERROR, lchan); + } if (conn->lchan == lchan) { lchan_forget_conn(conn->lchan); conn->lchan = NULL; } - /* If the conn has no lchan anymore, it was released by the BTS and needs to Clear towards MSC. */ - if (!conn->lchan) { + /* If the conn has no lchan anymore, it was released by the BTS and needs to Clear towards MSC. + * However, if a Location Request is still busy, do not send Clear Request. */ + if (!conn->lchan && !conn->lcs.loc_req) { switch (conn->fi->state) { case ST_WAIT_CC: /* The SCCP connection was not yet confirmed by a CC, the BSSAP is not fully established yet so we cannot release it. First wait for the CC, and release in gscon_fsm_wait_cc(). */ break; default: - /* Ensure that the FSM is in ST_CLEARING. */ - osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, -4); - /* fall thru, omit an error log if already in ST_CLEARING */ - case ST_CLEARING: - /* Request a Clear Command from the MSC. */ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); break; } @@ -763,6 +1001,10 @@ void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan conn->ho.new_lchan = NULL; detach_label = "ho.new_lchan"; } + if (conn->vgcs_chan.new_lchan == lchan) { + conn->vgcs_chan.new_lchan = NULL; + detach_label = "vgcs.new_lchan"; + } if (conn->lchan == lchan) { conn->lchan = NULL; detach_label = "primary lchan"; @@ -778,21 +1020,32 @@ void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan osmo_fsm_inst_name(conn->fi), detach_label); } - if ((conn->fi && conn->fi->state != ST_CLEARING) - && !conn->lchan + if (!conn->lchan && !conn->ho.new_lchan && !conn->assignment.new_lchan + && !conn->vgcs_chan.new_lchan && !conn->lcs.loc_req) gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); } static void gscon_forget_mgw_endpoint(struct gsm_subscriber_connection *conn) { + struct mgcp_client *mgcp_client; + + /* Put MGCP client back into MGW pool */ + mgcp_client = osmo_mgcpc_ep_client(conn->user_plane.mgw_endpoint); + mgcp_client_pool_put(mgcp_client); + + /* Be sure that the endpoint CI we are maintaining in user_plane + * is also removed from the other locations as well. */ + gscon_forget_mgw_endpoint_ci(conn, conn->user_plane.mgw_endpoint_ci_msc); + conn->user_plane.mgw_endpoint = NULL; conn->user_plane.mgw_endpoint_ci_msc = NULL; conn->ho.created_ci_for_msc = NULL; lchan_forget_mgw_endpoint(conn->lchan); lchan_forget_mgw_endpoint(conn->assignment.new_lchan); + lchan_forget_mgw_endpoint(conn->vgcs_chan.new_lchan); lchan_forget_mgw_endpoint(conn->ho.new_lchan); } @@ -803,50 +1056,27 @@ void gscon_forget_mgw_endpoint_ci(struct gsm_subscriber_connection *conn, struct if (conn->user_plane.mgw_endpoint_ci_msc == ci) conn->user_plane.mgw_endpoint_ci_msc = NULL; + + if (conn->assignment.created_ci_for_msc == ci) + conn->assignment.created_ci_for_msc = NULL; } static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_subscriber_connection *conn = fi->priv; - const struct gscon_clear_cmd_data *ccd; - struct osmo_mobile_identity *mi_imsi; + const struct tlv_parsed *tp; + struct osmo_mobile_identity mi_imsi; /* Regular allstate event processing */ switch (event) { case GSCON_EV_A_CLEAR_CMD: - conn->rx_clear_command = true; - - /* Give the handover_fsm a chance to book this as handover success before tearing down everything, - * making it look like a sudden death failure. */ - if (conn->ho.fi) - osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL); - - if (conn->lcs.loc_req) - osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL); - OSMO_ASSERT(data); - ccd = data; - if (conn->lchan) - conn->lchan->release.is_csfb = ccd->is_csfb; - /* MSC tells us to cleanly shut down */ - if (conn->fi->state != ST_CLEARING) - osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, -4); - LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) after BSSMAP Clear Command\n"); - gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(ccd->cause_0808)); - /* FIXME: Release all terestrial resources in ST_CLEARING */ - /* According to 3GPP 48.008 3.1.9.1. "The BSS need not wait for the radio channel - * release to be completed or for the guard timer to expire before returning the - * CLEAR COMPLETE message" */ - - /* Close MGCP connections */ - osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint); - - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE]); - gscon_sigtran_send(conn, gsm0808_create_clear_complete()); + conn->clear_cause = *(const enum gsm0808_cause *)data; + conn_fsm_state_chg(ST_WAIT_SCCP_RLSD); break; case GSCON_EV_A_DISC_IND: - /* MSC or SIGTRAN network has hard-released SCCP connection, - * terminate the FSM now. */ + /* MSC or SIGTRAN network has hard-released SCCP connection, terminate the FSM now. + * Cleanup is done in gscon_pre_term() and gscon_cleanup(). */ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); break; case GSCON_EV_FORGET_MGW_ENDPOINT: @@ -859,7 +1089,9 @@ static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *d conn->lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(conn->lchan->release.rsl_error_cause); } - gscon_bssmap_clear(conn, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); + /* Request BSSMAP Clear, but do not abort an ongoing Location Request */ + if (!conn->lcs.loc_req) + gscon_bssmap_clear(conn, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); break; case GSCON_EV_MGW_MDCX_RESP_MSC: LOGPFSML(fi, LOGL_DEBUG, "Rx MDCX of MSC side (LCLS?)\n"); @@ -868,14 +1100,26 @@ static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *d break; case GSCON_EV_A_COMMON_ID_IND: OSMO_ASSERT(data); - mi_imsi = data; + tp = data; + if (osmo_mobile_identity_decode(&mi_imsi, TLVP_VAL(tp, GSM0808_IE_IMSI), TLVP_LEN(tp, GSM0808_IE_IMSI), false) + || mi_imsi.type != GSM_MI_TYPE_IMSI) { + LOGPFSML(fi, LOGL_ERROR, "CommonID: could not parse IMSI\n"); + return; + } if (!conn->bsub) - conn->bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers, mi_imsi->imsi, + conn->bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers, mi_imsi.imsi, BSUB_USE_CONN); else { /* we already have a bsc_subscr associated; maybe that subscriber has no IMSI yet? */ if (!conn->bsub->imsi[0]) - bsc_subscr_set_imsi(conn->bsub, mi_imsi->imsi); + bsc_subscr_set_imsi(conn->bsub, mi_imsi.imsi); + } + if (TLVP_PRESENT(tp, GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID)) { + conn->fast_return.allowed = true; /* Always allowed for CSFB */ + conn->fast_return.last_eutran_plmn_valid = true; + osmo_plmn_from_bcd(TLVP_VAL(tp, GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID), &conn->fast_return.last_eutran_plmn); + LOGPFSML(fi, LOGL_DEBUG, "subscr comes from E-UTRAN PLMN %s\n", + osmo_plmn_name(&conn->fast_return.last_eutran_plmn)); } gscon_update_id(conn); break; @@ -891,6 +1135,7 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau lchan_forget_conn(conn->lchan); lchan_forget_conn(conn->assignment.new_lchan); + lchan_forget_conn(conn->vgcs_chan.new_lchan); lchan_forget_conn(conn->ho.new_lchan); lb_close_conn(conn); @@ -899,9 +1144,14 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau LOGPFSML(fi, LOGL_DEBUG, "Disconnecting SCCP\n"); struct bsc_msc_data *msc = conn->sccp.msc; /* FIXME: include a proper cause value / error message? */ - osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn_id, &msc->a.bsc_addr, 0); + osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn.conn_id, &msc->a.bsc_addr, 0); conn->sccp.state = SUBSCR_SCCP_ST_NONE; } + if (conn->sccp.conn.conn_id != SCCP_CONN_ID_UNSET && conn->sccp.msc) { + struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(conn->sccp.msc->a.sccp); + bsc_sccp_inst_unregister_gscon(bsc_sccp, &conn->sccp.conn); + conn->sccp.conn.conn_id = SCCP_CONN_ID_UNSET; + } if (conn->bsub) { LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n"); @@ -916,8 +1166,21 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_client *mgcp_client; + + /* Put MGCP client back into MGW pool */ + mgcp_client = osmo_mgcpc_ep_client(conn->user_plane.mgw_endpoint); + mgcp_client_pool_put(mgcp_client); osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint); + conn->user_plane.mgw_endpoint = NULL; + conn->user_plane.mgw_endpoint_ci_msc = NULL; + + if (conn->ho.fi) + osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL); + + if (conn->lcs.loc_req) + osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL); if (conn->lcls.fi) { /* request termination of LCLS FSM */ @@ -925,15 +1188,19 @@ static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause ca conn->lcls.fi = NULL; } + if (conn->vgcs_call.fi) + osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_CLEANUP, NULL); + + if (conn->vgcs_chan.fi) + osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_CLEANUP, NULL); + LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) because this conn is terminating\n"); - /* when things go smoothly, the lchan should have been released before FSM instance termination. So if this is - * necessary it's probably "abnormal". */ - gscon_release_lchans(conn, true, GSM48_RR_CAUSE_ABNORMAL_UNSPEC); + gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(conn->clear_cause)); /* drop pending messages */ gscon_dtap_queue_flush(conn, 0); - penalty_timers_free(&conn->hodec2.penalty_timers); + penalty_timers_clear(&conn->hodec2.penalty_timers, NULL); } static int gscon_timer_cb(struct osmo_fsm_inst *fi) @@ -958,8 +1225,9 @@ static int gscon_timer_cb(struct osmo_fsm_inst *fi) case -4: /* The MSC has sent a BSSMAP Clear Command, we acknowledged that, but the conn was never * disconnected. */ - LOGPFSML(fi, LOGL_ERROR, "Long after a BSSMAP Clear Command, the conn is still not" - " released. For sanity, discarding this conn now.\n"); + LOGPFSML(fi, LOGL_ERROR, "Long after expecting %s, the conn is still not" + " released. For sanity, discarding this conn now.\n", + fi->state == ST_WAIT_CLEAR_CMD ? "BSSMAP Clear Command" : "SCCP RLSD"); a_reset_conn_fail(conn->sccp.msc); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); break; @@ -987,7 +1255,7 @@ static struct osmo_fsm gscon_fsm = { .event_names = gscon_fsm_event_names, }; -void bsc_subscr_conn_fsm_init() +static __attribute__((constructor)) void bsc_subscr_conn_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&gscon_fsm) == 0); OSMO_ASSERT(osmo_fsm_register(&lcls_fsm) == 0); @@ -1004,8 +1272,11 @@ struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *ne conn->network = net; INIT_LLIST_HEAD(&conn->dtap_queue); - /* BTW, penalty timers will be initialized on-demand. */ - conn->sccp.conn_id = -1; + INIT_LLIST_HEAD(&conn->hodec2.penalty_timers); + bscp_sccp_conn_node_init(&conn->sccp.conn, conn); + bscp_sccp_conn_node_init(&conn->lcs.lb.conn, conn); + /* Default clear cause (on RR translates to GSM48_RR_CAUSE_ABNORMAL_UNSPEC) */ + conn->clear_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE; /* don't allocate from 'conn' context, as gscon_cleanup() will call talloc_free(conn) before * libosmocore will call talloc_free(conn->fi), i.e. avoid use-after-free during cleanup */ @@ -1089,19 +1360,23 @@ static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, en switch (rllr_ind) { case BSC_RLLR_IND_EST_CONF: - rsl_data_request(msg, OBSC_LINKID_CB(msg)); + rsl_data_request(msg, link_id); break; case BSC_RLLR_IND_REL_IND: - bsc_sapi_n_reject(lchan->conn, OBSC_LINKID_CB(msg), + bsc_sapi_n_reject(lchan->conn, RSL_LINK_ID2DLCI(link_id), GSM0808_CAUSE_MS_NOT_EQUIPPED); msgb_free(msg); break; case BSC_RLLR_IND_ERR_IND: case BSC_RLLR_IND_TIMEOUT: - bsc_sapi_n_reject(lchan->conn, OBSC_LINKID_CB(msg), + bsc_sapi_n_reject(lchan->conn, RSL_LINK_ID2DLCI(link_id), GSM0808_CAUSE_BSS_NOT_EQUIPPED); msgb_free(msg); break; + default: + LOGPLCHAN(lchan, DRLL, LOGL_NOTICE, "Received unknown rllr_ind %u\n", rllr_ind); + msgb_free(msg); + break; } } @@ -1123,7 +1398,6 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn, sapi = link_id & 0x7; msg->lchan = conn->lchan; - msg->dst = msg->lchan->ts->trx->rsl_link; /* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */ if (allow_sacch && sapi != 0) { @@ -1139,7 +1413,7 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn, rc = rll_establish(msg->lchan, sapi, rll_ind_cb, msg); if (rc) { msgb_free(msg); - bsc_sapi_n_reject(conn, link_id, GSM0808_CAUSE_BSS_NOT_EQUIPPED); + bsc_sapi_n_reject(conn, RSL_LINK_ID2DLCI(link_id), GSM0808_CAUSE_BSS_NOT_EQUIPPED); goto failed_to_send; } return; @@ -1154,7 +1428,6 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn, failed_to_send: LOGPFSML(conn->fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n"); gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); - osmo_fsm_inst_state_chg(conn->fi, ST_ACTIVE, 0, 0); } void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn, @@ -1174,7 +1447,7 @@ void gscon_update_id(struct gsm_subscriber_connection *conn) { osmo_fsm_inst_update_id_f(conn->fi, "msc%u-conn%u%s%s", conn->sccp.msc ? conn->sccp.msc->nr : UINT_MAX, - conn->sccp.conn_id, + conn->sccp.conn.conn_id, conn->bsub? "_" : "", conn->bsub? bsc_subscr_id(conn->bsub) : ""); } diff --git a/src/osmo-bsc/bsc_subscriber.c b/src/osmo-bsc/bsc_subscriber.c index 4a4829866..1f69d442d 100644 --- a/src/osmo-bsc/bsc_subscriber.c +++ b/src/osmo-bsc/bsc_subscriber.c @@ -30,6 +30,7 @@ #include <osmocom/core/logging.h> #include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/paging.h> #include <osmocom/bsc/debug.h> static void bsc_subscr_free(struct bsc_subscr *bsub); @@ -64,26 +65,40 @@ static int bsub_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, co return 0; } -static struct bsc_subscr *bsc_subscr_alloc(struct llist_head *list) +struct bsc_subscr_store *bsc_subscr_store_alloc(void *ctx) +{ + struct bsc_subscr_store *bsubst; + + bsubst = talloc_zero(ctx, struct bsc_subscr_store); + if (!bsubst) + return NULL; + + INIT_LLIST_HEAD(&bsubst->bsub_list); + return bsubst; +} + +static struct bsc_subscr *bsc_subscr_alloc(struct bsc_subscr_store *bsubst) { struct bsc_subscr *bsub; - bsub = talloc_zero(list, struct bsc_subscr); + bsub = talloc_zero(bsubst, struct bsc_subscr); if (!bsub) return NULL; + bsub->store = bsubst; bsub->tmsi = GSM_RESERVED_TMSI; bsub->use_count = (struct osmo_use_count){ .talloc_object = bsub, .use_cb = bsub_use_cb, }; + INIT_LLIST_HEAD(&bsub->active_paging_requests); - llist_add_tail(&bsub->entry, list); + llist_add_tail(&bsub->entry, &bsubst->bsub_list); return bsub; } -struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list, +struct bsc_subscr *bsc_subscr_find_by_imsi(struct bsc_subscr_store *bsubst, const char *imsi, const char *use_token) { @@ -92,7 +107,7 @@ struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list, if (!imsi || !*imsi) return NULL; - llist_for_each_entry(bsub, list, entry) { + llist_for_each_entry(bsub, &bsubst->bsub_list, entry) { if (!strcmp(bsub->imsi, imsi)) { bsc_subscr_get(bsub, use_token); return bsub; @@ -101,17 +116,17 @@ struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list, return NULL; } -struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list, - uint32_t tmsi, - const char *use_token) +static struct bsc_subscr *bsc_subscr_find_by_imei(struct bsc_subscr_store *bsubst, + const char *imei, + const char *use_token) { struct bsc_subscr *bsub; - if (tmsi == GSM_RESERVED_TMSI) + if (!imei || !*imei) return NULL; - llist_for_each_entry(bsub, list, entry) { - if (bsub->tmsi == tmsi) { + llist_for_each_entry(bsub, &bsubst->bsub_list, entry) { + if (!strcmp(bsub->imei, imei)) { bsc_subscr_get(bsub, use_token); return bsub; } @@ -119,19 +134,83 @@ struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list, return NULL; } -struct bsc_subscr *bsc_subscr_find_by_mi(struct llist_head *list, const struct osmo_mobile_identity *mi, - const char *use_token) +static struct bsc_subscr *bsc_subscr_find_by_tmsi(struct bsc_subscr_store *bsubst, + uint32_t tmsi, + const char *use_token) { - if (!mi) - return NULL; - switch (mi->type) { - case GSM_MI_TYPE_IMSI: - return bsc_subscr_find_by_imsi(list, mi->imsi, use_token); - case GSM_MI_TYPE_TMSI: - return bsc_subscr_find_by_tmsi(list, mi->tmsi, use_token); - default: + const struct rb_node *node = bsubst->bsub_tree_tmsi.rb_node; + struct bsc_subscr *bsub; + + if (tmsi == GSM_RESERVED_TMSI) return NULL; + + while (node) { + bsub = container_of(node, struct bsc_subscr, node_tmsi); + if (tmsi < bsub->tmsi) + node = node->rb_left; + else if (tmsi > bsub->tmsi) + node = node->rb_right; + else { + bsc_subscr_get(bsub, use_token); + return bsub; + } } + + return NULL; +} + +static int bsc_subscr_store_insert_bsub_tmsi(struct bsc_subscr *bsub) +{ + struct bsc_subscr_store *bsubst = bsub->store; + struct rb_node **n = &(bsubst->bsub_tree_tmsi.rb_node); + struct rb_node *parent = NULL; + + OSMO_ASSERT(bsub->tmsi != GSM_RESERVED_TMSI); + + while (*n) { + struct bsc_subscr *it; + + it = container_of(*n, struct bsc_subscr, node_tmsi); + + parent = *n; + if (bsub->tmsi < it->tmsi) { + n = &((*n)->rb_left); + } else if (bsub->tmsi > it->tmsi) { + n = &((*n)->rb_right); + } else { + LOGP(DMSC, LOGL_ERROR, "Trying to reserve already reserved tmsi %u\n", bsub->tmsi); + return -EEXIST; + } + } + + rb_link_node(&bsub->node_tmsi, parent, n); + rb_insert_color(&bsub->node_tmsi, &bsubst->bsub_tree_tmsi); + return 0; +} + +int bsc_subscr_set_tmsi(struct bsc_subscr *bsub, uint32_t tmsi) +{ + int rc = 0; + + if (!bsub) + return -EINVAL; + + if (bsub->tmsi == tmsi) + return 0; + + /* bsub was already inserted, remove and re-insert with new tmsi */ + if (bsub->tmsi != GSM_RESERVED_TMSI) + rb_erase(&bsub->node_tmsi, &bsub->store->bsub_tree_tmsi); + + bsub->tmsi = tmsi; + + /* If new tmsi is set, insert bsub into rbtree: */ + if (bsub->tmsi != GSM_RESERVED_TMSI) { + if ((rc = bsc_subscr_store_insert_bsub_tmsi(bsub)) < 0) + bsub->tmsi = GSM_RESERVED_TMSI; + } + + return rc; } void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi) @@ -141,15 +220,22 @@ void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi) osmo_strlcpy(bsub->imsi, imsi, sizeof(bsub->imsi)); } -struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list, +void bsc_subscr_set_imei(struct bsc_subscr *bsub, const char *imei) +{ + if (!bsub) + return; + osmo_strlcpy(bsub->imei, imei, sizeof(bsub->imei)); +} + +struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct bsc_subscr_store *bsubst, const char *imsi, const char *use_token) { struct bsc_subscr *bsub; - bsub = bsc_subscr_find_by_imsi(list, imsi, use_token); + bsub = bsc_subscr_find_by_imsi(bsubst, imsi, use_token); if (bsub) return bsub; - bsub = bsc_subscr_alloc(list); + bsub = bsc_subscr_alloc(bsubst); if (!bsub) return NULL; bsc_subscr_set_imsi(bsub, imsi); @@ -157,32 +243,53 @@ struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list, return bsub; } -struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list, +static struct bsc_subscr *bsc_subscr_find_or_create_by_imei(struct bsc_subscr_store *bsubst, + const char *imei, + const char *use_token) +{ + struct bsc_subscr *bsub; + bsub = bsc_subscr_find_by_imei(bsubst, imei, use_token); + if (bsub) + return bsub; + bsub = bsc_subscr_alloc(bsubst); + if (!bsub) + return NULL; + bsc_subscr_set_imei(bsub, imei); + bsc_subscr_get(bsub, use_token); + return bsub; +} + +struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct bsc_subscr_store *bsubst, uint32_t tmsi, const char *use_token) { struct bsc_subscr *bsub; - bsub = bsc_subscr_find_by_tmsi(list, tmsi, use_token); + bsub = bsc_subscr_find_by_tmsi(bsubst, tmsi, use_token); if (bsub) return bsub; - bsub = bsc_subscr_alloc(list); + bsub = bsc_subscr_alloc(bsubst); if (!bsub) return NULL; - bsub->tmsi = tmsi; + if (bsc_subscr_set_tmsi(bsub, tmsi) < 0) { + bsc_subscr_free(bsub); + return NULL; + } bsc_subscr_get(bsub, use_token); return bsub; } -struct bsc_subscr *bsc_subscr_find_or_create_by_mi(struct llist_head *list, const struct osmo_mobile_identity *mi, +struct bsc_subscr *bsc_subscr_find_or_create_by_mi(struct bsc_subscr_store *bsubst, const struct osmo_mobile_identity *mi, const char *use_token) { if (!mi) return NULL; switch (mi->type) { case GSM_MI_TYPE_IMSI: - return bsc_subscr_find_or_create_by_imsi(list, mi->imsi, use_token); + return bsc_subscr_find_or_create_by_imsi(bsubst, mi->imsi, use_token); + case GSM_MI_TYPE_IMEI: + return bsc_subscr_find_or_create_by_imei(bsubst, mi->imei, use_token); case GSM_MI_TYPE_TMSI: - return bsc_subscr_find_or_create_by_tmsi(list, mi->tmsi, use_token); + return bsc_subscr_find_or_create_by_tmsi(bsubst, mi->tmsi, use_token); default: return NULL; } @@ -198,6 +305,8 @@ static int bsc_subscr_name_buf(char *buf, size_t buflen, struct bsc_subscr *bsub } if (bsub->imsi[0]) OSMO_STRBUF_PRINTF(sb, "-IMSI-%s", bsub->imsi); + else if (bsub->imei[0]) + OSMO_STRBUF_PRINTF(sb, "-IMEI-%s", bsub->imei); if (bsub->tmsi != GSM_RESERVED_TMSI) OSMO_STRBUF_PRINTF(sb, "-TMSI-0x%08x", bsub->tmsi); return sb.chars_needed; @@ -222,6 +331,11 @@ const char *bsc_subscr_id(struct bsc_subscr *bsub) static void bsc_subscr_free(struct bsc_subscr *bsub) { + OSMO_ASSERT(llist_empty(&bsub->active_paging_requests)); + + if (bsub->tmsi != GSM_RESERVED_TMSI) + rb_erase(&bsub->node_tmsi, &bsub->store->bsub_tree_tmsi); + llist_del(&bsub->entry); talloc_free(bsub); } @@ -246,3 +360,41 @@ void log_set_filter_bsc_subscr(struct log_target *target, } else target->filter_map &= ~(1 << LOG_FLT_BSC_SUBSCR); } + +void bsc_subscr_add_active_paging_request(struct bsc_subscr *bsub, struct gsm_paging_request *req) +{ + bsub->active_paging_requests_len++; + bsc_subscr_get(bsub, BSUB_USE_PAGING_REQUEST); + llist_add_tail(&req->bsub_entry, &bsub->active_paging_requests); +} + +void bsc_subscr_remove_active_paging_request(struct bsc_subscr *bsub, struct gsm_paging_request *req) +{ + llist_del(&req->bsub_entry); + bsub->active_paging_requests_len--; + bsc_subscr_put(bsub, BSUB_USE_PAGING_REQUEST); +} + +void bsc_subscr_remove_active_paging_request_all(struct bsc_subscr *bsub) +{ + /* Avoid accessing bsub after reaching 0 active_paging_request_len, + * since it could be freed during put(): */ + unsigned remaining = bsub->active_paging_requests_len; + while (remaining > 0) { + struct gsm_paging_request *req; + req = llist_first_entry(&bsub->active_paging_requests, + struct gsm_paging_request, bsub_entry); + bsc_subscr_remove_active_paging_request(bsub, req); + remaining--; + } +} + +struct gsm_paging_request *bsc_subscr_find_req_by_bts(const struct bsc_subscr *bsub, const struct gsm_bts *bts) +{ + struct gsm_paging_request *req; + llist_for_each_entry(req, &bsub->active_paging_requests, bsub_entry) { + if (req->bts == bts) + return req; + } + return NULL; +} diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c index e91e01cc7..8690a47c3 100644 --- a/src/osmo-bsc/bsc_vty.c +++ b/src/osmo-bsc/bsc_vty.c @@ -22,6 +22,8 @@ #include <unistd.h> #include <time.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> #include <osmocom/vty/command.h> #include <osmocom/vty/buffer.h> #include <osmocom/vty/vty.h> @@ -30,53 +32,42 @@ #include <osmocom/vty/telnet_interface.h> #include <osmocom/vty/misc.h> #include <osmocom/vty/tdef_vty.h> -#include <osmocom/gsm/protocol/gsm_04_08.h> -#include <osmocom/gsm/gsm0502.h> #include <osmocom/ctrl/control_if.h> -#include <osmocom/gsm/gsm48.h> -#include <osmocom/gsm/gsm0808.h> #include <osmocom/gsm/gsm23236.h> -#include <osmocom/core/sockaddr_str.h> +#include <osmocom/gsm/gsm0502.h> -#include <arpa/inet.h> +#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> +#include <osmocom/mgcp_client/mgcp_client_pool.h> -#include <osmocom/core/byteswap.h> -#include <osmocom/core/linuxlist.h> -#include <osmocom/core/socket.h> +#include <osmocom/bsc/vty.h> #include <osmocom/bsc/gsm_data.h> -#include <osmocom/abis/e1_input.h> #include <osmocom/bsc/abis_nm.h> #include <osmocom/bsc/abis_om2000.h> -#include <osmocom/core/utils.h> -#include <osmocom/gsm/gsm_utils.h> -#include <osmocom/gsm/abis_nm.h> #include <osmocom/bsc/chan_alloc.h> -#include <osmocom/bsc/meas_rep.h> -#include <osmocom/bsc/vty.h> -#include <osmocom/gprs/gprs_ns.h> +#include <osmocom/bsc/bts_setup_ramp.h> #include <osmocom/bsc/system_information.h> #include <osmocom/bsc/debug.h> #include <osmocom/bsc/paging.h> #include <osmocom/bsc/ipaccess.h> #include <osmocom/bsc/abis_rsl.h> -#include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/osmo_bsc_rf.h> -#include <osmocom/bsc/pcu_if.h> #include <osmocom/bsc/handover_fsm.h> #include <osmocom/bsc/handover_cfg.h> #include <osmocom/bsc/handover_vty.h> #include <osmocom/bsc/gsm_04_08_rr.h> -#include <osmocom/bsc/acc.h> #include <osmocom/bsc/meas_feed.h> -#include <osmocom/bsc/neighbor_ident.h> -#include <osmocom/bsc/handover.h> #include <osmocom/bsc/timeslot_fsm.h> #include <osmocom/bsc/lchan_fsm.h> #include <osmocom/bsc/lchan_select.h> #include <osmocom/bsc/smscb.h> #include <osmocom/bsc/osmo_bsc.h> #include <osmocom/bsc/bts.h> -#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> +#include <osmocom/bsc/bsc_subscr_conn_fsm.h> +#include <osmocom/bsc/assignment_fsm.h> +#include <osmocom/bsc/bssmap_reset.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/lchan.h> +#include <osmocom/bsc/pcu_if.h> #include <inttypes.h> @@ -84,56 +75,6 @@ #define X(x) (1 << x) -#define BTS_NR_STR "BTS Number\n" -#define TRX_NR_STR "TRX Number\n" -#define TS_NR_STR "Timeslot Number\n" -#define SS_NR_STR "Sub-slot Number\n" -#define LCHAN_NR_STR "Logical Channel Number\n" -#define BTS_TRX_STR BTS_NR_STR TRX_NR_STR -#define BTS_TRX_TS_STR BTS_TRX_STR TS_NR_STR -#define BTS_TRX_TS_LCHAN_STR BTS_TRX_TS_STR LCHAN_NR_STR -#define BTS_NR_TRX_TS_STR2 \ - "BTS for manual command\n" BTS_NR_STR \ - "TRX for manual command\n" TRX_NR_STR \ - "Timeslot for manual command\n" TS_NR_STR -#define BTS_NR_TRX_TS_SS_STR2 \ - BTS_NR_TRX_TS_STR2 \ - "Sub-slot for manual command\n" SS_NR_STR - -/* FIXME: this should go to some common file */ -static const struct value_string gprs_ns_timer_strs[] = { - { 0, "tns-block" }, - { 1, "tns-block-retries" }, - { 2, "tns-reset" }, - { 3, "tns-reset-retries" }, - { 4, "tns-test" }, - { 5, "tns-alive" }, - { 6, "tns-alive-retries" }, - { 0, NULL } -}; - -static const struct value_string gprs_bssgp_cfg_strs[] = { - { 0, "blocking-timer" }, - { 1, "blocking-retries" }, - { 2, "unblocking-retries" }, - { 3, "reset-timer" }, - { 4, "reset-retries" }, - { 5, "suspend-timer" }, - { 6, "suspend-retries" }, - { 7, "resume-timer" }, - { 8, "resume-retries" }, - { 9, "capability-update-timer" }, - { 10, "capability-update-retries" }, - { 0, NULL } -}; - -static const struct value_string bts_neigh_mode_strs[] = { - { NL_MODE_AUTOMATIC, "automatic" }, - { NL_MODE_MANUAL, "manual" }, - { NL_MODE_MANUAL_SI5SEP, "manual-si5" }, - { 0, NULL } -}; - const struct value_string bts_loc_fix_names[] = { { BTS_LOC_FIX_INVALID, "invalid" }, { BTS_LOC_FIX_2D, "fix2d" }, @@ -141,30 +82,12 @@ const struct value_string bts_loc_fix_names[] = { { 0, NULL } }; -struct cmd_node net_node = { +static struct cmd_node net_node = { GSMNET_NODE, "%s(config-net)# ", 1, }; -struct cmd_node bts_node = { - BTS_NODE, - "%s(config-net-bts)# ", - 1, -}; - -struct cmd_node trx_node = { - TRX_NODE, - "%s(config-net-bts-trx)# ", - 1, -}; - -struct cmd_node ts_node = { - TS_NODE, - "%s(config-net-bts-trx-ts)# ", - 1, -}; - static struct gsm_network *vty_global_gsm_network = NULL; struct gsm_network *gsmnet_from_vty(struct vty *v) @@ -179,12 +102,40 @@ struct gsm_network *gsmnet_from_vty(struct vty *v) return vty_global_gsm_network; } -static int dummy_config_write(struct vty *v) +int dummy_config_write(struct vty *v) { return CMD_SUCCESS; } -static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) +/* resolve a gsm_bts_trx_ts basd on the given numeric identifiers */ +static struct gsm_bts_trx_ts *vty_get_ts(struct vty *vty, const char *bts_str, const char *trx_str, + const char *ts_str) +{ + int bts_nr = atoi(bts_str); + int trx_nr = atoi(trx_str); + int ts_nr = atoi(ts_str); + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return NULL; + } + + trx = gsm_bts_trx_num(bts, trx_nr); + if (!trx) { + vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE); + return NULL; + } + + ts = &trx->ts[ts_nr]; + + return ts; +} + +void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) { vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s", abis_nm_opstate_name(nms->operational), @@ -192,7 +143,7 @@ static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) abis_nm_avail_name(nms->availability), VTY_NEWLINE); } -static void dump_pchan_load_vty(struct vty *vty, char *prefix, +void dump_pchan_load_vty(struct vty *vty, char *prefix, const struct pchan_load *pl) { int i; @@ -268,6 +219,9 @@ static void net_dump_vty(struct vty *vty, struct gsm_network *net) vty_out(vty, " Last RF Lock Command: %s%s", net->rf_ctrl->last_rf_lock_ctrl_command, VTY_NEWLINE); + + if (net->pcu_sock_path) + vty_out(vty, " PCU Socket Path: %s%s", net->pcu_sock_path, VTY_NEWLINE); } DEFUN(bsc_show_net, bsc_show_net_cmd, "show network", @@ -278,271 +232,6 @@ DEFUN(bsc_show_net, bsc_show_net_cmd, "show network", return CMD_SUCCESS; } - -static void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l) -{ - struct e1inp_line *line; - - if (!e1l) { - vty_out(vty, " None%s", VTY_NEWLINE); - return; - } - - line = e1l->ts->line; - - vty_out(vty, " E1 Line %u, Type %s: Timeslot %u, Mode %s%s", - line->num, line->driver->name, e1l->ts->num, - e1inp_signtype_name(e1l->type), VTY_NEWLINE); - vty_out(vty, " E1 TEI %u, SAPI %u%s", - e1l->tei, e1l->sapi, VTY_NEWLINE); -} - -/*! Dump the IP addresses and ports of the input signal link's timeslot. - * This only makes sense for links connected with ipaccess. - * Example output: "(r=10.1.42.1:55416<->l=10.1.42.123:3003)" */ -static void e1isl_dump_vty_tcp(struct vty *vty, const struct e1inp_sign_link *e1l) -{ - if (e1l) { - char *name = osmo_sock_get_name(NULL, e1l->ts->driver.ipaccess.fd.fd); - vty_out(vty, "%s", name); - talloc_free(name); - } - vty_out(vty, "%s", VTY_NEWLINE); -} - -static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv) -{ - int count = 0; - int i; - for (i = 0; i < 1024; i++) { - if (!bitvec_get_bit_pos(bv, i)) - continue; - vty_out(vty, " %u", i); - count ++; - } - if (!count) - vty_out(vty, " (none)"); - else - vty_out(vty, " (%d)", count); -} - -static void bts_dump_vty_cbch(struct vty *vty, const struct bts_smscb_chan_state *cstate) -{ - vty_out(vty, " CBCH %s: %u messages, %u pages, %lu-entry sched_arr, %u%% load%s", - bts_smscb_chan_state_name(cstate), llist_count(&cstate->messages), - bts_smscb_chan_page_count(cstate), cstate->sched_arr_size, - bts_smscb_chan_load_percent(cstate), VTY_NEWLINE); -} - -static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts) -{ - unsigned int i; - bool no_features = true; - vty_out(vty, " Features:%s", VTY_NEWLINE); - - for (i = 0; i < _NUM_BTS_FEAT; i++) { - if (osmo_bts_has_feature(&bts->features, i)) { - vty_out(vty, " %03u ", i); - vty_out(vty, "%-40s%s", osmo_bts_feature_name(i), VTY_NEWLINE); - no_features = false; - } - } - - if (no_features) - vty_out(vty, " (not available)%s", VTY_NEWLINE); -} - -static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) -{ - struct pchan_load pl; - unsigned long long sec; - struct gsm_bts_trx *trx; - int ts_hopping_total; - int ts_non_hopping_total; - - vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, " - "BSIC %u (NCC=%u, BCC=%u) and %u TRX%s", - bts->nr, btstype2str(bts->type), gsm_band_name(bts->band), - bts->cell_identity, - bts->location_area_code, bts->bsic, - bts->bsic >> 3, bts->bsic & 7, - bts->num_trx, VTY_NEWLINE); - vty_out(vty, " Description: %s%s", - bts->description ? bts->description : "(null)", VTY_NEWLINE); - - vty_out(vty, " ARFCNs:"); - ts_hopping_total = 0; - ts_non_hopping_total = 0; - llist_for_each_entry(trx, &bts->trx_list, list) { - int ts_nr; - int ts_hopping = 0; - int ts_non_hopping = 0; - for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { - struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; - if (ts->hopping.enabled) - ts_hopping++; - else - ts_non_hopping++; - } - - if (ts_non_hopping) - vty_out(vty, " %u", trx->arfcn); - ts_hopping_total += ts_hopping; - ts_non_hopping_total += ts_non_hopping; - } - if (ts_hopping_total) { - if (ts_non_hopping_total) - vty_out(vty, " / Hopping on %d of %d timeslots", - ts_hopping_total, ts_hopping_total + ts_non_hopping_total); - else - vty_out(vty, " Hopping on all %d timeslots", ts_hopping_total); - } - vty_out(vty, "%s", VTY_NEWLINE); - - if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH)) - vty_out(vty, " PCU version %s connected%s", bts->pcu_version, - VTY_NEWLINE); - vty_out(vty, " MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE); - vty_out(vty, " Minimum Rx Level for Access: %i dBm%s", - rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min), - VTY_NEWLINE); - vty_out(vty, " Cell Reselection Hysteresis: %u dBm%s", - bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE); - vty_out(vty, " Access Control Class rotation allow mask: 0x%" PRIx16 "%s", - bts->acc_mgr.allowed_subset_mask, VTY_NEWLINE); - vty_out(vty, " Access Control Class ramping: %senabled%s", - acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "not ", VTY_NEWLINE); - if (acc_ramp_is_enabled(&bts->acc_ramp)) { - vty_out(vty, " Access Control Class ramping step interval: %u seconds%s", - acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE); - vty_out(vty, " Access Control Class channel load thresholds: (%" PRIu8 ", %" PRIu8 ")%s", - bts->acc_ramp.chan_load_lower_threshold, - bts->acc_ramp.chan_load_upper_threshold, VTY_NEWLINE); - vty_out(vty, " enabling %u Access Control Class%s per ramping step%s", - acc_ramp_get_step_size(&bts->acc_ramp), - acc_ramp_get_step_size(&bts->acc_ramp) > 1 ? "es" : "", VTY_NEWLINE); - } - vty_out(vty, " RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer, - VTY_NEWLINE); - vty_out(vty, " RACH Max transmissions: %u%s", - rach_max_trans_raw2val(bts->si_common.rach_control.max_trans), - VTY_NEWLINE); - if (bts->si_common.rach_control.cell_bar) - vty_out(vty, " CELL IS BARRED%s", VTY_NEWLINE); - if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED) - vty_out(vty, " Uplink DTX: %s%s", - (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? - "enabled" : "forced", VTY_NEWLINE); - else - vty_out(vty, " Uplink DTX: not enabled%s", VTY_NEWLINE); - vty_out(vty, " Downlink DTX: %senabled%s", bts->dtxd ? "" : "not ", - VTY_NEWLINE); - vty_out(vty, " Channel Description Attachment: %s%s", - (bts->si_common.chan_desc.att) ? "yes" : "no", VTY_NEWLINE); - vty_out(vty, " Channel Description BS-PA-MFRMS: %u%s", - bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE); - vty_out(vty, " Channel Description BS-AG_BLKS-RES: %u%s", - bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE); - vty_out(vty, " System Information present: 0x%08x, static: 0x%08x%s", - bts->si_valid, bts->si_mode_static, VTY_NEWLINE); - vty_out(vty, " Early Classmark Sending: 2G %s, 3G %s%s%s", - bts->early_classmark_allowed ? "allowed" : "forbidden", - bts->early_classmark_allowed_3g ? "allowed" : "forbidden", - bts->early_classmark_allowed_3g && !bts->early_classmark_allowed ? - " (forbidden by 2G bit)" : "", - VTY_NEWLINE); - if (bts->pcu_sock_path) - vty_out(vty, " PCU Socket Path: %s%s", bts->pcu_sock_path, VTY_NEWLINE); - if (is_ipaccess_bts(bts)) - vty_out(vty, " Unit ID: %u/%u/0, OML Stream ID 0x%02x%s", - bts->ip_access.site_id, bts->ip_access.bts_id, - bts->oml_tei, VTY_NEWLINE); - else if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) - vty_out(vty, " Skip Reset: %d%s", - bts->nokia.skip_reset, VTY_NEWLINE); - vty_out(vty, " NM State: "); - net_dump_nmstate(vty, &bts->mo.nm_state); - vty_out(vty, " Site Mgr NM State: "); - net_dump_nmstate(vty, &bts->site_mgr.mo.nm_state); - - if (bts->gprs.mode != BTS_GPRS_NONE) { - vty_out(vty, " GPRS NSE: "); - net_dump_nmstate(vty, &bts->gprs.nse.mo.nm_state); - vty_out(vty, " GPRS CELL: "); - net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state); - vty_out(vty, " GPRS NSVC0: "); - net_dump_nmstate(vty, &bts->gprs.nsvc[0].mo.nm_state); - vty_out(vty, " GPRS NSVC1: "); - net_dump_nmstate(vty, &bts->gprs.nsvc[1].mo.nm_state); - } else - vty_out(vty, " GPRS: not configured%s", VTY_NEWLINE); - - vty_out(vty, " Paging: %u pending requests, %u free slots%s", - paging_pending_requests_nr(bts), - bts->paging.available_slots, VTY_NEWLINE); - if (is_ipaccess_bts(bts)) { - vty_out(vty, " OML Link: "); - e1isl_dump_vty_tcp(vty, bts->oml_link); - vty_out(vty, " OML Link state: %s", get_model_oml_status(bts)); - sec = bts_uptime(bts); - if (sec) - vty_out(vty, " %llu days %llu hours %llu min. %llu sec.", - OSMO_SEC2DAY(sec), OSMO_SEC2HRS(sec), OSMO_SEC2MIN(sec), sec % 60); - vty_out(vty, "%s", VTY_NEWLINE); - } else { - vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); - e1isl_dump_vty(vty, bts->oml_link); - } - - vty_out(vty, " Neighbor Cells: "); - switch (bts->neigh_list_manual_mode) { - default: - case NL_MODE_AUTOMATIC: - vty_out(vty, "Automatic"); - /* generate_bcch_chan_list() should populate si_common.neigh_list */ - break; - case NL_MODE_MANUAL: - vty_out(vty, "Manual"); - break; - case NL_MODE_MANUAL_SI5SEP: - vty_out(vty, "Manual/separate SI5"); - break; - } - vty_out(vty, ", ARFCNs:"); - vty_out_neigh_list(vty, &bts->si_common.neigh_list); - if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) { - vty_out(vty, " SI5:"); - vty_out_neigh_list(vty, &bts->si_common.si5_neigh_list); - } - vty_out(vty, "%s", VTY_NEWLINE); - - /* FIXME: chan_desc */ - memset(&pl, 0, sizeof(pl)); - bts_chan_load(&pl, bts); - vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); - dump_pchan_load_vty(vty, " ", &pl); - - bts_dump_vty_cbch(vty, &bts->cbch_basic); - bts_dump_vty_cbch(vty, &bts->cbch_extended); - - vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s", - bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL].current, - bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL].current, - VTY_NEWLINE); - vty_out(vty, " Channel Failures : %"PRIu64" rf_failures, %"PRIu64" rll failures%s", - bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL].current, - bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR].current, - VTY_NEWLINE); - vty_out(vty, " BTS failures : %"PRIu64" OML, %"PRIu64" RSL%s", - bts->bts_ctrs->ctr[BTS_CTR_BTS_OML_FAIL].current, - bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL].current, - VTY_NEWLINE); - - vty_out_stat_item_group(vty, " ", bts->bts_statg); - - bts_dump_vty_features(vty, bts); -} - DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]", SHOW_STR "Display information about a BTS\n" "BTS number\n") @@ -568,6 +257,22 @@ DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]", return CMD_SUCCESS; } +DEFUN(show_bts_brief, show_bts_brief_cmd, "show bts brief", + SHOW_STR "Display information about a BTS\n" + "Display availability status of all BTS\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + + /* Print OML state of BTSs. */ + llist_for_each_entry(bts, &net->bts_list, list) { + vty_out(vty, "BTS %d:", bts->nr); + bts_dump_vty_oml_link_state(vty, bts); + } + + return CMD_SUCCESS; +} + DEFUN(show_bts_fail_rep, show_bts_fail_rep_cmd, "show bts <0-255> fail-rep [reset]", SHOW_STR "Display information about a BTS\n" "BTS number\n" "OML failure reports\n" @@ -656,479 +361,6 @@ DEFUN(show_rejected_bts, show_rejected_bts_cmd, "show rejected-bts", return CMD_SUCCESS; } -/* utility functions */ -static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line, - const char *ts, const char *ss) -{ - e1_link->e1_nr = atoi(line); - e1_link->e1_ts = atoi(ts); - if (!strcmp(ss, "full")) - e1_link->e1_ts_ss = 255; - else - e1_link->e1_ts_ss = atoi(ss); -} - -static void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link, - const char *prefix) -{ - if (!e1_link->e1_ts) - return; - - if (e1_link->e1_ts_ss == 255) - vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s", - prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE); - else - vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s", - prefix, e1_link->e1_nr, e1_link->e1_ts, - e1_link->e1_ts_ss, VTY_NEWLINE); -} - - -static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts) -{ - vty_out(vty, " timeslot %u%s", ts->nr, VTY_NEWLINE); - if (ts->tsc != -1) - vty_out(vty, " training_sequence_code %u%s", ts->tsc, VTY_NEWLINE); - if (ts->pchan_from_config != GSM_PCHAN_NONE) - vty_out(vty, " phys_chan_config %s%s", - gsm_pchan_name(ts->pchan_from_config), VTY_NEWLINE); - vty_out(vty, " hopping enabled %u%s", - ts->hopping.enabled, VTY_NEWLINE); - if (ts->hopping.enabled) { - unsigned int i; - vty_out(vty, " hopping sequence-number %u%s", - ts->hopping.hsn, VTY_NEWLINE); - vty_out(vty, " hopping maio %u%s", - ts->hopping.maio, VTY_NEWLINE); - for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { - if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i)) - continue; - vty_out(vty, " hopping arfcn add %u%s", - i, VTY_NEWLINE); - } - } - config_write_e1_link(vty, &ts->e1_link, " "); - - if (ts->trx->bts->model->config_write_ts) - ts->trx->bts->model->config_write_ts(vty, ts); -} - -static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx) -{ - int i; - - vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE); - if (trx->description) - vty_out(vty, " description %s%s", trx->description, - VTY_NEWLINE); - vty_out(vty, " rf_locked %u%s", - trx->mo.force_rf_lock ? 1 : 0, - VTY_NEWLINE); - vty_out(vty, " arfcn %u%s", trx->arfcn, VTY_NEWLINE); - vty_out(vty, " nominal power %u%s", trx->nominal_power, VTY_NEWLINE); - vty_out(vty, " max_power_red %u%s", trx->max_power_red, VTY_NEWLINE); - config_write_e1_link(vty, &trx->rsl_e1_link, " rsl "); - vty_out(vty, " rsl e1 tei %u%s", trx->rsl_tei, VTY_NEWLINE); - - if (trx->bts->model->config_write_trx) - trx->bts->model->config_write_trx(vty, trx); - - for (i = 0; i < TRX_NR_TS; i++) - config_write_ts_single(vty, &trx->ts[i]); -} - -static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts) -{ - unsigned int i; - vty_out(vty, " gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode), - VTY_NEWLINE); - if (bts->gprs.mode == BTS_GPRS_NONE) - return; - - vty_out(vty, " gprs routing area %u%s", bts->gprs.rac, - VTY_NEWLINE); - vty_out(vty, " gprs network-control-order nc%u%s", - bts->gprs.net_ctrl_ord, VTY_NEWLINE); - if (!bts->gprs.ctrl_ack_type_use_block) - vty_out(vty, " gprs control-ack-type-rach%s", VTY_NEWLINE); - vty_out(vty, " gprs cell bvci %u%s", bts->gprs.cell.bvci, - VTY_NEWLINE); - for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++) - vty_out(vty, " gprs cell timer %s %u%s", - get_value_string(gprs_bssgp_cfg_strs, i), - bts->gprs.cell.timer[i], VTY_NEWLINE); - vty_out(vty, " gprs nsei %u%s", bts->gprs.nse.nsei, - VTY_NEWLINE); - for (i = 0; i < ARRAY_SIZE(bts->gprs.nse.timer); i++) - vty_out(vty, " gprs ns timer %s %u%s", - get_value_string(gprs_ns_timer_strs, i), - bts->gprs.nse.timer[i], VTY_NEWLINE); - for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) { - struct gsm_bts_gprs_nsvc *nsvc = - &bts->gprs.nsvc[i]; - struct osmo_sockaddr_str remote = {}; - uint16_t port; - - vty_out(vty, " gprs nsvc %u nsvci %u%s", i, - nsvc->nsvci, VTY_NEWLINE); - - vty_out(vty, " gprs nsvc %u local udp port %u%s", i, - nsvc->local_port, VTY_NEWLINE); - - if (osmo_sockaddr_str_from_sockaddr(&remote, &nsvc->remote.u.sas) || - remote.af != AF_UNSPEC) { - vty_out(vty, " gprs nsvc %u remote ip %s%s", i, - remote.ip, VTY_NEWLINE); - } - - /* Can't use remote.port because it's only valid when family != AF_UNSPEC, but the - * port can be even configured when the IP isn't */ - port = osmo_htons(nsvc->remote.u.sin.sin_port); - if (port) - vty_out(vty, " gprs nsvc %u remote udp port %u%s", i, - port, VTY_NEWLINE); - } - - /* EGPRS specific parameters */ - if (bts->gprs.mode == BTS_GPRS_EGPRS) { - if (bts->gprs.egprs_pkt_chan_request) - vty_out(vty, " gprs egprs-packet-channel-request%s", VTY_NEWLINE); - } -} - -/* Write the model data if there is one */ -static void config_write_bts_model(struct vty *vty, struct gsm_bts *bts) -{ - struct gsm_bts_trx *trx; - - if (!bts->model) - return; - - if (bts->model->config_write_bts) - bts->model->config_write_bts(vty, bts); - - llist_for_each_entry(trx, &bts->trx_list, list) - config_write_trx_single(vty, trx); -} - -static void write_amr_modes(struct vty *vty, const char *prefix, - const char *name, struct amr_mode *modes, int num) -{ - int i; - - vty_out(vty, " %s threshold %s", prefix, name); - for (i = 0; i < num - 1; i++) - vty_out(vty, " %d", modes[i].threshold); - vty_out(vty, "%s", VTY_NEWLINE); - vty_out(vty, " %s hysteresis %s", prefix, name); - for (i = 0; i < num - 1; i++) - vty_out(vty, " %d", modes[i].hysteresis); - vty_out(vty, "%s", VTY_NEWLINE); -} - -static void config_write_bts_amr(struct vty *vty, struct gsm_bts *bts, - struct amr_multirate_conf *mr, int full) -{ - struct gsm48_multi_rate_conf *mr_conf; - const char *prefix = (full) ? "amr tch-f" : "amr tch-h"; - int i, num; - - if (!(mr->gsm48_ie[1])) - return; - - mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie; - - num = 0; - vty_out(vty, " %s modes", prefix); - for (i = 0; i < ((full) ? 8 : 6); i++) { - if ((mr->gsm48_ie[1] & (1 << i))) { - vty_out(vty, " %d", i); - num++; - } - } - vty_out(vty, "%s", VTY_NEWLINE); - if (num > 4) - num = 4; - if (num > 1) { - write_amr_modes(vty, prefix, "ms", mr->ms_mode, num); - write_amr_modes(vty, prefix, "bts", mr->bts_mode, num); - } - vty_out(vty, " %s start-mode ", prefix); - if (mr_conf->icmi) { - num = 0; - for (i = 0; i < ((full) ? 8 : 6) && num < 4; i++) { - if ((mr->gsm48_ie[1] & (1 << i))) - num++; - if (mr_conf->smod == num - 1) { - vty_out(vty, "%d%s", num, VTY_NEWLINE); - break; - } - } - } else - vty_out(vty, "auto%s", VTY_NEWLINE); -} - -static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) -{ - int i; - uint8_t tmp; - - vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE); - vty_out(vty, " type %s%s", btstype2str(bts->type), VTY_NEWLINE); - if (bts->description) - vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE); - vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE); - vty_out(vty, " cell_identity %u%s", bts->cell_identity, VTY_NEWLINE); - vty_out(vty, " location_area_code %u%s", bts->location_area_code, - VTY_NEWLINE); - if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED) - vty_out(vty, " dtx uplink%s%s", - (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? "" : " force", - VTY_NEWLINE); - if (bts->dtxd) - vty_out(vty, " dtx downlink%s", VTY_NEWLINE); - vty_out(vty, " base_station_id_code %u%s", bts->bsic, VTY_NEWLINE); - vty_out(vty, " ms max power %u%s", bts->ms_max_power, VTY_NEWLINE); - vty_out(vty, " cell reselection hysteresis %u%s", - bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE); - vty_out(vty, " rxlev access min %u%s", - bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE); - - if (bts->si_common.cell_ro_sel_par.present) { - struct gsm48_si_selection_params *sp; - sp = &bts->si_common.cell_ro_sel_par; - - if (sp->cbq) - vty_out(vty, " cell bar qualify %u%s", - sp->cbq, VTY_NEWLINE); - - if (sp->cell_resel_off) - vty_out(vty, " cell reselection offset %u%s", - sp->cell_resel_off*2, VTY_NEWLINE); - - if (sp->temp_offs == 7) - vty_out(vty, " temporary offset infinite%s", - VTY_NEWLINE); - else if (sp->temp_offs) - vty_out(vty, " temporary offset %u%s", - sp->temp_offs*10, VTY_NEWLINE); - - if (sp->penalty_time == 31) - vty_out(vty, " penalty time reserved%s", - VTY_NEWLINE); - else if (sp->penalty_time) - vty_out(vty, " penalty time %u%s", - (sp->penalty_time*20)+20, VTY_NEWLINE); - } - - if (gsm_bts_get_radio_link_timeout(bts) < 0) - vty_out(vty, " radio-link-timeout infinite%s", VTY_NEWLINE); - else - vty_out(vty, " radio-link-timeout %d%s", - gsm_bts_get_radio_link_timeout(bts), VTY_NEWLINE); - - vty_out(vty, " channel allocator %s%s", - bts->chan_alloc_reverse ? "descending" : "ascending", - VTY_NEWLINE); - vty_out(vty, " rach tx integer %u%s", - bts->si_common.rach_control.tx_integer, VTY_NEWLINE); - vty_out(vty, " rach max transmission %u%s", - rach_max_trans_raw2val(bts->si_common.rach_control.max_trans), - VTY_NEWLINE); - - vty_out(vty, " channel-description attach %u%s", - bts->si_common.chan_desc.att, VTY_NEWLINE); - vty_out(vty, " channel-description bs-pa-mfrms %u%s", - bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE); - vty_out(vty, " channel-description bs-ag-blks-res %u%s", - bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE); - - if (bts->ccch_load_ind_thresh != 10) - vty_out(vty, " ccch load-indication-threshold %u%s", - bts->ccch_load_ind_thresh, VTY_NEWLINE); - if (bts->rach_b_thresh != -1) - vty_out(vty, " rach nm busy threshold %u%s", - bts->rach_b_thresh, VTY_NEWLINE); - if (bts->rach_ldavg_slots != -1) - vty_out(vty, " rach nm load average %u%s", - bts->rach_ldavg_slots, VTY_NEWLINE); - if (bts->si_common.rach_control.cell_bar) - vty_out(vty, " cell barred 1%s", VTY_NEWLINE); - if ((bts->si_common.rach_control.t2 & 0x4) == 0) - vty_out(vty, " rach emergency call allowed 1%s", VTY_NEWLINE); - if ((bts->si_common.rach_control.t3) != 0) - for (i = 0; i < 8; i++) - if (bts->si_common.rach_control.t3 & (0x1 << i)) - vty_out(vty, " rach access-control-class %d barred%s", i, VTY_NEWLINE); - if ((bts->si_common.rach_control.t2 & 0xfb) != 0) - for (i = 0; i < 8; i++) - if ((i != 2) && (bts->si_common.rach_control.t2 & (0x1 << i))) - vty_out(vty, " rach access-control-class %d barred%s", i+8, VTY_NEWLINE); - if (bts->acc_mgr.len_allowed_adm < 10) - vty_out(vty, " access-control-class-rotate %" PRIu8 "%s", bts->acc_mgr.len_allowed_adm, VTY_NEWLINE); - if (bts->acc_mgr.rotation_time_sec != ACC_MGR_QUANTUM_DEFAULT) - vty_out(vty, " access-control-class-rotate-quantum %" PRIu32 "%s", bts->acc_mgr.rotation_time_sec, VTY_NEWLINE); - vty_out(vty, " %saccess-control-class-ramping%s", acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "no ", VTY_NEWLINE); - if (acc_ramp_is_enabled(&bts->acc_ramp)) { - vty_out(vty, " access-control-class-ramping-step-interval %u%s", - acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE); - vty_out(vty, " access-control-class-ramping-step-size %u%s", acc_ramp_get_step_size(&bts->acc_ramp), - VTY_NEWLINE); - vty_out(vty, " access-control-class-ramping-chan-load %u %u%s", - bts->acc_ramp.chan_load_lower_threshold, bts->acc_ramp.chan_load_upper_threshold, VTY_NEWLINE); - } - if (!bts->si_unused_send_empty) - vty_out(vty, " no system-information unused-send-empty%s", VTY_NEWLINE); - for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) { - if (bts->si_mode_static & (1 << i)) { - vty_out(vty, " system-information %s mode static%s", - get_value_string(osmo_sitype_strs, i), VTY_NEWLINE); - vty_out(vty, " system-information %s static %s%s", - get_value_string(osmo_sitype_strs, i), - osmo_hexdump_nospc(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN), - VTY_NEWLINE); - } - } - vty_out(vty, " early-classmark-sending %s%s", - bts->early_classmark_allowed ? "allowed" : "forbidden", VTY_NEWLINE); - vty_out(vty, " early-classmark-sending-3g %s%s", - bts->early_classmark_allowed_3g ? "allowed" : "forbidden", VTY_NEWLINE); - switch (bts->type) { - case GSM_BTS_TYPE_NANOBTS: - case GSM_BTS_TYPE_OSMOBTS: - vty_out(vty, " ipa unit-id %u %u%s", - bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE); - if (bts->ip_access.rsl_ip) { - struct in_addr ia; - ia.s_addr = htonl(bts->ip_access.rsl_ip); - vty_out(vty, " ipa rsl-ip %s%s", inet_ntoa(ia), - VTY_NEWLINE); - } - vty_out(vty, " oml ipa stream-id %u line %u%s", - bts->oml_tei, bts->oml_e1_link.e1_nr, VTY_NEWLINE); - break; - case GSM_BTS_TYPE_NOKIA_SITE: - vty_out(vty, " nokia_site skip-reset %d%s", bts->nokia.skip_reset, VTY_NEWLINE); - vty_out(vty, " nokia_site no-local-rel-conf %d%s", - bts->nokia.no_loc_rel_cnf, VTY_NEWLINE); - vty_out(vty, " nokia_site bts-reset-timer %d%s", bts->nokia.bts_reset_timer_cnf, VTY_NEWLINE); - /* fall through: Nokia requires "oml e1" parameters also */ - default: - config_write_e1_link(vty, &bts->oml_e1_link, " oml "); - vty_out(vty, " oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE); - break; - } - - /* if we have a limit, write it */ - if (bts->paging.free_chans_need >= 0) - vty_out(vty, " paging free %d%s", bts->paging.free_chans_need, VTY_NEWLINE); - - vty_out(vty, " neighbor-list mode %s%s", - get_value_string(bts_neigh_mode_strs, bts->neigh_list_manual_mode), VTY_NEWLINE); - if (bts->neigh_list_manual_mode != NL_MODE_AUTOMATIC) { - for (i = 0; i < 1024; i++) { - if (bitvec_get_bit_pos(&bts->si_common.neigh_list, i)) - vty_out(vty, " neighbor-list add arfcn %u%s", - i, VTY_NEWLINE); - } - } - if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) { - for (i = 0; i < 1024; i++) { - if (bitvec_get_bit_pos(&bts->si_common.si5_neigh_list, i)) - vty_out(vty, " si5 neighbor-list add arfcn %u%s", - i, VTY_NEWLINE); - } - } - - for (i = 0; i < MAX_EARFCN_LIST; i++) { - struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; - if (e->arfcn[i] != OSMO_EARFCN_INVALID) { - vty_out(vty, " si2quater neighbor-list add earfcn %u " - "thresh-hi %u", e->arfcn[i], e->thresh_hi); - - vty_out(vty, " thresh-lo %u", - e->thresh_lo_valid ? e->thresh_lo : 32); - - vty_out(vty, " prio %u", - e->prio_valid ? e->prio : 8); - - vty_out(vty, " qrxlv %u", - e->qrxlm_valid ? e->qrxlm : 32); - - tmp = e->meas_bw[i]; - vty_out(vty, " meas %u", - (tmp != OSMO_EARFCN_MEAS_INVALID) ? tmp : 8); - - vty_out(vty, "%s", VTY_NEWLINE); - } - } - - for (i = 0; i < bts->si_common.uarfcn_length; i++) { - vty_out(vty, " si2quater neighbor-list add uarfcn %u %u %u%s", - bts->si_common.data.uarfcn_list[i], - bts->si_common.data.scramble_list[i] & ~(1 << 9), - (bts->si_common.data.scramble_list[i] >> 9) & 1, - VTY_NEWLINE); - } - - neighbor_ident_vty_write(vty, " ", bts); - - vty_out(vty, " codec-support fr"); - if (bts->codec.hr) - vty_out(vty, " hr"); - if (bts->codec.efr) - vty_out(vty, " efr"); - if (bts->codec.amr) - vty_out(vty, " amr"); - vty_out(vty, "%s", VTY_NEWLINE); - - config_write_bts_amr(vty, bts, &bts->mr_full, 1); - config_write_bts_amr(vty, bts, &bts->mr_half, 0); - - config_write_bts_gprs(vty, bts); - - if (bts->excl_from_rf_lock) - vty_out(vty, " rf-lock-exclude%s", VTY_NEWLINE); - - if (bts->force_combined_si_set) - vty_out(vty, " %sforce-combined-si%s", - bts->force_combined_si ? "" : "no ", VTY_NEWLINE); - - for (i = 0; i < ARRAY_SIZE(bts->depends_on); ++i) { - int j; - - if (bts->depends_on[i] == 0) - continue; - - for (j = 0; j < sizeof(bts->depends_on[i]) * 8; ++j) { - int bts_nr; - - if ((bts->depends_on[i] & (1<<j)) == 0) - continue; - - bts_nr = (i * sizeof(bts->depends_on[i]) * 8) + j; - vty_out(vty, " depends-on-bts %d%s", bts_nr, VTY_NEWLINE); - } - } - if (bts->pcu_sock_path) - vty_out(vty, " pcu-socket %s%s", bts->pcu_sock_path, VTY_NEWLINE); - - ho_vty_write_bts(vty, bts); - - config_write_bts_model(vty, bts); -} - -static int config_write_bts(struct vty *v) -{ - struct gsm_network *gsmnet = gsmnet_from_vty(v); - struct gsm_bts *bts; - - llist_for_each_entry(bts, &gsmnet->bts_list, list) - config_write_bts_single(v, bts); - - return CMD_SUCCESS; -} - static int config_write_net(struct vty *vty) { struct gsm_network *gsmnet = gsmnet_from_vty(vty); @@ -1169,6 +401,7 @@ static int config_write_net(struct vty *vty) uint16_t meas_port; char *meas_host; const char *meas_scenario; + unsigned int max_len = meas_feed_txqueue_max_length_get(); meas_feed_cfg_get(&meas_host, &meas_port); meas_scenario = meas_feed_scenario_get(); @@ -1179,6 +412,9 @@ static int config_write_net(struct vty *vty) if (strlen(meas_scenario) > 0) vty_out(vty, " meas-feed scenario %s%s", meas_scenario, VTY_NEWLINE); + if (max_len != MEAS_FEED_TXQUEUE_MAX_LEN_DEFAULT) + vty_out(vty, " meas-feed write-queue-max-length %u%s", + max_len, VTY_NEWLINE); } if (gsmnet->allow_unusable_timeslots) @@ -1194,38 +430,16 @@ static int config_write_net(struct vty *vty) vty_out(vty, "%s", VTY_NEWLINE); } - return CMD_SUCCESS; -} + if (gsmnet->pcu_sock_path) + vty_out(vty, " pcu-socket %s%s", gsmnet->pcu_sock_path, VTY_NEWLINE); + if (gsmnet->pcu_sock_wqueue_len_max != BSC_PCU_SOCK_WQUEUE_LEN_DEFAULT) + vty_out(vty, " pcu-socket-wqueue-length %u%s", gsmnet->pcu_sock_wqueue_len_max, + VTY_NEWLINE); -static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx, bool print_rsl, bool show_connected) -{ - if (show_connected && !trx->rsl_link) - return; - - if (!show_connected && trx->rsl_link) - return; - - vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s", - trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE); - vty_out(vty, "Description: %s%s", - trx->description ? trx->description : "(null)", VTY_NEWLINE); - vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, " - "resulting BS power: %d dBm%s", - trx->nominal_power, trx->max_power_red, - trx->nominal_power - trx->max_power_red, VTY_NEWLINE); - vty_out(vty, " Radio Carrier NM State: "); - net_dump_nmstate(vty, &trx->mo.nm_state); - if (print_rsl) - vty_out(vty, " RSL State: %s%s", trx->rsl_link? "connected" : "disconnected", VTY_NEWLINE); - vty_out(vty, " Baseband Transceiver NM State: "); - net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state); - if (is_ipaccess_bts(trx->bts)) { - vty_out(vty, " ip.access stream ID: 0x%02x ", trx->rsl_tei); - e1isl_dump_vty_tcp(vty, trx->rsl_link); - } else { - vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); - e1isl_dump_vty(vty, trx->rsl_link); - } + neighbor_ident_vty_write_network(vty, " "); + mgcp_client_pool_config_write(vty, " "); + + return CMD_SUCCESS; } static void trx_dump_vty_all(struct vty *vty, struct gsm_bts_trx *trx) @@ -1284,56 +498,6 @@ DEFUN(show_trx, return CMD_SUCCESS; } -/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots. - * Don't do anything if the ts is not dynamic. */ -static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts) -{ - enum gsm_phys_chan_config target; - if (ts_is_pchan_switching(ts, &target)) { - vty_out(vty, " switching %s -> %s", gsm_pchan_name(ts->pchan_is), - gsm_pchan_name(target)); - } else if (ts->pchan_is != ts->pchan_on_init) { - vty_out(vty, " as %s", gsm_pchan_name(ts->pchan_is)); - } -} - -static void vty_out_dyn_ts_details(struct vty *vty, struct gsm_bts_trx_ts *ts) -{ - /* show dyn TS details, if applicable */ - switch (ts->pchan_on_init) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - vty_out(vty, " Osmocom Dyn TS:"); - vty_out_dyn_ts_status(vty, ts); - vty_out(vty, VTY_NEWLINE); - break; - case GSM_PCHAN_TCH_F_PDCH: - vty_out(vty, " IPACC Dyn PDCH TS:"); - vty_out_dyn_ts_status(vty, ts); - vty_out(vty, VTY_NEWLINE); - break; - default: - /* no dyn ts */ - break; - } -} - -static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts) -{ - vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s (active %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan_on_init), - gsm_pchan_name(ts->pchan_is)); - if (ts->pchan_is != ts->pchan_on_init) - vty_out(vty, " (%s mode)", gsm_pchan_name(ts->pchan_is)); - vty_out(vty, ", TSC %u%s NM State: ", gsm_ts_tsc(ts), VTY_NEWLINE); - vty_out_dyn_ts_details(vty, ts); - net_dump_nmstate(vty, &ts->mo.nm_state); - if (!is_ipaccess_bts(ts->trx->bts)) - vty_out(vty, " E1 Line %u, Timeslot %u, Subslot %u%s", - ts->e1_link.e1_nr, ts->e1_link.e1_ts, - ts->e1_link.e1_ts_ss, VTY_NEWLINE); -} - DEFUN(show_ts, show_ts_cmd, "show timeslot [<0-255>] [<0-255>] [<0-7>]", @@ -1410,7 +574,7 @@ DEFUN(show_ts, return CMD_SUCCESS; } -static void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub) +void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub) { if (strlen(bsub->imsi)) vty_out(vty, " IMSI: %s%s", bsub->imsi, VTY_NEWLINE); @@ -1420,39 +584,6 @@ static void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub) vty_out(vty, " Use count: %s%s", osmo_use_count_to_str_c(OTC_SELECT, &bsub->use_count), VTY_NEWLINE); } -static void meas_rep_dump_uni_vty(struct vty *vty, - struct gsm_meas_rep_unidir *mru, - const char *prefix, - const char *dir) -{ - vty_out(vty, "%s RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ", - prefix, dir, rxlev2dbm(mru->full.rx_lev), - dir, rxlev2dbm(mru->sub.rx_lev)); - vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s", - dir, mru->full.rx_qual, dir, mru->sub.rx_qual, - VTY_NEWLINE); -} - -static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr, - const char *prefix) -{ - vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE); - vty_out(vty, "%s Flags: %s%s%s%s%s", prefix, - mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "", - mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "", - mr->flags & MEAS_REP_F_FPC ? "FPC " : "", - mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ", - VTY_NEWLINE); - if (mr->flags & MEAS_REP_F_MS_TO) - vty_out(vty, "%s MS Timing Offset: %d%s", prefix, mr->ms_timing_offset, VTY_NEWLINE); - if (mr->flags & MEAS_REP_F_MS_L1) - vty_out(vty, "%s L1 MS Power: %u dBm, Timing Advance: %u%s", - prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE); - if (mr->flags & MEAS_REP_F_DL_VALID) - meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl"); - meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul"); -} - static inline void print_all_trx_ext(struct vty *vty, bool show_connected) { struct gsm_network *net = gsmnet_from_vty(vty); @@ -1481,88 +612,13 @@ DEFUN(show_trx_con, return CMD_SUCCESS; } -static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) -{ - int idx; - - vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s", - lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, - lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE); - vty_out_dyn_ts_details(vty, lchan->ts); - vty_out(vty, " Connection: %u, State: %s%s%s%s", - lchan->conn ? 1: 0, lchan_state_name(lchan), - lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? " Error reason: " : "", - lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? lchan->last_error : "", - VTY_NEWLINE); - vty_out(vty, " BS Power: %u dBm, MS Power: %u dBm%s", - lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red - - lchan->bs_power*2, - ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), - VTY_NEWLINE); - vty_out(vty, " Channel Mode / Codec: %s%s", - gsm48_chan_mode_name(lchan->tch_mode), - VTY_NEWLINE); - if (lchan->conn && lchan->conn->bsub) { - vty_out(vty, " Subscriber:%s", VTY_NEWLINE); - bsc_subscr_dump_vty(vty, lchan->conn->bsub); - } else - vty_out(vty, " No Subscriber%s", VTY_NEWLINE); - if (is_ipaccess_bts(lchan->ts->trx->bts)) { - struct in_addr ia; - if (lchan->abis_ip.bound_ip) { - ia.s_addr = htonl(lchan->abis_ip.bound_ip); - vty_out(vty, " Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s", - inet_ntoa(ia), lchan->abis_ip.bound_port, - lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id, - VTY_NEWLINE); - } - if (lchan->abis_ip.connect_ip) { - ia.s_addr = htonl(lchan->abis_ip.connect_ip); - vty_out(vty, " Conn. IP: %s Port %u RTP_TYPE=%u SPEECH_MODE=0x%02x%s", - inet_ntoa(ia), lchan->abis_ip.connect_port, - lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode, - VTY_NEWLINE); - } - - } - - /* we want to report the last measurement report */ - idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), - lchan->meas_rep_idx, 1); - meas_rep_dump_vty(vty, &lchan->meas_rep[idx], " "); -} - -static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan) -{ - struct gsm_meas_rep *mr; - int idx; - - /* we want to report the last measurement report */ - idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), - lchan->meas_rep_idx, 1); - mr = &lchan->meas_rep[idx]; - - vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s", - lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, - gsm_pchan_name(lchan->ts->pchan_on_init)); - vty_out_dyn_ts_status(vty, lchan->ts); - vty_out(vty, ", Lchan %u, Type %s, State %s - " - "L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s", - lchan->nr, - gsm_lchant_name(lchan->type), lchan_state_name(lchan), - mr->ms_l1.pwr, - rxlev2dbm(mr->dl.full.rx_lev), - rxlev2dbm(mr->ul.full.rx_lev), - VTY_NEWLINE); -} - static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty, void (*dump_cb)(struct vty *, struct gsm_lchan *), bool all) { struct gsm_lchan *lchan; - ts_for_each_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { if (lchan_state_is(lchan, LCHAN_ST_UNUSED) && all == false) continue; dump_cb(vty, lchan); @@ -1701,7 +757,7 @@ DEFUN(show_lchan_summary_all, static void dump_one_subscr_conn(struct vty *vty, const struct gsm_subscriber_connection *conn) { vty_out(vty, "conn ID=%u, MSC=%u, hodec2_fail=%d, mgw_ep=%s%s", - conn->sccp.conn_id, conn->sccp.msc->nr, conn->hodec2.failures, + conn->sccp.conn.conn_id, conn->sccp.msc->nr, conn->hodec2.failures, osmo_mgcpc_ep_name(conn->user_plane.mgw_endpoint), VTY_NEWLINE); if (conn->lcls.global_call_ref_len) { vty_out(vty, " LCLS GCR: %s%s", @@ -1727,14 +783,12 @@ DEFUN(show_subscr_conn, struct gsm_subscriber_connection *conn; struct gsm_network *net = gsmnet_from_vty(vty); bool no_conns = true; - unsigned int count = 0; vty_out(vty, "Active subscriber connections: %s", VTY_NEWLINE); llist_for_each_entry(conn, &net->subscr_conns, entry) { dump_one_subscr_conn(vty, conn); no_conns = false; - count++; } if (no_conns) @@ -1743,23 +797,35 @@ DEFUN(show_subscr_conn, return CMD_SUCCESS; } -static int trigger_ho_or_as(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_bts *to_bts) +static int trigger_as(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_lchan *to_lchan) { - if (!to_bts || from_lchan->ts->trx->bts == to_bts) { - LOGP(DHO, LOGL_NOTICE, "%s Manually triggering Assignment from VTY\n", - gsm_lchan_name(from_lchan)); - to_bts = from_lchan->ts->trx->bts; - } else - LOGP(DHO, LOGL_NOTICE, "%s (ARFCN %u) --> BTS %u Manually triggering Handover from VTY\n", - gsm_lchan_name(from_lchan), from_lchan->ts->trx->arfcn, to_bts->nr); - { - struct handover_out_req req = { - .from_hodec_id = HODEC_USER, - .old_lchan = from_lchan, - .target_nik = *bts_ident_key(to_bts), - }; - handover_request(&req); + LOG_LCHAN(from_lchan, LOGL_NOTICE, "Manually triggering Assignment from VTY\n"); + if (!to_lchan) { + struct gsm_bts *bts = from_lchan->ts->trx->bts; + to_lchan = lchan_select_by_type(bts, from_lchan->type, + SELECT_FOR_ASSIGNMENT, + from_lchan); + vty_out(vty, "Error: cannot find free lchan of type %s%s", + gsm_chan_t_name(from_lchan->type), VTY_NEWLINE); } + if (reassignment_request_to_lchan(ASSIGN_FOR_VTY, from_lchan, to_lchan, -1, -1)) { + vty_out(vty, "Error: not allowed to start assignment for %s%s", + gsm_lchan_name(from_lchan), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +static int trigger_ho(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_bts *to_bts) +{ + struct handover_out_req req = { + .from_hodec_id = HODEC_USER, + .old_lchan = from_lchan, + }; + bts_cell_ab(&req.target_cell_ab, to_bts); + LOGP(DHO, LOGL_NOTICE, "%s (ARFCN %u) --> BTS %u Manually triggering Handover from VTY\n", + gsm_lchan_name(from_lchan), from_lchan->ts->trx->arfcn, to_bts->nr); + handover_request(&req); return CMD_SUCCESS; } @@ -1773,11 +839,10 @@ static int ho_or_as(struct vty *vty, const char *argv[], int argc) unsigned int trx_nr = atoi(argv[1]); unsigned int ts_nr = atoi(argv[2]); unsigned int ss_nr = atoi(argv[3]); - unsigned int bts_nr_new; const char *action; if (argc > 4) { - bts_nr_new = atoi(argv[4]); + unsigned int bts_nr_new = atoi(argv[4]); /* Lookup the BTS where we want to handover to */ llist_for_each_entry(bts, &net->bts_list, list) { @@ -1806,7 +871,10 @@ static int ho_or_as(struct vty *vty, const char *argv[], int argc) conn->lchan->ts->nr == ts_nr && conn->lchan->nr == ss_nr) { vty_out(vty, "starting %s for lchan %s...%s", action, conn->lchan->name, VTY_NEWLINE); lchan_dump_full_vty(vty, conn->lchan); - return trigger_ho_or_as(vty, conn->lchan, new_bts); + if (new_bts) + return trigger_ho(vty, conn->lchan, new_bts); + else + return trigger_as(vty, conn->lchan, NULL); } } @@ -1816,6 +884,29 @@ static int ho_or_as(struct vty *vty, const char *argv[], int argc) return CMD_WARNING; } +/* tsc_set and tsc: -1 to automatically determine which TSC Set / which TSC to use. */ +static int trigger_vamos_mode_modify(struct vty *vty, struct gsm_lchan *lchan, bool vamos, int tsc_set, int tsc) +{ + struct lchan_modify_info info = { + .modify_for = MODIFY_FOR_VTY, + .ch_mode_rate = lchan->current_ch_mode_rate, + .ch_indctr = lchan->current_ch_indctr, + .tsc_set = { + .present = (tsc_set >= 0), + .val = tsc_set, + }, + .tsc = { + .present = (tsc >= 0), + .val = tsc, + }, + }; + if (vamos) + info.type_for = LCHAN_TYPE_FOR_VAMOS; + + lchan_mode_modify(lchan, &info); + return CMD_SUCCESS; +} + #define MANUAL_HANDOVER_STR "Manually trigger handover (for debugging)\n" #define MANUAL_ASSIGNMENT_STR "Manually trigger assignment (for debugging)\n" @@ -1857,7 +948,7 @@ static struct gsm_lchan *find_used_voice_lchan(struct vty *vty, int random_idx) if (ts->fi->state != TS_ST_IN_USE) continue; - ts_for_each_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED) && (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)) { @@ -1899,18 +990,20 @@ static struct gsm_bts *find_other_bts_with_free_slots(struct vty *vty, struct gs continue; llist_for_each_entry(trx, &bts->trx_list, list) { - struct gsm_lchan *lchan = lchan_select_by_type(bts, free_type); + struct gsm_lchan *lchan = lchan_select_by_type(bts, free_type, + SELECT_FOR_HANDOVER, + NULL); if (!lchan) continue; vty_out(vty, "Found unused %s slot: %s%s", - gsm_lchant_name(free_type), gsm_lchan_name(lchan), VTY_NEWLINE); + gsm_chan_t_name(free_type), gsm_lchan_name(lchan), VTY_NEWLINE); lchan_dump_full_vty(vty, lchan); return bts; } } vty_out(vty, "%% Cannot find any BTS (other than BTS %u) with free %s lchan%s", - not_this_bts? not_this_bts->nr : 255, gsm_lchant_name(free_type), VTY_NEWLINE); + not_this_bts ? not_this_bts->nr : 255, gsm_chan_t_name(free_type), VTY_NEWLINE); return NULL; } @@ -1931,7 +1024,7 @@ DEFUN(handover_any, handover_any_cmd, if (!to_bts) return CMD_WARNING; - return trigger_ho_or_as(vty, from_lchan, to_bts); + return trigger_ho(vty, from_lchan, to_bts); } DEFUN(assignment_any, assignment_any_cmd, @@ -1946,18 +1039,19 @@ DEFUN(assignment_any, assignment_any_cmd, if (!from_lchan) return CMD_WARNING; - return trigger_ho_or_as(vty, from_lchan, NULL); + return trigger_as(vty, from_lchan, NULL); } DEFUN(handover_any_to_arfcn_bsic, handover_any_to_arfcn_bsic_cmd, - "handover any to " NEIGHBOR_IDENT_VTY_KEY_PARAMS, + "handover any to " CELL_AB_VTY_PARAMS, MANUAL_HANDOVER_STR "Pick any actively used TCH/F or TCH/H lchan to handover to another cell." " This is likely to fail outside of a lab setup where you are certain that" " all MS are able to see the target cell.\n" "'to'\n" - NEIGHBOR_IDENT_VTY_KEY_DOC) + CELL_AB_VTY_DOC) { + struct cell_ab ab = {}; struct handover_out_req req; struct gsm_lchan *from_lchan; @@ -1970,12 +1064,8 @@ DEFUN(handover_any_to_arfcn_bsic, handover_any_to_arfcn_bsic_cmd, .old_lchan = from_lchan, }; - if (!neighbor_ident_bts_parse_key_params(vty, from_lchan->ts->trx->bts, - argv, &req.target_nik)) { - vty_out(vty, "%% BTS %u does not know about this neighbor%s", - from_lchan->ts->trx->bts->nr, VTY_NEWLINE); - return CMD_WARNING; - } + neighbor_ident_vty_parse_arfcn_bsic(&ab, argv); + req.target_cell_ab = ab; handover_request(&req); return CMD_SUCCESS; @@ -1983,7 +1073,7 @@ DEFUN(handover_any_to_arfcn_bsic, handover_any_to_arfcn_bsic_cmd, static void paging_dump_vty(struct vty *vty, struct gsm_paging_request *pag) { - vty_out(vty, "Paging on BTS %u%s", pag->bts->nr, VTY_NEWLINE); + vty_out(vty, "Paging on BTS %u (%u request timeouts)%s", pag->bts->nr, pag->attempts, VTY_NEWLINE); bsc_subscr_dump_vty(vty, pag->bsub); } @@ -1991,10 +1081,9 @@ static void bts_paging_dump_vty(struct vty *vty, struct gsm_bts *bts) { struct gsm_paging_request *pag; - if (!bts->paging.bts) - return; - - llist_for_each_entry(pag, &bts->paging.pending_requests, entry) + llist_for_each_entry(pag, &bts->paging.initial_req_list, entry) + paging_dump_vty(vty, pag); + llist_for_each_entry(pag, &bts->paging.retrans_req_list, entry) paging_dump_vty(vty, pag); } @@ -2098,7 +1187,7 @@ DEFUN_DEPRECATED(cfg_net_dtx, return CMD_SUCCESS; } -#define NRI_STR "Mapping of Network Resource Indicators to this MSC, for MSC pooling\n" +#define NRI_STR "Mapping of Network Resource Indicators, for MSC pooling\n" #define NULL_NRI_STR "Define NULL-NRI values that cause re-assignment of an MS to a different MSC, for MSC pooling.\n" #define NRI_FIRST_LAST_STR "First value of the NRI value range, should not surpass the configured 'nri bitlen'.\n" \ "Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the" \ @@ -2115,7 +1204,7 @@ DEFUN_ATTR(cfg_net_nri_bitlen, "nri bitlen <1-15>", NRI_STR "Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).\n" - "bit count (default: " OSMO_STRINGIFY_VAL(NRI_BITLEN_DEFAULT) ")\n", + "bit count (default: " OSMO_STRINGIFY_VAL(OSMO_NRI_BITLEN_DEFAULT) ")\n", CMD_ATTR_IMMEDIATE) { struct gsm_network *gsmnet = gsmnet_from_vty(vty); @@ -2145,7 +1234,7 @@ DEFUN_ATTR(cfg_net_nri_null_add, DEFUN_ATTR(cfg_net_nri_null_del, cfg_net_nri_null_del_cmd, "nri null del <0-32767> [<0-32767>]", - NRI_STR NULL_NRI_STR "Remove NRI value or range from the NRI mapping for this MSC\n" + NRI_STR NULL_NRI_STR "Remove NRI value or range from the NRI mapping\n" NRI_FIRST_LAST_STR, CMD_ATTR_IMMEDIATE) { @@ -2161,2799 +1250,6 @@ DEFUN_ATTR(cfg_net_nri_null_del, return CMD_SUCCESS; } -/* per-BTS configuration */ -DEFUN_ATTR(cfg_bts, - cfg_bts_cmd, - "bts <0-255>", - "Select a BTS to configure\n" - BTS_NR_STR, - CMD_ATTR_IMMEDIATE) -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - int bts_nr = atoi(argv[0]); - struct gsm_bts *bts; - - if (bts_nr > gsmnet->num_bts) { - vty_out(vty, "%% The next unused BTS number is %u%s", - gsmnet->num_bts, VTY_NEWLINE); - return CMD_WARNING; - } else if (bts_nr == gsmnet->num_bts) { - /* allocate a new one */ - bts = bsc_bts_alloc_register(gsmnet, GSM_BTS_TYPE_UNKNOWN, - HARDCODED_BSIC); - osmo_stat_item_inc(gsmnet->bsc_statg->items[BSC_STAT_NUM_BTS_TOTAL], 1); - } else - bts = gsm_bts_num(gsmnet, bts_nr); - - if (!bts) { - vty_out(vty, "%% Unable to allocate BTS %u%s", - gsmnet->num_bts, VTY_NEWLINE); - return CMD_WARNING; - } - - vty->index = bts; - vty->index_sub = &bts->description; - vty->node = BTS_NODE; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_type, - cfg_bts_type_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "type TYPE", /* dynamically created */ - "Set the BTS type\n" "Type\n") -{ - struct gsm_bts *bts = vty->index; - int rc; - - rc = gsm_set_bts_type(bts, str2btstype(argv[0])); - if (rc < 0) - return CMD_WARNING; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_band, - cfg_bts_band_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "band BAND", - "Set the frequency band of this BTS\n" "Frequency band\n") -{ - struct gsm_bts *bts = vty->index; - int band = gsm_band_parse(argv[0]); - - if (band < 0) { - vty_out(vty, "%% BAND %d is not a valid GSM band%s", - band, VTY_NEWLINE); - return CMD_WARNING; - } - - bts->band = band; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_dtxu, - cfg_bts_dtxu_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "dtx uplink [force]", - "Configure discontinuous transmission\n" - "Enable Uplink DTX for this BTS\n" - "MS 'shall' use DTXu instead of 'may' use (might not be supported by " - "older phones).\n") -{ - struct gsm_bts *bts = vty->index; - - bts->dtxu = (argc > 0) ? GSM48_DTX_SHALL_BE_USED : GSM48_DTX_MAY_BE_USED; - if (!is_ipaccess_bts(bts)) - vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration " - "neither supported nor tested!%s", VTY_NEWLINE); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_no_dtxu, - cfg_bts_no_dtxu_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "no dtx uplink", - NO_STR "Configure discontinuous transmission\n" - "Disable Uplink DTX for this BTS\n") -{ - struct gsm_bts *bts = vty->index; - - bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_dtxd, - cfg_bts_dtxd_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "dtx downlink", - "Configure discontinuous transmission\n" - "Enable Downlink DTX for this BTS\n") -{ - struct gsm_bts *bts = vty->index; - - bts->dtxd = true; - if (!is_ipaccess_bts(bts)) - vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration " - "neither supported nor tested!%s", VTY_NEWLINE); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_no_dtxd, - cfg_bts_no_dtxd_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "no dtx downlink", - NO_STR "Configure discontinuous transmission\n" - "Disable Downlink DTX for this BTS\n") -{ - struct gsm_bts *bts = vty->index; - - bts->dtxd = false; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_ci, - cfg_bts_ci_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "cell_identity <0-65535>", - "Set the Cell identity of this BTS\n" "Cell Identity\n") -{ - struct gsm_bts *bts = vty->index; - int ci = atoi(argv[0]); - - if (ci < 0 || ci > 0xffff) { - vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s", - ci, VTY_NEWLINE); - return CMD_WARNING; - } - bts->cell_identity = ci; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_lac, - cfg_bts_lac_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "location_area_code <0-65535>", - "Set the Location Area Code (LAC) of this BTS\n" "LAC\n") -{ - struct gsm_bts *bts = vty->index; - int lac = atoi(argv[0]); - - if (lac < 0 || lac > 0xffff) { - vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s", - lac, VTY_NEWLINE); - return CMD_WARNING; - } - - if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) { - vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s", - lac, VTY_NEWLINE); - return CMD_WARNING; - } - - bts->location_area_code = lac; - - return CMD_SUCCESS; -} - - -/* compatibility wrapper for old config files */ -DEFUN_HIDDEN(cfg_bts_tsc, - cfg_bts_tsc_cmd, - "training_sequence_code <0-7>", - "Set the Training Sequence Code (TSC) of this BTS\n" "TSC\n") -{ - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_bsic, - cfg_bts_bsic_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "base_station_id_code <0-63>", - "Set the Base Station Identity Code (BSIC) of this BTS\n" - "BSIC of this BTS\n") -{ - struct gsm_bts *bts = vty->index; - int bsic = atoi(argv[0]); - - if (bsic < 0 || bsic > 0x3f) { - vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s", - bsic, VTY_NEWLINE); - return CMD_WARNING; - } - bts->bsic = bsic; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_unit_id, - cfg_bts_unit_id_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "ipa unit-id <0-65534> <0-255>", - "Abis/IP specific options\n" - "Set the IPA BTS Unit ID\n" - "Unit ID (Site)\n" - "Unit ID (BTS)\n") -{ - struct gsm_bts *bts = vty->index; - int site_id = atoi(argv[0]); - int bts_id = atoi(argv[1]); - - if (!is_ipaccess_bts(bts)) { - vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->ip_access.site_id = site_id; - bts->ip_access.bts_id = bts_id; - - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_bts_unit_id, - cfg_bts_deprecated_unit_id_cmd, - "ip.access unit_id <0-65534> <0-255>", - "Abis/IP specific options\n" - "Set the IPA BTS Unit ID\n" - "Unit ID (Site)\n" - "Unit ID (BTS)\n"); - -DEFUN_USRATTR(cfg_bts_rsl_ip, - cfg_bts_rsl_ip_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "ipa rsl-ip A.B.C.D", - "Abis/IP specific options\n" - "Set the IPA RSL IP Address of the BSC\n" - "Destination IP address for RSL connection\n") -{ - struct gsm_bts *bts = vty->index; - struct in_addr ia; - - if (!is_ipaccess_bts(bts)) { - vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); - return CMD_WARNING; - } - - inet_aton(argv[0], &ia); - bts->ip_access.rsl_ip = ntohl(ia.s_addr); - - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_bts_rsl_ip, - cfg_bts_deprecated_rsl_ip_cmd, - "ip.access rsl-ip A.B.C.D", - "Abis/IP specific options\n" - "Set the IPA RSL IP Address of the BSC\n" - "Destination IP address for RSL connection\n"); - -#define NOKIA_STR "Nokia *Site related commands\n" - -DEFUN_USRATTR(cfg_bts_nokia_site_skip_reset, - cfg_bts_nokia_site_skip_reset_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "nokia_site skip-reset (0|1)", - NOKIA_STR - "Skip the reset step during bootstrap process of this BTS\n" - "Do NOT skip the reset\n" "Skip the reset\n") -{ - struct gsm_bts *bts = vty->index; - - if (bts->type != GSM_BTS_TYPE_NOKIA_SITE) { - vty_out(vty, "%% BTS is not of Nokia *Site type%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->nokia.skip_reset = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_nokia_site_no_loc_rel_cnf, - cfg_bts_nokia_site_no_loc_rel_cnf_cmd, - "nokia_site no-local-rel-conf (0|1)", - NOKIA_STR - "Do not wait for RELease CONFirm message when releasing channel locally\n" - "Wait for RELease CONFirm\n" "Do not wait for RELease CONFirm\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - - if (!is_nokia_bts(bts)) { - vty_out(vty, "%% BTS is not of Nokia *Site type%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - bts->nokia.no_loc_rel_cnf = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_nokia_site_bts_reset_timer_cnf, - cfg_bts_nokia_site_bts_reset_timer_cnf_cmd, - "nokia_site bts-reset-timer <15-100>", - NOKIA_STR - "The amount of time (in sec.) between BTS_RESET is sent,\n" - "and the BTS is being bootstrapped.\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - - if (!is_nokia_bts(bts)) { - vty_out(vty, "%% BTS is not of Nokia *Site type%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - bts->nokia.bts_reset_timer_cnf = atoi(argv[0]); - - return CMD_SUCCESS; -} -#define OML_STR "Organization & Maintenance Link\n" -#define IPA_STR "A-bis/IP Specific Options\n" - -DEFUN_USRATTR(cfg_bts_stream_id, - cfg_bts_stream_id_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "oml ipa stream-id <0-255> line E1_LINE", - OML_STR IPA_STR - "Set the ipa Stream ID of the OML link of this BTS\n" "Stream Identifier\n" - "Virtual E1 Line Number\n" "Virtual E1 Line Number\n") -{ - struct gsm_bts *bts = vty->index; - int stream_id = atoi(argv[0]), linenr = atoi(argv[1]); - - if (!is_ipaccess_bts(bts)) { - vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->oml_tei = stream_id; - /* This is used by e1inp_bind_ops callback for each BTS model. */ - bts->oml_e1_link.e1_nr = linenr; - - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_bts_stream_id, - cfg_bts_deprecated_stream_id_cmd, - "oml ip.access stream_id <0-255> line E1_LINE", - OML_STR IPA_STR - "Set the ip.access Stream ID of the OML link of this BTS\n" - "Stream Identifier\n" "Virtual E1 Line Number\n" "Virtual E1 Line Number\n"); - -#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n" - -/* NOTE: This requires a full restart as bsc_network_configure() is executed - * only once on startup from osmo_bsc_main.c */ -DEFUN(cfg_bts_oml_e1, - cfg_bts_oml_e1_cmd, - "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", - OML_E1_STR - "E1/T1 line number to be used for OML\n" - "E1/T1 line number to be used for OML\n" - "E1/T1 timeslot to be used for OML\n" - "E1/T1 timeslot to be used for OML\n" - "E1/T1 sub-slot to be used for OML\n" - "Use E1/T1 sub-slot 0\n" - "Use E1/T1 sub-slot 1\n" - "Use E1/T1 sub-slot 2\n" - "Use E1/T1 sub-slot 3\n" - "Use full E1 slot 3\n" - ) -{ - struct gsm_bts *bts = vty->index; - - parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_oml_e1_tei, - cfg_bts_oml_e1_tei_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "oml e1 tei <0-63>", - OML_E1_STR - "Set the TEI to be used for OML\n" - "TEI Number\n") -{ - struct gsm_bts *bts = vty->index; - - bts->oml_tei = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_challoc, - cfg_bts_challoc_cmd, - "channel allocator (ascending|descending)", - "Channel Allocator\n" "Channel Allocator\n" - "Allocate Timeslots and Transceivers in ascending order\n" - "Allocate Timeslots and Transceivers in descending order\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - - if (!strcmp(argv[0], "ascending")) - bts->chan_alloc_reverse = 0; - else - bts->chan_alloc_reverse = 1; - - return CMD_SUCCESS; -} - -#define RACH_STR "Random Access Control Channel\n" - -DEFUN_USRATTR(cfg_bts_rach_tx_integer, - cfg_bts_rach_tx_integer_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "rach tx integer <0-15>", - RACH_STR - "Set the raw tx integer value in RACH Control parameters IE\n" - "Set the raw tx integer value in RACH Control parameters IE\n" - "Raw tx integer value in RACH Control parameters IE\n") -{ - struct gsm_bts *bts = vty->index; - bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf; - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_rach_max_trans, - cfg_bts_rach_max_trans_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "rach max transmission (1|2|4|7)", - RACH_STR - "Set the maximum number of RACH burst transmissions\n" - "Set the maximum number of RACH burst transmissions\n" - "Maximum number of 1 RACH burst transmissions\n" - "Maximum number of 2 RACH burst transmissions\n" - "Maximum number of 4 RACH burst transmissions\n" - "Maximum number of 7 RACH burst transmissions\n") -{ - struct gsm_bts *bts = vty->index; - bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0])); - return CMD_SUCCESS; -} - -#define CD_STR "Channel Description\n" - -DEFUN_USRATTR(cfg_bts_chan_desc_att, - cfg_bts_chan_desc_att_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "channel-description attach (0|1)", - CD_STR - "Set if attachment is required\n" - "Attachment is NOT required\n" - "Attachment is required (standard)\n") -{ - struct gsm_bts *bts = vty->index; - bts->si_common.chan_desc.att = atoi(argv[0]); - return CMD_SUCCESS; -} -ALIAS_DEPRECATED(cfg_bts_chan_desc_att, - cfg_bts_chan_dscr_att_cmd, - "channel-descrption attach (0|1)", - CD_STR - "Set if attachment is required\n" - "Attachment is NOT required\n" - "Attachment is required (standard)\n"); - -DEFUN_USRATTR(cfg_bts_chan_desc_bs_pa_mfrms, - cfg_bts_chan_desc_bs_pa_mfrms_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "channel-description bs-pa-mfrms <2-9>", - CD_STR - "Set number of multiframe periods for paging groups\n" - "Number of multiframe periods for paging groups\n") -{ - struct gsm_bts *bts = vty->index; - int bs_pa_mfrms = atoi(argv[0]); - - bts->si_common.chan_desc.bs_pa_mfrms = bs_pa_mfrms - 2; - return CMD_SUCCESS; -} -ALIAS_DEPRECATED(cfg_bts_chan_desc_bs_pa_mfrms, - cfg_bts_chan_dscr_bs_pa_mfrms_cmd, - "channel-descrption bs-pa-mfrms <2-9>", - CD_STR - "Set number of multiframe periods for paging groups\n" - "Number of multiframe periods for paging groups\n"); - -DEFUN_USRATTR(cfg_bts_chan_desc_bs_ag_blks_res, - cfg_bts_chan_desc_bs_ag_blks_res_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "channel-description bs-ag-blks-res <0-7>", - CD_STR - "Set number of blocks reserved for access grant\n" - "Number of blocks reserved for access grant\n") -{ - struct gsm_bts *bts = vty->index; - int bs_ag_blks_res = atoi(argv[0]); - - bts->si_common.chan_desc.bs_ag_blks_res = bs_ag_blks_res; - return CMD_SUCCESS; -} -ALIAS_DEPRECATED(cfg_bts_chan_desc_bs_ag_blks_res, - cfg_bts_chan_dscr_bs_ag_blks_res_cmd, - "channel-descrption bs-ag-blks-res <0-7>", - CD_STR - "Set number of blocks reserved for access grant\n" - "Number of blocks reserved for access grant\n"); - -#define CCCH_STR "Common Control Channel\n" - -DEFUN_USRATTR(cfg_bts_ccch_load_ind_thresh, - cfg_bts_ccch_load_ind_thresh_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "ccch load-indication-threshold <0-100>", - CCCH_STR - "Percentage of CCCH load at which BTS sends RSL CCCH LOAD IND\n" - "CCCH Load Threshold in percent (Default: 10)\n") -{ - struct gsm_bts *bts = vty->index; - bts->ccch_load_ind_thresh = atoi(argv[0]); - return CMD_SUCCESS; -} - -#define NM_STR "Network Management\n" - -DEFUN_USRATTR(cfg_bts_rach_nm_b_thresh, - cfg_bts_rach_nm_b_thresh_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "rach nm busy threshold <0-255>", - RACH_STR NM_STR - "Set the NM Busy Threshold\n" - "Set the NM Busy Threshold\n" - "NM Busy Threshold in dB\n") -{ - struct gsm_bts *bts = vty->index; - bts->rach_b_thresh = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_rach_nm_ldavg, - cfg_bts_rach_nm_ldavg_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "rach nm load average <0-65535>", - RACH_STR NM_STR - "Set the NM Loadaverage Slots value\n" - "Set the NM Loadaverage Slots value\n" - "NM Loadaverage Slots value\n") -{ - struct gsm_bts *bts = vty->index; - bts->rach_ldavg_slots = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_cell_barred, - cfg_bts_cell_barred_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "cell barred (0|1)", - "Should this cell be barred from access?\n" - "Should this cell be barred from access?\n" - "Cell should NOT be barred\n" - "Cell should be barred\n") - -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.rach_control.cell_bar = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_rach_ec_allowed, - cfg_bts_rach_ec_allowed_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "rach emergency call allowed (0|1)", - RACH_STR - "Should this cell allow emergency calls?\n" - "Should this cell allow emergency calls?\n" - "Should this cell allow emergency calls?\n" - "Do NOT allow emergency calls\n" - "Allow emergency calls\n") -{ - struct gsm_bts *bts = vty->index; - - if (atoi(argv[0]) == 0) - bts->si_common.rach_control.t2 |= 0x4; - else - bts->si_common.rach_control.t2 &= ~0x4; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_rach_ac_class, - cfg_bts_rach_ac_class_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "rach access-control-class (0|1|2|3|4|5|6|7|8|9|11|12|13|14|15) (barred|allowed)", - RACH_STR - "Set access control class\n" - "Access control class 0\n" - "Access control class 1\n" - "Access control class 2\n" - "Access control class 3\n" - "Access control class 4\n" - "Access control class 5\n" - "Access control class 6\n" - "Access control class 7\n" - "Access control class 8\n" - "Access control class 9\n" - "Access control class 11 for PLMN use\n" - "Access control class 12 for security services\n" - "Access control class 13 for public utilities (e.g. water/gas suppliers)\n" - "Access control class 14 for emergency services\n" - "Access control class 15 for PLMN staff\n" - "barred to use access control class\n" - "allowed to use access control class\n") -{ - struct gsm_bts *bts = vty->index; - - uint8_t control_class; - uint8_t allowed = 0; - - if (strcmp(argv[1], "allowed") == 0) - allowed = 1; - - control_class = atoi(argv[0]); - if (control_class < 8) - if (allowed) - bts->si_common.rach_control.t3 &= ~(0x1 << control_class); - else - bts->si_common.rach_control.t3 |= (0x1 << control_class); - else - if (allowed) - bts->si_common.rach_control.t2 &= ~(0x1 << (control_class - 8)); - else - bts->si_common.rach_control.t2 |= (0x1 << (control_class - 8)); - - if (control_class < 10) - acc_mgr_perm_subset_changed(&bts->acc_mgr, &bts->si_common.rach_control); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_ms_max_power, - cfg_bts_ms_max_power_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "ms max power <0-40>", - "MS Options\n" - "Maximum transmit power of the MS\n" - "Maximum transmit power of the MS\n" - "Maximum transmit power of the MS in dBm\n") -{ - struct gsm_bts *bts = vty->index; - - bts->ms_max_power = atoi(argv[0]); - - return CMD_SUCCESS; -} - -#define CELL_STR "Cell Parameters\n" - -DEFUN_USRATTR(cfg_bts_cell_resel_hyst, - cfg_bts_cell_resel_hyst_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "cell reselection hysteresis <0-14>", - CELL_STR "Cell re-selection parameters\n" - "Cell Re-Selection Hysteresis in dB\n" - "Cell Re-Selection Hysteresis in dB\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_rxlev_acc_min, - cfg_bts_rxlev_acc_min_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "rxlev access min <0-63>", - "Minimum RxLev needed for cell access\n" - "Minimum RxLev needed for cell access\n" - "Minimum RxLev needed for cell access\n" - "Minimum RxLev needed for cell access (better than -110dBm)\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_cell_bar_qualify, - cfg_bts_cell_bar_qualify_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "cell bar qualify (0|1)", - CELL_STR "Cell Bar Qualify\n" "Cell Bar Qualify\n" - "Set CBQ to 0\n" "Set CBQ to 1\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_ro_sel_par.present = 1; - bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_cell_resel_ofs, - cfg_bts_cell_resel_ofs_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "cell reselection offset <0-126>", - CELL_STR "Cell Re-Selection Parameters\n" - "Cell Re-Selection Offset (CRO) in dB\n" - "Cell Re-Selection Offset (CRO) in dB\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_ro_sel_par.present = 1; - bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(argv[0])/2; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_temp_ofs, - cfg_bts_temp_ofs_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "temporary offset <0-60>", - "Cell selection temporary negative offset\n" - "Cell selection temporary negative offset\n" - "Cell selection temporary negative offset in dB\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_ro_sel_par.present = 1; - bts->si_common.cell_ro_sel_par.temp_offs = atoi(argv[0])/10; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_temp_ofs_inf, - cfg_bts_temp_ofs_inf_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "temporary offset infinite", - "Cell selection temporary negative offset\n" - "Cell selection temporary negative offset\n" - "Sets cell selection temporary negative offset to infinity\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_ro_sel_par.present = 1; - bts->si_common.cell_ro_sel_par.temp_offs = 7; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_penalty_time, - cfg_bts_penalty_time_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "penalty time <20-620>", - "Cell selection penalty time\n" - "Cell selection penalty time\n" - "Cell selection penalty time in seconds (by 20s increments)\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_ro_sel_par.present = 1; - bts->si_common.cell_ro_sel_par.penalty_time = (atoi(argv[0])-20)/20; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_penalty_time_rsvd, - cfg_bts_penalty_time_rsvd_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "penalty time reserved", - "Cell selection penalty time\n" - "Cell selection penalty time\n" - "Set cell selection penalty time to reserved value 31, " - "(indicate that CELL_RESELECT_OFFSET is subtracted from C2 " - "and TEMPORARY_OFFSET is ignored)\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_ro_sel_par.present = 1; - bts->si_common.cell_ro_sel_par.penalty_time = 31; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_radio_link_timeout, - cfg_bts_radio_link_timeout_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "radio-link-timeout <4-64>", - "Radio link timeout criterion (BTS side)\n" - "Radio link timeout value (lost SACCH block)\n") -{ - struct gsm_bts *bts = vty->index; - - gsm_bts_set_radio_link_timeout(bts, atoi(argv[0])); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_radio_link_timeout_inf, - cfg_bts_radio_link_timeout_inf_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "radio-link-timeout infinite", - "Radio link timeout criterion (BTS side)\n" - "Infinite Radio link timeout value (use only for BTS RF testing)\n") -{ - struct gsm_bts *bts = vty->index; - - if (bts->type != GSM_BTS_TYPE_OSMOBTS) { - vty_out(vty, "%% infinite radio link timeout not supported by this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - vty_out(vty, "%% INFINITE RADIO LINK TIMEOUT, USE ONLY FOR BTS RF TESTING%s", VTY_NEWLINE); - gsm_bts_set_radio_link_timeout(bts, -1); - - return CMD_SUCCESS; -} - -#define GPRS_TEXT "GPRS Packet Network\n" - -#define GPRS_CHECK_ENABLED(bts) \ - do { \ - if (bts->gprs.mode == BTS_GPRS_NONE) { \ - vty_out(vty, "%% GPRS is not enabled on BTS %u%s", \ - bts->nr, VTY_NEWLINE); \ - return CMD_WARNING; \ - } \ - } while (0) - -DEFUN_USRATTR(cfg_bts_prs_bvci, - cfg_bts_gprs_bvci_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs cell bvci <2-65535>", - GPRS_TEXT - "GPRS Cell Settings\n" - "GPRS BSSGP VC Identifier\n" - "GPRS BSSGP VC Identifier\n") -{ - /* ETSI TS 101 343: values 0 and 1 are reserved for signalling and PTM */ - struct gsm_bts *bts = vty->index; - - GPRS_CHECK_ENABLED(bts); - - bts->gprs.cell.bvci = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_nsei, - cfg_bts_gprs_nsei_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs nsei <0-65535>", - GPRS_TEXT - "GPRS NS Entity Identifier\n" - "GPRS NS Entity Identifier\n") -{ - struct gsm_bts *bts = vty->index; - - GPRS_CHECK_ENABLED(bts); - - bts->gprs.nse.nsei = atoi(argv[0]); - - return CMD_SUCCESS; -} - -#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \ - "NSVC Logical Number\n" - -DEFUN_USRATTR(cfg_bts_gprs_nsvci, - cfg_bts_gprs_nsvci_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs nsvc <0-1> nsvci <0-65535>", - GPRS_TEXT NSVC_TEXT - "NS Virtual Connection Identifier\n" - "GPRS NS VC Identifier\n") -{ - struct gsm_bts *bts = vty->index; - int idx = atoi(argv[0]); - - GPRS_CHECK_ENABLED(bts); - - bts->gprs.nsvc[idx].nsvci = atoi(argv[1]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_nsvc_lport, - cfg_bts_gprs_nsvc_lport_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs nsvc <0-1> local udp port <0-65535>", - GPRS_TEXT NSVC_TEXT - "GPRS NS Local UDP Port\n" - "GPRS NS Local UDP Port\n" - "GPRS NS Local UDP Port\n" - "GPRS NS Local UDP Port Number\n") -{ - struct gsm_bts *bts = vty->index; - int idx = atoi(argv[0]); - - GPRS_CHECK_ENABLED(bts); - - bts->gprs.nsvc[idx].local_port = atoi(argv[1]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_nsvc_rport, - cfg_bts_gprs_nsvc_rport_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs nsvc <0-1> remote udp port <0-65535>", - GPRS_TEXT NSVC_TEXT - "GPRS NS Remote UDP Port\n" - "GPRS NS Remote UDP Port\n" - "GPRS NS Remote UDP Port\n" - "GPRS NS Remote UDP Port Number\n") -{ - struct gsm_bts *bts = vty->index; - int idx = atoi(argv[0]); - - GPRS_CHECK_ENABLED(bts); - - /* sockaddr_in and sockaddr_in6 have the port at the same position */ - bts->gprs.nsvc[idx].remote.u.sin.sin_port = htons(atoi(argv[1])); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_nsvc_rip, - cfg_bts_gprs_nsvc_rip_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs nsvc <0-1> remote ip " VTY_IPV46_CMD, - GPRS_TEXT NSVC_TEXT - "GPRS NS Remote IP Address\n" - "GPRS NS Remote IP Address\n" - "GPRS NS Remote IPv4 Address\n" - "GPRS NS Remote IPv6 Address\n") -{ - struct gsm_bts *bts = vty->index; - struct osmo_sockaddr_str remote; - int idx = atoi(argv[0]); - int ret; - - GPRS_CHECK_ENABLED(bts); - - ret = osmo_sockaddr_str_from_str2(&remote, argv[1]); - if (ret) { - vty_out(vty, "%% Invalid IP address %s%s", argv[1], VTY_NEWLINE); - return CMD_WARNING; - } - - /* Can't use osmo_sockaddr_str_to_sockaddr() because the port would be overriden */ - bts->gprs.nsvc[idx].remote.u.sas.ss_family = remote.af; - switch (remote.af) { - case AF_INET: - osmo_sockaddr_str_to_in_addr(&remote, &bts->gprs.nsvc[idx].remote.u.sin.sin_addr); - break; - case AF_INET6: - osmo_sockaddr_str_to_in6_addr(&remote, &bts->gprs.nsvc[idx].remote.u.sin6.sin6_addr); - break; - } - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_pag_free, cfg_bts_pag_free_cmd, - "paging free <-1-1024>", - "Paging options\n" - "Only page when having a certain amount of free slots\n" - "amount of required free paging slots. -1 to disable\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - - bts->paging.free_chans_need = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_ns_timer, - cfg_bts_gprs_ns_timer_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs ns timer " NS_TIMERS " <0-255>", - GPRS_TEXT "Network Service\n" - "Network Service Timer\n" - NS_TIMERS_HELP "Timer Value\n") -{ - struct gsm_bts *bts = vty->index; - int idx = get_string_value(gprs_ns_timer_strs, argv[0]); - int val = atoi(argv[1]); - - GPRS_CHECK_ENABLED(bts); - - if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.nse.timer)) - return CMD_WARNING; - - bts->gprs.nse.timer[idx] = val; - - return CMD_SUCCESS; -} - -#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)" -#define BSSGP_TIMERS_HELP \ - "Tbvc-block timeout\n" \ - "Tbvc-block retries\n" \ - "Tbvc-unblock retries\n" \ - "Tbvcc-reset timeout\n" \ - "Tbvc-reset retries\n" \ - "Tbvc-suspend timeout\n" \ - "Tbvc-suspend retries\n" \ - "Tbvc-resume timeout\n" \ - "Tbvc-resume retries\n" \ - "Tbvc-capa-update timeout\n" \ - "Tbvc-capa-update retries\n" - -DEFUN_USRATTR(cfg_bts_gprs_cell_timer, - cfg_bts_gprs_cell_timer_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs cell timer " BSSGP_TIMERS " <0-255>", - GPRS_TEXT "Cell / BSSGP\n" - "Cell/BSSGP Timer\n" - BSSGP_TIMERS_HELP "Timer Value\n") -{ - struct gsm_bts *bts = vty->index; - int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]); - int val = atoi(argv[1]); - - GPRS_CHECK_ENABLED(bts); - - if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer)) - return CMD_WARNING; - - bts->gprs.cell.timer[idx] = val; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_rac, - cfg_bts_gprs_rac_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs routing area <0-255>", - GPRS_TEXT - "GPRS Routing Area Code\n" - "GPRS Routing Area Code\n" - "GPRS Routing Area Code\n") -{ - struct gsm_bts *bts = vty->index; - - GPRS_CHECK_ENABLED(bts); - - bts->gprs.rac = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_ctrl_ack, - cfg_bts_gprs_ctrl_ack_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "gprs control-ack-type-rach", - GPRS_TEXT - "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to " - "four access bursts format instead of default RLC/MAC control block\n") -{ - struct gsm_bts *bts = vty->index; - - GPRS_CHECK_ENABLED(bts); - - bts->gprs.ctrl_ack_type_use_block = false; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_no_bts_gprs_ctrl_ack, - cfg_no_bts_gprs_ctrl_ack_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "no gprs control-ack-type-rach", - NO_STR GPRS_TEXT - "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to " - "default RLC/MAC control block\n") -{ - struct gsm_bts *bts = vty->index; - - GPRS_CHECK_ENABLED(bts); - - bts->gprs.ctrl_ack_type_use_block = true; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_net_ctrl_ord, - cfg_bts_gprs_net_ctrl_ord_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "gprs network-control-order (nc0|nc1|nc2)", - GPRS_TEXT - "GPRS Network Control Order\n" - "MS controlled cell re-selection, no measurement reporting\n" - "MS controlled cell re-selection, MS sends measurement reports\n" - "Network controlled cell re-selection, MS sends measurement reports\n") -{ - struct gsm_bts *bts = vty->index; - - GPRS_CHECK_ENABLED(bts); - - bts->gprs.net_ctrl_ord = atoi(argv[0] + 2); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_mode, - cfg_bts_gprs_mode_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "gprs mode (none|gprs|egprs)", - GPRS_TEXT - "GPRS Mode for this BTS\n" - "GPRS Disabled on this BTS\n" - "GPRS Enabled on this BTS\n" - "EGPRS (EDGE) Enabled on this BTS\n") -{ - struct gsm_bts *bts = vty->index; - enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0], NULL); - - if (!bts_gprs_mode_is_compat(bts, mode)) { - vty_out(vty, "%% This BTS type does not support %s%s", argv[0], - VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.mode = mode; - - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_bts_gprs_11bit_rach_support_for_egprs, - cfg_bts_gprs_11bit_rach_support_for_egprs_cmd, - "gprs 11bit_rach_support_for_egprs (0|1)", - GPRS_TEXT "EGPRS Packet Channel Request support\n" - "Disable EGPRS Packet Channel Request support\n" - "Enable EGPRS Packet Channel Request support\n") -{ - struct gsm_bts *bts = vty->index; - - vty_out(vty, "%% 'gprs 11bit_rach_support_for_egprs' is now deprecated: " - "use '[no] gprs egprs-packet-channel-request' instead%s", VTY_NEWLINE); - - bts->gprs.egprs_pkt_chan_request = (argv[0][0] == '1'); - - if (bts->gprs.mode == BTS_GPRS_NONE && bts->gprs.egprs_pkt_chan_request) { - vty_out(vty, "%% (E)GPRS is not enabled (see 'gprs mode')%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (bts->gprs.mode != BTS_GPRS_EGPRS) { - vty_out(vty, "%% EGPRS Packet Channel Request support requires " - "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE); - /* Do not return here, keep the old behaviour. */ - } - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_gprs_egprs_pkt_chan_req, - cfg_bts_gprs_egprs_pkt_chan_req_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "gprs egprs-packet-channel-request", - GPRS_TEXT "EGPRS Packet Channel Request support") -{ - struct gsm_bts *bts = vty->index; - - if (bts->gprs.mode != BTS_GPRS_EGPRS) { - vty_out(vty, "%% EGPRS Packet Channel Request support requires " - "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.egprs_pkt_chan_request = true; - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_no_gprs_egprs_pkt_chan_req, - cfg_bts_no_gprs_egprs_pkt_chan_req_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "no gprs egprs-packet-channel-request", - NO_STR GPRS_TEXT "EGPRS Packet Channel Request support") -{ - struct gsm_bts *bts = vty->index; - - if (bts->gprs.mode != BTS_GPRS_EGPRS) { - vty_out(vty, "%% EGPRS Packet Channel Request support requires " - "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.egprs_pkt_chan_request = false; - return CMD_SUCCESS; -} - -#define SI_TEXT "System Information Messages\n" -#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)" -#define SI_TYPE_HELP "System Information Type 1\n" \ - "System Information Type 2\n" \ - "System Information Type 3\n" \ - "System Information Type 4\n" \ - "System Information Type 5\n" \ - "System Information Type 6\n" \ - "System Information Type 7\n" \ - "System Information Type 8\n" \ - "System Information Type 9\n" \ - "System Information Type 10\n" \ - "System Information Type 13\n" \ - "System Information Type 16\n" \ - "System Information Type 17\n" \ - "System Information Type 18\n" \ - "System Information Type 19\n" \ - "System Information Type 20\n" \ - "System Information Type 2bis\n" \ - "System Information Type 2ter\n" \ - "System Information Type 2quater\n" \ - "System Information Type 5bis\n" \ - "System Information Type 5ter\n" - -DEFUN_USRATTR(cfg_bts_si_mode, - cfg_bts_si_mode_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "system-information " SI_TYPE_TEXT " mode (static|computed)", - SI_TEXT SI_TYPE_HELP - "System Information Mode\n" - "Static user-specified\n" - "Dynamic, BSC-computed\n") -{ - struct gsm_bts *bts = vty->index; - int type; - - type = get_string_value(osmo_sitype_strs, argv[0]); - if (type < 0) { - vty_out(vty, "%% Error SI Type%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (!strcmp(argv[1], "static")) - bts->si_mode_static |= (1 << type); - else - bts->si_mode_static &= ~(1 << type); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_si_static, - cfg_bts_si_static_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "system-information " SI_TYPE_TEXT " static HEXSTRING", - SI_TEXT SI_TYPE_HELP - "Static System Information filling\n" - "Static user-specified SI content in HEX notation\n") -{ - struct gsm_bts *bts = vty->index; - int rc, type; - - type = get_string_value(osmo_sitype_strs, argv[0]); - if (type < 0) { - vty_out(vty, "%% Error SI Type%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (!(bts->si_mode_static & (1 << type))) { - vty_out(vty, "%% SI Type %s is not configured in static mode%s", - get_value_string(osmo_sitype_strs, type), VTY_NEWLINE); - return CMD_WARNING; - } - - /* Fill buffer with padding pattern */ - memset(GSM_BTS_SI(bts, type), 0x2b, GSM_MACBLOCK_LEN); - - /* Parse the user-specified SI in hex format, [partially] overwriting padding */ - rc = osmo_hexparse(argv[1], GSM_BTS_SI(bts, type), GSM_MACBLOCK_LEN); - if (rc < 0 || rc > GSM_MACBLOCK_LEN) { - vty_out(vty, "%% Error parsing HEXSTRING%s", VTY_NEWLINE); - return CMD_WARNING; - } - - /* Mark this SI as present */ - bts->si_valid |= (1 << type); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_si_unused_send_empty, - cfg_bts_si_unused_send_empty_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "system-information unused-send-empty", - SI_TEXT - "Send BCCH Info with empty 'Full BCCH Info' TLV to notify disabled SI. " - "Some nanoBTS fw versions are known to fail upon receival of these messages.\n") -{ - struct gsm_bts *bts = vty->index; - - bts->si_unused_send_empty = true; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_no_si_unused_send_empty, - cfg_bts_no_si_unused_send_empty_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "no system-information unused-send-empty", - NO_STR SI_TEXT - "Avoid sending BCCH Info with empty 'Full BCCH Info' TLV to notify disabled SI. " - "Some nanoBTS fw versions are known to fail upon receival of these messages.\n") -{ - struct gsm_bts *bts = vty->index; - - if (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) { - vty_out(vty, "%% This command is only intended for ipaccess nanoBTS. See OS#3707.%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - bts->si_unused_send_empty = false; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_early_cm, - cfg_bts_early_cm_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "early-classmark-sending (allowed|forbidden)", - "Early Classmark Sending\n" - "Early Classmark Sending is allowed\n" - "Early Classmark Sending is forbidden\n") -{ - struct gsm_bts *bts = vty->index; - - if (!strcmp(argv[0], "allowed")) - bts->early_classmark_allowed = true; - else - bts->early_classmark_allowed = false; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_early_cm_3g, - cfg_bts_early_cm_3g_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "early-classmark-sending-3g (allowed|forbidden)", - "3G Early Classmark Sending\n" - "3G Early Classmark Sending is allowed\n" - "3G Early Classmark Sending is forbidden\n") -{ - struct gsm_bts *bts = vty->index; - - if (!strcmp(argv[0], "allowed")) - bts->early_classmark_allowed_3g = true; - else - bts->early_classmark_allowed_3g = false; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_neigh_mode, - cfg_bts_neigh_mode_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "neighbor-list mode (automatic|manual|manual-si5)", - "Neighbor List\n" "Mode of Neighbor List generation\n" - "Automatically from all BTS in this BSC\n" "Manual\n" - "Manual with different lists for SI2 and SI5\n") -{ - struct gsm_bts *bts = vty->index; - int mode = get_string_value(bts_neigh_mode_strs, argv[0]); - - switch (mode) { - case NL_MODE_MANUAL_SI5SEP: - case NL_MODE_MANUAL: - /* make sure we clear the current list when switching to - * manual mode */ - if (bts->neigh_list_manual_mode == 0) - memset(&bts->si_common.data.neigh_list, 0, - sizeof(bts->si_common.data.neigh_list)); - break; - default: - break; - } - - bts->neigh_list_manual_mode = mode; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_neigh, - cfg_bts_neigh_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "neighbor-list (add|del) arfcn <0-1023>", - "Neighbor List\n" "Add to manual neighbor list\n" - "Delete from manual neighbor list\n" "ARFCN of neighbor\n" - "ARFCN of neighbor\n") -{ - struct gsm_bts *bts = vty->index; - struct bitvec *bv = &bts->si_common.neigh_list; - uint16_t arfcn = atoi(argv[1]); - enum gsm_band unused; - - if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) { - vty_out(vty, "%% Cannot configure neighbor list in " - "automatic mode%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { - vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); - return CMD_WARNING; - } - - if (!strcmp(argv[0], "add")) - bitvec_set_bit_pos(bv, arfcn, 1); - else - bitvec_set_bit_pos(bv, arfcn, 0); - - return CMD_SUCCESS; -} - -/* help text should be kept in sync with EARFCN_*_INVALID defines */ -DEFUN_USRATTR(cfg_bts_si2quater_neigh_add, - cfg_bts_si2quater_neigh_add_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "si2quater neighbor-list add earfcn <0-65535> thresh-hi <0-31> " - "thresh-lo <0-32> prio <0-8> qrxlv <0-32> meas <0-8>", - "SI2quater Neighbor List\n" "SI2quater Neighbor List\n" - "Add to manual SI2quater neighbor list\n" - "EARFCN of neighbor\n" "EARFCN of neighbor\n" - "threshold high bits\n" "threshold high bits\n" - "threshold low bits\n" "threshold low bits (32 means NA)\n" - "priority\n" "priority (8 means NA)\n" - "QRXLEVMIN\n" "QRXLEVMIN (32 means NA)\n" - "measurement bandwidth\n" "measurement bandwidth (8 means NA)\n") -{ - struct gsm_bts *bts = vty->index; - struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; - uint16_t arfcn = atoi(argv[0]); - uint8_t thresh_hi = atoi(argv[1]), thresh_lo = atoi(argv[2]), - prio = atoi(argv[3]), qrx = atoi(argv[4]), meas = atoi(argv[5]); - int r = bts_earfcn_add(bts, arfcn, thresh_hi, thresh_lo, prio, qrx, meas); - - switch (r) { - case 1: - vty_out(vty, "%% Warning: multiple threshold-high are not supported, overriding with %u%s", - thresh_hi, VTY_NEWLINE); - break; - case EARFCN_THRESH_LOW_INVALID: - vty_out(vty, "%% Warning: multiple threshold-low are not supported, overriding with %u%s", - thresh_lo, VTY_NEWLINE); - break; - case EARFCN_QRXLV_INVALID + 1: - vty_out(vty, "%% Warning: multiple QRXLEVMIN are not supported, overriding with %u%s", - qrx, VTY_NEWLINE); - break; - case EARFCN_PRIO_INVALID: - vty_out(vty, "%% Warning: multiple priorities are not supported, overriding with %u%s", - prio, VTY_NEWLINE); - break; - default: - if (r < 0) { - vty_out(vty, "%% Unable to add ARFCN %u: %s%s", arfcn, strerror(-r), VTY_NEWLINE); - return CMD_WARNING; - } - } - - if (si2q_num(bts) <= SI2Q_MAX_NUM) - return CMD_SUCCESS; - - vty_out(vty, "%% Warning: not enough space in SI2quater (%u/%u used) for a given EARFCN %u%s", - bts->si2q_count, SI2Q_MAX_NUM, arfcn, VTY_NEWLINE); - osmo_earfcn_del(e, arfcn); - - return CMD_WARNING; -} - -DEFUN_USRATTR(cfg_bts_si2quater_neigh_del, - cfg_bts_si2quater_neigh_del_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "si2quater neighbor-list del earfcn <0-65535>", - "SI2quater Neighbor List\n" - "SI2quater Neighbor List\n" - "Delete from SI2quater manual neighbor list\n" - "EARFCN of neighbor\n" - "EARFCN\n") -{ - struct gsm_bts *bts = vty->index; - struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; - uint16_t arfcn = atoi(argv[0]); - int r = osmo_earfcn_del(e, arfcn); - if (r < 0) { - vty_out(vty, "%% Unable to delete arfcn %u: %s%s", arfcn, - strerror(-r), VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_si2quater_uarfcn_add, - cfg_bts_si2quater_uarfcn_add_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "si2quater neighbor-list add uarfcn <0-16383> <0-511> <0-1>", - "SI2quater Neighbor List\n" - "SI2quater Neighbor List\n" "Add to manual SI2quater neighbor list\n" - "UARFCN of neighbor\n" "UARFCN of neighbor\n" "scrambling code\n" - "diversity bit\n") -{ - struct gsm_bts *bts = vty->index; - uint16_t arfcn = atoi(argv[0]), scramble = atoi(argv[1]); - - switch(bts_uarfcn_add(bts, arfcn, scramble, atoi(argv[2]))) { - case -ENOMEM: - vty_out(vty, "%% Unable to add UARFCN: max number of UARFCNs (%u) reached%s", - MAX_EARFCN_LIST, VTY_NEWLINE); - return CMD_WARNING; - case -ENOSPC: - vty_out(vty, "%% Warning: not enough space in SI2quater for a given UARFCN (%u, %u)%s", - arfcn, scramble, VTY_NEWLINE); - return CMD_WARNING; - case -EADDRINUSE: - vty_out(vty, "%% Unable to add UARFCN: (%u, %u) is already added%s", - arfcn, scramble, VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_si2quater_uarfcn_del, - cfg_bts_si2quater_uarfcn_del_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "si2quater neighbor-list del uarfcn <0-16383> <0-511>", - "SI2quater Neighbor List\n" - "SI2quater Neighbor List\n" - "Delete from SI2quater manual neighbor list\n" - "UARFCN of neighbor\n" - "UARFCN\n" - "scrambling code\n") -{ - struct gsm_bts *bts = vty->index; - - if (bts_uarfcn_del(bts, atoi(argv[0]), atoi(argv[1])) < 0) { - vty_out(vty, "%% Unable to delete uarfcn: pair not found%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_si5_neigh, - cfg_bts_si5_neigh_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "si5 neighbor-list (add|del) arfcn <0-1023>", - "SI5 Neighbor List\n" - "SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n" - "Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n" - "ARFCN of neighbor\n") -{ - enum gsm_band unused; - struct gsm_bts *bts = vty->index; - struct bitvec *bv = &bts->si_common.si5_neigh_list; - uint16_t arfcn = atoi(argv[1]); - - if (!bts->neigh_list_manual_mode) { - vty_out(vty, "%% Cannot configure neighbor list in " - "automatic mode%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { - vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); - return CMD_WARNING; - } - - if (!strcmp(argv[0], "add")) - bitvec_set_bit_pos(bv, arfcn, 1); - else - bitvec_set_bit_pos(bv, arfcn, 0); - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_pcu_sock, - cfg_bts_pcu_sock_cmd, - "pcu-socket PATH", - "PCU Socket Path for using OsmoPCU co-located with BSC (legacy BTS)\n" - "Path in the file system for the unix-domain PCU socket\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - int rc; - - osmo_talloc_replace_string(bts, &bts->pcu_sock_path, argv[0]); - pcu_sock_exit(bts); - rc = pcu_sock_init(bts->pcu_sock_path, bts); - if (rc < 0) { - vty_out(vty, "%% Error creating PCU socket `%s' for BTS %u%s", - bts->pcu_sock_path, bts->nr, VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_acc_rotate, - cfg_bts_acc_rotate_cmd, - "access-control-class-rotate <0-10>", - "Enable Access Control Class allowed subset rotation\n" - "Size of the rotating allowed ACC 0-9 subset (default=10, no subset)\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - int len_allowed_adm = atoi(argv[0]); - acc_mgr_set_len_allowed_adm(&bts->acc_mgr, len_allowed_adm); - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_acc_rotate_quantum, - cfg_bts_acc_rotate_quantum_cmd, - "access-control-class-rotate-quantum <1-65535>", - "Time between rotation of ACC 0-9 generated subsets\n" - "Time in seconds (default=" OSMO_STRINGIFY_VAL(ACC_MGR_QUANTUM_DEFAULT) ")\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - uint32_t rotation_time_sec = (uint32_t)atoi(argv[0]); - acc_mgr_set_rotation_time(&bts->acc_mgr, rotation_time_sec); - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_acc_ramping, - cfg_bts_acc_ramping_cmd, - "access-control-class-ramping", - "Enable Access Control Class ramping\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - struct gsm_bts_trx *trx; - - if (!acc_ramp_is_enabled(&bts->acc_ramp)) { - acc_ramp_set_enabled(&bts->acc_ramp, true); - /* Start ramping if at least one TRX is usable */ - llist_for_each_entry(trx, &bts->trx_list, list) { - if (trx_is_usable(trx)) { - acc_ramp_trigger(&bts->acc_ramp); - break; - } - } - } - - /* - * ACC ramping takes effect either when the BTS reconnects RSL, - * or when RF administrative state changes to 'unlocked'. - */ - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_no_acc_ramping, - cfg_bts_no_acc_ramping_cmd, - "no access-control-class-ramping", - NO_STR - "Disable Access Control Class ramping\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - - if (acc_ramp_is_enabled(&bts->acc_ramp)) { - acc_ramp_abort(&bts->acc_ramp); - acc_ramp_set_enabled(&bts->acc_ramp, false); - if (gsm_bts_set_system_infos(bts) != 0) { - vty_out(vty, "%% Filed to (re)generate System Information " - "messages, check the logs%s", VTY_NEWLINE); - return CMD_WARNING; - } - } - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_acc_ramping_step_interval, - cfg_bts_acc_ramping_step_interval_cmd, - "access-control-class-ramping-step-interval (<" - OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MIN) "-" - OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MAX) ">|dynamic)", - "Configure Access Control Class ramping step interval\n" - "Set a fixed step interval (in seconds)\n" - "Use dynamic step interval based on BTS channel load (deprecated, don't use, ignored)\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - bool dynamic = (strcmp(argv[0], "dynamic") == 0); - int error; - - if (dynamic) { - vty_out(vty, "%% access-control-class-ramping-step-interval 'dynamic' value is deprecated, ignoring it%s", VTY_NEWLINE); - return CMD_SUCCESS; - } - - error = acc_ramp_set_step_interval(&bts->acc_ramp, atoi(argv[0])); - if (error != 0) { - if (error == -ERANGE) - vty_out(vty, "%% Unable to set ACC ramp step interval: value out of range%s", VTY_NEWLINE); - else - vty_out(vty, "%% Unable to set ACC ramp step interval: unknown error%s", VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_acc_ramping_step_size, - cfg_bts_acc_ramping_step_size_cmd, - "access-control-class-ramping-step-size (<" - OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MIN) "-" - OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MAX) ">)", - "Configure Access Control Class ramping step size\n" - "Set the number of Access Control Classes to enable per ramping step\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - int error; - - error = acc_ramp_set_step_size(&bts->acc_ramp, atoi(argv[0])); - if (error != 0) { - if (error == -ERANGE) - vty_out(vty, "%% Unable to set ACC ramp step size: value out of range%s", VTY_NEWLINE); - else - vty_out(vty, "%% Unable to set ACC ramp step size: unknown error%s", VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_acc_ramping_chan_load, - cfg_bts_acc_ramping_chan_load_cmd, - "access-control-class-ramping-chan-load <0-100> <0-100>", - "Configure Access Control Class ramping channel load thresholds\n" - "Lower Channel load threshold (%) below which subset size of allowed broadcast ACCs can be increased\n" - "Upper channel load threshold (%) above which subset size of allowed broadcast ACCs can be decreased\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - int rc; - - rc = acc_ramp_set_chan_load_thresholds(&bts->acc_ramp, atoi(argv[0]), atoi(argv[1])); - if (rc < 0) { - vty_out(vty, "%% Unable to set ACC channel load thresholds%s", VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n" - -DEFUN_ATTR(cfg_bts_excl_rf_lock, - cfg_bts_excl_rf_lock_cmd, - "rf-lock-exclude", - EXCL_RFLOCK_STR, - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - bts->excl_from_rf_lock = 1; - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_no_excl_rf_lock, - cfg_bts_no_excl_rf_lock_cmd, - "no rf-lock-exclude", - NO_STR EXCL_RFLOCK_STR, - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - bts->excl_from_rf_lock = 0; - return CMD_SUCCESS; -} - -#define FORCE_COMB_SI_STR "Force the generation of a single SI (no ter/bis)\n" - -DEFUN_USRATTR(cfg_bts_force_comb_si, - cfg_bts_force_comb_si_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "force-combined-si", - FORCE_COMB_SI_STR) -{ - struct gsm_bts *bts = vty->index; - bts->force_combined_si = 1; - bts->force_combined_si_set = true; - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_no_force_comb_si, - cfg_bts_no_force_comb_si_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "no force-combined-si", - NO_STR FORCE_COMB_SI_STR) -{ - struct gsm_bts *bts = vty->index; - bts->force_combined_si = 0; - bts->force_combined_si_set = true; - return CMD_SUCCESS; -} - -static void _get_codec_from_arg(struct vty *vty, int argc, const char *argv[]) -{ - struct gsm_bts *bts = vty->index; - struct bts_codec_conf *codec = &bts->codec; - int i; - - codec->hr = 0; - codec->efr = 0; - codec->amr = 0; - for (i = 0; i < argc; i++) { - if (!strcmp(argv[i], "hr")) - codec->hr = 1; - if (!strcmp(argv[i], "efr")) - codec->efr = 1; - if (!strcmp(argv[i], "amr")) - codec->amr = 1; - } -} - -#define CODEC_PAR_STR " (hr|efr|amr)" -#define CODEC_HELP_STR "Half Rate\n" \ - "Enhanced Full Rate\nAdaptive Multirate\n" - -DEFUN_USRATTR(cfg_bts_codec0, - cfg_bts_codec0_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "codec-support fr", - "Codec Support settings\nFullrate\n") -{ - _get_codec_from_arg(vty, 0, argv); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_codec1, - cfg_bts_codec1_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "codec-support fr" CODEC_PAR_STR, - "Codec Support settings\nFullrate\n" - CODEC_HELP_STR) -{ - _get_codec_from_arg(vty, 1, argv); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_codec2, - cfg_bts_codec2_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR, - "Codec Support settings\nFullrate\n" - CODEC_HELP_STR CODEC_HELP_STR) -{ - _get_codec_from_arg(vty, 2, argv); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_codec3, - cfg_bts_codec3_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR, - "Codec Support settings\nFullrate\n" - CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR) -{ - _get_codec_from_arg(vty, 3, argv); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_codec4, - cfg_bts_codec4_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR, - "Codec Support settings\nFullrate\n" - CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR) -{ - _get_codec_from_arg(vty, 4, argv); - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_depends_on, cfg_bts_depends_on_cmd, - "depends-on-bts <0-255>", - "This BTS can only be started if another one is up\n" - BTS_NR_STR, CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - struct gsm_bts *other_bts; - int dep = atoi(argv[0]); - - - if (!is_ipaccess_bts(bts)) { - vty_out(vty, "%% This feature is only available for IP systems.%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - other_bts = gsm_bts_num(bts->network, dep); - if (!other_bts || !is_ipaccess_bts(other_bts)) { - vty_out(vty, "%% This feature is only available for IP systems.%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - if (dep >= bts->nr) { - vty_out(vty, "%% Need to depend on an already declared unit.%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - bts_depend_mark(bts, dep); - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd, - "no depends-on-bts <0-255>", - NO_STR "This BTS can only be started if another one is up\n" - BTS_NR_STR, CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts *bts = vty->index; - int dep = atoi(argv[0]); - - bts_depend_clear(bts, dep); - return CMD_SUCCESS; -} - -#define AMR_TEXT "Adaptive Multi Rate settings\n" -#define AMR_MODE_TEXT "Codec modes to use with AMR codec\n" -#define AMR_START_TEXT "Initial codec to use with AMR\n" \ - "Automatically\nFirst codec\nSecond codec\nThird codec\nFourth codec\n" -#define AMR_TH_TEXT "AMR threshold between codecs\nMS side\nBTS side\n" -#define AMR_HY_TEXT "AMR hysteresis between codecs\nMS side\nBTS side\n" - -static int get_amr_from_arg(struct vty *vty, int argc, const char *argv[], int full) -{ - struct gsm_bts *bts = vty->index; - struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; - struct gsm48_multi_rate_conf *mr_conf = - (struct gsm48_multi_rate_conf *) mr->gsm48_ie; - int i; - int mode; - int mode_prev = -1; - - /* Check if mode parameters are in order */ - for (i = 0; i < argc; i++) { - mode = atoi(argv[i]); - if (mode_prev > mode) { - vty_out(vty, "%% Modes must be listed in order%s", - VTY_NEWLINE); - return -1; - } - - if (mode_prev == mode) { - vty_out(vty, "%% Modes must be unique %s", VTY_NEWLINE); - return -2; - } - mode_prev = mode; - } - - /* Prepare the multirate configuration IE */ - mr->gsm48_ie[1] = 0; - for (i = 0; i < argc; i++) - mr->gsm48_ie[1] |= 1 << atoi(argv[i]); - mr_conf->icmi = 0; - - /* Store actual mode identifier values */ - for (i = 0; i < argc; i++) { - mr->ms_mode[i].mode = atoi(argv[i]); - mr->bts_mode[i].mode = atoi(argv[i]); - } - mr->num_modes = argc; - - /* Trim excess threshold and hysteresis values from previous config */ - for (i = argc - 1; i < 4; i++) { - mr->ms_mode[i].threshold = 0; - mr->bts_mode[i].threshold = 0; - mr->ms_mode[i].hysteresis = 0; - mr->bts_mode[i].hysteresis = 0; - } - return 0; -} - -static void get_amr_th_from_arg(struct vty *vty, int argc, const char *argv[], int full) -{ - struct gsm_bts *bts = vty->index; - struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; - struct amr_mode *modes; - int i; - - modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode; - for (i = 0; i < argc - 1; i++) - modes[i].threshold = atoi(argv[i + 1]); -} - -static void get_amr_hy_from_arg(struct vty *vty, int argc, const char *argv[], int full) -{ - struct gsm_bts *bts = vty->index; - struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; - struct amr_mode *modes; - int i; - - modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode; - for (i = 0; i < argc - 1; i++) - modes[i].hysteresis = atoi(argv[i + 1]); -} - -static void get_amr_start_from_arg(struct vty *vty, const char *argv[], int full) -{ - struct gsm_bts *bts = vty->index; - struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; - struct gsm48_multi_rate_conf *mr_conf = - (struct gsm48_multi_rate_conf *) mr->gsm48_ie; - int num = 0, i; - - for (i = 0; i < ((full) ? 8 : 6); i++) { - if ((mr->gsm48_ie[1] & (1 << i))) { - num++; - } - } - - if (argv[0][0] == 'a' || num == 0) - mr_conf->icmi = 0; - else { - mr_conf->icmi = 1; - if (num < atoi(argv[0])) - mr_conf->smod = num - 1; - else - mr_conf->smod = atoi(argv[0]) - 1; - } -} - -/* Give the current amr configuration a final consistency check by feeding the - * the configuration into the gsm48 multirate IE generator function */ -static int check_amr_config(struct vty *vty) -{ - int rc = 0; - struct amr_multirate_conf *mr; - const struct gsm48_multi_rate_conf *mr_conf; - struct gsm_bts *bts = vty->index; - int vty_rc = CMD_SUCCESS; - - mr = &bts->mr_full; - mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie; - rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes); - if (rc != 0) { - vty_out(vty, - "%% Invalid AMR multirate configuration (tch-f, ms) - check parameters%s", - VTY_NEWLINE); - vty_rc = CMD_WARNING; - } - - rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes); - if (rc != 0) { - vty_out(vty, - "%% Invalid AMR multirate configuration (tch-f, bts) - check parameters%s", - VTY_NEWLINE); - vty_rc = CMD_WARNING; - } - - mr = &bts->mr_half; - mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie; - rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes); - if (rc != 0) { - vty_out(vty, - "%% Invalid AMR multirate configuration (tch-h, ms) - check parameters%s", - VTY_NEWLINE); - vty_rc = CMD_WARNING; - } - - rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes); - if (rc != 0) { - vty_out(vty, - "%% Invalid AMR multirate configuration (tch-h, bts) - check parameters%s", - VTY_NEWLINE); - vty_rc = CMD_WARNING; - } - - return vty_rc; -} - -#define AMR_TCHF_PAR_STR " (0|1|2|3|4|5|6|7)" -#define AMR_TCHF_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" \ - "10,2k\n12,2k\n" - -#define AMR_TCHH_PAR_STR " (0|1|2|3|4|5)" -#define AMR_TCHH_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" - -#define AMR_TH_HELP_STR "Threshold between codec 1 and 2\n" -#define AMR_HY_HELP_STR "Hysteresis between codec 1 and 2\n" - -DEFUN_USRATTR(cfg_bts_amr_fr_modes1, - cfg_bts_amr_fr_modes1_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f modes" AMR_TCHF_PAR_STR, - AMR_TEXT "Full Rate\n" AMR_MODE_TEXT - AMR_TCHF_HELP_STR) -{ - if (get_amr_from_arg(vty, 1, argv, 1)) - return CMD_WARNING; - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_modes2, - cfg_bts_amr_fr_modes2_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, - AMR_TEXT "Full Rate\n" AMR_MODE_TEXT - AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) -{ - if (get_amr_from_arg(vty, 2, argv, 1)) - return CMD_WARNING; - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_modes3, - cfg_bts_amr_fr_modes3_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, - AMR_TEXT "Full Rate\n" AMR_MODE_TEXT - AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) -{ - if (get_amr_from_arg(vty, 3, argv, 1)) - return CMD_WARNING; - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_modes4, - cfg_bts_amr_fr_modes4_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, - AMR_TEXT "Full Rate\n" AMR_MODE_TEXT - AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) -{ - if (get_amr_from_arg(vty, 4, argv, 1)) - return CMD_WARNING; - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_start_mode, - cfg_bts_amr_fr_start_mode_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f start-mode (auto|1|2|3|4)", - AMR_TEXT "Full Rate\n" AMR_START_TEXT) -{ - get_amr_start_from_arg(vty, argv, 1); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_thres1, - cfg_bts_amr_fr_thres1_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f threshold (ms|bts) <0-63>", - AMR_TEXT "Full Rate\n" AMR_TH_TEXT - AMR_TH_HELP_STR) -{ - get_amr_th_from_arg(vty, 2, argv, 1); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_thres2, - cfg_bts_amr_fr_thres2_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f threshold (ms|bts) <0-63> <0-63>", - AMR_TEXT "Full Rate\n" AMR_TH_TEXT - AMR_TH_HELP_STR AMR_TH_HELP_STR) -{ - get_amr_th_from_arg(vty, 3, argv, 1); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_thres3, - cfg_bts_amr_fr_thres3_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f threshold (ms|bts) <0-63> <0-63> <0-63>", - AMR_TEXT "Full Rate\n" AMR_TH_TEXT - AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR) -{ - get_amr_th_from_arg(vty, 4, argv, 1); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_hyst1, - cfg_bts_amr_fr_hyst1_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f hysteresis (ms|bts) <0-15>", - AMR_TEXT "Full Rate\n" AMR_HY_TEXT - AMR_HY_HELP_STR) -{ - get_amr_hy_from_arg(vty, 2, argv, 1); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_fr_hyst2, - cfg_bts_amr_fr_hyst2_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f hysteresis (ms|bts) <0-15> <0-15>", - AMR_TEXT "Full Rate\n" AMR_HY_TEXT - AMR_HY_HELP_STR AMR_HY_HELP_STR) -{ - get_amr_hy_from_arg(vty, 3, argv, 1); - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_bts_amr_fr_hyst3, - cfg_bts_amr_fr_hyst3_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-f hysteresis (ms|bts) <0-15> <0-15> <0-15>", - AMR_TEXT "Full Rate\n" AMR_HY_TEXT - AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR) -{ - get_amr_hy_from_arg(vty, 4, argv, 1); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_modes1, - cfg_bts_amr_hr_modes1_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h modes" AMR_TCHH_PAR_STR, - AMR_TEXT "Half Rate\n" AMR_MODE_TEXT - AMR_TCHH_HELP_STR) -{ - if (get_amr_from_arg(vty, 1, argv, 0)) - return CMD_WARNING; - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_modes2, - cfg_bts_amr_hr_modes2_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, - AMR_TEXT "Half Rate\n" AMR_MODE_TEXT - AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) -{ - if (get_amr_from_arg(vty, 2, argv, 0)) - return CMD_WARNING; - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_modes3, - cfg_bts_amr_hr_modes3_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, - AMR_TEXT "Half Rate\n" AMR_MODE_TEXT - AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) -{ - if (get_amr_from_arg(vty, 3, argv, 0)) - return CMD_WARNING; - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_modes4, - cfg_bts_amr_hr_modes4_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, - AMR_TEXT "Half Rate\n" AMR_MODE_TEXT - AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) -{ - if (get_amr_from_arg(vty, 4, argv, 0)) - return CMD_WARNING; - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_start_mode, - cfg_bts_amr_hr_start_mode_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h start-mode (auto|1|2|3|4)", - AMR_TEXT "Half Rate\n" AMR_START_TEXT) -{ - get_amr_start_from_arg(vty, argv, 0); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_thres1, - cfg_bts_amr_hr_thres1_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h threshold (ms|bts) <0-63>", - AMR_TEXT "Half Rate\n" AMR_TH_TEXT - AMR_TH_HELP_STR) -{ - get_amr_th_from_arg(vty, 2, argv, 0); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_thres2, - cfg_bts_amr_hr_thres2_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h threshold (ms|bts) <0-63> <0-63>", - AMR_TEXT "Half Rate\n" AMR_TH_TEXT - AMR_TH_HELP_STR AMR_TH_HELP_STR) -{ - get_amr_th_from_arg(vty, 3, argv, 0); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_thres3, - cfg_bts_amr_hr_thres3_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h threshold (ms|bts) <0-63> <0-63> <0-63>", - AMR_TEXT "Half Rate\n" AMR_TH_TEXT - AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR) -{ - get_amr_th_from_arg(vty, 4, argv, 0); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_hyst1, - cfg_bts_amr_hr_hyst1_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h hysteresis (ms|bts) <0-15>", - AMR_TEXT "Half Rate\n" AMR_HY_TEXT - AMR_HY_HELP_STR) -{ - get_amr_hy_from_arg(vty, 2, argv, 0); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_hyst2, - cfg_bts_amr_hr_hyst2_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h hysteresis (ms|bts) <0-15> <0-15>", - AMR_TEXT "Half Rate\n" AMR_HY_TEXT - AMR_HY_HELP_STR AMR_HY_HELP_STR) -{ - get_amr_hy_from_arg(vty, 3, argv, 0); - return check_amr_config(vty); -} - -DEFUN_USRATTR(cfg_bts_amr_hr_hyst3, - cfg_bts_amr_hr_hyst3_cmd, - X(BSC_VTY_ATTR_NEW_LCHAN), - "amr tch-h hysteresis (ms|bts) <0-15> <0-15> <0-15>", - AMR_TEXT "Half Rate\n" AMR_HY_TEXT - AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR) -{ - get_amr_hy_from_arg(vty, 4, argv, 0); - return check_amr_config(vty); -} - -#define TNUM_STR "T-number, optionally preceded by 't' or 'T'\n" -DEFUN_ATTR(cfg_bts_t3113_dynamic, cfg_bts_t3113_dynamic_cmd, - "timer-dynamic TNNNN", - "Calculate T3113 dynamically based on channel config and load\n" - TNUM_STR, - CMD_ATTR_IMMEDIATE) -{ - struct osmo_tdef *d; - struct gsm_bts *bts = vty->index; - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - - d = osmo_tdef_vty_parse_T_arg(vty, gsmnet->T_defs, argv[0]); - if (!d) - return CMD_WARNING; - - switch (d->T) { - case 3113: - bts->T3113_dynamic = true; - break; - default: - vty_out(vty, "%% T%d cannot be set to dynamic%s", d->T, VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_bts_no_t3113_dynamic, cfg_bts_no_t3113_dynamic_cmd, - "no timer-dynamic TNNNN", - NO_STR - "Set given timer to non-dynamic and use the default or user provided fixed value\n" - TNUM_STR, - CMD_ATTR_IMMEDIATE) -{ - struct osmo_tdef *d; - struct gsm_bts *bts = vty->index; - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - - d = osmo_tdef_vty_parse_T_arg(vty, gsmnet->T_defs, argv[0]); - if (!d) - return CMD_WARNING; - - switch (d->T) { - case 3113: - bts->T3113_dynamic = false; - break; - default: - vty_out(vty, "%% T%d already is non-dynamic%s", d->T, VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -#define TRX_TEXT "Radio Transceiver\n" - -/* per TRX configuration */ -DEFUN_ATTR(cfg_trx, - cfg_trx_cmd, - "trx <0-255>", - TRX_TEXT - "Select a TRX to configure\n", - CMD_ATTR_IMMEDIATE) -{ - int trx_nr = atoi(argv[0]); - struct gsm_bts *bts = vty->index; - struct gsm_bts_trx *trx; - - if (trx_nr > bts->num_trx) { - vty_out(vty, "%% The next unused TRX number in this BTS is %u%s", - bts->num_trx, VTY_NEWLINE); - return CMD_WARNING; - } else if (trx_nr == bts->num_trx) { - /* we need to allocate a new one */ - trx = gsm_bts_trx_alloc(bts); - } else - trx = gsm_bts_trx_num(bts, trx_nr); - - if (!trx) - return CMD_WARNING; - - vty->index = trx; - vty->index_sub = &trx->description; - vty->node = TRX_NODE; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_trx_arfcn, - cfg_trx_arfcn_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "arfcn <0-1023>", - "Set the ARFCN for this TRX\n" - "Absolute Radio Frequency Channel Number\n") -{ - enum gsm_band unused; - struct gsm_bts_trx *trx = vty->index; - int arfcn = atoi(argv[0]); - - if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { - vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); - return CMD_WARNING; - } - - /* FIXME: check if this ARFCN is supported by this TRX */ - - trx->arfcn = arfcn; - - /* FIXME: patch ARFCN into SYSTEM INFORMATION */ - /* FIXME: use OML layer to update the ARFCN */ - /* FIXME: use RSL layer to update SYSTEM INFORMATION */ - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_trx_nominal_power, - cfg_trx_nominal_power_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "nominal power <-20-100>", - "Nominal TRX RF Power in dBm\n" - "Nominal TRX RF Power in dBm\n" - "Nominal TRX RF Power in dBm\n") -{ - struct gsm_bts_trx *trx = vty->index; - - trx->nominal_power = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_trx_max_power_red, - cfg_trx_max_power_red_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "max_power_red <0-100>", - "Reduction of maximum BS RF Power (relative to nominal power)\n" - "Reduction of maximum BS RF Power in dB\n") -{ - int maxpwr_r = atoi(argv[0]); - struct gsm_bts_trx *trx = vty->index; - int upper_limit = 24; /* default 12.21 max power red. */ - - /* FIXME: check if our BTS type supports more than 12 */ - if (maxpwr_r < 0 || maxpwr_r > upper_limit) { - vty_out(vty, "%% Power %d dB is not in the valid range%s", - maxpwr_r, VTY_NEWLINE); - return CMD_WARNING; - } - if (maxpwr_r & 1) { - vty_out(vty, "%% Power %d dB is not an even value%s", - maxpwr_r, VTY_NEWLINE); - return CMD_WARNING; - } - - trx->max_power_red = maxpwr_r; - - /* FIXME: make sure we update this using OML */ - - return CMD_SUCCESS; -} - -/* NOTE: This requires a full restart as bsc_network_configure() is executed - * only once on startup from osmo_bsc_main.c */ -DEFUN(cfg_trx_rsl_e1, - cfg_trx_rsl_e1_cmd, - "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", - "RSL Parameters\n" - "E1/T1 interface to be used for RSL\n" - "E1/T1 interface to be used for RSL\n" - "E1/T1 Line Number to be used for RSL\n" - "E1/T1 Timeslot to be used for RSL\n" - "E1/T1 Timeslot to be used for RSL\n" - "E1/T1 Sub-slot to be used for RSL\n" - "E1/T1 Sub-slot 0 is to be used for RSL\n" - "E1/T1 Sub-slot 1 is to be used for RSL\n" - "E1/T1 Sub-slot 2 is to be used for RSL\n" - "E1/T1 Sub-slot 3 is to be used for RSL\n" - "E1/T1 full timeslot is to be used for RSL\n") -{ - struct gsm_bts_trx *trx = vty->index; - - parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_trx_rsl_e1_tei, - cfg_trx_rsl_e1_tei_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), - "rsl e1 tei <0-63>", - "RSL Parameters\n" - "Set the TEI to be used for RSL\n" - "Set the TEI to be used for RSL\n" - "TEI to be used for RSL\n") -{ - struct gsm_bts_trx *trx = vty->index; - - trx->rsl_tei = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_trx_rf_locked, - cfg_trx_rf_locked_cmd, - "rf_locked (0|1)", - "Set or unset the RF Locking (Turn off RF of the TRX)\n" - "TRX is NOT RF locked (active)\n" - "TRX is RF locked (turned off)\n", - CMD_ATTR_IMMEDIATE) -{ - int locked = atoi(argv[0]); - struct gsm_bts_trx *trx = vty->index; - - gsm_trx_lock_rf(trx, locked, "vty"); - return CMD_SUCCESS; -} - -/* per TS configuration */ -DEFUN_ATTR(cfg_ts, - cfg_ts_cmd, - "timeslot <0-7>", - "Select a Timeslot to configure\n" - "Timeslot number\n", - CMD_ATTR_IMMEDIATE) -{ - int ts_nr = atoi(argv[0]); - struct gsm_bts_trx *trx = vty->index; - struct gsm_bts_trx_ts *ts; - - if (ts_nr >= TRX_NR_TS) { - vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s", - TRX_NR_TS, VTY_NEWLINE); - return CMD_WARNING; - } - - ts = &trx->ts[ts_nr]; - - vty->index = ts; - vty->node = TS_NODE; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_ts_pchan, - cfg_ts_pchan_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "phys_chan_config PCHAN", /* dynamically generated! */ - "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - int pchanc; - - pchanc = gsm_pchan_parse(argv[0]); - if (pchanc < 0) - return CMD_WARNING; - - ts->pchan_from_config = pchanc; - - return CMD_SUCCESS; -} - -/* used for backwards compatibility with old config files that still - * have uppercase pchan type names */ -DEFUN_HIDDEN(cfg_ts_pchan_compat, - cfg_ts_pchan_compat_cmd, - "phys_chan_config PCHAN", - "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - int pchanc; - - pchanc = gsm_pchan_parse(argv[0]); - if (pchanc < 0) { - vty_out(vty, "Unknown physical channel name '%s'%s", argv[0], VTY_NEWLINE); - return CMD_ERR_NO_MATCH; - } - - ts->pchan_from_config = pchanc; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_ts_tsc, - cfg_ts_tsc_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "training_sequence_code <0-7>", - "Training Sequence Code of the Timeslot\n" "TSC\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - - if (!osmo_bts_has_feature(&ts->trx->bts->model->features, BTS_FEAT_MULTI_TSC)) { - vty_out(vty, "%% This BTS does not support a TSC != BCC, " - "falling back to BCC%s", VTY_NEWLINE); - ts->tsc = -1; - return CMD_WARNING; - } - - ts->tsc = atoi(argv[0]); - - return CMD_SUCCESS; -} - -#define HOPPING_STR "Configure frequency hopping\n" - -DEFUN_USRATTR(cfg_ts_hopping, - cfg_ts_hopping_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "hopping enabled (0|1)", - HOPPING_STR "Enable or disable frequency hopping\n" - "Disable frequency hopping\n" "Enable frequency hopping\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - int enabled = atoi(argv[0]); - - if (enabled && !osmo_bts_has_feature(&ts->trx->bts->model->features, BTS_FEAT_HOPPING)) { - vty_out(vty, "%% BTS model does not seem to support freq. hopping%s", VTY_NEWLINE); - /* Allow enabling frequency hopping anyway, because the BTS might not have - * connected yet (thus not sent the feature vector), so we cannot know for - * sure. Jet print a warning and let it go. */ - } - - ts->hopping.enabled = enabled; - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_ts_hsn, - cfg_ts_hsn_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "hopping sequence-number <0-63>", - HOPPING_STR - "Which hopping sequence to use for this channel\n" - "Hopping Sequence Number (HSN)\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - - ts->hopping.hsn = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_ts_maio, - cfg_ts_maio_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "hopping maio <0-63>", - HOPPING_STR - "Which hopping MAIO to use for this channel\n" - "Mobile Allocation Index Offset (MAIO)\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - - ts->hopping.maio = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_ts_arfcn_add, - cfg_ts_arfcn_add_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "hopping arfcn add <0-1023>", - HOPPING_STR "Configure hopping ARFCN list\n" - "Add an entry to the hopping ARFCN list\n" "ARFCN\n") -{ - enum gsm_band unused; - struct gsm_bts_trx_ts *ts = vty->index; - int arfcn = atoi(argv[0]); - - if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { - vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); - return CMD_WARNING; - } - - bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_ts_arfcn_del, - cfg_ts_arfcn_del_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "hopping arfcn del <0-1023>", - HOPPING_STR "Configure hopping ARFCN list\n" - "Delete an entry to the hopping ARFCN list\n" "ARFCN\n") -{ - enum gsm_band unused; - struct gsm_bts_trx_ts *ts = vty->index; - int arfcn = atoi(argv[0]); - - if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { - vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); - return CMD_WARNING; - } - - bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0); - - return CMD_SUCCESS; -} - -DEFUN_USRATTR(cfg_ts_arfcn_del_all, - cfg_ts_arfcn_del_all_cmd, - X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), - "hopping arfcn del-all", - HOPPING_STR "Configure hopping ARFCN list\n" - "Delete all previously configured entries\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - - bitvec_zero(&ts->hopping.arfcns); - - return CMD_SUCCESS; -} - -/* NOTE: This will have an effect on newly created voice lchans since the E1 - * voice channels are handled by osmo-mgw and the information put in e1_link - * here is only used to generate the MGCP messages for the mgw. */ -DEFUN_ATTR(cfg_ts_e1_subslot, - cfg_ts_e1_subslot_cmd, - "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", - "E1/T1 channel connected to this on-air timeslot\n" - "E1/T1 channel connected to this on-air timeslot\n" - "E1/T1 line connected to this on-air timeslot\n" - "E1/T1 timeslot connected to this on-air timeslot\n" - "E1/T1 timeslot connected to this on-air timeslot\n" - "E1/T1 sub-slot connected to this on-air timeslot\n" - "E1/T1 sub-slot 0 connected to this on-air timeslot\n" - "E1/T1 sub-slot 1 connected to this on-air timeslot\n" - "E1/T1 sub-slot 2 connected to this on-air timeslot\n" - "E1/T1 sub-slot 3 connected to this on-air timeslot\n" - "Full E1/T1 timeslot connected to this on-air timeslot\n", - CMD_ATTR_IMMEDIATE) -{ - struct gsm_bts_trx_ts *ts = vty->index; - - parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]); - - return CMD_SUCCESS; -} - int print_counter(struct rate_ctr_group *bsc_ctrs, struct rate_ctr *ctr, const struct rate_ctr_desc *desc, void *data) { struct vty *vty = data; @@ -4994,8 +1290,8 @@ DEFUN(drop_bts, return CMD_WARNING; } - if (!is_ipaccess_bts(bts)) { - vty_out(vty, "%% This command only works for ipaccess.%s", VTY_NEWLINE); + if (!is_ipa_abisip_bts(bts)) { + vty_out(vty, "%% This command only works for IPA Abis/IP.%s", VTY_NEWLINE); return CMD_WARNING; } @@ -5041,8 +1337,8 @@ DEFUN(restart_bts, restart_bts_cmd, return CMD_WARNING; } - if (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) { - vty_out(vty, "%% This command only works for ipaccess nanoBTS.%s", + if (!is_ipa_abisip_bts(bts)) { + vty_out(vty, "%% This command only works for IPA Abis/IP.%s", VTY_NEWLINE); return CMD_WARNING; } @@ -5054,7 +1350,8 @@ DEFUN(restart_bts, restart_bts_cmd, return CMD_SUCCESS; } -DEFUN(bts_resend, bts_resend_cmd, +DEFUN(bts_resend_sysinfo, + bts_resend_sysinfo_cmd, "bts <0-255> resend-system-information", "BTS Specific Commands\n" BTS_NR_STR "Re-generate + re-send BCCH SYSTEM INFORMATION\n") @@ -5087,8 +1384,91 @@ DEFUN(bts_resend, bts_resend_cmd, return CMD_SUCCESS; } +DEFUN(bts_resend_power_ctrl_params, + bts_resend_power_ctrl_params_cmd, + "bts <0-255> resend-power-control-defaults", + "BTS Specific Commands\n" BTS_NR_STR + "Re-generate + re-send default MS/BS Power control parameters\n") +{ + const struct gsm_bts_trx *trx; + const struct gsm_bts *bts; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bts->ms_power_ctrl.mode != GSM_PWR_CTRL_MODE_DYN_BTS) { + vty_out(vty, "%% Not Sending default MS/BS Power control parameters " + "because BTS%d is not using dyn-bts mode%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bts->model->power_ctrl_send_def_params == NULL) { + vty_out(vty, "%% Sending default MS/BS Power control parameters " + "for BTS%d is not implemented%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } -DEFUN(smscb_cmd, smscb_cmd_cmd, + llist_for_each_entry(trx, &bts->trx_list, list) { + if (bts->model->power_ctrl_send_def_params(trx) != 0) { + vty_out(vty, "%% Failed to send default MS/BS Power control parameters " + "to BTS%d/TRX%d%s", bts_nr, trx->nr, VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN(bts_c0_power_red, + bts_c0_power_red_cmd, + "bts <0-255> c0-power-reduction <0-6>", + "BTS Specific Commands\n" BTS_NR_STR + "BCCH carrier power reduction operation\n" + "Power reduction value (in dB, even numbers only)\n") +{ + int bts_nr = atoi(argv[0]); + int red = atoi(argv[1]); + struct gsm_bts *bts; + int rc; + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (red % 2 != 0) { + vty_out(vty, "%% Incorrect BCCH power reduction value, " + "an even number is expected%s", VTY_NEWLINE); + return CMD_WARNING; + } + + rc = gsm_bts_set_c0_power_red(bts, red); + switch (rc) { + case 0: /* success */ + return CMD_SUCCESS; + case -ENOTCONN: + vty_out(vty, "%% BTS%u is offline%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + case -ENOTSUP: + vty_out(vty, "%% BCCH carrier power reduction operation mode " + "is not supported for BTS%u%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + default: + vty_out(vty, "%% Failed to %sable BCCH carrier power reduction " + "operation mode for BTS%u%s", red ? "en" : "dis", + bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } +} + +/* this command is now hidden, as it's a low-level debug hack, and people should + * instead use osmo-cbc these days */ +DEFUN_HIDDEN(smscb_cmd, smscb_cmd_cmd, "bts <0-255> smscb-command (normal|schedule|default) <1-4> HEXSTRING", "BTS related commands\n" BTS_NR_STR "SMS Cell Broadcast\n" @@ -5152,39 +1532,12 @@ DEFUN(smscb_cmd, smscb_cmd_cmd, return CMD_WARNING; } + /* SDCCH4 might not be correct here if the CBCH is on a SDCCH8? */ rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, false, buf, rc); return CMD_SUCCESS; } -/* resolve a gsm_bts_trx_ts basd on the given numeric identifiers */ -static struct gsm_bts_trx_ts *vty_get_ts(struct vty *vty, const char *bts_str, const char *trx_str, - const char *ts_str) -{ - int bts_nr = atoi(bts_str); - int trx_nr = atoi(trx_str); - int ts_nr = atoi(ts_str); - struct gsm_bts *bts; - struct gsm_bts_trx *trx; - struct gsm_bts_trx_ts *ts; - - bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); - if (!bts) { - vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); - return NULL; - } - - trx = gsm_bts_trx_num(bts, trx_nr); - if (!trx) { - vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE); - return NULL; - } - - ts = &trx->ts[ts_nr]; - - return ts; -} - DEFUN(pdch_act, pdch_act_cmd, "bts <0-255> trx <0-255> timeslot <0-7> pdch (activate|deactivate)", BTS_NR_TRX_TS_STR2 @@ -5201,15 +1554,15 @@ DEFUN(pdch_act, pdch_act_cmd, return CMD_WARNING; } - if (!is_ipaccess_bts(ts->trx->bts)) { - vty_out(vty, "%% This command only works for ipaccess BTS%s", + if (!is_ipa_abisip_bts(ts->trx->bts)) { + vty_out(vty, "%% This command only works for IPA Abis/IP BTS%s", VTY_NEWLINE); return CMD_WARNING; } - if (ts->pchan_on_init != GSM_PCHAN_TCH_F_TCH_H_PDCH + if (ts->pchan_on_init != GSM_PCHAN_OSMO_DYN && ts->pchan_on_init != GSM_PCHAN_TCH_F_PDCH) { - vty_out(vty, "%% Timeslot %u is not dynamic TCH/F_TCH/H_PDCH or TCH/F_PDCH%s", + vty_out(vty, "%% Timeslot %u is not dynamic TCH/F_TCH/H_SDCCH8_PDCH or TCH/F_PDCH%s", ts->nr, VTY_NEWLINE); return CMD_WARNING; } @@ -5241,15 +1594,24 @@ DEFUN(pdch_act, pdch_act_cmd, /* Activate / Deactivate a single lchan with a specific codec mode */ static int lchan_act_single(struct vty *vty, struct gsm_lchan *lchan, const char *codec_str, int amr_mode, int activate) { - struct lchan_activate_info info = { }; + struct lchan_activate_info info = {0}; uint16_t amr_modes[8] = { GSM0808_SC_CFG_AMR_4_75, GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20, GSM0808_SC_CFG_AMR_5_90, GSM0808_SC_CFG_AMR_6_70, GSM0808_SC_CFG_AMR_7_40, GSM0808_SC_CFG_AMR_7_95, GSM0808_SC_CFG_AMR_10_2, GSM0808_SC_CFG_AMR_12_2 }; if (activate) { + if (!codec_str) { + vty_out(vty, "%% Error: need a channel type argument to activate%s", VTY_NEWLINE); + return CMD_WARNING; + } + LOG_LCHAN(lchan, LOGL_NOTICE, "attempt from VTY to activate lchan %s with codec %s\n", gsm_lchan_name(lchan), codec_str); + if (!lchan->fi) { + vty_out(vty, "%% Cannot activate: Channel not initialized%s", VTY_NEWLINE); + return CMD_WARNING; + } int lchan_t; if (lchan->fi->state != LCHAN_ST_UNUSED) { @@ -5262,10 +1624,10 @@ static int lchan_act_single(struct vty *vty, struct gsm_lchan *lchan, const char if (lchan_t < 0) { if (lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH && !strcmp(codec_str, "fr")) lchan_t = GSM_LCHAN_TCH_F; - else if (lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH && !strcmp(codec_str, "hr")) + else if (lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN && !strcmp(codec_str, "hr")) lchan_t = GSM_LCHAN_TCH_H; else if ((lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH - || lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH) + || lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) && !strcmp(codec_str, "fr")) lchan_t = GSM_LCHAN_TCH_F; else { @@ -5276,42 +1638,40 @@ static int lchan_act_single(struct vty *vty, struct gsm_lchan *lchan, const char } /* configure the lchan */ - lchan->type = lchan_t; + lchan_select_set_type(lchan, lchan_t); if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr")) { - info = (struct lchan_activate_info) { - .activ_for = FOR_VTY, - .chan_mode = GSM48_CMODE_SPEECH_V1, - .requires_voice_stream = false, - }; + info.ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_V1; } else if (!strcmp(codec_str, "efr")) { - info = (struct lchan_activate_info) { - .activ_for = FOR_VTY, - .chan_mode = GSM48_CMODE_SPEECH_EFR, - .s15_s0 = amr_modes[amr_mode], - .requires_voice_stream = false, - }; + info.ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_EFR; } else if (!strcmp(codec_str, "amr")) { if (amr_mode == -1) { vty_out(vty, "%% AMR requires specification of AMR mode%s", VTY_NEWLINE); return CMD_WARNING; } - info = (struct lchan_activate_info) { - .activ_for = FOR_VTY, - .chan_mode = GSM48_CMODE_SPEECH_AMR, - .s15_s0 = amr_modes[amr_mode], - .requires_voice_stream = false, - }; + info.ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_AMR; + info.ch_mode_rate.s15_s0 = amr_modes[amr_mode]; } else if (!strcmp(codec_str, "sig")) { - info = (struct lchan_activate_info) { - .activ_for = FOR_VTY, - .chan_mode = GSM48_CMODE_SIGN, - .requires_voice_stream = false, - }; + info.ch_mode_rate.chan_mode = GSM48_CMODE_SIGN; } else { vty_out(vty, "%% Invalid channel mode specified!%s", VTY_NEWLINE); return CMD_WARNING; } + info.activ_for = ACTIVATE_FOR_VTY; + info.ch_indctr = GSM0808_CHAN_SIGN; + info.ch_mode_rate.chan_rate = chan_t_to_chan_rate(lchan_t); + + if (activate == 2 || lchan->vamos.is_secondary) { + info.type_for = LCHAN_TYPE_FOR_VAMOS; + if (lchan->vamos.is_secondary) { + info.tsc_set.present = true; + info.tsc_set.val = 1; + } + info.tsc.present = true; + info.tsc.val = 0; + info.ch_mode_rate.chan_mode = gsm48_chan_mode_to_vamos(info.ch_mode_rate.chan_mode); + } + vty_out(vty, "%% activating lchan %s as %s%s", gsm_lchan_name(lchan), gsm_chan_t_name(lchan->type), VTY_NEWLINE); lchan_activate(lchan, &info); @@ -5323,7 +1683,8 @@ static int lchan_act_single(struct vty *vty, struct gsm_lchan *lchan, const char } vty_out(vty, "%% Asking for release of %s in state %s%s", gsm_lchan_name(lchan), osmo_fsm_inst_state_name(lchan->fi), VTY_NEWLINE); - lchan_release(lchan, !!(lchan->conn), false, 0); + lchan_release(lchan, !!(lchan->conn), false, 0, + gscon_last_eutran_plmn(lchan->conn)); } return CMD_SUCCESS; @@ -5340,7 +1701,7 @@ static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate) for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { ts = &trx->ts[ts_nr]; - ts_for_each_potential_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { switch (ts->pchan_on_init) { case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_CCCH_SDCCH4_CBCH: @@ -5351,7 +1712,7 @@ static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate) break; case GSM_PCHAN_TCH_F: case GSM_PCHAN_TCH_F_PDCH: - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: codec_str = "fr"; break; case GSM_PCHAN_TCH_H: @@ -5364,9 +1725,9 @@ static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate) if (codec_str && skip_next == false) { lchan_act_single(vty, lchan, codec_str, -1, activate); - /* We use GSM_PCHAN_TCH_F_TCH_H_PDCH slots as TCH_F for this test, so we + /* We use GSM_PCHAN_OSMO_DYN slots as TCH_F for this test, so we * must not use the TCH_H reserved lchan in subslot 1. */ - if (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH) + if (ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) skip_next = true; } else { @@ -5379,41 +1740,83 @@ static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate) return CMD_SUCCESS; } -/* Debug/Measurement command to activate a given logical channel - * manually in a given mode/codec. This is useful for receiver - * performance testing (FER/RBER/...) */ -DEFUN(lchan_act, lchan_act_cmd, - "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> (activate|deactivate) (hr|fr|efr|amr|sig) [<0-7>]", - BTS_NR_TRX_TS_SS_STR2 - "Manual Channel Activation (e.g. for BER test)\n" - "Manual Channel Deactivation (e.g. for BER test)\n" - "Half-Rate v1\n" "Full-Rate\n" "Enhanced Full Rate\n" "Adaptive Multi-Rate\n" "Signalling\n" "AMR Mode\n") +static int lchan_act_deact(struct vty *vty, const char **argv, int argc) { struct gsm_bts_trx_ts *ts; + struct gsm_bts *bts; struct gsm_lchan *lchan; - int ss_nr = atoi(argv[3]); - const char *act_str = argv[4]; - const char *codec_str = argv[5]; + bool vamos = (strcmp(argv[3], "vamos-sub-slot") == 0); + int ss_nr = atoi(argv[4]); + const char *act_str = NULL; + const char *codec_str = NULL; int activate; int amr_mode = -1; + if (argc > 5) + act_str = argv[5]; if (argc > 6) - amr_mode = atoi(argv[6]); + codec_str = argv[6]; + if (argc > 7) + amr_mode = atoi(argv[7]); ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); if (!ts) return CMD_WARNING; + if (ss_nr >= ts->max_primary_lchans) { + vty_out(vty, "Invalid sub-slot number %d for this timeslot type: %s (%u)%s", ss_nr, + gsm_pchan_name(ts->pchan_on_init), ts->max_primary_lchans, VTY_NEWLINE); + return CMD_WARNING; + } + + bts = ts->trx->bts; + if (vamos && bts->features_known && !osmo_bts_has_feature(&bts->features, BTS_FEAT_VAMOS)) { + vty_out(vty, "BTS does not support VAMOS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (vamos) + ss_nr += ts->max_primary_lchans; + lchan = &ts->lchan[ss_nr]; - if (!strcmp(act_str, "activate")) + if (!act_str) + activate = 0; + else if (!strcmp(act_str, "activate")) activate = 1; + else if (!strcmp(act_str, "activate-vamos")) + activate = 2; else - activate = 0; + return CMD_WARNING; return lchan_act_single(vty, lchan, codec_str, amr_mode, activate); } +/* Debug/Measurement command to activate a given logical channel + * manually in a given mode/codec. This is useful for receiver + * performance testing (FER/RBER/...) */ +DEFUN(lchan_act, lchan_act_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> (sub-slot|vamos-sub-slot) <0-7> (activate|activate-vamos) (hr|fr|efr|amr|sig) [<0-7>]", + BTS_NR_TRX_TS_STR2 + "Primary sub-slot\n" "VAMOS secondary shadow subslot, range <0-1>, only valid for TCH type timeslots\n" + SS_NR_STR + "Manual Channel Activation (e.g. for BER test)\n" + "Manual Channel Activation, in VAMOS mode\n" + "Half-Rate v1\n" "Full-Rate\n" "Enhanced Full Rate\n" "Adaptive Multi-Rate\n" "Signalling\n" "AMR Mode\n") +{ + return lchan_act_deact(vty, argv, argc); +} + +DEFUN(lchan_deact, lchan_deact_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> (sub-slot|vamos-sub-slot) <0-7> deactivate", + BTS_NR_TRX_TS_STR2 + "Primary sub-slot\n" "VAMOS secondary shadow subslot, range <0-1>, only valid for TCH type timeslots\n" + SS_NR_STR + "Manual Channel Deactivation (e.g. for BER test)\n") +{ + return lchan_act_deact(vty, argv, argc); +} + #define ACTIVATE_ALL_LCHANS_STR "Manual Channel Activation of all logical channels (e.g. for BER test)\n" #define DEACTIVATE_ALL_LCHANS_STR "Manual Channel Deactivation of all logical channels (e.g. for BER test)\n" @@ -5427,9 +1830,7 @@ DEFUN_HIDDEN(lchan_act_bts, lchan_act_all_cmd, struct gsm_network *net = gsmnet_from_vty(vty); const char *act_str = argv[0]; int activate; - int bts_nr; struct gsm_bts *bts; - int trx_nr; struct gsm_bts_trx *trx; if (!strcmp(act_str, "activate-all-lchan")) @@ -5437,14 +1838,17 @@ DEFUN_HIDDEN(lchan_act_bts, lchan_act_all_cmd, else activate = 0; - for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { - bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); - for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { - trx = gsm_bts_trx_num(bts, trx_nr); + llist_for_each_entry(bts, &net->bts_list, list) { + llist_for_each_entry(trx, &bts->trx_list, list) lchan_act_trx(vty, trx, activate); - } } + vty_out(vty, "%% All channels have been %s on all BTS/TRX, please " + "make sure that the radio link timeout is set to %s%s", + activate ? "activated" : "deactivated", + activate ? "'infinite'" : "its old value (e.g. 'oml')", + VTY_NEWLINE); + return CMD_SUCCESS; } @@ -5479,6 +1883,12 @@ DEFUN_HIDDEN(lchan_act_all_bts, lchan_act_all_bts_cmd, lchan_act_trx(vty, trx, activate); } + vty_out(vty, "%% All channels have been %s on all TRX of BTS%d, please " + "make sure that the radio link timeout is set to %s%s", + activate ? "activated" : "deactivated", bts_nr, + activate ? "'infinite'" : "its old value (e.g. 'oml')", + VTY_NEWLINE); + return CMD_SUCCESS; } @@ -5517,9 +1927,102 @@ DEFUN_HIDDEN(lchan_act_all_trx, lchan_act_all_trx_cmd, lchan_act_trx(vty, trx, activate); + vty_out(vty, "%% All channels have been %s on BTS%d/TRX%d, please " + "make sure that the radio link timeout is set to %s%s", + activate ? "activated" : "deactivated", bts_nr, trx_nr, + activate ? "'infinite'" : "its old value (e.g. 'oml')", + VTY_NEWLINE); + return CMD_SUCCESS; } +DEFUN(lchan_set_mspower, lchan_set_mspower_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> ms-power <0-40> [verify]\n", + BTS_NR_TRX_TS_SS_STR2 + "Manually force MS Uplink Power Level in dBm on the lchan (for testing)\n" + "Set transmit power of the MS in dBm\n" + "Check requested level against BAND and UE Power Class.\n") +{ + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int bts_nr = atoi(argv[0]); + int trx_nr = atoi(argv[1]); + int ss_nr = atoi(argv[3]); + bool verify = (argc > 5); + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + trx = gsm_bts_trx_num(bts, trx_nr); + if (!trx) { + vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); + if (!ts) { + vty_out(vty, "%% No such TS (%d)%s", atoi(argv[2]), VTY_NEWLINE); + return CMD_WARNING; + } + if (ss_nr >= ts->max_primary_lchans) { + vty_out(vty, "%% Invalid sub-slot number for this timeslot type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + lchan = &ts->lchan[ss_nr]; + if (!lchan->fi) + return CMD_WARNING; + + if (verify) { + lchan_update_ms_power_ctrl_level(lchan, atoi(argv[4])); + return CMD_SUCCESS; + } + lchan->ms_power = ms_pwr_ctl_lvl(ts->trx->bts->band, atoi(argv[4])); + rsl_chan_ms_power_ctrl(lchan); + return CMD_SUCCESS; +} + +DEFUN(vamos_modify_lchan, vamos_modify_lchan_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> modify (vamos|non-vamos) " TSC_ARGS_OPT, + BTS_NR_TRX_TS_SS_STR2 + "Manually send Channel Mode Modify (for debugging)\n" + "Enable VAMOS channel mode\n" "Disable VAMOS channel mode\n" + TSC_ARGS_DOC) +{ + struct gsm_bts_trx_ts *ts; + struct gsm_bts *bts; + struct gsm_lchan *lchan; + int ss_nr = atoi(argv[3]); + const char *vamos_str = argv[4]; + /* argv[5] is the "tsc" string from TSC_ARGS_OPT */ + int tsc_set = (argc > 6) ? atoi(argv[6]) : -1; + int tsc = (argc > 7) ? atoi(argv[7]) : -1; + + ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); + if (!ts) + return CMD_WARNING; + + if (ss_nr >= ts->max_primary_lchans) { + vty_out(vty, "%% Invalid sub-slot number for this timeslot type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts = ts->trx->bts; + if (bts->features_known && !osmo_bts_has_feature(&bts->features, BTS_FEAT_VAMOS)) { + vty_out(vty, "%% BTS does not support VAMOS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + lchan = &ts->lchan[ss_nr]; + + return trigger_vamos_mode_modify(vty, lchan, strcmp(vamos_str, "vamos") == 0, tsc_set, tsc); +} + /* Debug command to send lchans from state LCHAN_ST_UNUSED to state * LCHAN_ST_BORKEN and vice versa. */ DEFUN_HIDDEN(lchan_set_borken, lchan_set_borken_cmd, @@ -5552,7 +2055,7 @@ DEFUN_HIDDEN(lchan_set_borken, lchan_set_borken_cmd, } } else { if (lchan->fi->state == LCHAN_ST_BORKEN) { - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_VTY]); + rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_VTY)); osmo_fsm_inst_state_chg(lchan->fi, LCHAN_ST_UNUSED, 0, 0); } else { vty_out(vty, @@ -5585,14 +2088,14 @@ DEFUN(lchan_mdcx, lchan_mdcx_cmd, lchan = &ts->lchan[ss_nr]; - if (!is_ipaccess_bts(lchan->ts->trx->bts)) { - vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); + if (!is_ipa_abisip_bts(lchan->ts->trx->bts)) { + vty_out(vty, "%% BTS is not of IPA Abis/IP type%s", VTY_NEWLINE); return CMD_WARNING; } - if (ss_nr >= pchan_subslots(ts->pchan_is)) { + if (ss_nr >= ts->max_primary_lchans) { vty_out(vty, "%% subslot index %d too large for physical channel %s (%u slots)%s", - ss_nr, gsm_pchan_name(ts->pchan_is), pchan_subslots(ts->pchan_is), + ss_nr, gsm_pchan_name(ts->pchan_is), ts->max_primary_lchans, VTY_NEWLINE); return CMD_WARNING; } @@ -5605,6 +2108,98 @@ DEFUN(lchan_mdcx, lchan_mdcx_cmd, return CMD_SUCCESS; } +DEFUN(lchan_reassign, lchan_reassign_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> (sub-slot|vamos-sub-slot) <0-7> " + "reassign-to trx <0-255> timeslot <0-7> (sub-slot|vamos-sub-slot) <0-7> " + TSC_ARGS_OPT, + BTS_NR_TRX_TS_STR2 + "Primary sub-slot\n" "VAMOS secondary shadow subslot, range <0-1>, only valid for TCH type timeslots\n" + SS_NR_STR + "Trigger Assignment to an unused lchan on the same cell\n" + "Target TRX\nTRX nr\nTarget timeslot\ntimeslot nr\n" + "Primary sub-slot\n" "VAMOS secondary shadow subslot, range <0-1>, only valid for TCH type timeslots\n" + SS_NR_STR + TSC_ARGS_DOC) +{ + const char *bts_str = argv[0]; + const char *from_trx_str = argv[1]; + const char *from_ts_str = argv[2]; + bool from_vamos = (strcmp(argv[3], "vamos-sub-slot") == 0); + int from_ss_nr = atoi(argv[4]); + const char *to_trx_str = argv[5]; + const char *to_ts_str = argv[6]; + bool to_vamos = (strcmp(argv[7], "vamos-sub-slot") == 0); + int to_ss_nr = atoi(argv[8]); + int tsc_set = (argc > 10) ? atoi(argv[10]) : -1; + int tsc = (argc > 11) ? atoi(argv[11]) : -1; + + struct gsm_bts_trx_ts *from_ts; + struct gsm_bts_trx_ts *to_ts; + struct gsm_lchan *from_lchan; + struct gsm_lchan *to_lchan; + + from_ts = vty_get_ts(vty, bts_str, from_trx_str, from_ts_str); + if (!from_ts) + return CMD_WARNING; + to_ts = vty_get_ts(vty, bts_str, to_trx_str, to_ts_str); + if (!to_ts) + return CMD_WARNING; + + if (!ts_is_capable_of_pchan(to_ts, from_ts->pchan_is)) { + vty_out(vty, "cannot re-assign, target timeslot has mismatching physical channel config: %s -> %s%s", + gsm_pchan_name(from_ts->pchan_is), gsm_pchan_name(to_ts->pchan_on_init), VTY_NEWLINE); + return CMD_WARNING; + } + + if (from_ss_nr >= from_ts->max_primary_lchans) { + vty_out(vty, "cannot re-assign, invalid source subslot number: %d%s", + from_ss_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (to_ss_nr >= to_ts->max_primary_lchans) { + vty_out(vty, "cannot re-assign, invalid target subslot number: %d%s", + to_ss_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (from_vamos) + from_ss_nr += from_ts->max_primary_lchans; + from_lchan = &from_ts->lchan[from_ss_nr]; + + if (to_vamos) + to_ss_nr += to_ts->max_primary_lchans; + to_lchan = &to_ts->lchan[to_ss_nr]; + + if (!lchan_state_is(from_lchan, LCHAN_ST_ESTABLISHED)) { + vty_out(vty, "cannot re-assign, source lchan is not in ESTABLISHED state%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (!to_lchan->fi) { + vty_out(vty, "cannot re-assign, target lchan is not initialized%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (!lchan_state_is(to_lchan, LCHAN_ST_UNUSED)) { + vty_out(vty, "cannot re-assign, target lchan is already in use%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* Set lchan type, so that activation will work out. */ + lchan_select_set_type(to_lchan, chan_mode_to_chan_type(from_lchan->current_ch_mode_rate.chan_mode, + from_lchan->current_ch_mode_rate.chan_rate)); + + LOG_LCHAN(from_lchan, LOGL_NOTICE, "VTY requests re-assignment of this lchan to %s%s\n", + gsm_lchan_name(to_lchan), to_lchan->vamos.is_secondary ? " (to VAMOS mode)" : ""); + LOG_LCHAN(to_lchan, LOGL_NOTICE, "VTY requests re-assignment of %s to this lchan%s TSC %d/%d\n", + gsm_lchan_name(from_lchan), to_lchan->vamos.is_secondary ? " (to VAMOS mode)" : "", + tsc_set, tsc); + if (reassignment_request_to_lchan(ASSIGN_FOR_VTY, from_lchan, to_lchan, tsc_set, tsc)) { + vty_out(vty, "failed to request re-assignment%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + DEFUN(ctrl_trap, ctrl_trap_cmd, "ctrl-interface generate-trap TRAP VALUE", "Commands related to the CTRL Interface\n" @@ -5683,12 +2278,13 @@ DEFUN_USRATTR(cfg_net_mnc, DEFUN_USRATTR(cfg_net_encryption, cfg_net_encryption_cmd, X(BSC_VTY_ATTR_NEW_LCHAN), - "encryption a5 <0-3> [<0-3>] [<0-3>] [<0-3>]", + "encryption a5 <0-4> [<0-4>] [<0-4>] [<0-4>] [<0-4>]", "Encryption options\n" "GSM A5 Air Interface Encryption\n" "A5/n Algorithm Number\n" "A5/n Algorithm Number\n" "A5/n Algorithm Number\n" + "A5/n Algorithm Number\n" "A5/n Algorithm Number\n") { struct gsm_network *gsmnet = gsmnet_from_vty(vty); @@ -5726,7 +2322,7 @@ DEFUN_ATTR(cfg_net_timezone, "Timezone offset (45 minutes)\n", CMD_ATTR_IMMEDIATE) { - struct gsm_network *net = vty->index; + struct gsm_network *net = gsmnet_from_vty(vty); int tzhr = atoi(argv[0]); int tzmn = atoi(argv[1]); @@ -5750,7 +2346,7 @@ DEFUN_ATTR(cfg_net_timezone_dst, "DST offset (hours)\n", CMD_ATTR_IMMEDIATE) { - struct gsm_network *net = vty->index; + struct gsm_network *net = gsmnet_from_vty(vty); int tzhr = atoi(argv[0]); int tzmn = atoi(argv[1]); int tzdst = atoi(argv[2]); @@ -5770,22 +2366,23 @@ DEFUN_ATTR(cfg_net_no_timezone, "Disable network timezone override, use system tz\n", CMD_ATTR_IMMEDIATE) { - struct gsm_network *net = vty->index; + struct gsm_network *net = gsmnet_from_vty(vty); net->tz.override = 0; return CMD_SUCCESS; } -/* FIXME: changing this value would not affect generated System Information */ -DEFUN(cfg_net_per_loc_upd, cfg_net_per_loc_upd_cmd, - "periodic location update <6-1530>", - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval in Minutes\n") +DEFUN_USRATTR(cfg_net_per_loc_upd, + cfg_net_per_loc_upd_cmd, + BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK, + "periodic location update <6-1530>", + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval in Minutes\n") { - struct gsm_network *net = vty->index; + struct gsm_network *net = gsmnet_from_vty(vty); struct osmo_tdef *d = osmo_tdef_get_entry(net->T_defs, 3212); OSMO_ASSERT(d); @@ -5794,15 +2391,16 @@ DEFUN(cfg_net_per_loc_upd, cfg_net_per_loc_upd_cmd, return CMD_SUCCESS; } -/* FIXME: changing this value would not affect generated System Information */ -DEFUN(cfg_net_no_per_loc_upd, cfg_net_no_per_loc_upd_cmd, - "no periodic location update", - NO_STR - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval\n") +DEFUN_USRATTR(cfg_net_no_per_loc_upd, + cfg_net_no_per_loc_upd_cmd, + BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK, + "no periodic location update", + NO_STR + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval\n") { - struct gsm_network *net = vty->index; + struct gsm_network *net = gsmnet_from_vty(vty); struct osmo_tdef *d = osmo_tdef_get_entry(net->T_defs, 3212); OSMO_ASSERT(d); @@ -5839,6 +2437,16 @@ DEFUN_ATTR(cfg_net_meas_feed_scenario, cfg_net_meas_feed_scenario_cmd, return CMD_SUCCESS; } +DEFUN_ATTR(cfg_net_meas_feed_wqueue_max_len, cfg_net_meas_feed_wqueue_max_len_cmd, + "meas-feed write-queue-max-length <1-65535>", + MEAS_FEED_STR "Set the maximum length of the message write queue towards the UDP socket\n" + "Maximum number of messages to be queued waiting for transmission\n", + CMD_ATTR_IMMEDIATE) +{ + meas_feed_txqueue_max_length_set(atoi(argv[0])); + return CMD_SUCCESS; +} + static void legacy_timers(struct vty *vty, const char **T_arg) { if (!strcmp((*T_arg), "T993111") || !strcmp((*T_arg), "t993111")) { @@ -5895,6 +2503,63 @@ DEFUN(cfg_net_allow_unusable_timeslots, cfg_net_allow_unusable_timeslots_cmd, return CMD_SUCCESS; } +DEFUN_ATTR(cfg_bts_pcu_sock_wqueue_len, cfg_bts_pcu_sock_wqueue_len_cmd, + "pcu-socket-wqueue-length <1-2147483646>", + "Configure the PCU socket queue length\n" + "Queue length\n", + CMD_ATTR_IMMEDIATE) +{ + size_t dropped_msgs = 0; + struct gsm_network *net = gsmnet_from_vty(vty); + size_t old = net->pcu_sock_wqueue_len_max; + net->pcu_sock_wqueue_len_max = atoi(argv[0]); + if (net->pcu_state) + dropped_msgs = osmo_wqueue_set_maxlen(&net->pcu_state->upqueue, net->pcu_sock_wqueue_len_max); + if (dropped_msgs) { + LOGP(DPCU, LOGL_INFO, "Have dropped %zu messages due to shortened max. message queue size (from: %zu to %u)\n", + dropped_msgs, old, net->pcu_sock_wqueue_len_max); + vty_out(vty, "Have dropped %zu messages due to shortened max. message queue size (from: %zu to %u)%s", + dropped_msgs, old, net->pcu_sock_wqueue_len_max, VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_net_pcu_sock_path, + cfg_net_pcu_sock_path_cmd, + "pcu-socket PATH", + "PCU Socket Path for using OsmoPCU co-located with BSC\n" + "Path in the file system for the unix-domain PCU socket\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int rc; + + osmo_talloc_replace_string(net, &net->pcu_sock_path, argv[0]); + pcu_sock_exit(net); + rc = pcu_sock_init(net); + if (rc < 0) { + vty_out(vty, "%% Error creating PCU socket `%s'%s", + net->pcu_sock_path, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_net_no_pcu_sock, + cfg_net_no_pcu_sock_cmd, + "no pcu-socket", + NO_STR "Disable BSC co-located PCU\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + pcu_sock_exit(net); + talloc_free(net->pcu_sock_path); + net->pcu_sock_path = NULL; + return CMD_SUCCESS; +} + static struct bsc_msc_data *bsc_msc_data(struct vty *vty) { return vty->index; @@ -5962,7 +2627,7 @@ static void write_msc_amr_options(struct vty *vty, struct bsc_msc_data *msc) if (msc->amr_octet_aligned) vty_out(vty, " amr-payload octet-aligned%s", VTY_NEWLINE); else - vty_out(vty, " amr-payload bandwith-efficient%s", VTY_NEWLINE); + vty_out(vty, " amr-payload bandwidth-efficient%s", VTY_NEWLINE); } static void msc_write_nri(struct vty *vty, struct bsc_msc_data *msc, bool verbose); @@ -5985,10 +2650,10 @@ static void write_msc(struct vty *vty, struct bsc_msc_data *msc) if (i != 0) vty_out(vty, " "); - if (msc->audio_support[i]->hr) - vty_out(vty, "hr%.1u", msc->audio_support[i]->ver); + if (msc->audio_support[i].hr) + vty_out(vty, "hr%u", msc->audio_support[i].ver); else - vty_out(vty, "fr%.1u", msc->audio_support[i]->ver); + vty_out(vty, "fr%u", msc->audio_support[i].ver); } vty_out(vty, "%s", VTY_NEWLINE); @@ -6061,11 +2726,22 @@ static int config_write_bsc(struct vty *vty) vty_out(vty, " bsc-auto-rf-off %d%s", bsc_gsmnet->auto_off_timeout, VTY_NEWLINE); + if (bsc_gsmnet->bts_setup_ramp.enabled) + vty_out(vty, " bts-setup-ramping%s", VTY_NEWLINE); + + if (bsc_gsmnet->bts_setup_ramp.step_size > 0) + vty_out(vty, " bts-setup-ramping-step-size %d%s", + bsc_gsmnet->bts_setup_ramp.step_size, VTY_NEWLINE); + + if (bsc_gsmnet->bts_setup_ramp.step_interval > 0) + vty_out(vty, " bts-setup-ramping-step-interval %d%s", + bsc_gsmnet->bts_setup_ramp.step_interval, VTY_NEWLINE); + return CMD_SUCCESS; } -DEFUN_ATTR(cfg_net_bsc_ncc, - cfg_net_bsc_ncc_cmd, +DEFUN_ATTR(cfg_net_msc_ncc, + cfg_net_msc_ncc_cmd, "core-mobile-network-code <1-999>", "Use this network code for the core network\n" "MNC value\n", CMD_ATTR_IMMEDIATE) @@ -6083,8 +2759,8 @@ DEFUN_ATTR(cfg_net_bsc_ncc, return CMD_SUCCESS; } -DEFUN_ATTR(cfg_net_bsc_mcc, - cfg_net_bsc_mcc_cmd, +DEFUN_ATTR(cfg_net_msc_mcc, + cfg_net_msc_mcc_cmd, "core-mobile-country-code <1-999>", "Use this country code for the core network\n" "MCC value\n", CMD_ATTR_IMMEDIATE) @@ -6099,8 +2775,8 @@ DEFUN_ATTR(cfg_net_bsc_mcc, return CMD_SUCCESS; } -DEFUN_DEPRECATED(cfg_net_bsc_lac, - cfg_net_bsc_lac_cmd, +DEFUN_DEPRECATED(cfg_net_msc_lac, + cfg_net_msc_lac_cmd, "core-location-area-code <0-65535>", "Legacy configuration that no longer has any effect\n-\n") { @@ -6108,8 +2784,8 @@ DEFUN_DEPRECATED(cfg_net_bsc_lac, return CMD_SUCCESS; } -DEFUN_DEPRECATED(cfg_net_bsc_ci, - cfg_net_bsc_ci_cmd, +DEFUN_DEPRECATED(cfg_net_msc_ci, + cfg_net_msc_ci_cmd, "core-cell-identity <0-65535>", "Legacy configuration that no longer has any effect\n-\n") { @@ -6117,8 +2793,8 @@ DEFUN_DEPRECATED(cfg_net_bsc_ci, return CMD_SUCCESS; } -DEFUN_DEPRECATED(cfg_net_bsc_rtp_base, - cfg_net_bsc_rtp_base_cmd, +DEFUN_DEPRECATED(cfg_net_msc_rtp_base, + cfg_net_msc_rtp_base_cmd, "ip.access rtp-base <1-65000>", "deprecated\n" "deprecated, RTP is handled by the MGW\n" "deprecated\n") { @@ -6126,53 +2802,64 @@ DEFUN_DEPRECATED(cfg_net_bsc_rtp_base, return CMD_SUCCESS; } -DEFUN_USRATTR(cfg_net_bsc_codec_list, - cfg_net_bsc_codec_list_cmd, +DEFUN_USRATTR(cfg_net_msc_codec_list, + cfg_net_msc_codec_list_cmd, BSC_VTY_ATTR_NEW_LCHAN, "codec-list .LIST", - "Set the allowed audio codecs\n" - "List of audio codecs, e.g. fr3 fr1 hr3\n") + "Set the allowed audio codecs and their order of preference\n" + "List of audio codecs in order of preference, e.g. 'codec-list fr3 fr2 fr1 hr3 hr1'." + " (fr3: AMR-FR, hr3: AMR-HR, fr2: GSM-EFR, fr1: GSM-FR, hr1: GSM-HR)\n") { struct bsc_msc_data *data = bsc_msc_data(vty); + struct gsm_audio_support tmp[ARRAY_SIZE(data->audio_support)]; + const char *arg = NULL; int i; - /* free the old list... if it exists */ - if (data->audio_support) { - talloc_free(data->audio_support); - data->audio_support = NULL; - data->audio_length = 0; + /* Nr of arguments must fit in the array */ + if (argc > ARRAY_SIZE(data->audio_support)) { + vty_out(vty, "Too many items in 'msc' / 'codec-list': %d. There can be at most %zu entries.%s", + argc, ARRAY_SIZE(data->audio_support), VTY_NEWLINE); + return CMD_ERR_EXEED_ARGC_MAX; } - /* create a new array */ - data->audio_support = - talloc_zero_array(bsc_gsmnet, struct gsm_audio_support *, argc); - data->audio_length = argc; + /* check all given arguments first */ + for (i = 0; i < argc; i++) { + int j; + int ver; + arg = argv[i]; - for (i = 0; i < argc; ++i) { - /* check for hrX or frX */ - if (strlen(argv[i]) != 3 - || argv[i][1] != 'r' - || (argv[i][0] != 'h' && argv[i][0] != 'f') - || argv[i][2] < 0x30 - || argv[i][2] > 0x39) - goto error; + if (strncmp("hr", arg, 2) == 0) + tmp[i].hr = 1; + else if (strncmp("fr", arg, 2) == 0) + tmp[i].hr = 0; + else + goto invalid_arg; + + ver = atoi(arg + 2); + if (ver < 1 || ver > 7) + goto invalid_arg; + tmp[i].ver = ver; - data->audio_support[i] = talloc_zero(data->audio_support, - struct gsm_audio_support); - data->audio_support[i]->ver = atoi(argv[i] + 2); + if (tmp[i].hr == 1 && ver == 2) + goto invalid_arg; - if (strncmp("hr", argv[i], 2) == 0) - data->audio_support[i]->hr = 1; - else if (strncmp("fr", argv[i], 2) == 0) - data->audio_support[i]->hr = 0; + /* prevent duplicate entries */ + for (j = 0; j < i; j++) { + if (gsm_audio_support_cmp(&tmp[j], &tmp[i]) == 0) { + vty_out(vty, "duplicate entry in 'msc' / 'codec-list': %s%s", argv[i], VTY_NEWLINE); + return CMD_WARNING; + } + } } + memcpy(data->audio_support, tmp, sizeof(data->audio_support)); + data->audio_length = argc; + return CMD_SUCCESS; -error: - vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s", - argv[i], VTY_NEWLINE); - return CMD_ERR_INCOMPLETE; +invalid_arg: + vty_out(vty, "%s is not a valid codec version%s", osmo_quote_cstr_c(OTC_SELECT, arg, -1), VTY_NEWLINE); + return CMD_WARNING; } #define LEGACY_STR "This command has no effect, it is kept to support legacy config files\n" @@ -6481,6 +3168,88 @@ DEFUN_ATTR(cfg_net_no_rf_off_time, return CMD_SUCCESS; } +DEFUN_ATTR(cfg_bsc_bts_setup_ramping, + cfg_bsc_bts_setup_ramping_cmd, + "bts-setup-ramping", + "Enable BTS setup ramping to limit the amount of BTS to configure within a time window.\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + bts_setup_ramp_enable(net); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bsc_no_bts_setup_ramping, + cfg_bsc_no_bts_setup_ramping_cmd, + "no bts-setup-ramping", + NO_STR + "Disable BTS ramping and configure all waiting BTS.\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + bts_setup_ramp_disable(net); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bsc_bts_ramping_step_interval, + cfg_bsc_bts_setup_ramping_step_interval_cmd, + "bts-setup-ramping-step-interval <0-65535>", + "Configure the BTS setup ramping step interval. The time between ramping steps.\n" + "Set a step interval (in seconds)\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int interval_size = atoi(argv[0]); + bts_setup_ramp_set_step_interval(net, interval_size); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bsc_bts_ramping_step_size, + cfg_bsc_bts_setup_ramping_step_size_cmd, + "bts-setup-ramping-step-size <0-65535>", + "Configure the BTS setup ramping step size. The amount of BTS to allow to configure within a ramping interval\n" + "Amount of BTS to setup while a step size\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int seconds = atoi(argv[0]); + bts_setup_ramp_set_step_size(net, seconds); + return CMD_SUCCESS; +} + +DEFUN(bts_unblock_setup_ramping, + bts_unblock_setup_ramping_cmd, + "bts <0-255> unblock-setup-ramping", + "BTS Specific Commands\n" BTS_NR_STR + "Unblock and allow to configure a BTS if kept back by BTS ramping\n") +{ + struct gsm_network *gsmnet; + struct gsm_bts *bts; + unsigned int bts_nr; + + gsmnet = gsmnet_from_vty(vty); + + bts_nr = atoi(argv[0]); + if (bts_nr >= gsmnet->num_bts) { + vty_out(vty, "%% BTS number must be between 0 and %d. It was %d.%s", + gsmnet->num_bts, bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "%% BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bts_setup_ramp_unblock_bts(bts)) { + vty_out(vty, "%% The BTS is not blocked by BTS ramping.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + DEFUN(show_statistics, show_statistics_cmd, "show statistics", @@ -6497,13 +3266,19 @@ DEFUN(show_mscs, { struct bsc_msc_data *msc; llist_for_each_entry(msc, &bsc_gsmnet->mscs, entry) { - vty_out(vty, "%d %s %s ", + vty_out(vty, "MSC %d: %s <-> ", msc->a.cs7_instance, - osmo_ss7_asp_protocol_name(msc->a.asp_proto), osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.bsc_addr)); vty_out(vty, "%s%s", osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.msc_addr), VTY_NEWLINE); + vty_out(vty, " ASP protocol: %s%s", + osmo_ss7_asp_protocol_name(msc->a.asp_proto), + VTY_NEWLINE); + vty_out(vty, " BSSMAP state: %s%s", + msc->a.bssmap_reset ? osmo_fsm_inst_state_name(msc->a.bssmap_reset->fi) : "", + VTY_NEWLINE); + } return CMD_SUCCESS; @@ -6614,7 +3389,7 @@ DEFUN(show_subscr_all, vty_out(vty, " IMSI TMSI Use%s", VTY_NEWLINE); /* " 001010123456789 ffffffff 1" */ - llist_for_each_entry(bsc_subscr, bsc_gsmnet->bsc_subscribers, entry) + llist_for_each_entry(bsc_subscr, &bsc_gsmnet->bsc_subscribers->bsub_list, entry) dump_one_sub(vty, bsc_subscr); return CMD_SUCCESS; @@ -6647,7 +3422,7 @@ ALIAS_DEPRECATED(cfg_net_msc_dest, cfg_net_msc_no_dest_cmd, DEFUN_USRATTR(cfg_net_msc_amr_octet_align, cfg_net_msc_amr_octet_align_cmd, X(BSC_VTY_ATTR_NEW_LCHAN), - "amr-payload (octet-aligned|bandwith-efficient", + "amr-payload (octet-aligned|bandwidth-efficient)", "Set AMR payload framing mode\n" "payload fields aligned on octet boundaries\n" "payload fields packed (AoIP)\n") @@ -6656,11 +3431,22 @@ DEFUN_USRATTR(cfg_net_msc_amr_octet_align, if (strcmp(argv[0], "octet-aligned") == 0) data->amr_octet_aligned = true; - else if (strcmp(argv[0], "bandwith-efficient") == 0) + else if (strcmp(argv[0], "bandwidth-efficient") == 0) + data->amr_octet_aligned = false; + else { data->amr_octet_aligned = false; + vty_out(vty, "%% Command 'amr-payload': Option 'bandwith-efficient' " + "containing typo is deprecated, use 'bandwidth-efficient' instead!%s", + VTY_NEWLINE); + } return CMD_SUCCESS; } +ALIAS_DEPRECATED(cfg_net_msc_amr_octet_align, + cfg_net_msc_amr_octet_align_deprecated_cmd, + "amr-payload bandwith-efficient", + "Set AMR payload framing mode\n" + "payload fields packed (AoIP)\n"); DEFUN_ATTR(cfg_msc_nri_add, cfg_msc_nri_add_cmd, "nri add <0-32767> [<0-32767>]", @@ -6810,30 +3596,30 @@ DEFUN_HIDDEN(mscpool_roundrobin_next, mscpool_roundrobin_next_cmd, return CMD_SUCCESS; } -int bsc_vty_init(struct gsm_network *network) +DEFUN(msc_bssmap_reset, msc_bssmap_reset_cmd, + "msc " MSC_NR_RANGE " bssmap reset", + "Query or manipulate a specific A-interface link\n" + "MSC nr\n" + "Query or manipulate BSSMAP layer of A-interface\n" + "Flip this MSC to disconnected state and re-send BSSMAP RESET\n") { - cfg_ts_pchan_cmd.string = - vty_cmd_string_from_valstr(tall_bsc_ctx, - gsm_pchant_names, - "phys_chan_config (", "|", ")", - VTY_DO_LOWER); - cfg_ts_pchan_cmd.doc = - vty_cmd_string_from_valstr(tall_bsc_ctx, - gsm_pchant_descs, - "Physical Channel Combination\n", - "\n", "", 0); - - cfg_bts_type_cmd.string = - vty_cmd_string_from_valstr(tall_bsc_ctx, - bts_type_names, - "type (", "|", ")", - VTY_DO_LOWER); - cfg_bts_type_cmd.doc = - vty_cmd_string_from_valstr(tall_bsc_ctx, - bts_type_descs, - "BTS Vendor/Type\n", - "\n", "", 0); + int msc_nr = atoi(argv[0]); + struct bsc_msc_data *msc; + msc = osmo_msc_data_find(bsc_gsmnet, msc_nr); + + if (!msc) { + vty_out(vty, "%% No such MSC: nr %d\n", msc_nr); + return CMD_WARNING; + } + + LOGP(DMSC, LOGL_NOTICE, "(msc%d) VTY requests BSSMAP RESET\n", msc_nr); + bssmap_reset_resend_reset(msc->a.bssmap_reset); + return CMD_SUCCESS; +} + +int bsc_vty_init(struct gsm_network *network) +{ OSMO_ASSERT(vty_global_gsm_network == NULL); vty_global_gsm_network = network; @@ -6852,14 +3638,19 @@ int bsc_vty_init(struct gsm_network *network) install_element(GSMNET_NODE, &cfg_net_dyn_ts_allow_tch_f_cmd); install_element(GSMNET_NODE, &cfg_net_meas_feed_dest_cmd); install_element(GSMNET_NODE, &cfg_net_meas_feed_scenario_cmd); + install_element(GSMNET_NODE, &cfg_net_meas_feed_wqueue_max_len_cmd); install_element(GSMNET_NODE, &cfg_net_timer_cmd); install_element(GSMNET_NODE, &cfg_net_allow_unusable_timeslots_cmd); + install_element(GSMNET_NODE, &cfg_net_pcu_sock_path_cmd); + install_element(GSMNET_NODE, &cfg_bts_pcu_sock_wqueue_len_cmd); + install_element(GSMNET_NODE, &cfg_net_no_pcu_sock_cmd); /* Timer configuration commands (generic osmo_tdef API) */ osmo_tdef_vty_groups_init(GSMNET_NODE, bsc_tdef_group); install_element_ve(&bsc_show_net_cmd); install_element_ve(&show_bts_cmd); + install_element_ve(&show_bts_brief_cmd); install_element_ve(&show_bts_fail_rep_cmd); install_element_ve(&show_rejected_bts_cmd); install_element_ve(&show_trx_cmd); @@ -6890,167 +3681,26 @@ int bsc_vty_init(struct gsm_network *network) install_element(GSMNET_NODE, &cfg_net_nri_null_add_cmd); install_element(GSMNET_NODE, &cfg_net_nri_null_del_cmd); - install_element(GSMNET_NODE, &cfg_bts_cmd); - install_node(&bts_node, config_write_bts); - install_element(BTS_NODE, &cfg_bts_type_cmd); - install_element(BTS_NODE, &cfg_description_cmd); - install_element(BTS_NODE, &cfg_no_description_cmd); - install_element(BTS_NODE, &cfg_bts_band_cmd); - install_element(BTS_NODE, &cfg_bts_ci_cmd); - install_element(BTS_NODE, &cfg_bts_dtxu_cmd); - install_element(BTS_NODE, &cfg_bts_dtxd_cmd); - install_element(BTS_NODE, &cfg_bts_no_dtxu_cmd); - install_element(BTS_NODE, &cfg_bts_no_dtxd_cmd); - install_element(BTS_NODE, &cfg_bts_lac_cmd); - install_element(BTS_NODE, &cfg_bts_tsc_cmd); - install_element(BTS_NODE, &cfg_bts_bsic_cmd); - install_element(BTS_NODE, &cfg_bts_unit_id_cmd); - install_element(BTS_NODE, &cfg_bts_deprecated_unit_id_cmd); - install_element(BTS_NODE, &cfg_bts_rsl_ip_cmd); - install_element(BTS_NODE, &cfg_bts_deprecated_rsl_ip_cmd); - install_element(BTS_NODE, &cfg_bts_nokia_site_skip_reset_cmd); - install_element(BTS_NODE, &cfg_bts_nokia_site_no_loc_rel_cnf_cmd); - install_element(BTS_NODE, &cfg_bts_nokia_site_bts_reset_timer_cnf_cmd); - install_element(BTS_NODE, &cfg_bts_stream_id_cmd); - install_element(BTS_NODE, &cfg_bts_deprecated_stream_id_cmd); - install_element(BTS_NODE, &cfg_bts_oml_e1_cmd); - install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd); - install_element(BTS_NODE, &cfg_bts_challoc_cmd); - install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd); - install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd); - install_element(BTS_NODE, &cfg_bts_chan_desc_att_cmd); - install_element(BTS_NODE, &cfg_bts_chan_dscr_att_cmd); - install_element(BTS_NODE, &cfg_bts_chan_desc_bs_pa_mfrms_cmd); - install_element(BTS_NODE, &cfg_bts_chan_dscr_bs_pa_mfrms_cmd); - install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_cmd); - install_element(BTS_NODE, &cfg_bts_chan_dscr_bs_ag_blks_res_cmd); - install_element(BTS_NODE, &cfg_bts_ccch_load_ind_thresh_cmd); - install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd); - install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd); - install_element(BTS_NODE, &cfg_bts_cell_barred_cmd); - install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd); - install_element(BTS_NODE, &cfg_bts_rach_ac_class_cmd); - install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd); - install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd); - install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd); - install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd); - install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd); - install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd); - install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd); - install_element(BTS_NODE, &cfg_bts_penalty_time_cmd); - install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd); - install_element(BTS_NODE, &cfg_bts_radio_link_timeout_cmd); - install_element(BTS_NODE, &cfg_bts_radio_link_timeout_inf_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_11bit_rach_support_for_egprs_cmd); - install_element(BTS_NODE, &cfg_bts_no_gprs_egprs_pkt_chan_req_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_egprs_pkt_chan_req_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_net_ctrl_ord_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_ctrl_ack_cmd); - install_element(BTS_NODE, &cfg_no_bts_gprs_ctrl_ack_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd); - install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd); - install_element(BTS_NODE, &cfg_bts_pag_free_cmd); - install_element(BTS_NODE, &cfg_bts_si_mode_cmd); - install_element(BTS_NODE, &cfg_bts_si_static_cmd); - install_element(BTS_NODE, &cfg_bts_si_unused_send_empty_cmd); - install_element(BTS_NODE, &cfg_bts_no_si_unused_send_empty_cmd); - install_element(BTS_NODE, &cfg_bts_early_cm_cmd); - install_element(BTS_NODE, &cfg_bts_early_cm_3g_cmd); - install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd); - install_element(BTS_NODE, &cfg_bts_neigh_cmd); - install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd); - install_element(BTS_NODE, &cfg_bts_si2quater_neigh_add_cmd); - install_element(BTS_NODE, &cfg_bts_si2quater_neigh_del_cmd); - install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_add_cmd); - install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_del_cmd); - install_element(BTS_NODE, &cfg_bts_excl_rf_lock_cmd); - install_element(BTS_NODE, &cfg_bts_no_excl_rf_lock_cmd); - install_element(BTS_NODE, &cfg_bts_force_comb_si_cmd); - install_element(BTS_NODE, &cfg_bts_no_force_comb_si_cmd); - install_element(BTS_NODE, &cfg_bts_codec0_cmd); - install_element(BTS_NODE, &cfg_bts_codec1_cmd); - install_element(BTS_NODE, &cfg_bts_codec2_cmd); - install_element(BTS_NODE, &cfg_bts_codec3_cmd); - install_element(BTS_NODE, &cfg_bts_codec4_cmd); - install_element(BTS_NODE, &cfg_bts_depends_on_cmd); - install_element(BTS_NODE, &cfg_bts_no_depends_on_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_modes1_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_modes2_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_modes3_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_modes4_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_thres1_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_thres2_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_thres3_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_hyst1_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_hyst2_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_hyst3_cmd); - install_element(BTS_NODE, &cfg_bts_amr_fr_start_mode_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_modes1_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_modes2_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_modes3_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_modes4_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_thres1_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_thres2_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_thres3_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_hyst1_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_hyst2_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_hyst3_cmd); - install_element(BTS_NODE, &cfg_bts_amr_hr_start_mode_cmd); - install_element(BTS_NODE, &cfg_bts_pcu_sock_cmd); - install_element(BTS_NODE, &cfg_bts_acc_rotate_cmd); - install_element(BTS_NODE, &cfg_bts_acc_rotate_quantum_cmd); - install_element(BTS_NODE, &cfg_bts_acc_ramping_cmd); - install_element(BTS_NODE, &cfg_bts_no_acc_ramping_cmd); - install_element(BTS_NODE, &cfg_bts_acc_ramping_step_interval_cmd); - install_element(BTS_NODE, &cfg_bts_acc_ramping_step_size_cmd); - install_element(BTS_NODE, &cfg_bts_acc_ramping_chan_load_cmd); - install_element(BTS_NODE, &cfg_bts_t3113_dynamic_cmd); - install_element(BTS_NODE, &cfg_bts_no_t3113_dynamic_cmd); - neighbor_ident_vty_init(network, network->neighbor_bss_cells); - /* See also handover commands added on bts level from handover_vty.c */ - - install_element(BTS_NODE, &cfg_trx_cmd); - install_node(&trx_node, dummy_config_write); - install_element(TRX_NODE, &cfg_trx_arfcn_cmd); - install_element(TRX_NODE, &cfg_description_cmd); - install_element(TRX_NODE, &cfg_no_description_cmd); - install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); - install_element(TRX_NODE, &cfg_trx_max_power_red_cmd); - install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd); - install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd); - install_element(TRX_NODE, &cfg_trx_rf_locked_cmd); - - install_element(TRX_NODE, &cfg_ts_cmd); - install_node(&ts_node, dummy_config_write); - install_element(TS_NODE, &cfg_ts_pchan_cmd); - install_element(TS_NODE, &cfg_ts_pchan_compat_cmd); - install_element(TS_NODE, &cfg_ts_tsc_cmd); - install_element(TS_NODE, &cfg_ts_hopping_cmd); - install_element(TS_NODE, &cfg_ts_hsn_cmd); - install_element(TS_NODE, &cfg_ts_maio_cmd); - install_element(TS_NODE, &cfg_ts_arfcn_add_cmd); - install_element(TS_NODE, &cfg_ts_arfcn_del_cmd); - install_element(TS_NODE, &cfg_ts_arfcn_del_all_cmd); - install_element(TS_NODE, &cfg_ts_e1_subslot_cmd); + bts_vty_init(); + mgcp_client_pool_vty_init(GSMNET_NODE, MGW_NODE, NULL, vty_global_gsm_network->mgw.mgw_pool); install_element(ENABLE_NODE, &drop_bts_cmd); install_element(ENABLE_NODE, &restart_bts_cmd); - install_element(ENABLE_NODE, &bts_resend_cmd); + install_element(ENABLE_NODE, &bts_unblock_setup_ramping_cmd); + install_element(ENABLE_NODE, &bts_resend_sysinfo_cmd); + install_element(ENABLE_NODE, &bts_resend_power_ctrl_params_cmd); + install_element(ENABLE_NODE, &bts_c0_power_red_cmd); install_element(ENABLE_NODE, &pdch_act_cmd); install_element(ENABLE_NODE, &lchan_act_cmd); + install_element(ENABLE_NODE, &lchan_deact_cmd); install_element(ENABLE_NODE, &lchan_act_all_cmd); install_element(ENABLE_NODE, &lchan_act_all_bts_cmd); install_element(ENABLE_NODE, &lchan_act_all_trx_cmd); + install_element(ENABLE_NODE, &vamos_modify_lchan_cmd); install_element(ENABLE_NODE, &lchan_mdcx_cmd); install_element(ENABLE_NODE, &lchan_set_borken_cmd); + install_element(ENABLE_NODE, &lchan_reassign_cmd); + install_element(ENABLE_NODE, &lchan_set_mspower_cmd); install_element(ENABLE_NODE, &handover_subscr_conn_cmd); install_element(ENABLE_NODE, &assignment_subscr_conn_cmd); @@ -7077,14 +3727,18 @@ int bsc_vty_init(struct gsm_network *network) install_element(BSC_NODE, &cfg_net_no_rf_off_time_cmd); install_element(BSC_NODE, &cfg_net_bsc_missing_msc_ussd_cmd); install_element(BSC_NODE, &cfg_net_bsc_no_missing_msc_text_cmd); + install_element(BSC_NODE, &cfg_bsc_bts_setup_ramping_cmd); + install_element(BSC_NODE, &cfg_bsc_no_bts_setup_ramping_cmd); + install_element(BSC_NODE, &cfg_bsc_bts_setup_ramping_step_size_cmd); + install_element(BSC_NODE, &cfg_bsc_bts_setup_ramping_step_interval_cmd); install_node(&msc_node, config_write_msc); - install_element(MSC_NODE, &cfg_net_bsc_ncc_cmd); - install_element(MSC_NODE, &cfg_net_bsc_mcc_cmd); - install_element(MSC_NODE, &cfg_net_bsc_lac_cmd); - install_element(MSC_NODE, &cfg_net_bsc_ci_cmd); - install_element(MSC_NODE, &cfg_net_bsc_rtp_base_cmd); - install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd); + install_element(MSC_NODE, &cfg_net_msc_ncc_cmd); + install_element(MSC_NODE, &cfg_net_msc_mcc_cmd); + install_element(MSC_NODE, &cfg_net_msc_lac_cmd); + install_element(MSC_NODE, &cfg_net_msc_ci_cmd); + install_element(MSC_NODE, &cfg_net_msc_rtp_base_cmd); + install_element(MSC_NODE, &cfg_net_msc_codec_list_cmd); install_element(MSC_NODE, &cfg_net_msc_dest_cmd); install_element(MSC_NODE, &cfg_net_msc_no_dest_cmd); install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd); @@ -7104,6 +3758,7 @@ int bsc_vty_init(struct gsm_network *network) install_element(MSC_NODE, &cfg_net_msc_amr_5_15_cmd); install_element(MSC_NODE, &cfg_net_msc_amr_4_75_cmd); install_element(MSC_NODE, &cfg_net_msc_amr_octet_align_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_octet_align_deprecated_cmd); install_element(MSC_NODE, &cfg_net_msc_lcls_mode_cmd); install_element(MSC_NODE, &cfg_net_msc_lcls_mismtch_cmd); install_element(MSC_NODE, &cfg_msc_cs7_bsc_addr_cmd); @@ -7114,7 +3769,11 @@ int bsc_vty_init(struct gsm_network *network) install_element(MSC_NODE, &cfg_msc_show_nri_cmd); install_element(MSC_NODE, &cfg_msc_allow_attach_cmd); install_element(MSC_NODE, &cfg_msc_no_allow_attach_cmd); - + install_element(MSC_NODE, &cfg_msc_mgw_x_osmo_ign_cmd); + install_element(MSC_NODE, &cfg_msc_no_mgw_x_osmo_ign_cmd); + install_element(MSC_NODE, &cfg_msc_osmux_cmd); + /* Deprecated: Old MGCP config without pooling support in MSC node: */ + mgcp_client_vty_init(network, MSC_NODE, network->mgw.conf); /* Deprecated: ping time config, kept to support legacy config files. */ install_element(MSC_NODE, &cfg_net_msc_no_ping_time_cmd); install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd); @@ -7129,13 +3788,9 @@ int bsc_vty_init(struct gsm_network *network) install_element(ENABLE_NODE, &gen_position_trap_cmd); install_element(ENABLE_NODE, &mscpool_roundrobin_next_cmd); + install_element(ENABLE_NODE, &msc_bssmap_reset_cmd); install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd); - mgcp_client_vty_init(network, MSC_NODE, network->mgw.conf); - install_element(MSC_NODE, &cfg_msc_mgw_x_osmo_ign_cmd); - install_element(MSC_NODE, &cfg_msc_no_mgw_x_osmo_ign_cmd); - install_element(MSC_NODE, &cfg_msc_osmux_cmd); - return 0; } diff --git a/src/osmo-bsc/bssmap_reset.c b/src/osmo-bsc/bssmap_reset.c index fcf2bab30..018ecbac1 100644 --- a/src/osmo-bsc/bssmap_reset.c +++ b/src/osmo-bsc/bssmap_reset.c @@ -47,7 +47,7 @@ static const struct osmo_tdef_state_timeout bssmap_reset_timeouts[32] = { osmo_tdef_fsm_inst_state_chg(FI, STATE, \ bssmap_reset_timeouts, \ (bsc_gsmnet)->T_defs, \ - 5) + -1) struct bssmap_reset *bssmap_reset_alloc(void *ctx, const char *label, const struct bssmap_reset_cfg *cfg) { @@ -70,6 +70,14 @@ struct bssmap_reset *bssmap_reset_alloc(void *ctx, const char *label, const stru return bssmap_reset; } +void bssmap_reset_term_and_free(struct bssmap_reset *bssmap_reset) +{ + if (!bssmap_reset) + return; + osmo_fsm_inst_term(bssmap_reset->fi, OSMO_FSM_TERM_REQUEST, NULL); + talloc_free(bssmap_reset); +} + static void link_up(struct bssmap_reset *bssmap_reset) { LOGPFSML(bssmap_reset->fi, LOGL_NOTICE, "link up\n"); @@ -190,7 +198,7 @@ static int bssmap_reset_fsm_timer_cb(struct osmo_fsm_inst *fi) static struct osmo_fsm_state bssmap_reset_fsm_states[] = { [BSSMAP_RESET_ST_DISC] = { - .name = "DISC", + .name = "DISCONNECTED", .in_event_mask = 0 | S(BSSMAP_RESET_EV_RX_RESET) | S(BSSMAP_RESET_EV_RX_RESET_ACK) @@ -205,7 +213,7 @@ static struct osmo_fsm_state bssmap_reset_fsm_states[] = { .action = bssmap_reset_disc_action, }, [BSSMAP_RESET_ST_CONN] = { - .name = "CONN", + .name = "CONNECTED", .in_event_mask = 0 | S(BSSMAP_RESET_EV_RX_RESET) | S(BSSMAP_RESET_EV_RX_RESET_ACK) @@ -235,7 +243,21 @@ bool bssmap_reset_is_conn_ready(const struct bssmap_reset *bssmap_reset) return bssmap_reset->fi->state == BSSMAP_RESET_ST_CONN; } -static __attribute__((constructor)) void bssmap_reset_fsm_init() +void bssmap_reset_resend_reset(struct bssmap_reset *bssmap_reset) +{ + OSMO_ASSERT(bssmap_reset); + + /* Immediately (1ms) kick off reset sending mechanism */ + osmo_fsm_inst_state_chg_ms(bssmap_reset->fi, BSSMAP_RESET_ST_DISC, 1, 0); +} + +void bssmap_reset_set_disconnected(struct bssmap_reset *bssmap_reset) +{ + /* Go to disconnected state, with the normal RESET timeout to re-send RESET. */ + bssmap_reset_fsm_state_chg(bssmap_reset->fi, BSSMAP_RESET_ST_DISC); +} + +static __attribute__((constructor)) void bssmap_reset_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&bssmap_reset_fsm) == 0); } diff --git a/src/osmo-bsc/bts.c b/src/osmo-bsc/bts.c index aa7ba1dc0..8cc9e9af0 100644 --- a/src/osmo-bsc/bts.c +++ b/src/osmo-bsc/bts.c @@ -1,5 +1,5 @@ /* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org> - * (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> * * All Rights Reserved * @@ -24,6 +24,8 @@ #include <osmocom/bsc/bts.h> #include <osmocom/bsc/debug.h> #include <osmocom/bsc/nm_common_fsm.h> +#include <osmocom/bsc/paging.h> +#include <osmocom/bsc/smscb.h> const struct value_string bts_attribute_names[] = { OSMO_VALUE_STRING(BTS_TYPE_VARIANT), @@ -47,7 +49,7 @@ const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = { { BTS_OSMO_LITECELL15, "osmo-bts-lc15" }, { BTS_OSMO_OCTPHY, "osmo-bts-octphy" }, { BTS_OSMO_SYSMO, "osmo-bts-sysmo" }, - { BTS_OSMO_TRX, "omso-bts-trx" }, + { BTS_OSMO_TRX, "osmo-bts-trx" }, { 0, NULL } }; @@ -67,7 +69,7 @@ const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = { { GSM_BTS_TYPE_NANOBTS, "nanobts" }, { GSM_BTS_TYPE_RBS2000, "rbs2000" }, { GSM_BTS_TYPE_NOKIA_SITE, "nokia_site" }, - { GSM_BTS_TYPE_OSMOBTS, "sysmobts" }, + { GSM_BTS_TYPE_OSMOBTS, "osmo-bts" }, { 0, NULL } }; @@ -77,7 +79,7 @@ const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = { { GSM_BTS_TYPE_NANOBTS, "ip.access nanoBTS or compatible" }, { GSM_BTS_TYPE_RBS2000, "Ericsson RBS2000 Series" }, { GSM_BTS_TYPE_NOKIA_SITE, "Nokia {Metro,Ultra,In}Site" }, - { GSM_BTS_TYPE_OSMOBTS, "sysmocom sysmoBTS" }, + { GSM_BTS_TYPE_OSMOBTS, "Osmocom Base Transceiver Station" }, { 0, NULL } }; @@ -91,12 +93,6 @@ const char *btstype2str(enum gsm_bts_type type) return get_value_string(bts_type_names, type); } -static void bts_init_cbch_state(struct bts_smscb_chan_state *cstate, struct gsm_bts *bts) -{ - cstate->bts = bts; - INIT_LLIST_HEAD(&cstate->messages); -} - static LLIST_HEAD(bts_models); struct gsm_bts_model *bts_model_find(enum gsm_bts_type type) @@ -121,9 +117,20 @@ int gsm_bts_model_register(struct gsm_bts_model *model) return 0; } -static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 }; -static const uint8_t bts_cell_timer_default[] = - { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 }; +static const uint8_t bts_cell_timer_default[11] = { + 3, /* blocking timer (T1) */ + 3, /* blocking retries */ + 3, /* unblocking retries */ + 3, /* reset timer (T2) */ + 3, /* reset retries */ + 10, /* suspend timer (T3) in 100ms */ + 3, /* suspend retries */ + 10, /* resume timer (T4) in 100ms */ + 3, /* resume retries */ + 10, /* capability update timer (T5) */ + 3, /* capability update retries */ +}; + static const struct gprs_rlc_cfg rlc_cfg_default = { .parameter = { [RLC_T3142] = 20, @@ -149,14 +156,25 @@ static const struct gprs_rlc_cfg rlc_cfg_default = { static int gsm_bts_talloc_destructor(struct gsm_bts *bts) { - if (bts->site_mgr.mo.fi) { - osmo_fsm_inst_free(bts->site_mgr.mo.fi); - bts->site_mgr.mo.fi = NULL; + paging_destructor(bts); + bts_setup_ramp_remove(bts); + + osmo_timer_del(&bts->cbch_timer); + + bts->site_mgr->bts[0] = NULL; + + if (bts->gprs.cell.mo.fi) { + osmo_fsm_inst_free(bts->gprs.cell.mo.fi); + bts->gprs.cell.mo.fi = NULL; } + if (bts->mo.fi) { osmo_fsm_inst_free(bts->mo.fi); bts->mo.fi = NULL; } + + osmo_stat_item_group_free(bts->bts_statg); + rate_ctr_group_free(bts->bts_ctrs); return 0; } @@ -164,11 +182,9 @@ static int gsm_bts_talloc_destructor(struct gsm_bts *bts) * This part is shared among the thin programs in osmo-bsc/src/utils/. * osmo-bsc requires further initialization that pulls in more dependencies (see * bsc_bts_alloc_register()). */ -struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) +struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, struct gsm_bts_sm *bts_sm, uint8_t bts_num) { - struct gsm_bts *bts = talloc_zero(net, struct gsm_bts); - struct gsm48_multi_rate_conf mr_cfg; - int i; + struct gsm_bts *bts = talloc_zero(bts_sm, struct gsm_bts); if (!bts) return NULL; @@ -182,35 +198,24 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) bts->ms_max_power = 15; /* dBm */ - bts->site_mgr.mo.fi = osmo_fsm_inst_alloc(&nm_bts_sm_fsm, bts, &bts->site_mgr, - LOGL_INFO, NULL); - osmo_fsm_inst_update_id_f(bts->site_mgr.mo.fi, "bts_sm"); - gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff); + bts->site_mgr = bts_sm; bts->mo.fi = osmo_fsm_inst_alloc(&nm_bts_fsm, bts, bts, LOGL_INFO, NULL); osmo_fsm_inst_update_id_f(bts->mo.fi, "bts%d", bts->nr); gsm_mo_init(&bts->mo, bts, NM_OC_BTS, bts->nr, 0xff, 0xff); - for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) { - bts->gprs.nsvc[i].bts = bts; - bts->gprs.nsvc[i].id = i; - gsm_mo_init(&bts->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC, - bts->nr, i, 0xff); - } - memcpy(&bts->gprs.nse.timer, bts_nse_timer_default, - sizeof(bts->gprs.nse.timer)); - gsm_mo_init(&bts->gprs.nse.mo, bts, NM_OC_GPRS_NSE, - bts->nr, 0xff, 0xff); + /* 3GPP TS 08.18, chapter 5.4.1: 0 is reserved for signalling */ + bts->gprs.cell.bvci = 2; memcpy(&bts->gprs.cell.timer, bts_cell_timer_default, sizeof(bts->gprs.cell.timer)); - gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL, - bts->nr, 0xff, 0xff); memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default, sizeof(bts->gprs.cell.rlc_cfg)); - - /* 3GPP TS 08.18, chapter 5.4.1: 0 is reserved for signalling */ - bts->gprs.cell.bvci = 2; + bts->gprs.cell.mo.fi = osmo_fsm_inst_alloc(&nm_gprs_cell_fsm, bts, + &bts->gprs.cell, LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(bts->gprs.cell.mo.fi, "gprs-cell%d", bts->nr); + gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL, + bts->nr, 0xff, 0xff); /* init statistics */ bts->bts_ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr); @@ -220,6 +225,51 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) } bts->bts_statg = osmo_stat_item_group_alloc(bts, &bts_statg_desc, bts->nr); + bts->all_allocated.sdcch = (struct osmo_time_cc){ + .cfg = { + .gran_usec = 1*1000000, + .forget_sum_usec = 60*1000000, + .rate_ctr = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_ALL_ALLOCATED_SDCCH), + .T_gran = -16, + .T_round_threshold = -17, + .T_forget_sum = -18, + .T_defs = net->T_defs, + }, + }; + bts->all_allocated.static_sdcch = (struct osmo_time_cc){ + .cfg = { + .gran_usec = 1*1000000, + .forget_sum_usec = 60*1000000, + .rate_ctr = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_ALL_ALLOCATED_STATIC_SDCCH), + .T_gran = -16, + .T_round_threshold = -17, + .T_forget_sum = -18, + .T_defs = net->T_defs, + }, + }; + bts->all_allocated.tch = (struct osmo_time_cc){ + .cfg = { + .gran_usec = 1*1000000, + .forget_sum_usec = 60*1000000, + .rate_ctr = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_ALL_ALLOCATED_TCH), + .T_gran = -16, + .T_round_threshold = -17, + .T_forget_sum = -18, + .T_defs = net->T_defs, + }, + }; + bts->all_allocated.static_tch = (struct osmo_time_cc){ + .cfg = { + .gran_usec = 1*1000000, + .forget_sum_usec = 60*1000000, + .rate_ctr = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_ALL_ALLOCATED_STATIC_TCH), + .T_gran = -16, + .T_round_threshold = -17, + .T_forget_sum = -18, + .T_defs = net->T_defs, + }, + }; + /* create our primary TRX */ bts->c0 = gsm_bts_trx_alloc(bts); if (!bts->c0) { @@ -231,11 +281,11 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) bts->c0->ts[0].pchan_from_config = GSM_PCHAN_CCCH_SDCCH4; /* TODO: really?? */ bts->ccch_load_ind_thresh = 10; /* 10% of Load: Start sending CCCH LOAD IND */ + bts->ccch_load_ind_period = 1; /* Send CCCH LOAD IND every 1 second */ bts->rach_b_thresh = -1; bts->rach_ldavg_slots = -1; - bts->paging.free_chans_need = -1; - INIT_LLIST_HEAD(&bts->paging.pending_requests); + paging_init(bts); bts->features.data = &bts->_features_data[0]; bts->features.data_len = sizeof(bts->_features_data); @@ -254,6 +304,10 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) bts->neigh_list_manual_mode = NL_MODE_AUTOMATIC; bts->early_classmark_allowed_3g = true; /* 3g Early Classmark Sending controlled by bts->early_classmark_allowed param */ bts->si_unused_send_empty = true; + bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_ALWAYS; + bts->chan_alloc_dyn_params.ul_rxlev_thresh = 50; /* >= -60 dBm */ + bts->chan_alloc_dyn_params.ul_rxlev_avg_num = 2; /* at least 2 samples */ + bts->chan_alloc_dyn_params.c0_chan_load_thresh = 60; /* >= 60% */ bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */ bts->si_common.cell_sel_par.rxlev_acc_min = 0; bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list; @@ -274,18 +328,25 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) bts->si_common.rach_control.tx_integer = 9; /* 12 slots spread - 217/115 slots delay */ bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */ bts->si_common.rach_control.t2 = 4; /* no emergency calls */ + bts->si_common.chan_desc.mscr = 1; /* Indicate R99 MSC in SI3 */ bts->si_common.chan_desc.att = 1; /* attachment required */ bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5; /* paging frames */ bts->si_common.chan_desc.bs_ag_blks_res = 1; /* reserved AGCH blocks */ bts->si_common.chan_desc.t3212 = osmo_tdef_get(net->T_defs, 3212, OSMO_TDEF_CUSTOM, -1); + bts->si_common.cell_options.pwrc = 0; /* PWRC not set */ + bts->si_common.cell_sel_par.acs = 0; + bts->si_common.ncc_permitted = 0xff; gsm_bts_set_radio_link_timeout(bts, 32); /* Use RADIO LINK TIMEOUT of 32 */ INIT_LLIST_HEAD(&bts->abis_queue); INIT_LLIST_HEAD(&bts->loc_list); - INIT_LLIST_HEAD(&bts->local_neighbors); + INIT_LLIST_HEAD(&bts->neighbors); INIT_LLIST_HEAD(&bts->oml_fail_rep); INIT_LLIST_HEAD(&bts->chan_rqd_queue); + /* Don't pin the BTS to any MGW by default: */ + bts->mgw_pool_target = -1; + /* Enable all codecs by default. These get reset to a more fine grained selection IF a * 'codec-support' config appears in the config file (see bsc_vty.c). */ bts->codec = (struct bts_codec_conf){ @@ -295,70 +356,201 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) }; /* Set reasonable defaults for AMR-FR and AMR-HR rate configuration. - * (see also 3GPP TS 28.062, Table 7.11.3.1.3-2) */ - mr_cfg = (struct gsm48_multi_rate_conf) { + * The values are taken from 3GPP TS 51.010-1 (version 13.11.0). + * See 14.2.19.4.1 and 14.2.20.4.1 for AMR-FR and AMR-HR, respectively. */ + static const struct gsm48_multi_rate_conf amr_fr_mr_cfg = { .m4_75 = 1, - .m5_15 = 0, .m5_90 = 1, - .m6_70 = 0, - .m7_40 = 1, - .m7_95 = 0, - .m10_2 = 0, + .m7_95 = 1, .m12_2 = 1 }; - memcpy(bts->mr_full.gsm48_ie, &mr_cfg, sizeof(bts->mr_full.gsm48_ie)); - bts->mr_full.ms_mode[0].mode = 0; - bts->mr_full.ms_mode[1].mode = 2; - bts->mr_full.ms_mode[2].mode = 4; - bts->mr_full.ms_mode[3].mode = 7; - bts->mr_full.bts_mode[0].mode = 0; - bts->mr_full.bts_mode[1].mode = 2; - bts->mr_full.bts_mode[2].mode = 4; - bts->mr_full.bts_mode[3].mode = 7; - for (i = 0; i < 3; i++) { - bts->mr_full.ms_mode[i].hysteresis = 8; - bts->mr_full.ms_mode[i].threshold = 32; - bts->mr_full.bts_mode[i].hysteresis = 8; - bts->mr_full.bts_mode[i].threshold = 32; - } - bts->mr_full.num_modes = 4; - - mr_cfg = (struct gsm48_multi_rate_conf) { + static const struct gsm48_multi_rate_conf amr_hr_mr_cfg = { .m4_75 = 1, - .m5_15 = 0, .m5_90 = 1, - .m6_70 = 0, - .m7_40 = 1, - .m7_95 = 0, - .m10_2 = 0, - .m12_2 = 0 + .m6_70 = 1, + .m7_95 = 1, }; - memcpy(bts->mr_half.gsm48_ie, &mr_cfg, sizeof(bts->mr_half.gsm48_ie)); - bts->mr_half.ms_mode[0].mode = 0; - bts->mr_half.ms_mode[1].mode = 2; - bts->mr_half.ms_mode[2].mode = 4; - bts->mr_half.ms_mode[3].mode = 7; - bts->mr_half.bts_mode[0].mode = 0; - bts->mr_half.bts_mode[1].mode = 2; - bts->mr_half.bts_mode[2].mode = 4; - bts->mr_half.bts_mode[3].mode = 7; - for (i = 0; i < 3; i++) { - bts->mr_half.ms_mode[i].hysteresis = 8; - bts->mr_half.ms_mode[i].threshold = 32; - bts->mr_half.bts_mode[i].hysteresis = 8; - bts->mr_half.bts_mode[i].threshold = 32; - } - bts->mr_half.num_modes = 3; - bts_init_cbch_state(&bts->cbch_basic, bts); - bts_init_cbch_state(&bts->cbch_extended, bts); + memcpy(bts->mr_full.gsm48_ie, &amr_fr_mr_cfg, sizeof(bts->mr_full.gsm48_ie)); + memcpy(bts->mr_half.gsm48_ie, &amr_hr_mr_cfg, sizeof(bts->mr_half.gsm48_ie)); + + /* ^ C/I (dB) | FR / HR | + * | | + * | | + * MODE4 | | + * = | ----+---- THR_MX_Up(3) | 20.5 / 18.0 | + * | | | + * | = ----+---- THR_MX_Dn(4) | 18.5 / 16.0 | + * MODE3 | | + * | = ----+---- THR_MX_Up(2) | 14.5 / 14.0 | + * | | | + * = | ----+---- THR_MX_Dn(3) | 12.5 / 12.0 | + * MODE2 | | + * = | ----+---- THR_MX_Up(1) | 8.5 / 10.0 | + * | | | + * | = ----+---- THR_MX_Dn(2) | 6.5 / 8.0 | + * MODE1 | | + * | | + * | | + */ + static const struct amr_mode amr_fr_ms_bts_mode[] = { + { + .mode = 0, /* 4.75k */ + .threshold = 13, /* THR_MX_Dn(2): 6.5 dB */ + .hysteresis = 4, /* THR_MX_Up(1): 8.5 dB */ + }, + { + .mode = 2, /* 5.90k */ + .threshold = 25, /* THR_MX_Dn(3): 12.5 dB */ + .hysteresis = 4, /* THR_MX_Up(2): 14.5 dB */ + }, + { + .mode = 5, /* 7.95k */ + .threshold = 37, /* THR_MX_Dn(4): 18.5 dB */ + .hysteresis = 4, /* THR_MX_Up(3): 20.5 dB */ + }, + { + .mode = 7, /* 12.2k */ + /* this is the last mode, so no threshold */ + }, + }; + static const struct amr_mode amr_hr_ms_bts_mode[] = { + { + .mode = 0, /* 4.75k */ + .threshold = 16, /* THR_MX_Dn(2): 8.0 dB */ + .hysteresis = 4, /* THR_MX_Up(1): 10.0 dB */ + }, + { + .mode = 2, /* 5.90k */ + .threshold = 24, /* THR_MX_Dn(3): 12.0 dB */ + .hysteresis = 4, /* THR_MX_Up(2): 14.0 dB */ + }, + { + .mode = 3, /* 6.70k */ + .threshold = 32, /* THR_MX_Dn(4): 16.0 dB */ + .hysteresis = 4, /* THR_MX_Up(3): 18.0 dB */ + }, + { + .mode = 5, /* 7.95k */ + /* this is the last mode, so no threshold */ + }, + }; + + memcpy(&bts->mr_full.ms_mode[0], &amr_fr_ms_bts_mode[0], sizeof(amr_fr_ms_bts_mode)); + memcpy(&bts->mr_full.bts_mode[0], &amr_fr_ms_bts_mode[0], sizeof(amr_fr_ms_bts_mode)); + bts->mr_full.num_modes = ARRAY_SIZE(amr_fr_ms_bts_mode); + + memcpy(&bts->mr_half.ms_mode[0], &amr_hr_ms_bts_mode[0], sizeof(amr_hr_ms_bts_mode)); + memcpy(&bts->mr_half.bts_mode[0], &amr_hr_ms_bts_mode[0], sizeof(amr_hr_ms_bts_mode)); + bts->mr_half.num_modes = ARRAY_SIZE(amr_hr_ms_bts_mode); + + bts->use_osmux = OSMUX_USAGE_OFF; + + bts_cbch_init(bts); + bts_etws_init(bts); + bts_setup_ramp_init_bts(bts); acc_mgr_init(&bts->acc_mgr, bts); acc_ramp_init(&bts->acc_ramp, bts); + /* Default RxQual threshold for ACCH repetition/overpower */ + bts->rep_acch_cap.rxqual = 4; + bts->top_acch_cap.rxqual = 4; + + /* Permit ACCH overpower only for speech channels using AMR */ + bts->top_acch_chan_mode = TOP_ACCH_CHAN_MODE_SPEECH_V3; + + /* MS Power Control parameters (defaults) */ + power_ctrl_params_def_reset(&bts->ms_power_ctrl, GSM_PWR_CTRL_DIR_UL); + + /* BS Power Control parameters (defaults) */ + power_ctrl_params_def_reset(&bts->bs_power_ctrl, GSM_PWR_CTRL_DIR_DL); + + /* Interference Measurement Parameters (defaults) */ + bts->interf_meas_params_cfg = interf_meas_params_def; + + bts->rach_max_delay = 63; + bts->rach_expiry_timeout = 32; + + /* SRVCC is enabled by default */ + bts->srvcc_fast_return_allowed = true; + return bts; } +/* Validate BTS configuration (ARFCN settings and physical channel configuration) */ +__attribute__((weak)) int gsm_bts_check_cfg(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + if (!bts->model) + return -EFAULT; + + switch (bts->band) { + case GSM_BAND_1800: + if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) { + LOGP(DNM, LOGL_ERROR, "(bts=%u) GSM1800 channel (%u) must be between 512-885.\n", + bts->nr, bts->c0->arfcn); + return -EINVAL; + } + break; + case GSM_BAND_1900: + if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) { + LOGP(DNM, LOGL_ERROR, "(bts=%u) GSM1900 channel (%u) must be between 512-810.\n", + bts->nr, bts->c0->arfcn); + return -EINVAL; + } + break; + case GSM_BAND_900: + if ((bts->c0->arfcn > 124 && bts->c0->arfcn < 955) || + bts->c0->arfcn > 1023) { + LOGP(DNM, LOGL_ERROR, "(bts=%u) GSM900 channel (%u) must be between 0-124, 955-1023.\n", + bts->nr, bts->c0->arfcn); + return -EINVAL; + } + break; + case GSM_BAND_850: + if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) { + LOGP(DNM, LOGL_ERROR, "(bts=%u) GSM850 channel (%u) must be between 128-251.\n", + bts->nr, bts->c0->arfcn); + return -EINVAL; + } + break; + default: + LOGP(DNM, LOGL_ERROR, "(bts=%u) Unsupported frequency band.\n", bts->nr); + return -EINVAL; + } + + if (bts->features_known) { + if (!bts_gprs_mode_is_compat(bts, bts->gprs.mode)) { + LOGP(DNM, LOGL_ERROR, "(bts=%u) GPRS mode set to '%s', but BTS does not support it\n", bts->nr, + bts_gprs_mode_name(bts->gprs.mode)); + return -EINVAL; + } + if (bts->use_osmux == OSMUX_USAGE_ONLY && + !osmo_bts_has_feature(&bts->features, BTS_FEAT_OSMUX)) { + LOGP(DNM, LOGL_ERROR, + "(bts=%u) osmux use set to 'only', but BTS does not support Osmux\n", + bts->nr); + return -EINVAL; + } + } + + /* Verify the physical channel mapping */ + llist_for_each_entry(trx, &bts->trx_list, list) { + if (!trx_has_valid_pchan_config(trx)) { + LOGP(DNM, LOGL_ERROR, "TRX %u has invalid timeslot " + "configuration\n", trx->nr); + return -EINVAL; + } + } + + if (!gsm_bts_check_ny1(bts)) + return -EINVAL; + + return 0; +} + static char ts2str[255]; char *gsm_bts_name(const struct gsm_bts *bts) @@ -387,6 +579,10 @@ bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cel case CELL_IDENT_WHOLE_GLOBAL: return gsm_bts_matches_lai(bts, &id->global.lai) && id->global.cell_identity == bts->cell_identity; + case CELL_IDENT_WHOLE_GLOBAL_PS: + return gsm_bts_matches_lai(bts, &id->global_ps.rai.lac) + && id->global_ps.rai.rac == bts->gprs.rac + && id->global_ps.cell_identity == bts->cell_identity; case CELL_IDENT_LAC_AND_CI: return id->lac_and_ci.lac == bts->location_area_code && id->lac_and_ci.ci == bts->cell_identity; @@ -403,96 +599,93 @@ bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cel case CELL_IDENT_UTRAN_PLMN_LAC_RNC: case CELL_IDENT_UTRAN_RNC: case CELL_IDENT_UTRAN_LAC_RNC: + case CELL_IDENT_SAI: return false; default: OSMO_ASSERT(false); } } -static struct gsm_bts_ref *gsm_bts_ref_find(const struct llist_head *list, const struct gsm_bts *bts) +/* Return a LAC+CI cell identity for the given BTS. + * (For matching a BTS within the local BSS, the PLMN code is not important.) */ +void gsm_bts_cell_id(struct gsm0808_cell_id *cell_id, const struct gsm_bts *bts) { - struct gsm_bts_ref *ref; - if (!bts) - return NULL; - llist_for_each_entry(ref, list, entry) { - if (ref->bts == bts) - return ref; - } - return NULL; -} - -/* Add a BTS reference to the local_neighbors list. - * Return 1 if added, 0 if such an entry already existed, and negative on errors. */ -int gsm_bts_local_neighbor_add(struct gsm_bts *bts, struct gsm_bts *neighbor) -{ - struct gsm_bts_ref *ref; - if (!bts || !neighbor) - return -ENOMEM; - - if (bts == neighbor) - return -EINVAL; - - /* Already got this entry? */ - ref = gsm_bts_ref_find(&bts->local_neighbors, neighbor); - if (ref) - return 0; - - ref = talloc_zero(bts, struct gsm_bts_ref); - if (!ref) - return -ENOMEM; - ref->bts = neighbor; - llist_add_tail(&ref->entry, &bts->local_neighbors); - return 1; + *cell_id = (struct gsm0808_cell_id){ + .id_discr = CELL_IDENT_LAC_AND_CI, + .id.lac_and_ci = { + .lac = bts->location_area_code, + .ci = bts->cell_identity, + }, + }; } -/* Remove a BTS reference from the local_neighbors list. - * Return 1 if removed, 0 if no such entry existed, and negative on errors. */ -int gsm_bts_local_neighbor_del(struct gsm_bts *bts, const struct gsm_bts *neighbor) +/* Same as gsm_bts_cell_id(), but return in a single-entry gsm0808_cell_id_list2. Useful for e.g. + * gsm0808_cell_id_list_add() and gsm0808_cell_id_lists_same(). */ +void gsm_bts_cell_id_list(struct gsm0808_cell_id_list2 *cell_id_list, const struct gsm_bts *bts) { - struct gsm_bts_ref *ref; - if (!bts || !neighbor) - return -ENOMEM; - - ref = gsm_bts_ref_find(&bts->local_neighbors, neighbor); - if (!ref) - return 0; - - llist_del(&ref->entry); - talloc_free(ref); - return 1; + struct gsm0808_cell_id cell_id; + struct gsm0808_cell_id_list2 add; + int rc; + gsm_bts_cell_id(&cell_id, bts); + gsm0808_cell_id_to_list(&add, &cell_id); + /* Since the target list is empty, this should always succeed. */ + (*cell_id_list) = (struct gsm0808_cell_id_list2){}; + rc = gsm0808_cell_id_list_add(cell_id_list, &add); + OSMO_ASSERT(rc > 0); } /* return the gsm_lchan for the CBCH (if it exists at all) */ struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts) { - struct gsm_lchan *lchan = NULL; struct gsm_bts_trx *trx = bts->c0; + /* According to 3GPP TS 45.002, table 3, CBCH can be allocated + * either on C0/TS0 (CCCH+SDCCH4) or on C0..n/TS0..3 (SDCCH/8). */ if (trx->ts[0].pchan_from_config == 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_from_config == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) { - lchan = &trx->ts[i].lchan[2]; - break; - } + return &trx->ts[0].lchan[2]; /* C0/TS0 */ + + llist_for_each_entry(trx, &bts->trx_list, list) { /* C0..n */ + unsigned int tn; + for (tn = 0; tn < 4; tn++) { /* TS0..3 */ + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + if (ts->pchan_from_config == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) + return &ts->lchan[2]; } } - return lchan; + return NULL; +} + +int gsm_set_bts_model(struct gsm_bts *bts, struct gsm_bts_model *model) +{ + bts->model = model; + + /* Copy hardcoded feature list from BTS model, unless the BTS supports + * reporting features at runtime (as of writing nanobts, OsmoBTS). */ + if (!model || model->features_get_reported) { + memset(bts->_features_data, 0, sizeof(bts->_features_data)); + bts->features_known = false; + } else { + memcpy(bts->_features_data, bts->model->_features_data, sizeof(bts->_features_data)); + bts->features_known = true; + } + + return 0; } int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type) { struct gsm_bts_model *model; + if (bts->type != GSM_BTS_TYPE_UNKNOWN && type != bts->type) + return -EBUSY; + model = bts_model_find(type); if (!model) return -EINVAL; bts->type = type; - bts->model = model; + gsm_set_bts_model(bts, model); if (model->start && !model->started) { int ret = model->start(bts->network); @@ -502,9 +695,22 @@ int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type) model->started = true; } + if (model->bts_init) { + int rc = model->bts_init(bts); + if (rc < 0) + return rc; + } + + /* handle those TRX which are already allocated at the time we set the type */ + if (model->trx_init) { + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) + model->trx_init(trx); + } + switch (bts->type) { - case GSM_BTS_TYPE_NANOBTS: case GSM_BTS_TYPE_OSMOBTS: + case GSM_BTS_TYPE_NANOBTS: /* Set the default OML Stream ID to 0xff */ bts->oml_tei = 0xff; bts->c0->nominal_power = 23; @@ -522,17 +728,21 @@ int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type) break; } + /* Enable dynamic Uplink power control by default (if supported) */ + if (model->power_ctrl_enc_rsl_params != NULL) + bts->ms_power_ctrl.mode = GSM_PWR_CTRL_MODE_DYN_BTS; + return 0; } int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode) { if (mode != BTS_GPRS_NONE && - !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_GPRS)) { + !osmo_bts_has_feature(&bts->features, BTS_FEAT_GPRS)) { return 0; } if (mode == BTS_GPRS_EGPRS && - !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_EGPRS)) { + !osmo_bts_has_feature(&bts->features, BTS_FEAT_EGPRS)) { return 0; } @@ -554,22 +764,81 @@ struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num) return NULL; } -unsigned long long bts_uptime(const struct gsm_bts *bts) +void bts_store_uptime(struct gsm_bts *bts) +{ + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_UPTIME_SECONDS), + bts->oml_link ? bts_updowntime(bts) : 0); +} + +void bts_store_lchan_durations(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + int i, j; + struct timespec now, elapsed; + uint64_t elapsed_ms; + uint64_t elapsed_tch_ms = 0; + uint64_t elapsed_sdcch_ms = 0; + + /* Ignore BTS that are not in operation. */ + if (!trx_is_usable(bts->c0)) + return; + + /* Grab storage time to be used for all lchans. */ + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + + /* Iterate over all lchans. */ + llist_for_each_entry(trx, &bts->trx_list, list) { + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + + /* Ignore lchans whose activation timestamps are not yet set. */ + if (lchan->active_stored.tv_sec == 0 && lchan->active_stored.tv_nsec == 0) + continue; + + /* Calculate elapsed time since last storage. */ + timespecsub(&now, &lchan->active_stored, &elapsed); + elapsed_ms = elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000; + + /* Assign elapsed time to appropriate bucket. */ + switch (lchan->type) { + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_TCH_F: + elapsed_tch_ms += elapsed_ms; + break; + case GSM_LCHAN_SDCCH: + elapsed_sdcch_ms += elapsed_ms; + break; + default: + continue; + } + + /* Update storage time. */ + lchan->active_stored = now; + } + } + } + + /* Export to rate counters. */ + rate_ctr_add(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_TCH_ACTIVE_MILLISECONDS_TOTAL), elapsed_tch_ms); + rate_ctr_add(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_SDCCH_ACTIVE_MILLISECONDS_TOTAL), elapsed_sdcch_ms); +} + +unsigned long long bts_updowntime(const struct gsm_bts *bts) { struct timespec tp; - if (!bts->uptime || !bts->oml_link) { - LOGP(DNM, LOGL_ERROR, "BTS %u OML link uptime unavailable\n", bts->nr); + if (!bts->updowntime) return 0; - } - if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { - LOGP(DNM, LOGL_ERROR, "BTS %u uptime computation failure: %s\n", bts->nr, strerror(errno)); + if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { + LOGP(DNM, LOGL_ERROR, "BTS %u uptime/downtime computation failure: %s\n", bts->nr, strerror(errno)); return 0; } /* monotonic clock helps to ensure that the conversion is valid */ - return difftime(tp.tv_sec, bts->uptime); + return difftime(tp.tv_sec, bts->updowntime); } char *get_model_oml_status(const struct gsm_bts *bts) @@ -587,10 +856,6 @@ void gsm_bts_mo_reset(struct gsm_bts *bts) unsigned int i; gsm_abis_mo_reset(&bts->mo); - gsm_abis_mo_reset(&bts->site_mgr.mo); - for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) - gsm_abis_mo_reset(&bts->gprs.nsvc[i].mo); - gsm_abis_mo_reset(&bts->gprs.nse.mo); gsm_abis_mo_reset(&bts->gprs.cell.mo); llist_for_each_entry(trx, &bts->trx_list, list) { @@ -617,7 +882,7 @@ void bts_depend_mark(struct gsm_bts *bts, int dep) int idx, bit; depends_calc_index_bit(dep, &idx, &bit); - bts->depends_on[idx] |= 1 << bit; + bts->depends_on[idx] |= 1U << bit; } void bts_depend_clear(struct gsm_bts *bts, int dep) @@ -625,7 +890,7 @@ void bts_depend_clear(struct gsm_bts *bts, int dep) int idx, bit; depends_calc_index_bit(dep, &idx, &bit); - bts->depends_on[idx] &= ~(1 << bit); + bts->depends_on[idx] &= ~(1U << bit); } int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other) @@ -634,17 +899,17 @@ int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other) depends_calc_index_bit(other->nr, &idx, &bit); /* Check if there is a depends bit */ - return (base->depends_on[idx] & (1 << bit)) > 0; + return (base->depends_on[idx] & (1U << bit)) > 0; } -static int bts_is_online(struct gsm_bts *bts) +static bool bts_is_online(const struct gsm_bts *bts) { /* TODO: support E1 BTS too */ - if (!is_ipaccess_bts(bts)) - return 1; + if (!is_ipa_abisip_bts(bts)) + return true; if (!bts->oml_link) - return 0; + return false; return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED; } @@ -707,19 +972,6 @@ void gsm_bts_all_ts_dispatch(struct gsm_bts *bts, uint32_t ts_ev, void *data) gsm_trx_all_ts_dispatch(trx, ts_ev, data); } - -/* 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; -} - /* set all system information types for a BTS */ int gsm_bts_set_system_infos(struct gsm_bts *bts) { @@ -740,19 +992,153 @@ int gsm_bts_set_system_infos(struct gsm_bts *bts) return 0; } +/* Send the given C0 power reduction value to the BTS */ +int gsm_bts_send_c0_power_red(const struct gsm_bts *bts, const uint8_t red) +{ + if (!bts_is_online(bts)) + return -ENOTCONN; + if (!osmo_bts_has_feature(&bts->features, BTS_FEAT_BCCH_POWER_RED)) + return -ENOTSUP; + if (bts->model->power_ctrl_send_c0_power_red == NULL) + return -ENOTSUP; + return bts->model->power_ctrl_send_c0_power_red(bts, red); +} + +/* Send the given C0 power reduction value to the BTS and update the internal state */ +int gsm_bts_set_c0_power_red(struct gsm_bts *bts, const uint8_t red) +{ + struct gsm_bts_trx *c0 = bts->c0; + unsigned int tn; + int rc; + + rc = gsm_bts_send_c0_power_red(bts, red); + if (rc != 0) + return rc; + + LOG_BTS(bts, DRSL, LOGL_NOTICE, "%sabling BCCH carrier power reduction " + "operation mode (maximum %u dB)\n", red ? "En" : "Dis", red); + + /* Timeslot 0 is always transmitting BCCH/CCCH */ + c0->ts[0].c0_max_power_red_db = 0; + + for (tn = 1; tn < ARRAY_SIZE(c0->ts); tn++) { + struct gsm_bts_trx_ts *ts = &c0->ts[tn]; + struct gsm_bts_trx_ts *prev = ts - 1; + + switch (ts->pchan_is) { + /* Not allowed on CCCH/BCCH */ + case GSM_PCHAN_CCCH: + /* Preceding timeslot shall not exceed 2 dB */ + if (prev->c0_max_power_red_db > 0) + prev->c0_max_power_red_db = 2; + /* fall-through */ + /* Not recommended on SDCCH/8 */ + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + ts->c0_max_power_red_db = 0; + break; + default: + ts->c0_max_power_red_db = red; + break; + } + } + + /* Timeslot 7 is always preceding BCCH/CCCH */ + if (c0->ts[7].c0_max_power_red_db > 0) + c0->ts[7].c0_max_power_red_db = 2; + + bts->c0_max_power_red_db = red; + + return 0; +} + +void gsm_bts_stats_reset(struct gsm_bts *bts) +{ + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_USED), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_USED), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_TOTAL), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_H_USED), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_H_TOTAL), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_USED), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_TOTAL), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_PDCH_USED), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_PDCH_TOTAL), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_USED), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_CBCH_USED), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_OSMO_DYN_USED), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_OSMO_DYN_TOTAL), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_T3113), 0); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_REQ_QUEUE_LENGTH), paging_pending_requests_nr(bts)); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_AVAILABLE_SLOTS), bts->paging.available_slots); +} + const struct rate_ctr_desc bts_ctr_description[] = { [BTS_CTR_CHREQ_TOTAL] = \ { "chreq:total", "Received channel requests" }, + [BTS_CTR_CHREQ_ATTEMPTED_EMERG] = \ + { "chreq:attempted_emerg", + "Received channel requests EMERG" }, + [BTS_CTR_CHREQ_ATTEMPTED_CALL] = \ + { "chreq:attempted_call", + "Received channel requests CALL" }, + [BTS_CTR_CHREQ_ATTEMPTED_LOCATION_UPD] = \ + { "chreq:attempted_location_upd", + "Received channel requests LOCATION_UPD" }, + [BTS_CTR_CHREQ_ATTEMPTED_PAG] = \ + { "chreq:attempted_pag", + "Received channel requests PAG" }, + [BTS_CTR_CHREQ_ATTEMPTED_PDCH] = \ + { "chreq:attempted_pdch", + "Received channel requests PDCH" }, + [BTS_CTR_CHREQ_ATTEMPTED_OTHER] = \ + { "chreq:attempted_other", + "Received channel requests OTHER" }, + [BTS_CTR_CHREQ_ATTEMPTED_UNKNOWN] = \ + { "chreq:attempted_unknown", + "Received channel requests UNKNOWN" }, [BTS_CTR_CHREQ_SUCCESSFUL] = \ { "chreq:successful", "Successful channel requests (immediate assign sent)" }, + [BTS_CTR_CHREQ_SUCCESSFUL_EMERG] = \ + { "chreq:successful_emerg", + "Sent Immediate Assignment for EMERG" }, + [BTS_CTR_CHREQ_SUCCESSFUL_CALL] = \ + { "chreq:successful_call", + "Sent Immediate Assignment for CALL" }, + [BTS_CTR_CHREQ_SUCCESSFUL_LOCATION_UPD] = \ + { "chreq:successful_location_upd", + "Sent Immediate Assignment for LOCATION_UPD" }, + [BTS_CTR_CHREQ_SUCCESSFUL_PAG] = \ + { "chreq:successful_pag", + "Sent Immediate Assignment for PAG" }, + [BTS_CTR_CHREQ_SUCCESSFUL_PDCH] = \ + { "chreq:successful_pdch", + "Sent Immediate Assignment for PDCH" }, + [BTS_CTR_CHREQ_SUCCESSFUL_OTHER] = \ + { "chreq:successful_other", + "Sent Immediate Assignment for OTHER" }, + [BTS_CTR_CHREQ_SUCCESSFUL_UNKNOWN] = \ + { "chreq:successful_unknown", + "Sent Immediate Assignment for UNKNOWN" }, [BTS_CTR_CHREQ_NO_CHANNEL] = \ { "chreq:no_channel", "Sent to MS no channel available" }, + [BTS_CTR_CHREQ_MAX_DELAY_EXCEEDED] = \ + { "chreq:max_delay_exceeded", + "Received channel requests with greater than permitted access delay" }, [BTS_CTR_CHAN_RF_FAIL] = \ { "chan:rf_fail", "Received a RF failure indication from BTS" }, + [BTS_CTR_CHAN_RF_FAIL_TCH] = \ + { "chan:rf_fail_tch", + "Received a RF failure indication from BTS on a TCH channel" }, + [BTS_CTR_CHAN_RF_FAIL_SDCCH] = \ + { "chan:rf_fail_sdcch", + "Received a RF failure indication from BTS on an SDCCH channel" }, [BTS_CTR_CHAN_RLL_ERR] = \ { "chan:rll_err", "Received a RLL failure with T200 cause from BTS" }, @@ -761,7 +1147,7 @@ const struct rate_ctr_desc bts_ctr_description[] = { "Received a TEI down on a OML link" }, [BTS_CTR_BTS_RSL_FAIL] = \ { "rsl_fail", - "Received a TEI down on a OML link" }, + "Received a TEI down on a RSL link" }, [BTS_CTR_CODEC_AMR_F] = \ { "codec:amr_f", "Count the usage of AMR/F codec by channel mode requested" }, @@ -795,12 +1181,33 @@ const struct rate_ctr_desc bts_ctr_description[] = { [BTS_CTR_PAGING_MSC_FLUSH] = \ { "paging:msc_flush", "Paging flushed due to MSC Reset BSSMAP message" }, + [BTS_CTR_PAGING_OVERLOAD] = \ + { "paging:overload", + "Paging dropped due to BSC Paging queue overload" }, [BTS_CTR_CHAN_ACT_TOTAL] = \ { "chan_act:total", "Total number of Channel Activations" }, + [BTS_CTR_CHAN_ACT_SDCCH] = \ + { "chan_act:sdcch", + "Number of SDCCH Channel Activations" }, + [BTS_CTR_CHAN_ACT_TCH] = \ + { "chan_act:tch", + "Number of TCH Channel Activations" }, [BTS_CTR_CHAN_ACT_NACK] = \ { "chan_act:nack", "Number of Channel Activations that the BTS NACKed" }, + [BTS_CTR_CHAN_TCH_ACTIVE_MILLISECONDS_TOTAL] = \ + { "chan_tch:active_milliseconds:total", + "Cumulative number of milliseconds of TCH channel activity" }, + [BTS_CTR_CHAN_SDCCH_ACTIVE_MILLISECONDS_TOTAL] = \ + { "chan_sdcch:active_milliseconds:total", + "Cumulative number of milliseconds of SDCCH channel activity" }, + [BTS_CTR_CHAN_TCH_FULLY_ESTABLISHED] = \ + { "chan_tch:fully_established", + "Number of TCH channels which have reached the fully established state" }, + [BTS_CTR_CHAN_SDCCH_FULLY_ESTABLISHED] = \ + { "chan_sdcch:fully_established", + "Number of SDCCH channels which have reached the fully established state" }, [BTS_CTR_RSL_UNKNOWN] = \ { "rsl:unknown", "Number of unknown/unsupported RSL messages received from BTS" }, @@ -851,6 +1258,9 @@ const struct rate_ctr_desc bts_ctr_description[] = { [BTS_CTR_LCHAN_BORKEN_EV_TEARDOWN] = \ { "lchan_borken:event:teardown", "lchan in a BORKEN state is shutting down (BTS disconnected?)" }, + [BTS_CTR_LCHAN_BORKEN_EV_TS_ERROR] = \ + { "lchan_borken:event:ts_error", + "LCHAN_EV_TS_ERROR received in a BORKEN state" }, [BTS_CTR_TS_BORKEN_FROM_NOT_INITIALIZED] = \ { "ts_borken:from_state:not_initialized", "Transitions from TS NOT_INITIALIZED state to BORKEN state" }, @@ -887,25 +1297,78 @@ const struct rate_ctr_desc bts_ctr_description[] = { [BTS_CTR_ASSIGNMENT_ATTEMPTED] = \ { "assignment:attempted", "Assignment attempts" }, + [BTS_CTR_ASSIGNMENT_ATTEMPTED_SIGN] = \ + { "assignment:attempted_sign", + "Assignment of signaling lchan attempts" }, + [BTS_CTR_ASSIGNMENT_ATTEMPTED_SPEECH] = \ + { "assignment:attempted_speech", + "Assignment of speech lchan attempts" }, [BTS_CTR_ASSIGNMENT_COMPLETED] = \ { "assignment:completed", "Assignment completed" }, + [BTS_CTR_ASSIGNMENT_COMPLETED_SIGN] = \ + { "assignment:completed_sign", + "Assignment of signaling lchan completed" }, + [BTS_CTR_ASSIGNMENT_COMPLETED_SPEECH] = \ + { "assignment:completed_speech", + "Assignment if speech lchan completed" }, [BTS_CTR_ASSIGNMENT_STOPPED] = \ { "assignment:stopped", "Connection ended during Assignment" }, + [BTS_CTR_ASSIGNMENT_STOPPED_SIGN] = \ + { "assignment:stopped_sign", + "Connection ended during signaling lchan Assignment" }, + [BTS_CTR_ASSIGNMENT_STOPPED_SPEECH] = \ + { "assignment:stopped_speech", + "Connection ended during speech lchan Assignment" }, [BTS_CTR_ASSIGNMENT_NO_CHANNEL] = \ { "assignment:no_channel", "Failure to allocate lchan for Assignment" }, + [BTS_CTR_ASSIGNMENT_NO_CHANNEL_SIGN] = \ + { "assignment:no_channel_sign", + "Failure to allocate signaling lchan for Assignment" }, + [BTS_CTR_ASSIGNMENT_NO_CHANNEL_SPEECH] = \ + { "assignment:no_channel_speech", + "Failure to allocate speech lchan for Assignment" }, [BTS_CTR_ASSIGNMENT_TIMEOUT] = \ { "assignment:timeout", "Assignment timed out" }, + [BTS_CTR_ASSIGNMENT_TIMEOUT_SIGN] = \ + { "assignment:timeout_sign", + "Assignment of signaling lchan timed out" }, + [BTS_CTR_ASSIGNMENT_TIMEOUT_SPEECH] = \ + { "assignment:timeout_speech", + "Assignment of speech lchan timed out" }, [BTS_CTR_ASSIGNMENT_FAILED] = \ { "assignment:failed", "Received Assignment Failure message" }, + [BTS_CTR_ASSIGNMENT_FAILED_SIGN] = \ + { "assignment:failed_sign", + "Received Assignment Failure message on signaling lchan" }, + [BTS_CTR_ASSIGNMENT_FAILED_SPEECH] = \ + { "assignment:failed_speech", + "Received Assignment Failure message on speech lchan" }, [BTS_CTR_ASSIGNMENT_ERROR] = \ { "assignment:error", "Assignment failed for other reason" }, - + [BTS_CTR_ASSIGNMENT_ERROR_SIGN] = \ + { "assignment:error_sign", + "Assignment of signaling lchan failed for other reason" }, + [BTS_CTR_ASSIGNMENT_ERROR_SPEECH] = \ + { "assignment:error_speech", + "Assignment of speech lchan failed for other reason" }, + [BTS_CTR_LOCATION_UPDATE_ACCEPT] = \ + { "location_update:accept", + "Location Update Accept" }, + [BTS_CTR_LOCATION_UPDATE_REJECT] = \ + { "location_update:reject", + "Location Update Reject" }, + [BTS_CTR_LOCATION_UPDATE_DETACH] = \ + { "location_update:detach", + "Location Update Detach" }, + [BTS_CTR_LOCATION_UPDATE_UNKNOWN] = \ + { "location_update:unknown", + "Location Update UNKNOWN" }, [BTS_CTR_HANDOVER_ATTEMPTED] = \ { "handover:attempted", "Intra-BSC handover attempts" }, @@ -952,10 +1415,10 @@ const struct rate_ctr_desc bts_ctr_description[] = { [BTS_CTR_INTRA_BSC_HO_ATTEMPTED] = \ { "intra_bsc_ho:attempted", - "Intra-BSC handover attempts" }, + "Intra-BSC inter-cell handover attempts" }, [BTS_CTR_INTRA_BSC_HO_COMPLETED] = \ { "intra_bsc_ho:completed", - "Intra-BSC handover completed" }, + "Intra-BSC inter-cell handover completed" }, [BTS_CTR_INTRA_BSC_HO_STOPPED] = \ { "intra_bsc_ho:stopped", "Connection ended during HO" }, @@ -970,7 +1433,29 @@ const struct rate_ctr_desc bts_ctr_description[] = { "Received Handover Fail messages" }, [BTS_CTR_INTRA_BSC_HO_ERROR] = \ { "intra_bsc_ho:error", - "Re-assignment failed for other reason" }, + "Intra-BSC inter-cell HO failed for other reason" }, + + [BTS_CTR_INCOMING_INTRA_BSC_HO_ATTEMPTED] = \ + { "incoming_intra_bsc_ho:attempted", + "Incoming intra-BSC inter-cell handover attempts" }, + [BTS_CTR_INCOMING_INTRA_BSC_HO_COMPLETED] = \ + { "incoming_intra_bsc_ho:completed", + "Incoming intra-BSC inter-cell handover completed" }, + [BTS_CTR_INCOMING_INTRA_BSC_HO_STOPPED] = \ + { "incoming_intra_bsc_ho:stopped", + "Connection ended during HO" }, + [BTS_CTR_INCOMING_INTRA_BSC_HO_NO_CHANNEL] = \ + { "incoming_intra_bsc_ho:no_channel", + "Failure to allocate lchan for HO" }, + [BTS_CTR_INCOMING_INTRA_BSC_HO_TIMEOUT] = \ + { "incoming_intra_bsc_ho:timeout", + "Handover timed out" }, + [BTS_CTR_INCOMING_INTRA_BSC_HO_FAILED] = \ + { "incoming_intra_bsc_ho:failed", + "Received Handover Fail messages" }, + [BTS_CTR_INCOMING_INTRA_BSC_HO_ERROR] = \ + { "incoming_intra_bsc_ho:error", + "Incoming intra-BSC inter-cell HO failed for other reason" }, [BTS_CTR_INTER_BSC_HO_OUT_ATTEMPTED] = \ { "interbsc_ho_out:attempted", @@ -1011,6 +1496,115 @@ const struct rate_ctr_desc bts_ctr_description[] = { [BTS_CTR_INTER_BSC_HO_IN_ERROR] = \ { "interbsc_ho_in:error", "Handover from remote BSS failed for other reason" }, + + [BTS_CTR_SRVCC_ATTEMPTED] = \ + { "srvcc:attempted", + "Intra-BSC handover attempts" }, + [BTS_CTR_SRVCC_COMPLETED] = \ + { "srvcc:completed", + "Intra-BSC handover completed" }, + [BTS_CTR_SRVCC_STOPPED] = \ + { "srvcc:stopped", + "Connection ended during HO" }, + [BTS_CTR_SRVCC_NO_CHANNEL] = \ + { "srvcc:no_channel", + "Failure to allocate lchan for HO" }, + [BTS_CTR_SRVCC_TIMEOUT] = \ + { "srvcc:timeout", + "Handover timed out" }, + [BTS_CTR_SRVCC_FAILED] = \ + { "srvcc:failed", + "Received Handover Fail messages" }, + [BTS_CTR_SRVCC_ERROR] = \ + { "srvcc:error", + "Re-assignment failed for other reason" }, + [BTS_CTR_ALL_ALLOCATED_SDCCH] = \ + { "all_allocated:sdcch", + "Cumulative counter of seconds where all SDCCH channels were allocated" }, + [BTS_CTR_ALL_ALLOCATED_STATIC_SDCCH] = \ + { "all_allocated:static_sdcch", + "Cumulative counter of seconds where all non-dynamic SDCCH channels were allocated" }, + [BTS_CTR_ALL_ALLOCATED_TCH] = \ + { "all_allocated:tch", + "Cumulative counter of seconds where all TCH channels were allocated" }, + [BTS_CTR_ALL_ALLOCATED_STATIC_TCH] = \ + { "all_allocated:static_tch", + "Cumulative counter of seconds where all non-dynamic TCH channels were allocated" }, + + [BTS_CTR_CM_SERV_REJ] = \ + { "cm_serv_rej", "MSC sent CM Service Reject" }, + [BTS_CTR_CM_SERV_REJ_IMSI_UNKNOWN_IN_HLR] = \ + { "cm_serv_rej:imsi_unknown_in_hlr", + "MSC sent CM Service Reject with cause IMSI_UNKNOWN_IN_HLR" }, + [BTS_CTR_CM_SERV_REJ_ILLEGAL_MS] = \ + { "cm_serv_rej:illegal_ms", + "MSC sent CM Service Reject with cause ILLEGAL_MS" }, + [BTS_CTR_CM_SERV_REJ_IMSI_UNKNOWN_IN_VLR] = \ + { "cm_serv_rej:imsi_unknown_in_vlr", + "MSC sent CM Service Reject with cause IMSI_UNKNOWN_IN_VLR" }, + [BTS_CTR_CM_SERV_REJ_IMEI_NOT_ACCEPTED] = \ + { "cm_serv_rej:imei_not_accepted", + "MSC sent CM Service Reject with cause IMEI_NOT_ACCEPTED" }, + [BTS_CTR_CM_SERV_REJ_ILLEGAL_ME] = \ + { "cm_serv_rej:illegal_me", + "MSC sent CM Service Reject with cause ILLEGAL_ME" }, + [BTS_CTR_CM_SERV_REJ_PLMN_NOT_ALLOWED] = \ + { "cm_serv_rej:plmn_not_allowed", + "MSC sent CM Service Reject with cause PLMN_NOT_ALLOWED" }, + [BTS_CTR_CM_SERV_REJ_LOC_NOT_ALLOWED] = \ + { "cm_serv_rej:loc_not_allowed", + "MSC sent CM Service Reject with cause LOC_NOT_ALLOWED" }, + [BTS_CTR_CM_SERV_REJ_ROAMING_NOT_ALLOWED] = \ + { "cm_serv_rej:roaming_not_allowed", + "MSC sent CM Service Reject with cause ROAMING_NOT_ALLOWED" }, + [BTS_CTR_CM_SERV_REJ_NETWORK_FAILURE] = \ + { "cm_serv_rej:network_failure", + "MSC sent CM Service Reject with cause NETWORK_FAILURE" }, + [BTS_CTR_CM_SERV_REJ_SYNCH_FAILURE] = \ + { "cm_serv_rej:synch_failure", + "MSC sent CM Service Reject with cause SYNCH_FAILURE" }, + [BTS_CTR_CM_SERV_REJ_CONGESTION] = \ + { "cm_serv_rej:congestion", + "MSC sent CM Service Reject with cause CONGESTION" }, + [BTS_CTR_CM_SERV_REJ_SRV_OPT_NOT_SUPPORTED] = \ + { "cm_serv_rej:srv_opt_not_supported", + "MSC sent CM Service Reject with cause SRV_OPT_NOT_SUPPORTED" }, + [BTS_CTR_CM_SERV_REJ_RQD_SRV_OPT_NOT_SUPPORTED] = \ + { "cm_serv_rej:rqd_srv_opt_not_supported", + "MSC sent CM Service Reject with cause RQD_SRV_OPT_NOT_SUPPORTED" }, + [BTS_CTR_CM_SERV_REJ_SRV_OPT_TMP_OUT_OF_ORDER] = \ + { "cm_serv_rej:srv_opt_tmp_out_of_order", + "MSC sent CM Service Reject with cause SRV_OPT_TMP_OUT_OF_ORDER" }, + [BTS_CTR_CM_SERV_REJ_CALL_CAN_NOT_BE_IDENTIFIED] = \ + { "cm_serv_rej:call_can_not_be_identified", + "MSC sent CM Service Reject with cause CALL_CAN_NOT_BE_IDENTIFIED" }, + [BTS_CTR_CM_SERV_REJ_INCORRECT_MESSAGE] = \ + { "cm_serv_rej:incorrect_message", + "MSC sent CM Service Reject with cause INCORRECT_MESSAGE" }, + [BTS_CTR_CM_SERV_REJ_INVALID_MANDANTORY_INF] = \ + { "cm_serv_rej:invalid_mandantory_inf", + "MSC sent CM Service Reject with cause INVALID_MANDANTORY_INF" }, + [BTS_CTR_CM_SERV_REJ_MSG_TYPE_NOT_IMPLEMENTED] = \ + { "cm_serv_rej:msg_type_not_implemented", + "MSC sent CM Service Reject with cause MSG_TYPE_NOT_IMPLEMENTED" }, + [BTS_CTR_CM_SERV_REJ_MSG_TYPE_NOT_COMPATIBLE] = \ + { "cm_serv_rej:msg_type_not_compatible", + "MSC sent CM Service Reject with cause MSG_TYPE_NOT_COMPATIBLE" }, + [BTS_CTR_CM_SERV_REJ_INF_ELEME_NOT_IMPLEMENTED] = \ + { "cm_serv_rej:inf_eleme_not_implemented", + "MSC sent CM Service Reject with cause INF_ELEME_NOT_IMPLEMENTED" }, + [BTS_CTR_CM_SERV_REJ_CONDTIONAL_IE_ERROR] = \ + { "cm_serv_rej:condtional_ie_error", + "MSC sent CM Service Reject with cause CONDTIONAL_IE_ERROR" }, + [BTS_CTR_CM_SERV_REJ_MSG_NOT_COMPATIBLE] = \ + { "cm_serv_rej:msg_not_compatible", + "MSC sent CM Service Reject with cause MSG_NOT_COMPATIBLE" }, + [BTS_CTR_CM_SERV_REJ_PROTOCOL_ERROR] = \ + { "cm_serv_rej:protocol_error", + "MSC sent CM Service Reject with cause PROTOCOL_ERROR" }, + [BTS_CTR_CM_SERV_REJ_RETRY_IN_NEW_CELL] = \ + { "cm_serv_rej:retry_in_new_cell", + "MSC sent CM Service Reject with cause 00110000..00111111, Retry upon entry in a new cell" }, }; const struct rate_ctr_group_desc bts_ctrg_desc = { @@ -1022,93 +1616,97 @@ const struct rate_ctr_group_desc bts_ctrg_desc = { }; const struct osmo_stat_item_desc bts_stat_desc[] = { + [BTS_STAT_UPTIME_SECONDS] = \ + { "uptime:seconds", + "Seconds of uptime", + "s", 60, 0 }, [BTS_STAT_CHAN_LOAD_AVERAGE] = \ { "chanloadavg", "Channel load average", - "%", 16, 0 }, + "%", 60, 0 }, [BTS_STAT_CHAN_CCCH_SDCCH4_USED] = \ { "chan_ccch_sdcch4:used", "Number of CCCH+SDCCH4 channels used", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL] = \ { "chan_ccch_sdcch4:total", "Number of CCCH+SDCCH4 channels total", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_TCH_F_USED] = \ { "chan_tch_f:used", "Number of TCH/F channels used", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_TCH_F_TOTAL] = \ { "chan_tch_f:total", "Number of TCH/F channels total", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_TCH_H_USED] = \ { "chan_tch_h:used", "Number of TCH/H channels used", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_TCH_H_TOTAL] = \ { "chan_tch_h:total", "Number of TCH/H channels total", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_SDCCH8_USED] = \ { "chan_sdcch8:used", "Number of SDCCH8 channels used", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_SDCCH8_TOTAL] = \ { "chan_sdcch8:total", "Number of SDCCH8 channels total", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_TCH_F_PDCH_USED] = \ - { "chan_tch_f_pdch:used", - "Number of TCH/F_PDCH channels used", - "", 16, 0 }, + { "chan_dynamic_ipaccess:used", + "Number of DYNAMIC/IPACCESS channels used", + "", 60, 0 }, [BTS_STAT_CHAN_TCH_F_PDCH_TOTAL] = \ - { "chan_tch_f_pdch:total", - "Number of TCH/F_PDCH channels total", - "", 16, 0 }, + { "chan_dynamic_ipaccess:total", + "Number of DYNAMIC/IPACCESS channels total", + "", 60, 0 }, [BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_USED] = \ { "chan_ccch_sdcch4_cbch:used", "Number of CCCH+SDCCH4+CBCH channels used", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL] = \ { "chan_ccch_sdcch4_cbch:total", "Number of CCCH+SDCCH4+CBCH channels total", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_SDCCH8_CBCH_USED] = \ { "chan_sdcch8_cbch:used", "Number of SDCCH8+CBCH channels used", - "", 16, 0 }, + "", 60, 0 }, [BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL] = \ { "chan_sdcch8_cbch:total", "Number of SDCCH8+CBCH channels total", - "", 16, 0 }, - [BTS_STAT_CHAN_TCH_F_TCH_H_PDCH_USED] = \ - { "chan_tch_f_tch_h_pdch:used", - "Number of TCH/F_TCH/H_PDCH channels used", - "", 16, 0 }, - [BTS_STAT_CHAN_TCH_F_TCH_H_PDCH_TOTAL] = \ - { "chan_tch_f_tch_h_pdch:total", - "Number of TCH/F_TCH/H_PDCH channels total", - "", 16, 0 }, + "", 60, 0 }, + [BTS_STAT_CHAN_OSMO_DYN_USED] = \ + { "chan_dynamic_osmocom:used", + "Number of DYNAMIC/OSMOCOM channels used", + "", 60, 0 }, + [BTS_STAT_CHAN_OSMO_DYN_TOTAL] = \ + { "chan_dynamic_osmocom:total", + "Number of DYNAMIC/OSMOCOM channels total", + "", 60, 0 }, [BTS_STAT_T3122] = \ { "T3122", "T3122 IMMEDIATE ASSIGNMENT REJECT wait indicator", - "s", 16, GSM_T3122_DEFAULT }, + "s", 60, GSM_T3122_DEFAULT }, [BTS_STAT_RACH_BUSY] = \ { "rach_busy", "RACH slots with signal above threshold", - "%", 16, 0 }, + "%", 60, 0 }, [BTS_STAT_RACH_ACCESS] = \ { "rach_access", "RACH slots with access bursts in them", - "%", 16, 0 }, + "%", 60, 0 }, [BTS_STAT_OML_CONNECTED] = \ { "oml_connected", "Number of OML links connected", "", 16, 0 }, [BTS_STAT_RSL_CONNECTED] = \ { "rsl_connected", - "Number of RSL links connected", + "Number of RSL links connected (same as num_trx:rsl_connected)", "", 16, 0 }, [BTS_STAT_LCHAN_BORKEN] = \ { "lchan_borken", @@ -1118,6 +1716,26 @@ const struct osmo_stat_item_desc bts_stat_desc[] = { { "ts_borken", "Number of timeslots in the BORKEN state", "", 16, 0 }, + [BTS_STAT_NUM_TRX_RSL_CONNECTED] = \ + { "num_trx:rsl_connected", + "Number of TRX in this BTS where RSL is up", + "" }, + [BTS_STAT_NUM_TRX_TOTAL] = \ + { "num_trx:total", + "Number of configured TRX in this BTS", + "" }, + [BTS_STAT_PAGING_REQ_QUEUE_LENGTH] = \ + { "paging:request_queue_length", + "Paging Request queue length", + "", 60, 0 }, + [BTS_STAT_PAGING_AVAILABLE_SLOTS] = \ + { "paging:available_slots", + "Available paging slots in this BTS", + "", 60, 0 }, + [BTS_STAT_PAGING_T3113] = \ + { "paging:t3113", + "T3113 paging timer", + "s", 60, 0 }, }; const struct osmo_stat_item_group_desc bts_statg_desc = { @@ -1127,3 +1745,20 @@ const struct osmo_stat_item_group_desc bts_statg_desc = { .num_items = ARRAY_SIZE(bts_stat_desc), .item_desc = bts_stat_desc, }; + +/* Return 'true' if and only if Ny1 satisfies network requirements */ +bool gsm_bts_check_ny1(const struct gsm_bts *bts) +{ + unsigned long T3105, ny1, ny1_recommended; + T3105 = osmo_tdef_get(bts->network->T_defs, 3105, OSMO_TDEF_MS, -1); + ny1 = osmo_tdef_get(bts->network->T_defs, -3105, OSMO_TDEF_CUSTOM, -1); + if (!(T3105 * ny1 > GSM_T3124_MAX + GSM_NY1_REQ_DELTA)) { + /* See comment for GSM_NY1_DEFAULT */ + ny1_recommended = (GSM_T3124_MAX + GSM_NY1_REQ_DELTA)/T3105 + 1; + LOGP(DNM, LOGL_ERROR, "Value of Ny1 should be higher. " + "Is: %lu, lowest recommendation: %lu\n", + ny1, ny1_recommended); + return false; + } + return true; +} diff --git a/src/osmo-bsc/bts_ctrl.c b/src/osmo-bsc/bts_ctrl.c new file mode 100644 index 000000000..814a17dca --- /dev/null +++ b/src/osmo-bsc/bts_ctrl.c @@ -0,0 +1,1580 @@ +/* + * (C) 2013-2015 by Holger Hans Peter Freyther + * (C) 2013-2022 by sysmocom s.f.m.c. GmbH + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <time.h> + +#include <osmocom/ctrl/control_cmd.h> + +#include <osmocom/bsc/ctrl.h> +#include <osmocom/bsc/osmo_bsc_rf.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/ipaccess.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/neighbor_ident.h> +#include <osmocom/bsc/system_information.h> + +#include <osmocom/gsm/sysinfo.h> + +static int location_equal(struct bts_location *a, struct bts_location *b) +{ + return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) && + (a->lon == b->lon) && (a->height == b->height)); +} + +static void cleanup_locations(struct llist_head *locations) +{ + struct bts_location *myloc, *tmp; + int invalpos = 0, i = 0; + + LOGP(DCTRL, LOGL_DEBUG, "Checking position list.\n"); + llist_for_each_entry_safe(myloc, tmp, locations, list) { + i++; + if (i > 3) { + LOGP(DCTRL, LOGL_DEBUG, "Deleting old position.\n"); + llist_del(&myloc->list); + talloc_free(myloc); + } else if (myloc->valid == BTS_LOC_FIX_INVALID) { + /* Only capture the newest of subsequent invalid positions */ + invalpos++; + if (invalpos > 1) { + LOGP(DCTRL, LOGL_DEBUG, "Deleting subsequent invalid position.\n"); + invalpos--; + i--; + llist_del(&myloc->list); + talloc_free(myloc); + } + } else { + invalpos = 0; + } + } + LOGP(DCTRL, LOGL_DEBUG, "Found %d positions.\n", i); +} + +static int get_bts_loc(struct ctrl_cmd *cmd, void *data); + +void ctrl_generate_bts_location_state_trap(struct gsm_bts *bts, struct bsc_msc_data *msc) +{ + struct ctrl_cmd *cmd; + const char *oper, *admin, *policy; + + cmd = ctrl_cmd_create(msc, CTRL_TYPE_TRAP); + if (!cmd) { + LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n"); + return; + } + + cmd->id = "0"; + cmd->variable = talloc_asprintf(cmd, "bts.%d.location-state", bts->nr); + + /* Prepare the location reply */ + cmd->node = bts; + get_bts_loc(cmd, NULL); + + oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts)); + admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts)); + policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts)); + + cmd->reply = talloc_asprintf_append(cmd->reply, + ",%s,%s,%s,%s,%s", + oper, admin, policy, + osmo_mcc_name(bts->network->plmn.mcc), + osmo_mnc_name(bts->network->plmn.mnc, + bts->network->plmn.mnc_3_digits)); + + osmo_bsc_send_trap(cmd, msc); + talloc_free(cmd); +} + +void bsc_gen_location_state_trap(struct gsm_bts *bts) +{ + struct bsc_msc_data *msc; + + llist_for_each_entry(msc, &bts->network->mscs, entry) + ctrl_generate_bts_location_state_trap(bts, msc); +} + +CTRL_CMD_DEFINE(bts_loc, "location"); +static int get_bts_loc(struct ctrl_cmd *cmd, void *data) +{ + struct bts_location *curloc; + struct gsm_bts *bts = (struct gsm_bts *) cmd->node; + if (!bts) { + cmd->reply = "bts not found."; + return CTRL_CMD_ERROR; + } + + if (llist_empty(&bts->loc_list)) { + cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0"); + return CTRL_CMD_REPLY; + } + + curloc = llist_entry(bts->loc_list.next, struct bts_location, list); + + cmd->reply = talloc_asprintf(cmd, "%lu,%s,%f,%f,%f", curloc->tstamp, + get_value_string(bts_loc_fix_names, curloc->valid), curloc->lat, curloc->lon, curloc->height); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_loc(struct ctrl_cmd *cmd, void *data) +{ + char *saveptr, *lat, *lon, *height, *tstamp, *valid, *tmp; + struct bts_location *curloc, *lastloc; + int ret; + struct gsm_bts *bts = (struct gsm_bts *) cmd->node; + if (!bts) { + cmd->reply = "bts not found."; + return CTRL_CMD_ERROR; + } + + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) + goto oom; + + tstamp = strtok_r(tmp, ",", &saveptr); + valid = strtok_r(NULL, ",", &saveptr); + lat = strtok_r(NULL, ",", &saveptr); + lon = strtok_r(NULL, ",", &saveptr); + height = strtok_r(NULL, "\0", &saveptr); + + /* Check if one of the strtok results was NULL. This will probably never occur since we will only see verified + * input in this code path */ + if ((tstamp == NULL) || (valid == NULL) || (lat == NULL) || (lon == NULL) || (height == NULL)) { + talloc_free(tmp); + cmd->reply = "parse error"; + return CTRL_CMD_ERROR; + } + + curloc = talloc_zero(tall_bsc_ctx, struct bts_location); + if (!curloc) { + talloc_free(tmp); + goto oom; + } + INIT_LLIST_HEAD(&curloc->list); + + curloc->tstamp = atol(tstamp); + curloc->valid = get_string_value(bts_loc_fix_names, valid); + curloc->lat = atof(lat); + curloc->lon = atof(lon); + curloc->height = atof(height); + talloc_free(tmp); + + lastloc = llist_entry(bts->loc_list.next, struct bts_location, list); + + /* Add location to the end of the list */ + llist_add(&curloc->list, &bts->loc_list); + + ret = get_bts_loc(cmd, data); + + if (!location_equal(curloc, lastloc)) + bsc_gen_location_state_trap(bts); + + cleanup_locations(&bts->loc_list); + + return ret; + +oom: + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; +} + +static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data) +{ + char *saveptr, *latstr, *lonstr, *heightstr, *tstampstr, *validstr, *tmp; + time_t tstamp; + int valid; + double lat, lon, height __attribute__((unused)); + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + tstampstr = strtok_r(tmp, ",", &saveptr); + validstr = strtok_r(NULL, ",", &saveptr); + latstr = strtok_r(NULL, ",", &saveptr); + lonstr = strtok_r(NULL, ",", &saveptr); + heightstr = strtok_r(NULL, "\0", &saveptr); + + if ((tstampstr == NULL) || (validstr == NULL) || (latstr == NULL) || + (lonstr == NULL) || (heightstr == NULL)) + goto err; + + tstamp = atol(tstampstr); + valid = get_string_value(bts_loc_fix_names, validstr); + lat = atof(latstr); + lon = atof(lonstr); + height = atof(heightstr); + talloc_free(tmp); + tmp = NULL; + + if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) || + (lon < -180) || (lon > 180) || (valid < 0)) { + goto err; + } + + return 0; + +err: + talloc_free(tmp); + cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>"); + return 1; +} + +/* BTS related commands below */ +CTRL_CMD_DEFINE_RANGE(bts_lac, "location-area-code", struct gsm_bts, location_area_code, 0, 65535); +CTRL_CMD_DEFINE_RANGE(bts_ci, "cell-identity", struct gsm_bts, cell_identity, 0, 65535); +CTRL_CMD_DEFINE_RANGE(bts_bsic, "bsic", struct gsm_bts, bsic, 0, 63); +CTRL_CMD_DEFINE_RANGE(bts_rach_max_delay, "rach-max-delay", struct gsm_bts, rach_max_delay, 1, 127); +CTRL_CMD_DEFINE_RANGE(bts_rach_expiry_timeout, "rach-expiry-timeout", struct gsm_bts, rach_expiry_timeout, 4, 64); +CTRL_CMD_DEFINE_RANGE(bts_ms_max_power, "ms-max-power", struct gsm_bts, ms_max_power, 0, 40); + +static int set_bts_apply_config(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + if (!is_ipa_abisip_bts(bts)) { + cmd->reply = "BTS is not IPA Abis/IP based"; + return CTRL_CMD_ERROR; + } + + ipaccess_drop_oml(bts, "ctrl bts.apply-configuration"); + cmd->reply = "Tried to drop the BTS"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(bts_apply_config, "apply-configuration"); + +static int set_bts_si(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + rc = gsm_bts_set_system_infos(bts); + if (rc != 0) { + cmd->reply = "Failed to generate SI"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "Generated new System Information"; + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_WO_NOVRF(bts_si, "send-new-system-informations"); + +static int set_bts_power_ctrl_defs(struct ctrl_cmd *cmd, void *data) +{ + const struct gsm_bts *bts = cmd->node; + const struct gsm_bts_trx *trx; + + if (bts->ms_power_ctrl.mode != GSM_PWR_CTRL_MODE_DYN_BTS) { + cmd->reply = "BTS is not using dyn-bts mode"; + return CTRL_CMD_ERROR; + } + + if (bts->model->power_ctrl_send_def_params == NULL) { + cmd->reply = "Not implemented for this BTS model"; + return CTRL_CMD_ERROR; + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (bts->model->power_ctrl_send_def_params(trx) != 0) { + cmd->reply = "power_ctrl_send_def_params() failed"; + return CTRL_CMD_ERROR; + } + } + + cmd->reply = "Default power control parameters have been sent"; + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_WO_NOVRF(bts_power_ctrl_defs, "send-power-control-defaults"); + +static int get_bts_chan_load(struct ctrl_cmd *cmd, void *data) +{ + int i; + struct pchan_load pl; + struct gsm_bts *bts; + const char *space = ""; + + bts = cmd->node; + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + + cmd->reply = talloc_strdup(cmd, ""); + + for (i = 0; i < ARRAY_SIZE(pl.pchan); ++i) { + const struct load_counter *lc = &pl.pchan[i]; + + /* These can never have user load */ + if (i == GSM_PCHAN_NONE) + continue; + if (i == GSM_PCHAN_CCCH) + continue; + if (i == GSM_PCHAN_PDCH) + continue; + if (i == GSM_PCHAN_UNKNOWN) + continue; + + cmd->reply = talloc_asprintf_append(cmd->reply, + "%s%s,%u,%u", + space, gsm_pchan_name(i), lc->used, lc->total); + if (!cmd->reply) + goto error; + space = " "; + } + + return CTRL_CMD_REPLY; + +error: + cmd->reply = "Memory allocation failure"; + return CTRL_CMD_ERROR; +} + +CTRL_CMD_DEFINE_RO(bts_chan_load, "channel-load"); + +static int get_bts_oml_conn(struct ctrl_cmd *cmd, void *data) +{ + const struct gsm_bts *bts = cmd->node; + + cmd->reply = get_model_oml_status(bts); + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(bts_oml_conn, "oml-connection-state"); + +static int get_bts_oml_up(struct ctrl_cmd *cmd, void *data) +{ + const struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%llu", bts->oml_link ? bts_updowntime(bts) : 0); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(bts_oml_up, "oml-uptime"); + +static int verify_bts_gprs_mode(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int valid; + enum bts_gprs_mode mode; + struct gsm_bts *bts = cmd->node; + + mode = bts_gprs_mode_parse(value, &valid); + if (!valid) { + cmd->reply = "Mode is not known"; + return 1; + } + + if (!bts_gprs_mode_is_compat(bts, mode)) { + cmd->reply = "bts does not support this mode"; + return 1; + } + + return 0; +} + +static int get_bts_gprs_mode(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_strdup(cmd, bts_gprs_mode_name(bts->gprs.mode)); + return CTRL_CMD_REPLY; +} + +static int set_bts_gprs_mode(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + bts->gprs.mode = bts_gprs_mode_parse(cmd->value, NULL); + return get_bts_gprs_mode(cmd, data); +} + +CTRL_CMD_DEFINE(bts_gprs_mode, "gprs-mode"); + +static int get_bts_rf_state(struct ctrl_cmd *cmd, void *data) +{ + const char *oper, *admin, *policy; + struct gsm_bts *bts = cmd->node; + + if (!bts) { + cmd->reply = "bts not found."; + return CTRL_CMD_ERROR; + } + + oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts)); + admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts)); + policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts)); + + cmd->reply = talloc_asprintf(cmd, "%s,%s,%s", oper, admin, policy); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(bts_rf_state, "rf_state"); + +/* Return a list of the states of each TRX for a given BTS. + * <bts_nr>,<trx_nr>,<opstate>,<adminstate>,<rf_policy>,<rsl_status>;<bts_nr>,<trx_nr>,...;...; + * For details on the string, see bsc_rf_states_c(); + */ +static int get_bts_rf_states(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + if (!bts) { + cmd->reply = "bts not found."; + return CTRL_CMD_ERROR; + } + + cmd->reply = bsc_rf_states_of_bts_c(cmd, bts); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(bts_rf_states, "rf_states"); + +static int verify_bts_c0_power_red(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + const int red = atoi(value); + + if (red < 0 || red > 6) { + cmd->reply = "Value is out of range"; + return 1; + } else if (red % 2 != 0) { + cmd->reply = "Value must be even"; + return 1; + } + + return 0; +} + +static int get_bts_c0_power_red(struct ctrl_cmd *cmd, void *data) +{ + const struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%u", bts->c0_max_power_red_db); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_c0_power_red(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + const int red = atoi(cmd->value); + int rc; + + rc = gsm_bts_set_c0_power_red(bts, red); + switch (rc) { + case 0: /* success */ + return get_bts_c0_power_red(cmd, data); + case -ENOTCONN: + cmd->reply = "BTS is offline"; + return CTRL_CMD_ERROR; + case -ENOTSUP: + cmd->reply = "BCCH carrier power reduction is not supported"; + return CTRL_CMD_ERROR; + default: + cmd->reply = "Failed to enable BCCH carrier power reduction"; + return CTRL_CMD_ERROR; + } +} + +CTRL_CMD_DEFINE(bts_c0_power_red, "c0-power-reduction"); + +static int get_bts_neighbor_list(struct ctrl_cmd *cmd, const struct bitvec *neigh_list) +{ + int i; + char *pos; + + /* The length of "1 2 3 ... 1023" is 4009, so 4096 is enough */ + cmd->reply = talloc_size(cmd, 4096); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + cmd->reply[0] = '\0'; + + pos = cmd->reply; + + for (i = 0; i < neigh_list->data_len * 8; i++) { + if (!bitvec_get_bit_pos(neigh_list, i)) + continue; + + pos += sprintf(pos, i == 0 ? "%u" : " %u", i); + } + + return CTRL_CMD_REPLY; +} + +static int get_bts_neighbor_list_si2(struct ctrl_cmd *cmd, void *data) +{ + const struct gsm_bts *bts = cmd->node; + return get_bts_neighbor_list(cmd, &bts->si_common.neigh_list); +} + +CTRL_CMD_DEFINE_RO(bts_neighbor_list_si2, "neighbor-list si2"); + +static int get_bts_neighbor_list_si5(struct ctrl_cmd *cmd, void *data) +{ + const struct gsm_bts *bts = cmd->node; + return get_bts_neighbor_list(cmd, &bts->si_common.si5_neigh_list); +} + +CTRL_CMD_DEFINE_RO(bts_neighbor_list_si5, "neighbor-list si5"); + +static int verify_bts_neighbor_list_add_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int arfcn; + + if (osmo_str_to_int(&arfcn, value, 10, 0, 1023) < 0) { + cmd->reply = "Invalid ARFCN value"; + return 1; + } + + return 0; +} + +static int set_bts_neighbor_list_add_del(struct ctrl_cmd *cmd, void *data, bool add, struct bitvec *neigh_list) +{ + int arfcn_int; + uint16_t arfcn; + enum gsm_band unused; + + if (osmo_str_to_int(&arfcn_int, cmd->value, 10, 0, 1023) < 0) { + cmd->reply = "Failed to parse ARFCN value"; + return CTRL_CMD_ERROR; + } + arfcn = (uint16_t) arfcn_int; + + if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { + cmd->reply = "Invalid arfcn detected"; + return CTRL_CMD_ERROR; + } + + if (add) + bitvec_set_bit_pos(neigh_list, arfcn, 1); + else + bitvec_set_bit_pos(neigh_list, arfcn, 0); + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +static int verify_bts_neighbor_list_add(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + return verify_bts_neighbor_list_add_del(cmd, value, _data); +} + +static int set_bts_neighbor_list_add(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) { + cmd->reply = "Neighbor list not in manual mode"; + return CTRL_CMD_ERROR; + } + return set_bts_neighbor_list_add_del(cmd, data, true, &bts->si_common.neigh_list); +} + +CTRL_CMD_DEFINE_WO(bts_neighbor_list_add, "neighbor-list add"); + +static int verify_bts_neighbor_list_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + return verify_bts_neighbor_list_add_del(cmd, value, _data); +} + +static int set_bts_neighbor_list_del(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) { + cmd->reply = "Neighbor list not in manual mode"; + return CTRL_CMD_ERROR; + } + return set_bts_neighbor_list_add_del(cmd, data, false, &bts->si_common.neigh_list); +} + +CTRL_CMD_DEFINE_WO(bts_neighbor_list_del, "neighbor-list del"); + +static int verify_bts_neighbor_list_si5_add(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + return verify_bts_neighbor_list_add_del(cmd, value, _data); +} + +static int set_bts_neighbor_list_si5_add(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + if (bts->neigh_list_manual_mode != NL_MODE_MANUAL_SI5SEP) { + cmd->reply = "Neighbor list not in manual mode with separate SI5"; + return CTRL_CMD_ERROR; + } + return set_bts_neighbor_list_add_del(cmd, data, true, &bts->si_common.si5_neigh_list); +} + +CTRL_CMD_DEFINE_WO(bts_neighbor_list_si5_add, "neighbor-list si5-add"); + +static int verify_bts_neighbor_list_si5_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + return verify_bts_neighbor_list_add_del(cmd, value, _data); +} + +static int set_bts_neighbor_list_si5_del(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + if (bts->neigh_list_manual_mode != NL_MODE_MANUAL_SI5SEP) { + cmd->reply = "Neighbor list not in manual mode with separate SI5"; + return CTRL_CMD_ERROR; + } + return set_bts_neighbor_list_add_del(cmd, data, false, &bts->si_common.si5_neigh_list); +} + +CTRL_CMD_DEFINE_WO(bts_neighbor_list_si5_del, "neighbor-list si5-del"); + +static int verify_bts_neighbor_list_mode(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (!strcmp(value, "automatic")) + return 0; + if (!strcmp(value, "manual")) + return 0; + if (!strcmp(value, "manual-si5")) + return 0; + + cmd->reply = "Invalid mode"; + return 1; +} + +static int set_bts_neighbor_list_mode(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int mode = NL_MODE_AUTOMATIC; + + if (!strcmp(cmd->value, "automatic")) + mode = NL_MODE_AUTOMATIC; + else if (!strcmp(cmd->value, "manual")) + mode = NL_MODE_MANUAL; + else if (!strcmp(cmd->value, "manual-si5")) + mode = NL_MODE_MANUAL_SI5SEP; + + switch (mode) { + case NL_MODE_MANUAL_SI5SEP: + case NL_MODE_MANUAL: + /* make sure we clear the current list when switching to + * manual mode */ + if (bts->neigh_list_manual_mode == 0) + memset(&bts->si_common.data.neigh_list, 0, sizeof(bts->si_common.data.neigh_list)); + break; + default: + break; + } + + bts->neigh_list_manual_mode = mode; + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO(bts_neighbor_list_mode, "neighbor-list mode"); + +/* si2quater neighbor management: delete an EARFCN. + * Format: bts.<0-255>.si2quater-neighbor-list.del.earfcn EARFCN + * EARFCN is in range 0..65535 */ +static int set_bts_si2quater_neighbor_list_del_earfcn(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)cmd->node; + int earfcn; + + if (osmo_str_to_int(&earfcn, cmd->value, 10, 0, 65535) < 0) { + cmd->reply = "Failed to parse neighbor EARFCN value"; + return CTRL_CMD_ERROR; + } + + if (bts_earfcn_del(bts, earfcn) < 0) { + cmd->reply = "Failed to delete a (not existent?) neighbor EARFCN"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(bts_si2quater_neighbor_list_del_earfcn, + "si2quater-neighbor-list del earfcn"); + +/* si2quater neighbor management: delete an UARFCN + * Format: bts.<0-255>.si2quater-neighbor-list.del.uarfcn UARFCN,SCRAMBLE + * UARFCN is in range 0..16383, SCRAMBLE is in range 0..511 */ +static int set_bts_si2quater_neighbor_list_del_uarfcn(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)cmd->node; + char *uarfcn_str, *scramble_str; + char *tmp, *saveptr; + int uarfcn, scramble; + + tmp = talloc_strdup(OTC_SELECT, cmd->value); + if (!tmp) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + uarfcn_str = strtok_r(tmp, ",", &saveptr); + scramble_str = strtok_r(NULL, ",", &saveptr); + + if (!uarfcn_str || osmo_str_to_int(&uarfcn, uarfcn_str, 10, 0, 16383) < 0) { + cmd->reply = "Failed to parse neighbor UARFCN value"; + return CTRL_CMD_ERROR; + } + + if (!scramble_str || osmo_str_to_int(&scramble, scramble_str, 10, 0, 511) < 0) { + cmd->reply = "Failed to parse neighbor scrambling code"; + return CTRL_CMD_ERROR; + } + + if (bts_uarfcn_del(bts, uarfcn, scramble) < 0) { + cmd->reply = "Failed to delete a (not existent?) neighbor UARFCN"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(bts_si2quater_neighbor_list_del_uarfcn, + "si2quater-neighbor-list del uarfcn"); + +static int verify_bts_si2quater_neighbor_list_add_earfcn(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + char *earfcn_str, *thresh_hi_str, *thresh_lo_str, *prio_str, *qrxlv_str, *meas_str, *saveptr, *tmp; + int earfcn, thresh_hi, thresh_lo, prio, qrxlv, meas; + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + earfcn_str = strtok_r(tmp, ",", &saveptr); + thresh_hi_str = strtok_r(NULL, ",", &saveptr); + thresh_lo_str = strtok_r(NULL, ",", &saveptr); + prio_str = strtok_r(NULL, ",", &saveptr); + qrxlv_str = strtok_r(NULL, ",", &saveptr); + meas_str = strtok_r(NULL, "\0", &saveptr); + + + if (!earfcn_str || osmo_str_to_int(&earfcn, earfcn_str, 10, 0, 65535) < 0) { + cmd->reply = "Failed to parse neighbor EARFCN value"; + return 1; + } + + if (!thresh_hi_str || osmo_str_to_int(&thresh_hi, thresh_hi_str, 10, 0, 31) < 0) { + cmd->reply = "Failed to parse neighbor threshold high bits value"; + return 1; + } + + if (!thresh_lo_str || osmo_str_to_int(&thresh_lo, thresh_lo_str, 10, 0, 32) < 0) { + cmd->reply = "Failed to parse neighbor threshold low bits value"; + return 1; + } + + if (!prio_str || osmo_str_to_int(&prio, prio_str, 10, 0, 8) < 0) { + cmd->reply = "Failed to parse neighbor priority value"; + return 1; + } + + if (!qrxlv_str || osmo_str_to_int(&qrxlv, qrxlv_str, 10, 0, 32) < 0) { + cmd->reply = "Failed to parse neighbor QRXLEVMIN value"; + return 1; + } + + if (!meas_str || osmo_str_to_int(&meas, meas_str, 10, 0, 8) < 0) { + cmd->reply = "Failed to parse neighbor measurement bandwidth"; + return 1; + } + + return 0; +} + +/* si2quater neighbor management: add an EARFCN + * Format: bts.<0-255>.si2quater-neighbor-list.add.earfcn <EARFCN>,<thresh-hi>,<thresh-lo>,<priority>,<QRXLEVMIN>,<measurement bandwidth> + * EARFCN is in range 0..65535, thresh-hi is in range 0..31, thresh-hi is in range 0..32, + * priority is in range 0..8, QRXLEVMIN is in range 0..32, measurement bandwidth is in range 0..8 */ +static int set_bts_si2quater_neighbor_list_add_earfcn(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)cmd->node; + char *earfcn_str, *thresh_hi_str, *thresh_lo_str, *prio_str, *qrxlv_str, *meas_str, *saveptr, *tmp; + int earfcn, thresh_hi, thresh_lo, prio, qrxlv, meas, result; + + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + earfcn_str = strtok_r(tmp, ",", &saveptr); + thresh_hi_str = strtok_r(NULL, ",", &saveptr); + thresh_lo_str = strtok_r(NULL, ",", &saveptr); + prio_str = strtok_r(NULL, ",", &saveptr); + qrxlv_str = strtok_r(NULL, ",", &saveptr); + meas_str = strtok_r(NULL, "\0", &saveptr); + + + if (!earfcn_str || osmo_str_to_int(&earfcn, earfcn_str, 10, 0, 65535) < 0) { + cmd->reply = "Failed to parse neighbor EARFCN value"; + return CTRL_CMD_ERROR; + } + + if (!thresh_hi_str || osmo_str_to_int(&thresh_hi, thresh_hi_str, 10, 0, 31) < 0) { + cmd->reply = "Failed to parse neighbor threshold high bits value"; + return CTRL_CMD_ERROR; + } + + if (!thresh_lo_str || osmo_str_to_int(&thresh_lo, thresh_lo_str, 10, 0, 32) < 0) { + cmd->reply = "Failed to parse neighbor threshold low bits value"; + return CTRL_CMD_ERROR; + } + + if (!prio_str || osmo_str_to_int(&prio, prio_str, 10, 0, 8) < 0) { + cmd->reply = "Failed to parse neighbor priority value"; + return CTRL_CMD_ERROR; + } + + if (!qrxlv_str || osmo_str_to_int(&qrxlv, qrxlv_str, 10, 0, 32) < 0) { + cmd->reply = "Failed to parse neighbor QRXLEVMIN value"; + return CTRL_CMD_ERROR; + } + + if (!meas_str || osmo_str_to_int(&meas, meas_str, 10, 0, 8) < 0) { + cmd->reply = "Failed to parse neighbor measurement bandwidth"; + return CTRL_CMD_ERROR; + } + + result = bts_earfcn_add(bts, earfcn, thresh_hi, thresh_lo, prio, qrxlv, meas); + + if ((result == 0) && (si2q_num(bts) <= SI2Q_MAX_NUM)) { + cmd->reply = "OK"; + return CTRL_CMD_REPLY; + } + + switch (result) { + case 0: + cmd->reply = talloc_asprintf(cmd, "Not enough space in SI2quater (%u/%u used)", bts->si2q_count, SI2Q_MAX_NUM); + if (!cmd->reply) + cmd->reply = "OOM"; + break; + case 1: + cmd->reply = "Multiple threshold-high are not supported"; + break; + case EARFCN_THRESH_LOW_INVALID: + cmd->reply = "Multiple threshold-low are not supported"; + break; + case EARFCN_QRXLV_INVALID + 1: + cmd->reply = "Multiple QRXLEVMIN are not supported"; + break; + case EARFCN_PRIO_INVALID: + cmd->reply = "Multiple priorities are not supported"; + break; + default: + cmd->reply = talloc_asprintf(cmd, "Unable to add EARFCN: %s", strerror(-result)); + if (!cmd->reply) + cmd->reply = "OOM"; + } + + if (bts_earfcn_del(bts, earfcn) != 0) + cmd->reply = "Failed to roll-back adding EARFCN"; + + return CTRL_CMD_ERROR; +} + +CTRL_CMD_DEFINE_WO(bts_si2quater_neighbor_list_add_earfcn, + "si2quater-neighbor-list add earfcn"); + +static int verify_bts_si2quater_neighbor_list_add_uarfcn(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + char *uarfcn_str, *scramble_str, *diversity_str, *saveptr, *tmp; + int uarfcn, scramble; + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + uarfcn_str = strtok_r(tmp, ",", &saveptr); + scramble_str = strtok_r(NULL, ",", &saveptr); + diversity_str = strtok_r(NULL, "\0", &saveptr); + + if (!uarfcn_str || osmo_str_to_int(&uarfcn, uarfcn_str, 10, 0, 16383) < 0) { + cmd->reply = "Failed to parse neighbor UARFCN value"; + return 1; + } + + if (!scramble_str || osmo_str_to_int(&scramble, scramble_str, 10, 0, 511) < 0) { + cmd->reply = "Failed to parse neighbor scrambling code"; + return 1; + } + + if (!diversity_str || ((strcmp(diversity_str, "1") != 0) && (strcmp(diversity_str, "0") != 0))) { + cmd->reply = "Failed to parse neighbor diversity bit"; + return 1; + } + + return 0; +} + +/* si2quater neighbor management: add an UARFCN + * Format: bts.<0-255>.si2quater-neighbor-list.add.uarfcn <UARFCN>,<scrambling code>,<diversity bit> + * UARFCN is in range 0..16383, scrambling code is in range 0..511 */ +static int set_bts_si2quater_neighbor_list_add_uarfcn(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)cmd->node; + char *uarfcn_str, *scramble_str, *diversity_str, *saveptr, *tmp; + int uarfcn, scramble; + bool diversity; + + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + uarfcn_str = strtok_r(tmp, ",", &saveptr); + scramble_str = strtok_r(NULL, ",", &saveptr); + diversity_str = strtok_r(NULL, "\0", &saveptr); + + + if (!uarfcn_str || osmo_str_to_int(&uarfcn, uarfcn_str, 10, 0, 16383) < 0) { + cmd->reply = "Failed to parse neighbor UARFCN value"; + return CTRL_CMD_ERROR; + } + + if (!scramble_str || osmo_str_to_int(&scramble, scramble_str, 10, 0, 511) < 0) { + cmd->reply = "Failed to parse neighbor scrambling code"; + return CTRL_CMD_ERROR; + } + + diversity = strcmp(diversity_str, "1") == 0; + + switch (bts_uarfcn_add(bts, uarfcn, scramble, diversity)) { + case -ENOMEM: + cmd->reply = "max number of UARFCNs reached"; + return CTRL_CMD_ERROR; + case -ENOSPC: + cmd->reply = "not enough space in SI2quater"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO(bts_si2quater_neighbor_list_add_uarfcn, + "si2quater-neighbor-list add uarfcn"); + +static int verify_bts_cell_reselection_offset(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + const int cell_reselection_offset = atoi(value); + + if (cell_reselection_offset < 0 || cell_reselection_offset > 126) { + cmd->reply = "Value is out of range"; + return 1; + } else if (cell_reselection_offset % 2 != 0) { + cmd->reply = "Value must be even"; + return 1; + } + + return 0; +} + +static int get_bts_cell_reselection_offset(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + if (!bts->si_common.cell_ro_sel_par.present) { + cmd->reply = "0"; + return CTRL_CMD_REPLY; + } + + cmd->reply = talloc_asprintf(cmd, "%u", bts->si_common.cell_ro_sel_par.cell_resel_off * 2); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_cell_reselection_offset(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(cmd->value) / 2; + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(bts_cell_reselection_offset, "cell-reselection-offset"); + +static int verify_bts_cell_reselection_penalty_time(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int penalty_time; + + if (strcmp(value, "reserved") == 0) + return 0; + + penalty_time = atoi(value); + + if (penalty_time < 20 || penalty_time > 620) { + cmd->reply = "Value is out of range"; + return 1; + } else if (penalty_time % 20 != 0) { + cmd->reply = "Value must be a multiple of 20"; + return 1; + } + + return 0; +} + +/* According to 3GPP TS 45.008, PENALTY_TIME in the Control parameters section */ +static int get_bts_cell_reselection_penalty_time(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + if (!bts->si_common.cell_ro_sel_par.present) { + cmd->reply = "0"; + return CTRL_CMD_REPLY; + } + + if (bts->si_common.cell_ro_sel_par.penalty_time == 31) { + cmd->reply = "reserved"; + return CTRL_CMD_REPLY; + } + + /* Calculate the penalty time in seconds */ + cmd->reply = talloc_asprintf(cmd, "%u", (bts->si_common.cell_ro_sel_par.penalty_time * 20) + 20); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_cell_reselection_penalty_time(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + bts->si_common.cell_ro_sel_par.present = 1; + + if (strcmp(cmd->value, "reserved") == 0) + bts->si_common.cell_ro_sel_par.penalty_time = 31; + else + bts->si_common.cell_ro_sel_par.penalty_time = (atoi(cmd->value) - 20) / 20; + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(bts_cell_reselection_penalty_time, "cell-reselection-penalty-time"); + +static int verify_bts_cell_reselection_hysteresis(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + const int cell_reselection_hysteresis = atoi(value); + + if (cell_reselection_hysteresis < 0 || cell_reselection_hysteresis > 14) { + cmd->reply = "Value is out of range"; + return 1; + } else if (cell_reselection_hysteresis % 2 != 0) { + cmd->reply = "Value must be even"; + return 1; + } + + return 0; +} + +static int get_bts_cell_reselection_hysteresis(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%u", bts->si_common.cell_sel_par.cell_resel_hyst * 2); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_cell_reselection_hysteresis(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + bts->si_common.cell_sel_par.cell_resel_hyst = atoi(cmd->value) / 2; + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(bts_cell_reselection_hysteresis, "cell-reselection-hysteresis"); + + +static int verify_bts_radio_link_timeout(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int radio_link_timeout; + struct gsm_bts *bts = cmd->node; + + if (strcmp(value, "infinite") == 0) { + if (bts->type != GSM_BTS_TYPE_OSMOBTS) { + cmd->reply = "Infinite radio link timeout not supported by BTS"; + return 1; + } + return 0; + } + + radio_link_timeout = atoi(cmd->value); + + if (radio_link_timeout < 0 || radio_link_timeout > 64) { + cmd->reply = "Value is out of range"; + return 1; + } else if (radio_link_timeout % 4 != 0) { + cmd->reply = "Value must be a multiple of 4"; + return 1; + } + + return 0; +} + +static int get_bts_radio_link_timeout(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%u", gsm_bts_get_radio_link_timeout(bts)); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_radio_link_timeout(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + gsm_bts_set_radio_link_timeout(bts, atoi(cmd->value)); + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(bts_radio_link_timeout, "radio-link-timeout"); + +static int verify_bts_rxlev_access_min(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int rxlev_access_min = atoi(cmd->value); + + if (rxlev_access_min < 0 || rxlev_access_min > 63) { + cmd->reply = "Value is out of range"; + return 1; + } + + return 0; +} + +static int get_bts_rxlev_access_min(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%u", bts->si_common.cell_sel_par.rxlev_acc_min); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_rxlev_access_min(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + bts->si_common.cell_sel_par.rxlev_acc_min = atoi(cmd->value); + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(bts_rxlev_access_min, "rach-rxlev-access-min"); + +/* Return space concatenated set of pairs <class>,<barred/allowed> */ +static int get_bts_rach_access_control_class(struct ctrl_cmd *cmd, void *data) +{ + int i; + const struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_strdup(cmd, ""); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + for (i = 0; i < 8; i++) { + cmd->reply = talloc_asprintf_append(cmd->reply, + i == 0 ? "%u,%s" : " %u,%s", + i, bts->si_common.rach_control.t3 & (0x1 << i) ? "barred" : "allowed"); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + } + + for (i = 0; i < 8; i++) { + if (i != 2) + cmd->reply = talloc_asprintf_append(cmd->reply, + " %u,%s", + i + 8, bts->si_common.rach_control.t2 & (0x1 << i) ? "barred" : "allowed"); + else + cmd->reply = talloc_asprintf_append(cmd->reply, + " emergency,%s", + bts->si_common.rach_control.t2 & (0x1 << i) ? "barred" : "allowed"); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + } + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(bts_rach_access_control_class, "rach-access-control-classes"); + +static int verify_access_control_class(struct ctrl_cmd *cmd, const char *value) +{ + int acc; + + if (strcmp(value, "emergency") == 0) + return 0; + + acc = atoi(value); + + if (acc < 0 || acc > 15) { + cmd->reply = "Value is out of range"; + return 1; + } else if (acc == 10) { + cmd->reply = "Access control class 10 does not exist, consider using \"emergency\" instead"; + return 1; + } + + return 0; +} + +static int set_access_control_class(struct ctrl_cmd *cmd, bool allow) +{ + int acc; + struct gsm_bts *bts = cmd->node; + + if (strcmp(cmd->value, "emergency") == 0) { + if (allow) + bts->si_common.rach_control.t2 &= ~0x4; + else + bts->si_common.rach_control.t2 |= 0x4; + cmd->reply = "OK"; + return CTRL_CMD_REPLY; + } + + acc = atoi(cmd->value); + if (acc < 8) + if (allow) + bts->si_common.rach_control.t3 &= ~(0x1 << acc); + else + bts->si_common.rach_control.t3 |= (0x1 << acc); + else + if (allow) + bts->si_common.rach_control.t2 &= ~(0x1 << (acc - 8)); + else + bts->si_common.rach_control.t2 |= (0x1 << (acc - 8)); + + if (acc < 10) + acc_mgr_perm_subset_changed(&bts->acc_mgr, &bts->si_common.rach_control); + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +static int verify_bts_rach_access_control_class_bar(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + return verify_access_control_class(cmd, value); +} + +static int set_bts_rach_access_control_class_bar(struct ctrl_cmd *cmd, void *data) +{ + return set_access_control_class(cmd, false); +} + +CTRL_CMD_DEFINE_WO(bts_rach_access_control_class_bar, "rach-access-control-class bar"); + +static int verify_bts_rach_access_control_class_allow(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + return verify_access_control_class(cmd, value); +} + +static int set_bts_rach_access_control_class_allow(struct ctrl_cmd *cmd, void *data) +{ + return set_access_control_class(cmd, true); +} + +CTRL_CMD_DEFINE_WO(bts_rach_access_control_class_allow, "rach-access-control-class allow"); + +static int verify_bts_rach_cell_barred(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int bar = atoi(cmd->value); + + if ((bar != 0) && (bar != 1)) + return 1; + + return 0; +} + +static int get_bts_rach_cell_barred(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%u", bts->si_common.rach_control.cell_bar); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_rach_cell_barred(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + bts->si_common.rach_control.cell_bar = atoi(cmd->value); + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(bts_rach_cell_barred, "rach-cell-barred"); + +static int verify_bts_rach_max_trans(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int max_trans = atoi(cmd->value); + + if ((max_trans != 1) && (max_trans != 2) && (max_trans != 4) && (max_trans != 7)) + return 1; + + return 0; +} + +static int get_bts_rach_max_trans(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%u", rach_max_trans_raw2val(bts->si_common.rach_control.max_trans)); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_rach_max_trans(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(cmd->value)); + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(bts_rach_max_trans, "rach-max-transmission"); + +/* Return space concatenated set of tuples <UARFCN>,<scrambling code>,<diversity bit> */ +static int get_bts_neighbor_list_si2quater_uarfcn(struct ctrl_cmd *cmd, void *data) +{ + int i; + const struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_strdup(cmd, ""); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + for (i = 0; i < bts->si_common.uarfcn_length; i++) { + cmd->reply = talloc_asprintf_append(cmd->reply, + i == 0 ? "%u,%u,%u" : " %u,%u,%u", + bts->si_common.data.uarfcn_list[i], + bts->si_common.data.scramble_list[i] & ~(1 << 9), + (bts->si_common.data.scramble_list[i] >> 9) & 1); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + } + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(bts_neighbor_list_si2quater_uarfcn, "neighbor-list si2quater uarfcns"); + +/* Return space concatenated set of tuples <EARFCN>,<thresh-hi>,<thresh-lo>,<prio>,<qrxlv>,<meas> */ +static int get_bts_neighbor_list_si2quater_earfcn(struct ctrl_cmd *cmd, void *data) +{ + int i; + bool first_earfcn = true; + const struct gsm_bts *bts = cmd->node; + const struct osmo_earfcn_si2q *neighbors = &bts->si_common.si2quater_neigh_list; + + cmd->reply = talloc_strdup(cmd, ""); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + for (i = 0; i < MAX_EARFCN_LIST; i++) { + if (neighbors->arfcn[i] == OSMO_EARFCN_INVALID) + continue; + cmd->reply = talloc_asprintf_append(cmd->reply, + first_earfcn ? "%u,%u,%u,%u,%u,%u" : " %u,%u,%u,%u,%u,%u", + neighbors->arfcn[i], + neighbors->thresh_hi, + neighbors->thresh_lo_valid ? neighbors->thresh_lo : 32, + neighbors->prio_valid ? neighbors->prio : 8, + neighbors->qrxlm_valid ? neighbors->qrxlm : 32, + (neighbors->meas_bw[i] != OSMO_EARFCN_MEAS_INVALID) ? neighbors->meas_bw[i] : 8); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + first_earfcn = false; + } + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(bts_neighbor_list_si2quater_earfcn, "neighbor-list si2quater earfcns"); + +char *bts_lchan_dump_full_ctrl(const void *t, struct gsm_bts *bts) +{ + int trx_nr; + bool first_trx = true; + char *trx_dump, *dump; + struct gsm_bts_trx *trx; + + dump = talloc_strdup(t, ""); + if (!dump) + return NULL; + + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + trx_dump = trx_lchan_dump_full_ctrl(t, trx); + if (!trx_dump) + return NULL; + if (!strlen(trx_dump)) + continue; + dump = talloc_asprintf_append(dump, first_trx ? "%s" : "\n%s", trx_dump); + if (!dump) + return NULL; + first_trx = false; + } + + return dump; +} + +/* Return full information about all logical channels in a BTS. + * format: bts.<0-255>.show-lchan.full + * result format: New line delimited list of <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>, + * <ms power>,<interference dbm>, <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>, + * <ipa bound conn id>,<ipa conn ip>,<ipa conn port>,<ipa conn speech mode> + */ +static int get_bts_show_lchan_full(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + cmd->reply = bts_lchan_dump_full_ctrl(cmd, bts); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(bts_show_lchan_full, "show-lchan full"); + +int bsc_bts_ctrl_cmds_install(void) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_loc); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_lac); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_ci); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_bsic); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_max_delay); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_expiry_timeout); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_apply_config); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_power_ctrl_defs); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_chan_load); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_conn); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_up); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_gprs_mode); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_state); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_states); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_c0_power_red); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si2); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si5); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_add); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_del); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si5_add); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si5_del); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_mode); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si2quater_neighbor_list_del_earfcn); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si2quater_neighbor_list_del_uarfcn); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si2quater_neighbor_list_add_earfcn); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si2quater_neighbor_list_add_uarfcn); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_cell_reselection_offset); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_cell_reselection_penalty_time); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_cell_reselection_hysteresis); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_ms_max_power); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_radio_link_timeout); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rxlev_access_min); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_access_control_class); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_access_control_class_bar); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_access_control_class_allow); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_cell_barred); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_max_trans); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si2quater_uarfcn); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si2quater_earfcn); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_show_lchan_full); + + rc |= neighbor_ident_ctrl_init(); + + rc = bsc_bts_trx_ctrl_cmds_install(); + + return rc; +} diff --git a/src/osmo-bsc/bts_ericsson_rbs2000.c b/src/osmo-bsc/bts_ericsson_rbs2000.c index 1297b3025..36b378318 100644 --- a/src/osmo-bsc/bts_ericsson_rbs2000.c +++ b/src/osmo-bsc/bts_ericsson_rbs2000.c @@ -35,16 +35,8 @@ static void bootstrap_om_bts(struct gsm_bts *bts) { - struct gsm_bts_trx *trx; - LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); - /* Global init (not bootstrapping) */ - abis_om2k_bts_init(bts); - - llist_for_each_entry(trx, &bts->trx_list, list) - abis_om2k_trx_init(trx); - /* TODO: Should we wait for a Failure report? */ om2k_bts_fsm_start(bts); } @@ -53,12 +45,14 @@ static void bootstrap_om_trx(struct gsm_bts_trx *trx) { LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n", trx->bts->nr, trx->nr); - /* FIXME */ + + om2k_trx_fsm_start(trx); } static int shutdown_om(struct gsm_bts *bts) { gsm_bts_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL); + gsm_bts_stats_reset(bts); /* FIXME */ return 0; @@ -145,6 +139,10 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal, LOGP(DNM, LOGL_NOTICE, "Line-%u TS-%u TEI-%u SAPI-%u: Link " "Lost for Ericsson RBS2000. Re-starting DL Establishment\n", isd->line->num, isd->ts_nr, isd->tei, isd->sapi); + if (isd->tei == isd->trx->bts->oml_tei) + om2k_bts_fsm_reset(isd->trx->bts); + else + om2k_trx_fsm_reset(isd->trx); /* Some datalink for a given TEI/SAPI went down, try to re-start it */ e1i_ts = &isd->line->ts[isd->ts_nr-1]; OSMO_ASSERT(e1i_ts->type == E1INP_TS_TYPE_SIGN); @@ -177,6 +175,11 @@ static void config_write_bts(struct vty *vty, struct gsm_bts *bts) abis_om2k_config_write_bts(vty, bts); } +static void config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + abis_om2k_config_write_trx(vty, trx); +} + static int bts_model_rbs2k_start(struct gsm_network *net); static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line) @@ -184,17 +187,40 @@ static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line) e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); } +static int bts_model_rbs2k_bts_init(struct gsm_bts *bts) +{ + abis_om2k_bts_init(bts); + return 0; +} + +static int bts_model_rbs2k_trx_init(struct gsm_bts_trx *trx) +{ + abis_om2k_trx_init(trx); + return 0; +} + static struct gsm_bts_model model_rbs2k = { .type = GSM_BTS_TYPE_RBS2000, .name = "rbs2000", .start = bts_model_rbs2k_start, + .bts_init = bts_model_rbs2k_bts_init, + .trx_init = bts_model_rbs2k_trx_init, .oml_rcvmsg = &abis_om2k_rcvmsg, .config_write_bts = &config_write_bts, + .config_write_trx = &config_write_trx, .e1line_bind_ops = &bts_model_rbs2k_e1line_bind_ops, }; static int bts_model_rbs2k_start(struct gsm_network *net) { + osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); + osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL); + + return 0; +} + +int bts_model_rbs2k_init(void) +{ model_rbs2k.features.data = &model_rbs2k._features_data[0]; model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data); @@ -204,13 +230,5 @@ static int bts_model_rbs2k_start(struct gsm_network *net) osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_HSCSD); osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_MULTI_TSC); - osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); - osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL); - - return 0; -} - -int bts_model_rbs2k_init(void) -{ return gsm_bts_model_register(&model_rbs2k); } diff --git a/src/osmo-bsc/bts_init.c b/src/osmo-bsc/bts_init.c index 18f1ed4c8..0e3debcf4 100644 --- a/src/osmo-bsc/bts_init.c +++ b/src/osmo-bsc/bts_init.c @@ -24,7 +24,7 @@ int bts_init(void) bts_model_rbs2k_init(); bts_model_nanobts_init(); bts_model_nokia_site_init(); - bts_model_sysmobts_init(); + bts_model_osmobts_init(); /* Your new BTS here. */ return 0; } diff --git a/src/osmo-bsc/bts_ipaccess_nanobts.c b/src/osmo-bsc/bts_ipaccess_nanobts.c index 577be13f7..b0532e5d9 100644 --- a/src/osmo-bsc/bts_ipaccess_nanobts.c +++ b/src/osmo-bsc/bts_ipaccess_nanobts.c @@ -1,6 +1,7 @@ /* ip.access nanoBTS specific code */ /* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org> + * (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> * * All Rights Reserved * @@ -31,11 +32,13 @@ #include <osmocom/gsm/tlv.h> #include <osmocom/core/msgb.h> #include <osmocom/core/socket.h> +#include <osmocom/core/stat_item.h> #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/abis_nm.h> #include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/abis_osmo.h> #include <osmocom/bsc/debug.h> #include <osmocom/abis/subchan_demux.h> #include <osmocom/gsm/ipa.h> @@ -46,11 +49,16 @@ #include <osmocom/bsc/paging.h> #include <osmocom/bsc/timeslot_fsm.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bts_sm.h> #include <osmocom/bsc/nm_common_fsm.h> +#include <osmocom/bsc/bsc_stats.h> static int bts_model_nanobts_start(struct gsm_network *net); static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line); +static int power_ctrl_send_def_params(const struct gsm_bts_trx *trx); +static int power_ctrl_enc_rsl_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp); + static char *get_oml_status(const struct gsm_bts *bts) { if (bts->oml_link) @@ -66,6 +74,11 @@ struct gsm_bts_model bts_model_nanobts = { .oml_rcvmsg = &abis_nm_rcvmsg, .oml_status = &get_oml_status, .e1line_bind_ops = bts_model_nanobts_e1line_bind_ops, + + /* MS/BS Power control specific API */ + .power_ctrl_send_def_params = &power_ctrl_send_def_params, + .power_ctrl_enc_rsl_params = &power_ctrl_enc_rsl_params, + /* Some nanoBTS firmwares (if not all) don't support SI2ter and cause * problems on some MS if it is enabled, see OS#3063. Disable it by * default, can still be enabled through VTY cmd with same name. @@ -118,6 +131,7 @@ struct gsm_bts_model bts_model_nanobts = { [NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V }, }, }, + .features_get_reported = false, }; @@ -126,29 +140,17 @@ static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd) { uint8_t obj_class = nsd->obj_class; void *obj = nsd->obj; - struct gsm_nm_state *new_state = nsd->new_state; struct gsm_bts_sm *bts_sm; struct gsm_bts *bts; struct gsm_bts_trx *trx; struct gsm_bts_bb_trx *bb_transc; struct gsm_bts_trx_ts *ts; - struct gsm_bts_gprs_nsvc *nsvc; - - struct msgb *msgb; - - if (!is_ipaccess_bts(nsd->bts)) - return 0; - - /* This event-driven BTS setup is currently only required on nanoBTS */ + struct gsm_gprs_nsvc *nsvc; + struct gsm_gprs_nse *nse; + struct gsm_gprs_cell *cell; - /* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create - * endless loop */ - if (obj_class != NM_OC_BTS && - obj_class != NM_OC_BASEB_TRANSC && - obj_class != NM_OC_RADIO_CARRIER && - obj_class != NM_OC_CHANNEL && - evt != S_NM_STATECHG_OPER) + if (!is_ipa_abisip_bts(nsd->bts)) return 0; switch (obj_class) { @@ -166,7 +168,6 @@ static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd) break; case NM_OC_CHANNEL: ts = obj; - trx = ts->trx; osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_STATE_CHG_REP, nsd); break; case NM_OC_RADIO_CARRIER: @@ -174,64 +175,17 @@ static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd) osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_STATE_CHG_REP, nsd); break; case NM_OC_GPRS_NSE: - bts = container_of(obj, struct gsm_bts, gprs.nse); - if (bts->gprs.mode == BTS_GPRS_NONE) - break; - if (new_state->availability == NM_AVSTATE_DEPENDENCY) { - msgb = nanobts_attr_nse_get(bts); - abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, - 0xff, 0xff, msgb->data, - msgb->len); - msgb_free(msgb); - abis_nm_opstart(bts, obj_class, bts->bts_nr, - 0xff, 0xff); - } + nse = obj; + osmo_fsm_inst_dispatch(nse->mo.fi, NM_EV_STATE_CHG_REP, nsd); break; case NM_OC_GPRS_CELL: - bts = container_of(obj, struct gsm_bts, gprs.cell); - if (bts->gprs.mode == BTS_GPRS_NONE) - break; - if (new_state->availability == NM_AVSTATE_DEPENDENCY) { - msgb = nanobts_attr_cell_get(bts); - abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, - 0, 0xff, msgb->data, - msgb->len); - msgb_free(msgb); - abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr, - 0, 0xff, NM_STATE_UNLOCKED); - abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, bts->bts_nr, - 0xff, 0xff, NM_STATE_UNLOCKED); - abis_nm_opstart(bts, obj_class, bts->bts_nr, - 0, 0xff); - } + cell = obj; + osmo_fsm_inst_dispatch(cell->mo.fi, NM_EV_STATE_CHG_REP, nsd); break; case NM_OC_GPRS_NSVC: nsvc = obj; - bts = nsvc->bts; - if (bts->gprs.mode == BTS_GPRS_NONE) - break; - /* We skip NSVC1 since we only use NSVC0 */ - if (nsvc->id == 1) - break; - if (!osmo_bts_has_feature(&bts->features, BTS_FEAT_IPV6_NSVC) && - nsvc->remote.u.sa.sa_family == AF_INET6) { - LOGP(DLINP, LOGL_ERROR, "BTS %d does not support IPv6 but an IPv6 address was configured!\n", bts->nr); - break; - } - - if ((new_state->availability == NM_AVSTATE_OFF_LINE) || - (new_state->availability == NM_AVSTATE_DEPENDENCY)) { - msgb = nanobts_attr_nscv_get(bts); - abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, - nsvc->id, 0xff, - msgb->data, msgb->len); - msgb_free(msgb); - abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr, - nsvc->id, 0xff, - NM_STATE_UNLOCKED); - abis_nm_opstart(bts, obj_class, bts->bts_nr, - nsvc->id, 0xff); - } + osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_STATE_CHG_REP, nsd); + break; default: break; } @@ -241,121 +195,95 @@ static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd) /* Callback function to be called every time we receive a 12.21 SW activated report */ static int sw_activ_rep(struct msgb *mb) { - struct abis_om_fom_hdr *foh = msgb_l3(mb); + const struct abis_om_fom_hdr *foh = msgb_l3(mb); struct e1inp_sign_link *sign_link = mb->dst; struct gsm_bts *bts = sign_link->trx->bts; - struct gsm_bts_trx *trx; - struct gsm_bts_trx_ts *ts; + struct gsm_abis_mo *mo; + struct tlv_parsed tp; - if (!is_ipaccess_bts(bts)) + if (!is_ipa_abisip_bts(bts)) return 0; - switch (foh->obj_class) { - case NM_OC_SITE_MANAGER: - osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_SW_ACT_REP, NULL); - break; - case NM_OC_BTS: - osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_SW_ACT_REP, NULL); - break; - case NM_OC_BASEB_TRANSC: - if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr))) - return -EINVAL; - osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT_REP, NULL); - break; - case NM_OC_RADIO_CARRIER: - if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr))) - return -EINVAL; - osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT_REP, NULL); - break; - case NM_OC_CHANNEL: - if (!(ts = abis_nm_get_ts(mb))) - return -EINVAL; - osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_SW_ACT_REP, NULL); - break; + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + if (mo == NULL) { + LOGPFOH(DNM, LOGL_ERROR, foh, "Rx SW activated report for non-existent MO\n"); + return -ENOENT; + } + + if (abis_nm_tlv_parse(&tp, bts, &foh->data[0], msgb_l3len(mb) - sizeof(*foh)) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } + + mo->ipaccess.obj_version = 0; /* implicit default */ + if (TLVP_PRES_LEN(&tp, NM_ATT_IPACC_OBJ_VERSION, 1)) { + const uint8_t *versions = TLVP_VAL(&tp, NM_ATT_IPACC_OBJ_VERSION); + char buf[256]; + struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) }; + + /* nanoBTS may report several Object Versions; the first one will + * be used by default unless requested explicitly before OPSTARTing. */ + mo->ipaccess.obj_version = versions[0]; + + OSMO_STRBUF_PRINTF(sb, "%u (default)", versions[0]); + for (uint16_t i = 1; i < TLVP_LEN(&tp, NM_ATT_IPACC_OBJ_VERSION); i++) + OSMO_STRBUF_PRINTF(sb, ", %u", versions[i]); + LOGPFOH(DNM, LOGL_INFO, foh, "IPA Object Versions supported: %s\n", buf); } + + osmo_fsm_inst_dispatch(mo->fi, NM_EV_SW_ACT_REP, NULL); return 0; } -static void nm_rx_opstart_ack_chan(struct msgb *oml_msg) +static void nm_rx_opstart_ack(struct msgb *oml_msg) { - struct gsm_bts_trx_ts *ts; - ts = abis_nm_get_ts(oml_msg); - if (!ts) - /* error already logged in abis_nm_get_ts() */ - return; - if (!ts->fi) { - LOG_TS(ts, LOGL_ERROR, "Channel OPSTART ACK for uninitialized TS\n"); + const struct abis_om_fom_hdr *foh = msgb_l3(oml_msg); + struct e1inp_sign_link *sign_link = oml_msg->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct gsm_abis_mo *mo; + + if (!is_ipa_abisip_bts(bts)) return; - } - osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_OPSTART_ACK, NULL); - osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL); + + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + if (mo == NULL) + LOGPFOH(DNM, LOGL_ERROR, foh, "Rx OPSTART ACK for non-existent MO\n"); + else + osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL); } -static void nm_rx_opstart_ack(struct msgb *oml_msg) +static void nm_rx_opstart_nack(struct msgb *oml_msg) { - struct abis_om_fom_hdr *foh = msgb_l3(oml_msg); + const struct abis_om_fom_hdr *foh = msgb_l3(oml_msg); struct e1inp_sign_link *sign_link = oml_msg->dst; struct gsm_bts *bts = sign_link->trx->bts; - struct gsm_bts_trx *trx; + struct gsm_abis_mo *mo; - switch (foh->obj_class) { - case NM_OC_SITE_MANAGER: - osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_OPSTART_ACK, NULL); - break; - case NM_OC_BTS: - osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OPSTART_ACK, NULL); - break; - case NM_OC_RADIO_CARRIER: - if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr))) - return; - osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); - break; - case NM_OC_BASEB_TRANSC: - if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr))) - return; - osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_OPSTART_ACK, NULL); - break; - case NM_OC_CHANNEL: - nm_rx_opstart_ack_chan(oml_msg); - break; - default: - break; - } + if (!is_ipa_abisip_bts(bts)) + return; + + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + if (mo == NULL) + LOGPFOH(DNM, LOGL_ERROR, foh, "Rx OPSTART NACK for non-existent MO\n"); + else + osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_NACK, NULL); } -static void nm_rx_opstart_nack(struct msgb *oml_msg) +static void nm_rx_get_attr_rep(struct msgb *oml_msg) { struct abis_om_fom_hdr *foh = msgb_l3(oml_msg); struct e1inp_sign_link *sign_link = oml_msg->dst; struct gsm_bts *bts = sign_link->trx->bts; - struct gsm_bts_trx *trx; - struct gsm_bts_trx_ts *ts; + struct gsm_abis_mo *mo; - switch (foh->obj_class) { - case NM_OC_SITE_MANAGER: - osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_OPSTART_NACK, NULL); - break; - case NM_OC_BTS: - osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OPSTART_ACK, NULL); - break; - case NM_OC_RADIO_CARRIER: - if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr))) - return; - osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, NULL); - break; - case NM_OC_BASEB_TRANSC: - if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr))) - return; - osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_OPSTART_NACK, NULL); - break; - case NM_OC_CHANNEL: - if (!(ts = abis_nm_get_ts(oml_msg))) - return; - osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_OPSTART_NACK, NULL); - break; - default: - break; - } + if (!is_ipa_abisip_bts(bts)) + return; + + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + if (mo == NULL) + LOGPFOH(DNM, LOGL_ERROR, foh, "Rx Get Attribute Report for non-existent MO\n"); + else + osmo_fsm_inst_dispatch(mo->fi, NM_EV_GET_ATTR_REP, NULL); } static void nm_rx_set_bts_attr_ack(struct msgb *oml_msg) @@ -364,6 +292,9 @@ static void nm_rx_set_bts_attr_ack(struct msgb *oml_msg) struct e1inp_sign_link *sign_link = oml_msg->dst; struct gsm_bts *bts = sign_link->trx->bts; + if (!is_ipa_abisip_bts(bts)) + return; + if (foh->obj_class != NM_OC_BTS) { LOG_BTS(bts, DNM, LOGL_ERROR, "Set BTS Attr Ack received on non BTS object!\n"); return; @@ -377,8 +308,12 @@ static void nm_rx_set_radio_attr_ack(struct msgb *oml_msg) struct abis_om_fom_hdr *foh = msgb_l3(oml_msg); struct e1inp_sign_link *sign_link = oml_msg->dst; struct gsm_bts *bts = sign_link->trx->bts; - struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + struct gsm_bts_trx *trx; + + if (!is_ipa_abisip_bts(bts)) + return; + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); if (!trx || foh->obj_class != NM_OC_RADIO_CARRIER) { LOGPFOH(DNM, LOGL_ERROR, foh, "Set Radio Carrier Attr Ack received on non Radio Carrier object!\n"); return; @@ -389,8 +324,14 @@ static void nm_rx_set_radio_attr_ack(struct msgb *oml_msg) static void nm_rx_set_chan_attr_ack(struct msgb *oml_msg) { struct abis_om_fom_hdr *foh = msgb_l3(oml_msg); - struct gsm_bts_trx_ts *ts = abis_nm_get_ts(oml_msg); + struct e1inp_sign_link *sign_link = oml_msg->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct gsm_bts_trx_ts *ts; + if (!is_ipa_abisip_bts(bts)) + return; + + ts = abis_nm_get_ts(oml_msg); if (!ts || foh->obj_class != NM_OC_CHANNEL) { LOGPFOH(DNM, LOGL_ERROR, foh, "Set Channel Attr Ack received on non Radio Channel object!\n"); return; @@ -398,6 +339,91 @@ static void nm_rx_set_chan_attr_ack(struct msgb *oml_msg) osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_SET_ATTR_ACK, NULL); } +static void nm_rx_ipacc_set_attr_ack(struct ipacc_ack_signal_data *sig_data) +{ + struct gsm_bts *bts = sig_data->bts; + struct abis_om_fom_hdr *foh = sig_data->foh; + void *obj; + struct gsm_gprs_nse *nse; + struct gsm_gprs_cell *cell; + struct gsm_gprs_nsvc *nsvc; + + obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); + + switch (foh->obj_class) { + case NM_OC_GPRS_NSE: + nse = obj; + osmo_fsm_inst_dispatch(nse->mo.fi, NM_EV_SET_ATTR_ACK, NULL); + break; + case NM_OC_GPRS_CELL: + cell = obj; + osmo_fsm_inst_dispatch(cell->mo.fi, NM_EV_SET_ATTR_ACK, NULL); + break; + case NM_OC_GPRS_NSVC: + if (!(nsvc = gsm_bts_sm_nsvc_num(bts->site_mgr, foh->obj_inst.trx_nr))) + return; + osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_SET_ATTR_ACK, NULL); + break; + default: + LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC Set Attr Ack received on incorrect object class %d!\n", foh->obj_class); + } +} + +static void nm_rx_ipacc_rsl_connect_ack(struct ipacc_ack_signal_data *sig_data) +{ + struct gsm_bts *bts = sig_data->bts; + struct abis_om_fom_hdr *foh = sig_data->foh; + struct gsm_bts_trx *trx; + + if (foh->obj_class != NM_OC_BASEB_TRANSC) { + LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC RSL Connect ACK received on incorrect object class %d!\n", foh->obj_class); + return; + } + + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_RSL_CONNECT_ACK, NULL); +} + +static void nm_rx_ipacc_ack(struct ipacc_ack_signal_data *sig_data) +{ + switch (sig_data->foh->msg_type) { + case NM_MT_IPACC_SET_ATTR_ACK: + nm_rx_ipacc_set_attr_ack(sig_data); + break; + case NM_MT_IPACC_RSL_CONNECT_ACK: + nm_rx_ipacc_rsl_connect_ack(sig_data); + break; + default: + break; + } +} + +static void nm_rx_ipacc_rsl_connect_nack(struct ipacc_ack_signal_data *sig_data) +{ + struct gsm_bts *bts = sig_data->bts; + struct abis_om_fom_hdr *foh = sig_data->foh; + struct gsm_bts_trx *trx; + + if (foh->obj_class != NM_OC_BASEB_TRANSC) { + LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC RSL Connect NACK received on incorrect object class %d!\n", foh->obj_class); + return; + } + + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_RSL_CONNECT_NACK, NULL); +} + +static void nm_rx_ipacc_nack(struct ipacc_ack_signal_data *sig_data) +{ + switch (sig_data->foh->msg_type) { + case NM_MT_IPACC_RSL_CONNECT_ACK: + nm_rx_ipacc_rsl_connect_nack(sig_data); + break; + default: + break; + } +} + /* Callback function to be called every time we receive a signal from NM */ static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) @@ -408,8 +434,7 @@ static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal, switch (signal) { case S_NM_SW_ACTIV_REP: return sw_activ_rep(signal_data); - case S_NM_STATECHG_OPER: - case S_NM_STATECHG_ADM: + case S_NM_STATECHG: return nm_statechg_event(signal, signal_data); case S_NM_OPSTART_ACK: nm_rx_opstart_ack(signal_data); @@ -417,6 +442,9 @@ static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal, case S_NM_OPSTART_NACK: nm_rx_opstart_nack(signal_data); return 0; + case S_NM_GET_ATTR_REP: + nm_rx_get_attr_rep(signal_data); + return 0; case S_NM_SET_BTS_ATTR_ACK: nm_rx_set_bts_attr_ack(signal_data); return 0; @@ -426,6 +454,12 @@ static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal, case S_NM_SET_CHAN_ATTR_ACK: nm_rx_set_chan_attr_ack(signal_data); return 0; + case S_NM_IPACC_ACK: + nm_rx_ipacc_ack(signal_data); + return 0; + case S_NM_IPACC_NACK: + nm_rx_ipacc_nack(signal_data); + return 0; default: break; } @@ -461,7 +495,7 @@ find_bts_by_unitid(struct gsm_network *net, uint16_t site_id, uint16_t bts_id) struct gsm_bts *bts; llist_for_each_entry(bts, &net->bts_list, list) { - if (!is_ipaccess_bts(bts)) + if (!is_ipa_abisip_bts(bts)) continue; if (bts->ip_access.site_id == site_id && @@ -474,13 +508,13 @@ find_bts_by_unitid(struct gsm_network *net, uint16_t site_id, uint16_t bts_id) /* These are exported because they are used by the VTY interface. */ void ipaccess_drop_rsl(struct gsm_bts_trx *trx, const char *reason) { - if (!trx->rsl_link) + if (!trx->rsl_link_primary) return; LOG_TRX(trx, DLINP, LOGL_NOTICE, "Dropping RSL link: %s\n", reason); - e1inp_sign_link_destroy(trx->rsl_link); - trx->rsl_link = NULL; - osmo_stat_item_dec(trx->bts->bts_statg->items[BTS_STAT_RSL_CONNECTED], 1); + e1inp_sign_link_destroy(trx->rsl_link_primary); + trx->rsl_link_primary = NULL; + osmo_stat_item_dec(osmo_stat_item_group_get_item(trx->bts->bts_statg, BTS_STAT_RSL_CONNECTED), 1); if (trx->bts->c0 == trx) paging_flush_bts(trx->bts, NULL); @@ -492,6 +526,9 @@ void ipaccess_drop_oml(struct gsm_bts *bts, const char *reason) struct gsm_bts_trx *trx; struct gsm_bts_trx_ts *ts ; uint8_t tn; + uint8_t i; + struct timespec tp; + int rc; /* First of all, remove deferred drop if enabled */ osmo_timer_del(&bts->oml_drop_link_timer); @@ -502,8 +539,17 @@ void ipaccess_drop_oml(struct gsm_bts *bts, const char *reason) LOG_BTS(bts, DLINP, LOGL_NOTICE, "Dropping OML link: %s\n", reason); e1inp_sign_link_destroy(bts->oml_link); bts->oml_link = NULL; - bts->uptime = 0; - osmo_stat_item_dec(bts->bts_statg->items[BTS_STAT_OML_CONNECTED], 1); + rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp); + bts->updowntime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for downtime */ + osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_OML_CONNECTED), 1); + gsm_bts_stats_reset(bts); + + /* Also drop the associated OSMO link */ + OSMO_ASSERT(bts->osmo_link); + e1inp_sign_link_destroy(bts->osmo_link); + bts->osmo_link = NULL; + + bts_setup_ramp_remove(bts); /* we have issues reconnecting RSL, drop everything. */ llist_for_each_entry(trx, &bts->trx_list, list) { @@ -516,12 +562,22 @@ void ipaccess_drop_oml(struct gsm_bts *bts, const char *reason) } } - osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_OML_DOWN, NULL); osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OML_DOWN, NULL); - gsm_bts_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL); + osmo_fsm_inst_dispatch(bts->gprs.cell.mo.fi, NM_EV_OML_DOWN, NULL); + + osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_OML_DOWN, NULL); + osmo_fsm_inst_dispatch(bts->site_mgr->gprs.nse.mo.fi, NM_EV_OML_DOWN, NULL); + for (i = 0; i < ARRAY_SIZE(bts->site_mgr->gprs.nsvc); i++) + osmo_fsm_inst_dispatch(bts->site_mgr->gprs.nsvc[i].mo.fi, NM_EV_OML_DOWN, NULL); bts->ip_access.flags = 0; + if (bts->model->features_get_reported) { + /* Reset the feature vector */ + memset(bts->_features_data, 0, sizeof(bts->_features_data)); + bts->features_known = false; + } + /* * Go through the list and see if we are the depndency of a BTS * and then drop the BTS. This can lead to some recursion but it @@ -574,7 +630,7 @@ static void ipaccess_sign_link_reject(const struct ipaccess_unit *dev, const str /* Write to log and increase counter */ LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for %u/%u/%u, disconnecting\n", site_id, bts_id, trx_id); - rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_UNKNOWN_UNIT_ID]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_UNKNOWN_UNIT_ID)); /* Get remote IP */ if (osmo_sock_get_remote_ip(ts->driver.ipaccess.fd.fd, ip, sizeof(ip))) @@ -621,15 +677,24 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line, struct e1inp_sign_link *sign_link = NULL; struct timespec tp; int rc; + struct e1inp_ts *sign_ts = e1inp_line_ipa_oml_ts(line); bts = find_bts_by_unitid(bsc_gsmnet, dev->site_id, dev->bts_id); if (!bts) { - ipaccess_sign_link_reject(dev, &line->ts[E1INP_SIGN_OML - 1]); + ipaccess_sign_link_reject(dev, sign_ts); return NULL; } DEBUGP(DLINP, "%s: Identified BTS %u/%u/%u\n", e1inp_signtype_name(type), dev->site_id, dev->bts_id, dev->trx_id); + /* Check if this BTS has a valid configuration. If not we will drop it + * immediately. */ + if (gsm_bts_check_cfg(bts) != 0) { + LOGP(DLINP, LOGL_NOTICE, "(bts=%u) BTS config invalid, dropping BTS!\n", bts->nr); + ipaccess_drop_oml_deferred(bts); + return NULL; + } + switch(type) { case E1INP_SIGN_OML: /* remove old OML signal link for this BTS. */ @@ -644,17 +709,21 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line, /* create new OML link. */ sign_link = bts->oml_link = - e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML - 1], + e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, bts->c0, bts->oml_tei, 0); - rc = clock_gettime(CLOCK_MONOTONIC, &tp); - bts->uptime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */ + rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp); + bts->updowntime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */ if (!(sign_link->trx->bts->ip_access.flags & OML_UP)) { e1inp_event(sign_link->ts, S_L_INP_TEI_UP, sign_link->tei, sign_link->sapi); sign_link->trx->bts->ip_access.flags |= OML_UP; } - osmo_stat_item_inc(bts->bts_statg->items[BTS_STAT_OML_CONNECTED], 1); + osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_OML_CONNECTED), 1); + + /* Create link for E1INP_SIGN_OSMO */ + //SAPI must be 0, no IPAC_PROTO_EXT_PCU, see ipaccess_bts_read_cb + bts->osmo_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OSMO, bts->c0, IPAC_PROTO_OSMO, 0); break; case E1INP_SIGN_RSL: { struct e1inp_ts *ts; @@ -669,12 +738,12 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line, /* set new RSL link for this TRX. */ line = bts->oml_link->ts->line; - ts = &line->ts[E1INP_SIGN_RSL + dev->trx_id - 1]; + ts = e1inp_line_ipa_rsl_ts(line, dev->trx_id); e1inp_ts_config_sign(ts, line); - sign_link = trx->rsl_link = + sign_link = trx->rsl_link_primary = e1inp_sign_link_create(ts, E1INP_SIGN_RSL, - trx, trx->rsl_tei, 0); - trx->rsl_link->ts->sign.delay = 0; + trx, trx->rsl_tei_primary, 0); + trx->rsl_link_primary->ts->sign.delay = 0; if (!(sign_link->trx->bts->ip_access.flags & (RSL_UP << sign_link->trx->nr))) { e1inp_event(sign_link->ts, S_L_INP_TEI_UP, @@ -682,7 +751,7 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line, sign_link->trx->bts->ip_access.flags |= (RSL_UP << sign_link->trx->nr); } - osmo_stat_item_inc(bts->bts_statg->items[BTS_STAT_RSL_CONNECTED], 1); + osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_RSL_CONNECTED), 1); break; } default: @@ -694,7 +763,7 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line, static void ipaccess_sign_link_down(struct e1inp_line *line) { /* No matter what link went down, we close both signal links. */ - struct e1inp_ts *ts = &line->ts[E1INP_SIGN_OML-1]; + struct e1inp_ts *ts = e1inp_line_ipa_oml_ts(line); struct gsm_bts *bts = NULL; struct e1inp_sign_link *link; @@ -727,6 +796,9 @@ static int ipaccess_sign_link(struct msgb *msg) case E1INP_SIGN_OML: ret = abis_nm_rcvmsg(msg); break; + case E1INP_SIGN_OSMO: + ret = abis_osmo_rcvmsg(msg); + break; default: LOGP(DLINP, LOGL_ERROR, "Unknown signal link type %d\n", link->type); @@ -753,3 +825,153 @@ static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line) { e1inp_line_bind_ops(line, &ipaccess_e1inp_line_ops); } + +static void enc_meas_proc_params(struct msgb *msg, uint8_t ptype, + const struct gsm_power_ctrl_meas_params *mp) +{ + struct ipac_preproc_ave_cfg *ave_cfg; + uint8_t *ie_len; + + /* No averaging => no Measurement Averaging parameters */ + if (mp->algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE) + return; + + /* (TLV) Measurement Averaging parameters for RxLev/RxQual */ + ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_MEAS_AVG_CFG); + + ave_cfg = (struct ipac_preproc_ave_cfg *) msgb_put(msg, sizeof(*ave_cfg)); + ave_cfg->param_id = ptype & 0x03; + + /* H_REQAVE and H_REQT */ + ave_cfg->h_reqave = mp->h_reqave & 0x1f; + ave_cfg->h_reqt = mp->h_reqt & 0x1f; + + /* Averaging method and parameters */ + ave_cfg->ave_method = (mp->algo - 1) & 0x07; + switch (mp->algo) { + case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: + msgb_v_put(msg, mp->ewma.alpha); + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED: + case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN: + /* FIXME: unknown format */ + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED: + case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE: + /* No parameters here */ + break; + } + + /* Update length part of the containing IE */ + *ie_len = msg->tail - (ie_len + 1); +} + +static void enc_power_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp) +{ + struct ipac_preproc_pc_comp *thresh_comp; + struct ipac_preproc_pc_thresh *thresh; + + /* These parameters are valid for dynamic mode only */ + OSMO_ASSERT(cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS); + + /* (TLV) Measurement Averaging Configure */ + enc_meas_proc_params(msg, IPAC_RXQUAL_AVE, &cp->rxqual_meas); + enc_meas_proc_params(msg, IPAC_RXLEV_AVE, &cp->rxlev_meas); + + /* (TV) Thresholds: {L,U}_RXLEV_XX_P and {L,U}_RXQUAL_XX_P */ + if (cp->dir == GSM_PWR_CTRL_DIR_UL) + msgb_v_put(msg, RSL_IPAC_EIE_MS_PWR_CTL); + else + msgb_v_put(msg, RSL_IPAC_EIE_BS_PWR_CTL); + + thresh = (struct ipac_preproc_pc_thresh *) msgb_put(msg, sizeof(*thresh)); + + /* {L,U}_RXLEV_XX_P (see 3GPP TS 45.008, A.3.2.1, a & b) */ + thresh->l_rxlev = cp->rxlev_meas.lower_thresh & 0x3f; + thresh->u_rxlev = cp->rxlev_meas.upper_thresh & 0x3f; + + /* {L,U}_RXQUAL_XX_P (see 3GPP TS 45.008, A.3.2.1, c & d) */ + thresh->l_rxqual = cp->rxqual_meas.lower_thresh & 0x07; + thresh->u_rxqual = cp->rxqual_meas.upper_thresh & 0x07; + + /* (TV) PC Threshold Comparators */ + msgb_v_put(msg, RSL_IPAC_EIE_PC_THRESH_COMP); + + thresh_comp = (struct ipac_preproc_pc_comp *) msgb_put(msg, sizeof(*thresh_comp)); + + /* RxLev: P1, N1, P2, N2 (see 3GPP TS 45.008, A.3.2.1, a & b) */ + thresh_comp->p1 = cp->rxlev_meas.lower_cmp_p & 0x1f; + thresh_comp->n1 = cp->rxlev_meas.lower_cmp_n & 0x1f; + thresh_comp->p2 = cp->rxlev_meas.upper_cmp_p & 0x1f; + thresh_comp->n2 = cp->rxlev_meas.upper_cmp_n & 0x1f; + + /* RxQual: P3, N3, P4, N4 (see 3GPP TS 45.008, A.3.2.1, c & d) */ + thresh_comp->p3 = cp->rxqual_meas.lower_cmp_p & 0x1f; + thresh_comp->n3 = cp->rxqual_meas.lower_cmp_n & 0x1f; + thresh_comp->p4 = cp->rxqual_meas.upper_cmp_p & 0x1f; + thresh_comp->n4 = cp->rxqual_meas.upper_cmp_n & 0x1f; + + /* Minimum interval between power level changes (P_CON_INTERVAL) */ + thresh_comp->pc_interval = cp->ctrl_interval; + + /* Change step limitations: POWER_{INC,RED}_STEP_SIZE */ + thresh_comp->inc_step_size = cp->inc_step_size_db & 0x0f; + thresh_comp->red_step_size = cp->red_step_size_db & 0x0f; +} + +void osmobts_enc_power_params_osmo_ext(struct msgb *msg, const struct gsm_power_ctrl_params *cp); +static void add_power_params_ie(struct msgb *msg, enum abis_rsl_ie iei, + const struct gsm_bts_trx *trx, + const struct gsm_power_ctrl_params *cp) +{ + uint8_t *ie_len = msgb_tl_put(msg, iei); + uint8_t msg_len = msgb_length(msg); + + enc_power_params(msg, cp); + if (iei == RSL_IE_MS_POWER_PARAM && is_osmobts(trx->bts)) + osmobts_enc_power_params_osmo_ext(msg, cp); + + *ie_len = msgb_length(msg) - msg_len; +} + +static int power_ctrl_send_def_params(const struct gsm_bts_trx *trx) +{ + const struct gsm_power_ctrl_params *ms_power_ctrl = &trx->bts->ms_power_ctrl; + const struct gsm_power_ctrl_params *bs_power_ctrl = &trx->bts->bs_power_ctrl; + struct abis_rsl_common_hdr *ch; + struct msgb *msg; + + /* Sending this message does not make sense if neither MS Power control + * nor BS Power control is to be performed by the BTS itself ('dyn-bts'). */ + if (ms_power_ctrl->mode != GSM_PWR_CTRL_MODE_DYN_BTS && + bs_power_ctrl->mode != GSM_PWR_CTRL_MODE_DYN_BTS) + return 0; + + msg = rsl_msgb_alloc(); + if (msg == NULL) + return -ENOMEM; + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_TRX; + ch->msg_type = RSL_MT_IPAC_MEAS_PREPROC_DFT; + + /* BS/MS Power IEs (to be re-defined in channel specific messages) */ + msgb_tv_put(msg, RSL_IE_MS_POWER, 0); /* dummy value */ + msgb_tv_put(msg, RSL_IE_BS_POWER, 0); /* dummy value */ + + /* MS/BS Power Parameters IEs */ + if (ms_power_ctrl->mode == GSM_PWR_CTRL_MODE_DYN_BTS) + add_power_params_ie(msg, RSL_IE_MS_POWER_PARAM, trx, ms_power_ctrl); + if (bs_power_ctrl->mode == GSM_PWR_CTRL_MODE_DYN_BTS) + add_power_params_ie(msg, RSL_IE_BS_POWER_PARAM, trx, bs_power_ctrl); + + msg->dst = trx->rsl_link_primary; + + return abis_rsl_sendmsg(msg); +} + +static int power_ctrl_enc_rsl_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp) +{ + /* We send everything in "Measurement Pre-processing Defaults" */ + return 0; +} diff --git a/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c index b7239326c..23196fcee 100644 --- a/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c +++ b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c @@ -1,6 +1,6 @@ /* ip.access nanoBTS specific code, OML attribute table generator */ -/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> +/* (C) 2016-2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Philipp Maier @@ -26,19 +26,92 @@ #include <osmocom/bsc/bts.h> #include <osmocom/gsm/bts_features.h> -struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts) +const struct tlv_definition ipacc_eie_tlv_def = { + .def = { + /* TODO: add more values from enum ipac_eie */ + [NM_IPAC_EIE_FREQ_BANDS] = { TLV_TYPE_TL16V }, + [NM_IPAC_EIE_MAX_TA] = { TLV_TYPE_TL16V }, + [NM_IPAC_EIE_CIPH_ALGOS] = { TLV_TYPE_TL16V }, + [NM_IPAC_EIE_CHAN_TYPES] = { TLV_TYPE_TL16V }, + [NM_IPAC_EIE_CHAN_MODES] = { TLV_TYPE_TL16V }, + [NM_IPAC_EIE_GPRS_CODING] = { TLV_TYPE_TL16V }, + [NM_IPAC_EIE_RTP_FEATURES] = { TLV_TYPE_TL16V }, + [NM_IPAC_EIE_RSL_FEATURES] = { TLV_TYPE_TL16V }, + } +}; + +static inline uint32_t ipacc_parse_supp_flags(const struct abis_om_fom_hdr *foh, + const struct value_string *flags, + const struct tlv_p_entry *e, + const char *text) +{ + uint32_t u32 = 0; + + for (unsigned int i = 0; i < OSMO_MAX(e->len, 4); i++) + u32 |= e->val[i] << (i * 8); + for (const struct value_string *vs = flags; vs->value && vs->str; vs++) { + if (u32 & vs->value) + LOGPFOH(DNM, LOGL_INFO, foh, "%s '%s' is supported\n", text, vs->str); + } + + return u32; +} + +/* Parse ip.access Supported Features IE */ +int ipacc_parse_supp_features(const struct gsm_bts *bts, + const struct abis_om_fom_hdr *foh, + const uint8_t *data, uint16_t data_len) +{ + const struct tlv_p_entry *e; + struct tlv_parsed tp; + + if (tlv_parse(&tp, &ipacc_eie_tlv_def, data, data_len, 0, 0) < 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__); + return -EINVAL; + } + + /* TODO: store the flags in the respective MO state */ + if ((e = TLVP_GET(&tp, NM_IPAC_EIE_FREQ_BANDS)) != NULL) + ipacc_parse_supp_flags(foh, abis_nm_ipacc_freq_band_desc, e, "Freq. band"); + if ((e = TLVP_GET(&tp, NM_IPAC_EIE_CIPH_ALGOS)) != NULL) + ipacc_parse_supp_flags(foh, abis_nm_ipacc_ciph_algo_desc, e, "Ciphering algorithm"); + if ((e = TLVP_GET(&tp, NM_IPAC_EIE_CHAN_TYPES)) != NULL) + ipacc_parse_supp_flags(foh, abis_nm_ipacc_chant_desc, e, "Channel type"); + if ((e = TLVP_GET(&tp, NM_IPAC_EIE_CHAN_MODES)) != NULL) + ipacc_parse_supp_flags(foh, abis_nm_ipacc_chanm_desc, e, "Channel mode"); + if ((e = TLVP_GET(&tp, NM_IPAC_EIE_GPRS_CODING)) != NULL) + ipacc_parse_supp_flags(foh, abis_nm_ipacc_gprs_coding_desc, e, "GPRS Coding Scheme"); + if ((e = TLVP_GET(&tp, NM_IPAC_EIE_RTP_FEATURES)) != NULL) + ipacc_parse_supp_flags(foh, abis_nm_ipacc_rtp_feat_desc, e, "RTP Feature"); + if ((e = TLVP_GET(&tp, NM_IPAC_EIE_RSL_FEATURES)) != NULL) + ipacc_parse_supp_flags(foh, abis_nm_ipacc_rsl_feat_desc, e, "RSL Feature"); + if (TLVP_PRES_LEN(&tp, NM_IPAC_EIE_MAX_TA, 1)) { + uint8_t u8 = *TLVP_VAL(&tp, NM_IPAC_EIE_MAX_TA); + LOGPFOH(DNM, LOGL_DEBUG, foh, "Max Timing Advance %u\n", u8); + } + + return 0; +} + +/* 3GPP TS 52.021 section 8.6.1 Set BTS Attributes */ +struct msgb *nanobts_gen_set_bts_attr(struct gsm_bts *bts) { struct msgb *msgb; uint8_t buf[256]; int rlt; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); - memcpy(buf, "\x55\x5b\x61\x67\x6d\x73", 6); - msgb_tv_fixed_put(msgb, NM_ATT_INTERF_BOUND, 6, buf); + msgb = msgb_alloc(1024, __func__); + if (!msgb) + return NULL; - /* interference avg. period in numbers of SACCH multifr */ - msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, 0x06); + /* Interference level Boundaries: 0 .. X5 (3GPP TS 52.021 sec 9.4.25) */ + msgb_tv_fixed_put(msgb, NM_ATT_INTERF_BOUND, + sizeof(bts->interf_meas_params_cfg.bounds_dbm), + &bts->interf_meas_params_cfg.bounds_dbm[0]); + /* Intave: Interference Averaging period (3GPP TS 52.021 sec 9.4.24) */ + msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, bts->interf_meas_params_cfg.avg_period); + /* Connection Failure Criterion (3GPP TS 52.021 sec 9.4.14) */ rlt = gsm_bts_get_radio_link_timeout(bts); if (rlt == -1) { /* Osmocom extension: Use infinite radio link timeout */ @@ -51,28 +124,30 @@ struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts) } msgb_tl16v_put(msgb, NM_ATT_CONN_FAIL_CRIT, 2, buf); + /* T200 (3GPP TS 52.021 sec 9.4.53) */ memcpy(buf, "\x1e\x24\x24\xa8\x34\x21\xa8", 7); msgb_tv_fixed_put(msgb, NM_ATT_T200, 7, buf); + /* Max Timing Advance (3GPP TS 52.021 sec 9.4.31) */ msgb_tv_put(msgb, NM_ATT_MAX_TA, 0x3f); - /* seconds */ + /* Overload Period (3GPP TS 52.021 sec 9.4.39), seconds */ memcpy(buf, "\x00\x01\x0a", 3); msgb_tv_fixed_put(msgb, NM_ATT_OVERL_PERIOD, 3, buf); - /* percent */ + /* CCCH Load Threshold (3GPP TS 12.21 sec 9.4.12), percent */ msgb_tv_put(msgb, NM_ATT_CCCH_L_T, bts->ccch_load_ind_thresh); - /* seconds */ - msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, 1); + /* CCCH Load Indication Period (3GPP TS 12.21 sec 9.4.11), seconds */ + msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, bts->ccch_load_ind_period); - /* busy threshold in - dBm */ + /* RACH Busy Threshold (3GPP TS 12.21 sec 9.4.44), -dBm */ buf[0] = 90; /* -90 dBm as default "busy" threshold */ if (bts->rach_b_thresh != -1) buf[0] = bts->rach_b_thresh & 0xff; msgb_tv_put(msgb, NM_ATT_RACH_B_THRESH, buf[0]); - /* rach load averaging 1000 slots */ + /* RACH Load Averaging Slots (3GPP TS 12.21 sec 9.4.45), 1000 slots */ buf[0] = 0x03; buf[1] = 0xe8; if (bts->rach_ldavg_slots != -1) { @@ -81,16 +156,19 @@ struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts) } msgb_tv_fixed_put(msgb, NM_ATT_LDAVG_SLOTS, 2, buf); - /* 10 milliseconds */ - msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, osmo_tdef_get(bts->network->T_defs, 3105, OSMO_TDEF_MS, -1)); + /* BTS Air Timer (3GPP TS 12.21 sec 9.4.10), 10 milliseconds */ + msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, osmo_tdef_get(bts->network->T_defs, 3105, OSMO_TDEF_MS, -1)/10); - /* 10 retransmissions of physical config */ - msgb_tv_put(msgb, NM_ATT_NY1, 10); + /* NY1 (3GPP TS 12.21 sec 9.4.37), number of retransmissions of physical config */ + gsm_bts_check_ny1(bts); + msgb_tv_put(msgb, NM_ATT_NY1, osmo_tdef_get(bts->network->T_defs, -3105, OSMO_TDEF_CUSTOM, -1)); + /* BCCH ARFCN (3GPP TS 12.21 sec 9.4.8) */ buf[0] = (bts->c0->arfcn >> 8) & 0x0f; buf[1] = bts->c0->arfcn & 0xff; msgb_tv_fixed_put(msgb, NM_ATT_BCCH_ARFCN, 2, buf); + /* BSIC (3GPP TS 12.21 sec 9.4.9) */ msgb_tv_put(msgb, NM_ATT_BSIC, bts->bsic); abis_nm_ipaccess_cgi(buf, bts); @@ -99,111 +177,170 @@ struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts) return msgb; } -struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts) +struct msgb *nanobts_gen_set_nse_attr(struct gsm_bts_sm *bts_sm) { struct msgb *msgb; - uint8_t buf[256]; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); + uint8_t buf[2]; + struct abis_nm_ipacc_att_ns_cfg ns_cfg; + struct abis_nm_ipacc_att_bssgp_cfg bssgp_cfg; + struct gsm_bts *bts = gsm_bts_sm_get_bts(bts_sm); + + msgb = msgb_alloc(1024, __func__); + if (!msgb) + return NULL; /* NSEI 925 */ - buf[0] = bts->gprs.nse.nsei >> 8; - buf[1] = bts->gprs.nse.nsei & 0xff; + buf[0] = bts_sm->gprs.nse.nsei >> 8; + buf[1] = bts_sm->gprs.nse.nsei & 0xff; msgb_tl16v_put(msgb, NM_ATT_IPACC_NSEI, 2, buf); - /* all timers in seconds */ - OSMO_ASSERT(ARRAY_SIZE(bts->gprs.nse.timer) < sizeof(buf)); - memcpy(buf, bts->gprs.nse.timer, ARRAY_SIZE(bts->gprs.nse.timer)); - msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_CFG, 7, buf); - - /* all timers in seconds */ - buf[0] = 3; /* blockimg timer (T1) */ - buf[1] = 3; /* blocking retries */ - buf[2] = 3; /* unblocking retries */ - buf[3] = 3; /* reset timer (T2) */ - buf[4] = 3; /* reset retries */ - buf[5] = 10; /* suspend timer (T3) in 100ms */ - buf[6] = 3; /* suspend retries */ - buf[7] = 10; /* resume timer (T4) in 100ms */ - buf[8] = 3; /* resume retries */ - buf[9] = 10; /* capability update timer (T5) */ - buf[10] = 3; /* capability update retries */ - - OSMO_ASSERT(ARRAY_SIZE(bts->gprs.cell.timer) < sizeof(buf)); - memcpy(buf, bts->gprs.cell.timer, ARRAY_SIZE(bts->gprs.cell.timer)); - msgb_tl16v_put(msgb, NM_ATT_IPACC_BSSGP_CFG, 11, buf); + osmo_static_assert(ARRAY_SIZE(bts_sm->gprs.nse.timer) == 7, nse_timer_array_wrong_size); + ns_cfg = (struct abis_nm_ipacc_att_ns_cfg){ + .un_blocking_timer = bts_sm->gprs.nse.timer[0], + .un_blocking_retries = bts_sm->gprs.nse.timer[1], + .reset_timer = bts_sm->gprs.nse.timer[2], + .reset_retries = bts_sm->gprs.nse.timer[3], + .test_timer = bts_sm->gprs.nse.timer[4], + .alive_timer = bts_sm->gprs.nse.timer[5], + .alive_retries = bts_sm->gprs.nse.timer[6], + }; + msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_CFG, sizeof(ns_cfg), (const uint8_t *)&ns_cfg); + + osmo_static_assert(ARRAY_SIZE(bts->gprs.cell.timer) == 11, cell_timer_array_wrong_size); + bssgp_cfg = (struct abis_nm_ipacc_att_bssgp_cfg){ + .t1_s = bts->gprs.cell.timer[0], + .t1_blocking_retries = bts->gprs.cell.timer[1], + .t1_unblocking_retries = bts->gprs.cell.timer[2], + .t2_s = bts->gprs.cell.timer[3], + .t2_retries = bts->gprs.cell.timer[4], + .t3_100ms = bts->gprs.cell.timer[5], + .t3_retries = bts->gprs.cell.timer[6], + .t4_100ms = bts->gprs.cell.timer[7], + .t4_retries = bts->gprs.cell.timer[8], + .t5_s = bts->gprs.cell.timer[9], + .t5_retries = bts->gprs.cell.timer[10], + }; + msgb_tl16v_put(msgb, NM_ATT_IPACC_BSSGP_CFG, sizeof(bssgp_cfg), (const uint8_t *)&bssgp_cfg); return msgb; } -struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts) +struct msgb *nanobts_gen_set_cell_attr(struct gsm_bts *bts) { + const struct gsm_gprs_cell *cell = &bts->gprs.cell; + const struct gprs_rlc_cfg *rlcc = &cell->rlc_cfg; struct msgb *msgb; - uint8_t buf[256]; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); + uint8_t buf[2]; + + msgb = msgb_alloc(1024, __func__); + if (!msgb) + return NULL; /* routing area code */ buf[0] = bts->gprs.rac; msgb_tl16v_put(msgb, NM_ATT_IPACC_RAC, 1, buf); - buf[0] = 5; /* repeat time (50ms) */ - buf[1] = 3; /* repeat count */ + buf[0] = rlcc->paging.repeat_time / 50; /* units of 50ms */ + buf[1] = rlcc->paging.repeat_count; msgb_tl16v_put(msgb, NM_ATT_IPACC_GPRS_PAGING_CFG, 2, buf); /* BVCI 925 */ - buf[0] = bts->gprs.cell.bvci >> 8; - buf[1] = bts->gprs.cell.bvci & 0xff; + buf[0] = cell->bvci >> 8; + buf[1] = cell->bvci & 0xff; msgb_tl16v_put(msgb, NM_ATT_IPACC_BVCI, 2, buf); /* all timers in seconds, unless otherwise stated */ - buf[0] = 20; /* T3142 */ - buf[1] = 5; /* T3169 */ - buf[2] = 5; /* T3191 */ - buf[3] = 160; /* T3193 (units of 10ms) */ - buf[4] = 5; /* T3195 */ - buf[5] = 10; /* N3101 */ - buf[6] = 4; /* N3103 */ - buf[7] = 8; /* N3105 */ - buf[8] = 15; /* RLC CV countdown */ - msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG, 9, buf); - - if (bts->gprs.mode == BTS_GPRS_EGPRS) { - buf[0] = 0x8f; - buf[1] = 0xff; - } else { - buf[0] = 0x0f; + const struct abis_nm_ipacc_att_rlc_cfg rlc_cfg = { + .t3142 = rlcc->parameter[RLC_T3142], + .t3169 = rlcc->parameter[RLC_T3169], + .t3191 = rlcc->parameter[RLC_T3191], + .t3193_10ms = rlcc->parameter[RLC_T3193], + .t3195 = rlcc->parameter[RLC_T3195], + .n3101 = rlcc->parameter[RLC_N3101], + .n3103 = rlcc->parameter[RLC_N3103], + .n3105 = rlcc->parameter[RLC_N3105], + .rlc_cv_countdown = rlcc->parameter[CV_COUNTDOWN], + }; + msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG, sizeof(rlc_cfg), (const uint8_t *)&rlc_cfg); + + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + if (cell->mo.ipaccess.obj_version < 4) + break; + /* fall-through */ + case GSM_BTS_TYPE_OSMOBTS: + /* CS1..CS4 flags encoded in the first octet */ + buf[0] = rlcc->cs_mask & 0x0f; + /* MCS1..MSC8 flags encoded in the second octet */ buf[1] = 0x00; + if (bts->gprs.mode == BTS_GPRS_EGPRS) { + /* MSC9 is special and also goes to the first octet */ + if (rlcc->cs_mask & (1 << GPRS_MCS9)) + buf[0] |= (1 << 7); + buf[1] = (rlcc->cs_mask >> 4) & 0xff; + } + msgb_tl16v_put(msgb, NM_ATT_IPACC_CODING_SCHEMES, 2, buf); + break; + default: + break; + } + + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + if (cell->mo.ipaccess.obj_version < 20) + break; + /* fall-through */ + case GSM_BTS_TYPE_OSMOBTS: + { + const struct abis_nm_ipacc_att_rlc_cfg_2 rlc_cfg_2 = { + .t_dl_tbf_ext_10ms = htons(rlcc->parameter[T_DL_TBF_EXT] / 10), + .t_ul_tbf_ext_10ms = htons(rlcc->parameter[T_UL_TBF_EXT] / 10), + .initial_cs = rlcc->initial_cs, + }; + msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_2, + sizeof(rlc_cfg_2), (const uint8_t *)&rlc_cfg_2); + break; + } + default: + break; + } + + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + if (cell->mo.ipaccess.obj_version < 30) + break; + /* fall-through */ + case GSM_BTS_TYPE_OSMOBTS: + { + const struct abis_nm_ipacc_att_rlc_cfg_3 rlc_cfg_3 = { + .initial_mcs = rlcc->initial_mcs, + }; + msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_3, + sizeof(rlc_cfg_3), (const uint8_t *)&rlc_cfg_3); + break; + } + default: + break; } - msgb_tl16v_put(msgb, NM_ATT_IPACC_CODING_SCHEMES, 2, buf); - - buf[0] = 0; /* T downlink TBF extension (0..500, high byte) */ - buf[1] = 250; /* T downlink TBF extension (0..500, low byte) */ - buf[2] = 0; /* T uplink TBF extension (0..500, high byte) */ - buf[3] = 250; /* T uplink TBF extension (0..500, low byte) */ - buf[4] = 2; /* CS2 */ - msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_2, 5, buf); - -#if 0 - /* EDGE model only, breaks older models. - * Should inquire the BTS capabilities */ - buf[0] = 2; /* MCS2 */ - msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_3, 1, buf); -#endif return msgb; } -struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts) +struct msgb *nanobts_gen_set_nsvc_attr(struct gsm_gprs_nsvc *nsvc) { struct msgb *msgb; uint8_t buf[256]; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + msgb = msgb_alloc(1024, __func__); + if (!msgb) + return NULL; /* 925 */ - buf[0] = bts->gprs.nsvc[0].nsvci >> 8; - buf[1] = bts->gprs.nsvc[0].nsvci & 0xff; + buf[0] = nsvc->nsvci >> 8; + buf[1] = nsvc->nsvci & 0xff; msgb_tl16v_put(msgb, NM_ATT_IPACC_NSVCI, 2, buf); - switch (bts->gprs.nsvc->remote.u.sa.sa_family) { + switch (nsvc->remote.u.sa.sa_family) { case AF_INET6: /* all fields are encoded in network byte order */ /* protocol family */ @@ -211,20 +348,20 @@ struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts) /* padding */ buf[1] = 0x00; /* local udp port */ - osmo_store16be(bts->gprs.nsvc[0].local_port, &buf[2]); + osmo_store16be(nsvc->local_port, &buf[2]); /* remote udp port */ - memcpy(&buf[4], &bts->gprs.nsvc[0].remote.u.sin6.sin6_port, sizeof(uint16_t)); + memcpy(&buf[4], &nsvc->remote.u.sin6.sin6_port, sizeof(uint16_t)); /* remote ip address */ - memcpy(&buf[6], &bts->gprs.nsvc[0].remote.u.sin6.sin6_addr, sizeof(struct in6_addr)); + memcpy(&buf[6], &nsvc->remote.u.sin6.sin6_addr, sizeof(struct in6_addr)); msgb_tl16v_put(msgb, NM_ATT_OSMO_NS_LINK_CFG, 6 + sizeof(struct in6_addr), buf); break; case AF_INET: /* remote udp port */ - memcpy(&buf[0], &bts->gprs.nsvc[0].remote.u.sin.sin_port, sizeof(uint16_t)); + memcpy(&buf[0], &nsvc->remote.u.sin.sin_port, sizeof(uint16_t)); /* remote ip address */ - memcpy(&buf[2], &bts->gprs.nsvc[0].remote.u.sin.sin_addr, sizeof(struct in_addr)); + memcpy(&buf[2], &nsvc->remote.u.sin.sin_addr, sizeof(struct in_addr)); /* local udp port */ - osmo_store16be(bts->gprs.nsvc[0].local_port, &buf[6]); + osmo_store16be(nsvc->local_port, &buf[6]); msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf); break; default: @@ -234,12 +371,15 @@ struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts) return msgb; } -struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts, +struct msgb *nanobts_gen_set_radio_attr(struct gsm_bts *bts, struct gsm_bts_trx *trx) { struct msgb *msgb; uint8_t buf[256]; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + msgb = msgb_alloc(1024, __func__); + if (!msgb) + return NULL; /* number of -2dB reduction steps / Pn */ msgb_tv_put(msgb, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2); diff --git a/src/osmo-bsc/bts_nokia_site.c b/src/osmo-bsc/bts_nokia_site.c index 2b6f91876..dc8ff1495 100644 --- a/src/osmo-bsc/bts_nokia_site.c +++ b/src/osmo-bsc/bts_nokia_site.c @@ -80,6 +80,7 @@ static void bootstrap_om_trx(struct gsm_bts_trx *trx) static int shutdown_om(struct gsm_bts *bts) { + gsm_bts_stats_reset(bts); /* TODO !? */ return 0; } @@ -203,8 +204,7 @@ static int nm_sig_cb(unsigned int subsys, unsigned int signal, return 0; switch (signal) { - case S_NM_STATECHG_OPER: - case S_NM_STATECHG_ADM: + case S_NM_STATECHG: nm_statechg_evt(signal, signal_data); break; default: @@ -797,6 +797,9 @@ static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id, case GSM_PCHAN_CCCH_SDCCH4: chan_config = 1; break; + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + chan_config = 3; + break; case GSM_PCHAN_TCH_F: chan_config = 6; /* 9 should work too */ break; @@ -806,6 +809,9 @@ static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id, case GSM_PCHAN_SDCCH8_SACCH8C: chan_config = 4; break; + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + chan_config = 5; + break; case GSM_PCHAN_PDCH: chan_config = 11; break; @@ -1332,7 +1338,7 @@ static int find_element(uint8_t * data, int len, uint16_t id, uint8_t * value, GET_NEXT_BYTE; - /* encoding bit, construced means that other elements are contained */ + /* encoding bit, constructed means that other elements are contained */ constructed = ((ub & 0x20) ? 1 : 0); if ((ub & 0x1F) == 0x1F) { @@ -1391,7 +1397,7 @@ static int dump_elements(uint8_t * data, int len) GET_NEXT_BYTE; - /* encoding bit, construced means that other elements are contained */ + /* encoding bit, constructed means that other elements are contained */ constructed = ((ub & 0x20) ? 1 : 0); if ((ub & 0x1F) == 0x1F) { @@ -1452,7 +1458,7 @@ static void nokia_abis_nm_fake_1221_ok(struct gsm_bts *bts) struct gsm_bts_trx *trx; mo_ok(&bts->mo); - mo_ok(&bts->site_mgr.mo); + mo_ok(&bts->site_mgr->mo); llist_for_each_entry(trx, &bts->trx_list, list) { int i; @@ -1758,14 +1764,6 @@ static struct gsm_network *my_net; static int bts_model_nokia_site_start(struct gsm_network *net) { - model_nokia_site.features.data = &model_nokia_site._features_data[0]; - model_nokia_site.features.data_len = - sizeof(model_nokia_site._features_data); - - osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HOPPING); - osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HSCSD); - osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_MULTI_TSC); - osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL); osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL); @@ -1777,5 +1775,12 @@ static int bts_model_nokia_site_start(struct gsm_network *net) int bts_model_nokia_site_init(void) { + model_nokia_site.features.data = &model_nokia_site._features_data[0]; + model_nokia_site.features.data_len = sizeof(model_nokia_site._features_data); + + osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HOPPING); + osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HSCSD); + osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_MULTI_TSC); + return gsm_bts_model_register(&model_nokia_site); } diff --git a/src/osmo-bsc/bts_osmobts.c b/src/osmo-bsc/bts_osmobts.c new file mode 100644 index 000000000..9312a2a27 --- /dev/null +++ b/src/osmo-bsc/bts_osmobts.c @@ -0,0 +1,210 @@ +/* Osmocom OsmoBTS specific code */ + +/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org> + * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <arpa/inet.h> + +#include <osmocom/gsm/tlv.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/abis/subchan_demux.h> +#include <osmocom/abis/ipaccess.h> +#include <osmocom/core/logging.h> + +extern struct gsm_bts_model bts_model_nanobts; + +static struct gsm_bts_model model_osmobts; + +static void enc_osmo_meas_proc_params(struct msgb *msg, const struct gsm_power_ctrl_params *mp) +{ + struct osmo_preproc_ave_cfg *ave_cfg; + uint8_t *ie_len; + + /* No averaging => no Measurement Averaging parameters */ + if (mp->ci_fr_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE && + mp->ci_hr_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE && + mp->ci_amr_fr_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE && + mp->ci_amr_hr_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE && + mp->ci_sdcch_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE && + mp->ci_gprs_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE) + return; + + /* (TLV) Measurement Averaging parameters for RxLev/RxQual */ + ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG); + + ave_cfg = (struct osmo_preproc_ave_cfg *) msgb_put(msg, sizeof(*ave_cfg)); + +#define ENC_PROC(PARAMS, TO, TYPE) do { \ + (TO)->TYPE.ave_enabled = (PARAMS)->TYPE##_meas.algo != GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE; \ + if ((TO)->TYPE.ave_enabled) { \ + /* H_REQAVE and H_REQT */ \ + (TO)->TYPE.h_reqave = (PARAMS)->TYPE##_meas.h_reqave & 0x1f; \ + (TO)->TYPE.h_reqt = (PARAMS)->TYPE##_meas.h_reqt & 0x1f; \ + /* Averaging method and parameters */ \ + (TO)->TYPE.ave_method = ((PARAMS)->TYPE##_meas.algo - 1) & 0x07; \ + switch ((PARAMS)->TYPE##_meas.algo) { \ + case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: \ + msgb_v_put(msg, (PARAMS)->TYPE##_meas.ewma.alpha); \ + break; \ + case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED: \ + case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN: \ + /* FIXME: unknown format */ \ + break; \ + case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED: \ + case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE: \ + /* No parameters here */ \ + break; \ + } \ + } \ + } while (0) + ENC_PROC(mp, ave_cfg, ci_fr); + ENC_PROC(mp, ave_cfg, ci_hr); + ENC_PROC(mp, ave_cfg, ci_amr_fr); + ENC_PROC(mp, ave_cfg, ci_amr_hr); + ENC_PROC(mp, ave_cfg, ci_sdcch); + ENC_PROC(mp, ave_cfg, ci_gprs); +#undef ENC_PROC + + /* Update length part of the containing IE */ + *ie_len = msg->tail - (ie_len + 1); +} + +/* Appends Osmocom specific extension IEs into RSL_IE_MS_POWER_PARAM */ +void osmobts_enc_power_params_osmo_ext(struct msgb *msg, const struct gsm_power_ctrl_params *cp) +{ + struct osmo_preproc_pc_thresh *osmo_thresh; + struct osmo_preproc_pc_comp *osmo_thresh_comp; + uint8_t *ie_len; + + /* (TLV) Measurement Averaging Configure (C/I) */ + enc_osmo_meas_proc_params(msg, cp); + + /* (TLV) Thresholds (C/I) */ + ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_OSMO_MS_PWR_CTL); + osmo_thresh = (struct osmo_preproc_pc_thresh *) msgb_put(msg, sizeof(*osmo_thresh)); + #define ENC_THRESH_CI(TYPE) \ + do { \ + if (cp->TYPE##_meas.enabled) { \ + osmo_thresh->l_##TYPE = cp->TYPE##_meas.lower_thresh; \ + osmo_thresh->u_##TYPE = cp->TYPE##_meas.upper_thresh; \ + } else { \ + osmo_thresh->l_##TYPE = 0; \ + osmo_thresh->u_##TYPE = 0; \ + } \ + } while (0) + ENC_THRESH_CI(ci_fr); + ENC_THRESH_CI(ci_hr); + ENC_THRESH_CI(ci_amr_fr); + ENC_THRESH_CI(ci_amr_hr); + ENC_THRESH_CI(ci_sdcch); + ENC_THRESH_CI(ci_gprs); + #undef ENC_THRESH_CI + /* Update length part of the containing IE */ + *ie_len = msg->tail - (ie_len + 1); + + /* (TLV) PC Threshold Comparators (C/I) */ + ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_OSMO_PC_THRESH_COMP); + osmo_thresh_comp = (struct osmo_preproc_pc_comp *) msgb_put(msg, sizeof(*osmo_thresh_comp)); + #define ENC_THRESH_CI(TYPE) \ + do { \ + if (cp->TYPE##_meas.enabled) { \ + osmo_thresh_comp->TYPE.lower_p = cp->TYPE##_meas.lower_cmp_p & 0x1f; \ + osmo_thresh_comp->TYPE.lower_n = cp->TYPE##_meas.lower_cmp_n & 0x1f; \ + osmo_thresh_comp->TYPE.upper_p = cp->TYPE##_meas.upper_cmp_p & 0x1f; \ + osmo_thresh_comp->TYPE.upper_n = cp->TYPE##_meas.upper_cmp_n & 0x1f; \ + } else { \ + osmo_thresh_comp->TYPE.lower_p = 0; \ + osmo_thresh_comp->TYPE.lower_n = 0; \ + osmo_thresh_comp->TYPE.upper_p = 0; \ + osmo_thresh_comp->TYPE.upper_n = 0; \ + } \ + } while (0) + ENC_THRESH_CI(ci_fr); + ENC_THRESH_CI(ci_hr); + ENC_THRESH_CI(ci_amr_fr); + ENC_THRESH_CI(ci_amr_hr); + ENC_THRESH_CI(ci_sdcch); + ENC_THRESH_CI(ci_gprs); + #undef ENC_THRESH_CI + /* Update length part of the containing IE */ + *ie_len = msg->tail - (ie_len + 1); +} + +static int power_ctrl_send_c0_power_red(const struct gsm_bts *bts, const uint8_t red) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + + msg = rsl_msgb_alloc(); + if (msg == NULL) + return -ENOMEM; + + /* Abuse the standard BS POWER CONTROL message by specifying 'Common Channel' + * in the Protocol Discriminator field and 'BCCH' in the Channel Number IE. */ + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + dh->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN; + dh->c.msg_type = RSL_MT_BS_POWER_CONTROL; + dh->ie_chan = RSL_IE_CHAN_NR; + dh->chan_nr = RSL_CHAN_BCCH; + + msgb_tv_put(msg, RSL_IE_BS_POWER, red / 2); + + msg->dst = bts->c0->rsl_link_primary; + + return abis_rsl_sendmsg(msg); +} + +int bts_model_osmobts_init(void) +{ + model_osmobts = bts_model_nanobts; + model_osmobts.name = "osmo-bts"; + model_osmobts.type = GSM_BTS_TYPE_OSMOBTS; + model_osmobts.features_get_reported = true; + + /* Unlike nanoBTS, osmo-bts does support SI2bis and SI2ter fine */ + model_osmobts.force_combined_si = false; + + /* Power control API */ + model_osmobts.power_ctrl_send_c0_power_red = &power_ctrl_send_c0_power_red; + + model_osmobts.features.data = &model_osmobts._features_data[0]; + model_osmobts.features.data_len = + sizeof(model_osmobts._features_data); + memset(model_osmobts.features.data, 0, model_osmobts.features.data_len); + + /* Adjust bts_init/bts_model_init in OsmoBTS to report new features. + * See also: doc/bts-features.txt */ + + model_osmobts.nm_att_tlvdef.def[NM_ATT_OSMO_NS_LINK_CFG].type = TLV_TYPE_TL16V; + + return gsm_bts_model_register(&model_osmobts); +} diff --git a/src/osmo-bsc/bts_setup_ramp.c b/src/osmo-bsc/bts_setup_ramp.c new file mode 100644 index 000000000..247f77654 --- /dev/null +++ b/src/osmo-bsc/bts_setup_ramp.c @@ -0,0 +1,249 @@ +/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * Author: Alexander Couzens <acouzens@sysmocom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdbool.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> + +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bts_sm.h> +#include <osmocom/bsc/bts_setup_ramp.h> +#include <osmocom/bsc/nm_common_fsm.h> + + +static void _bts_setup_ramp_unblock_bts(struct gsm_bts *bts) +{ + llist_del_init(&bts->bts_setup_ramp.list); + bts->bts_setup_ramp.state = BTS_SETUP_RAMP_READY; + + nm_fsm_dispatch_all_configuring(bts, NM_EV_SETUP_RAMP_READY, NULL); +} + +/*! + * Unblock a BTS from BTS setup ramping to continue setup and configure. + * + * \param bts pointer to the bts + * \return 0 on success, -EINVAL when the BTS is not waiting. + */ +int bts_setup_ramp_unblock_bts(struct gsm_bts *bts) +{ + if (bts->bts_setup_ramp.state != BTS_SETUP_RAMP_WAIT) + return -EINVAL; + + if (llist_empty(&bts->bts_setup_ramp.list)) + return -EINVAL; + + _bts_setup_ramp_unblock_bts(bts); + return 0; +} + +/*! + * Timer callback and called by bts_setup_ramp_deactivate + * \param _net pointer to struct gsm_network + */ +static void bts_setup_ramp_timer_cb(void *_net) +{ + struct gsm_network *net = (struct gsm_network *) _net; + struct gsm_bts *bts, *n; + net->bts_setup_ramp.count = 0; + + llist_for_each_entry_safe(bts, n, &net->bts_setup_ramp.head, bts_setup_ramp.list) { + net->bts_setup_ramp.count++; + _bts_setup_ramp_unblock_bts(bts); + LOG_BTS(bts, DNM, LOGL_INFO, "Unblock BTS %d from BTS ramping.\n", bts->nr); + if (bts_setup_ramp_active(net) && net->bts_setup_ramp.count >= net->bts_setup_ramp.step_size) + break; + } + + if (bts_setup_ramp_active(net)) + osmo_timer_schedule(&net->bts_setup_ramp.timer, net->bts_setup_ramp.step_interval, 0); +} + +const struct value_string bts_setup_ramp_state_values[] = { + { BTS_SETUP_RAMP_INIT, "Initial" }, + { BTS_SETUP_RAMP_WAIT, "Waiting" }, + { BTS_SETUP_RAMP_READY, "Ready" }, + { 0, NULL }, +}; + +const char *bts_setup_ramp_get_state_str(struct gsm_bts *bts) +{ + return get_value_string_or_null(bts_setup_ramp_state_values, bts->bts_setup_ramp.state); +} + +/* return true when state has been changed. */ +static bool check_config(struct gsm_network *net) +{ + bool new_state = (net->bts_setup_ramp.enabled + && net->bts_setup_ramp.step_size > 0 + && net->bts_setup_ramp.step_interval > 0); + + if (!new_state && bts_setup_ramp_active(net)) { + net->bts_setup_ramp.active = false; + osmo_timer_del(&net->bts_setup_ramp.timer); + /* clear bts list */ + bts_setup_ramp_timer_cb(net); + return true; + } else if (new_state && !bts_setup_ramp_active(net)) { + net->bts_setup_ramp.active = true; + osmo_timer_schedule(&net->bts_setup_ramp.timer, net->bts_setup_ramp.step_interval, 0); + return true; + } + + return false; +} + +/*! + * Enable the bts setup ramping feature + * + * The BTS setup ramping prevents BSC overload when too many BTS tries to setup and + * configure at the same time. E.g. this might happen if there is a major network outage + * between all BTS and the BSC. + * + * \param[in] net a pointer to the gsm network + */ +void bts_setup_ramp_enable(struct gsm_network *net) +{ + net->bts_setup_ramp.enabled = true; + check_config(net); +} + +/*! + * Disable the bts setup ramping feature + * + * \param[in] net a pointer to the gsm network + */ +void bts_setup_ramp_disable(struct gsm_network *net) +{ + net->bts_setup_ramp.enabled = false; + check_config(net); +} + +/*! Checks if the bts setup ramp correct configured and active + * + * \param[in] net a pointer to the gsm network + * \return true if the bts setup ramp is active + */ +bool bts_setup_ramp_active(struct gsm_network *net) +{ + return net->bts_setup_ramp.active; +} + +/*! + * Check if the BTS should wait to setup. + * + * Can be called multiple times by the same BTS. + * + * \param bts pointer to the bts + * \return true if the bts should wait + */ +bool bts_setup_ramp_wait(struct gsm_bts *bts) +{ + struct gsm_network *net = bts->network; + + if (!bts_setup_ramp_active(net)) { + bts->bts_setup_ramp.state = BTS_SETUP_RAMP_READY; + return false; + } + + switch (bts->bts_setup_ramp.state) { + case BTS_SETUP_RAMP_INIT: + break; + case BTS_SETUP_RAMP_WAIT: + return true; + case BTS_SETUP_RAMP_READY: + return false; + } + + if (net->bts_setup_ramp.count < net->bts_setup_ramp.step_size) { + LOG_BTS(bts, DNM, LOGL_INFO, + "BTS %d can configure without waiting for BTS ramping.\n", bts->nr); + + net->bts_setup_ramp.count++; + bts->bts_setup_ramp.state = BTS_SETUP_RAMP_READY; + return false; + } + + bts->bts_setup_ramp.state = BTS_SETUP_RAMP_WAIT; + llist_add_tail(&bts->bts_setup_ramp.list, &net->bts_setup_ramp.head); + LOGP(DNM, LOGL_INFO, "BTS %d will wait for BTS ramping.\n", bts->nr); + + return true; +} + +void bts_setup_ramp_init_network(struct gsm_network *net) +{ + INIT_LLIST_HEAD(&net->bts_setup_ramp.head); + osmo_timer_setup(&net->bts_setup_ramp.timer, bts_setup_ramp_timer_cb, net); +} + +void bts_setup_ramp_init_bts(struct gsm_bts *bts) +{ + /* Initialize bts_setup_ramp.list (llist_entry) to have llist_empty() available */ + INIT_LLIST_HEAD(&bts->bts_setup_ramp.list); + bts->bts_setup_ramp.state = BTS_SETUP_RAMP_INIT; +} + +/*! + * Remove the bts from the bts setup ramp waiting list and resets the BTS setup ramping state. + * Should be called when removing the BTS + * + * \param bts pointer to the bts + */ +void bts_setup_ramp_remove(struct gsm_bts *bts) +{ + if (!llist_empty(&bts->bts_setup_ramp.list)) + llist_del_init(&bts->bts_setup_ramp.list); + bts->bts_setup_ramp.state = BTS_SETUP_RAMP_INIT; +} + +/*! + * Set the BTS setup ramping step interval. + * + * Within the time window of \param step_interval only a limited amount (see step_size) + * of BTS will be configured. + * + * \param[in] net a pointer to the gsm network + * \param step_interval in seconds + */ +void bts_setup_ramp_set_step_interval(struct gsm_network *net, unsigned int step_interval) +{ + net->bts_setup_ramp.step_interval = step_interval; + check_config(net); +} + +/*! + * Set the BTS setup ramping step_size + * + * Within the time window of step_interval only a limited amount of BTS (\param step_size) + * will be configured. + * + * \param[in] net a pointer to the gsm network + * \param step_size the step size + */ +void bts_setup_ramp_set_step_size(struct gsm_network *net, unsigned int step_size) +{ + net->bts_setup_ramp.step_size = step_size; + check_config(net); +} diff --git a/src/osmo-bsc/bts_siemens_bs11.c b/src/osmo-bsc/bts_siemens_bs11.c index 08694eab0..9e4f09103 100644 --- a/src/osmo-bsc/bts_siemens_bs11.c +++ b/src/osmo-bsc/bts_siemens_bs11.c @@ -401,7 +401,7 @@ static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts) abis_nm_set_channel_attr(ts, ccomb); - if (is_ipaccess_bts(ts->trx->bts)) + if (is_ipa_abisip_bts(ts->trx->bts)) return; switch (ts->pchan_from_config) { @@ -423,57 +423,35 @@ static void nm_reconfig_trx(struct gsm_bts_trx *trx) patch_nm_tables(trx->bts); - switch (trx->bts->type) { - case GSM_BTS_TYPE_BS11: - /* FIXME: discover this by fetching an attribute */ + /* FIXME: discover this by fetching an attribute */ #if 0 - trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */ + trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */ #else - trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */ + trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */ #endif - abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts, - e1l->e1_ts_ss); - abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr, - e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei); - - /* Set Radio Attributes */ - if (trx == trx->bts->c0) - abis_nm_set_radio_attr(trx, bs11_attr_radio, - sizeof(bs11_attr_radio)); - else { - uint8_t trx1_attr_radio[sizeof(bs11_attr_radio)]; - uint8_t arfcn_low = trx->arfcn & 0xff; - uint8_t arfcn_high = (trx->arfcn >> 8) & 0x0f; - memcpy(trx1_attr_radio, bs11_attr_radio, - sizeof(trx1_attr_radio)); - - /* patch ARFCN into TRX Attributes */ - trx1_attr_radio[2] &= 0xf0; - trx1_attr_radio[2] |= arfcn_high; - trx1_attr_radio[3] = arfcn_low; - - abis_nm_set_radio_attr(trx, trx1_attr_radio, - sizeof(trx1_attr_radio)); - } - break; - case GSM_BTS_TYPE_NANOBTS: - switch (trx->bts->band) { - case GSM_BAND_850: - case GSM_BAND_900: - trx->nominal_power = 20; - break; - case GSM_BAND_1800: - case GSM_BAND_1900: - trx->nominal_power = 23; - break; - default: - LOGP(DNM, LOGL_ERROR, "Unsupported nanoBTS GSM band %s\n", - gsm_band_name(trx->bts->band)); - break; - } - break; - default: - break; + abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts, + e1l->e1_ts_ss); + abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr, + e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei_primary); + + /* Set Radio Attributes */ + if (trx == trx->bts->c0) + abis_nm_set_radio_attr(trx, bs11_attr_radio, + sizeof(bs11_attr_radio)); + else { + uint8_t trx1_attr_radio[sizeof(bs11_attr_radio)]; + uint8_t arfcn_low = trx->arfcn & 0xff; + uint8_t arfcn_high = (trx->arfcn >> 8) & 0x0f; + memcpy(trx1_attr_radio, bs11_attr_radio, + sizeof(trx1_attr_radio)); + + /* patch ARFCN into TRX Attributes */ + trx1_attr_radio[2] &= 0xf0; + trx1_attr_radio[2] |= arfcn_high; + trx1_attr_radio[3] = arfcn_low; + + abis_nm_set_radio_attr(trx, trx1_attr_radio, + sizeof(trx1_attr_radio)); } for (i = 0; i < TRX_NR_TS; i++) @@ -484,17 +462,11 @@ static void nm_reconfig_bts(struct gsm_bts *bts) { struct gsm_bts_trx *trx; - switch (bts->type) { - case GSM_BTS_TYPE_BS11: - patch_nm_tables(bts); - abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/ - abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts)); - abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */ - abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */ - break; - default: - break; - } + patch_nm_tables(bts); + abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/ + abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts)); + abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */ + abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */ llist_for_each_entry(trx, &bts->trx_list, list) nm_reconfig_trx(trx); @@ -552,6 +524,7 @@ static int shutdown_om(struct gsm_bts *bts) /* Reset BTS Site manager resource */ abis_nm_bs11_reset_resource(bts); + gsm_bts_stats_reset(bts); gsm_bts_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL); return 0; @@ -603,13 +576,6 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal, static int bts_model_bs11_start(struct gsm_network *net) { - model_bs11.features.data = &model_bs11._features_data[0]; - model_bs11.features.data_len = sizeof(model_bs11._features_data); - - osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HOPPING); - osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HSCSD); - osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_MULTI_TSC); - osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL); @@ -618,5 +584,12 @@ static int bts_model_bs11_start(struct gsm_network *net) int bts_model_bs11_init(void) { + model_bs11.features.data = &model_bs11._features_data[0]; + model_bs11.features.data_len = sizeof(model_bs11._features_data); + + osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HOPPING); + osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HSCSD); + osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_MULTI_TSC); + return gsm_bts_model_register(&model_bs11); } diff --git a/src/osmo-bsc/bts_sm.c b/src/osmo-bsc/bts_sm.c new file mode 100644 index 000000000..ca572f146 --- /dev/null +++ b/src/osmo-bsc/bts_sm.c @@ -0,0 +1,112 @@ +/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org> + * (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/gsm/abis_nm.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bts_sm.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/nm_common_fsm.h> + +static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 }; + +static int gsm_bts_sm_talloc_destructor(struct gsm_bts_sm *bts_sm) +{ + int i; + for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nsvc); i++) { + if (bts_sm->gprs.nsvc[i].mo.fi) { + osmo_fsm_inst_free(bts_sm->gprs.nsvc[i].mo.fi); + bts_sm->gprs.nsvc[i].mo.fi = NULL; + } + } + if (bts_sm->gprs.nse.mo.fi) { + osmo_fsm_inst_free(bts_sm->gprs.nse.mo.fi); + bts_sm->gprs.nse.mo.fi = NULL; + } + + if (bts_sm->mo.fi) { + osmo_fsm_inst_free(bts_sm->mo.fi); + bts_sm->mo.fi = NULL; + } + return 0; +} + +struct gsm_bts_sm *gsm_bts_sm_alloc(struct gsm_network *net, uint8_t bts_num) +{ + struct gsm_bts_sm *bts_sm = talloc_zero(net, struct gsm_bts_sm); + struct gsm_bts *bts; + int i; + if (!bts_sm) + return NULL; + + talloc_set_destructor(bts_sm, gsm_bts_sm_talloc_destructor); + bts_sm->mo.fi = osmo_fsm_inst_alloc(&nm_bts_sm_fsm, bts_sm, bts_sm, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(bts_sm->mo.fi, "bts_sm"); + + bts = gsm_bts_alloc(net, bts_sm, bts_num); + if (!bts) { + talloc_free(bts_sm); + return NULL; + } + bts_sm->bts[0] = bts; + + gsm_mo_init(&bts_sm->mo, bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff); + + + bts_sm->gprs.nse.mo.fi = osmo_fsm_inst_alloc(&nm_gprs_nse_fsm, bts_sm, &bts_sm->gprs.nse, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(bts_sm->gprs.nse.mo.fi, "nse%d", bts_num); + gsm_mo_init(&bts_sm->gprs.nse.mo, bts, NM_OC_GPRS_NSE, bts->nr, 0xff, 0xff); + memcpy(&bts_sm->gprs.nse.timer, bts_nse_timer_default, + sizeof(bts_sm->gprs.nse.timer)); + + for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nsvc); i++) { + bts_sm->gprs.nsvc[i].bts = bts; + bts_sm->gprs.nsvc[i].id = i; + bts_sm->gprs.nsvc[i].mo.fi = osmo_fsm_inst_alloc( + &nm_gprs_nsvc_fsm, bts_sm, + &bts_sm->gprs.nsvc[i], + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(bts_sm->gprs.nsvc[i].mo.fi, + "nsvc%d", i); + gsm_mo_init(&bts_sm->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC, + bts->nr, i, 0xff); + } + memcpy(&bts_sm->gprs.nse.timer, bts_nse_timer_default, + sizeof(bts_sm->gprs.nse.timer)); + gsm_mo_init(&bts_sm->gprs.nse.mo, bts, NM_OC_GPRS_NSE, + bts->nr, 0xff, 0xff); + + return bts_sm; +} + +void gsm_bts_sm_mo_reset(struct gsm_bts_sm *bts_sm) +{ + int i; + gsm_abis_mo_reset(&bts_sm->mo); + + gsm_abis_mo_reset(&bts_sm->gprs.nse.mo); + for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nsvc); i++) + gsm_abis_mo_reset(&bts_sm->gprs.nsvc[i].mo); + + gsm_bts_mo_reset(bts_sm->bts[0]); +} diff --git a/src/osmo-bsc/bts_sysmobts.c b/src/osmo-bsc/bts_sysmobts.c deleted file mode 100644 index d7d15ebc2..000000000 --- a/src/osmo-bsc/bts_sysmobts.c +++ /dev/null @@ -1,67 +0,0 @@ -/* sysmocom sysmoBTS specific code */ - -/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org> - * - * 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 <http://www.gnu.org/licenses/>. - * - */ - -#include <arpa/inet.h> - -#include <osmocom/gsm/tlv.h> - -#include <osmocom/bsc/gsm_data.h> -#include <osmocom/bsc/signal.h> -#include <osmocom/bsc/abis_nm.h> -#include <osmocom/bsc/bts.h> -#include <osmocom/abis/e1_input.h> -#include <osmocom/gsm/tlv.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/talloc.h> -#include <osmocom/bsc/gsm_data.h> -#include <osmocom/bsc/abis_nm.h> -#include <osmocom/bsc/abis_rsl.h> -#include <osmocom/bsc/debug.h> -#include <osmocom/abis/subchan_demux.h> -#include <osmocom/abis/ipaccess.h> -#include <osmocom/core/logging.h> - -extern struct gsm_bts_model bts_model_nanobts; - -static struct gsm_bts_model model_sysmobts; - -int bts_model_sysmobts_init(void) -{ - model_sysmobts = bts_model_nanobts; - model_sysmobts.name = "sysmobts"; - model_sysmobts.type = GSM_BTS_TYPE_OSMOBTS; - - /* Unlike nanoBTS, sysmoBTS supports SI2bis and SI2ter fine */ - model_sysmobts.force_combined_si = false; - - model_sysmobts.features.data = &model_sysmobts._features_data[0]; - model_sysmobts.features.data_len = - sizeof(model_sysmobts._features_data); - memset(model_sysmobts.features.data, 0, model_sysmobts.features.data_len); - - osmo_bts_set_feature(&model_sysmobts.features, BTS_FEAT_GPRS); - osmo_bts_set_feature(&model_sysmobts.features, BTS_FEAT_EGPRS); - osmo_bts_set_feature(&model_sysmobts.features, BTS_FEAT_PAGING_COORDINATION); - - model_sysmobts.nm_att_tlvdef.def[NM_ATT_OSMO_NS_LINK_CFG].type = TLV_TYPE_TL16V; - - return gsm_bts_model_register(&model_sysmobts); -} diff --git a/src/osmo-bsc/bts_trx.c b/src/osmo-bsc/bts_trx.c index 0e5223874..4d2588d5b 100644 --- a/src/osmo-bsc/bts_trx.c +++ b/src/osmo-bsc/bts_trx.c @@ -31,6 +31,7 @@ #include <osmocom/bsc/pcu_if.h> #include <osmocom/bsc/debug.h> #include <osmocom/bsc/nm_common_fsm.h> +#include <osmocom/bsc/lchan.h> static int gsm_bts_trx_talloc_destructor(struct gsm_bts_trx *trx) { @@ -50,6 +51,7 @@ static int gsm_bts_trx_talloc_destructor(struct gsm_bts_trx *trx) osmo_fsm_inst_free(ts->mo.fi); ts->mo.fi = NULL; } + ts_fsm_free(ts); } return 0; } @@ -67,6 +69,8 @@ struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) trx->bts = bts; trx->nr = bts->num_trx++; + trx->rsl_tei_primary = trx->nr; + trx->mo.fi = osmo_fsm_inst_alloc(&nm_rcarrier_fsm, trx, trx, LOGL_INFO, NULL); osmo_fsm_inst_update_id_f(trx->mo.fi, "bts%d-trx%d", bts->nr, trx->nr); @@ -104,22 +108,21 @@ struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) ts->hopping.ma.data = ts->hopping.ma_data; for (l = 0; l < TS_MAX_LCHAN; l++) { - struct gsm_lchan *lchan; - char *name; - lchan = &ts->lchan[l]; - - lchan->ts = ts; - lchan->nr = l; - lchan->type = GSM_LCHAN_NONE; - - name = gsm_lchan_name_compute(lchan); - lchan->name = talloc_strdup(trx, name); + struct gsm_lchan *lchan = &ts->lchan[l]; + lchan_init(lchan, ts, l); } } if (trx->nr != 0) trx->nominal_power = bts->c0->nominal_power; + if (bts->model && bts->model->trx_init) { + if (bts->model->trx_init(trx) < 0) { + talloc_free(trx); + return NULL; + } + } + llist_add_tail(&trx->list, &bts->trx_list); return trx; @@ -146,37 +149,98 @@ 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 vamos = false; bool ok; if (rc) *rc = -EINVAL; - if (cbits == 0x01) { + /* Why call ts_is_capable_of_pchan() here? Dynamic timeslots may receive RSL Channel Activation ACK on a + * timeslot that is in transition between pchan modes. That ACK actually confirms the pchan switch, so instead + * of checking the current pchan mode, we must allow any pchans that a dyn TS is capable of. */ + + /* Interpret Osmocom specific cbits only for OsmoBTS type */ + if (trx->bts->model->type == GSM_BTS_TYPE_OSMOBTS) { + /* For VAMOS cbits, set vamos = true and handle cbits as their equivalent non-VAMOS cbits below. */ + switch (cbits) { + case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs: + case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(0): + case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(1): + cbits = (chan_nr & ~RSL_CHAN_OSMO_VAMOS_MASK) >> 3; + vamos = true; + break; + default: + break; + } + } + + switch (cbits) { + case ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs: 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) { + if (!ok) + LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr %x cbits %x: ts %s is not capable of GSM_PCHAN_TCH_F\n", + chan_nr, cbits, gsm_ts_and_pchan_name(ts)); + break; + case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0): + case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(1): lch_idx = cbits & 0x1; /* TCH/H */ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_H); - } else if ((cbits & 0x1c) == 0x04) { + if (!ok) + LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_TCH_H\n", + chan_nr, cbits, gsm_ts_and_pchan_name(ts)); + break; + case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(1): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(2): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(3): lch_idx = cbits & 0x3; /* SDCCH/4 */ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH_SDCCH4); - } else if ((cbits & 0x18) == 0x08) { + if (!ok) + LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_CCCH_SDCCH4\n", + chan_nr, cbits, gsm_ts_and_pchan_name(ts)); + break; + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(1): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(2): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(3): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(4): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(5): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(6): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(7): lch_idx = cbits & 0x7; /* SDCCH/8 */ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_SDCCH8_SACCH8C); - } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { + if (!ok) + LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_SDCCH8_SACCH8C\n", + chan_nr, cbits, gsm_ts_and_pchan_name(ts)); + break; + case ABIS_RSL_CHAN_NR_CBITS_BCCH: + case ABIS_RSL_CHAN_NR_CBITS_RACH: + case ABIS_RSL_CHAN_NR_CBITS_PCH_AGCH: lch_idx = 0; /* CCCH? */ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH); + if (!ok) + LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_CCCH\n", + chan_nr, cbits, gsm_ts_and_pchan_name(ts)); /* FIXME: we should not return first sdcch4 !!! */ - } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) { + break; + case ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH: lch_idx = 0; - ok = (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH); - } else + ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_PDCH); + if (!ok) + LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_PDCH\n", + chan_nr, cbits, gsm_ts_and_pchan_name(ts)); + break; + default: return NULL; + } if (rc && ok) *rc = 0; + if (vamos) + lch_idx += ts->max_primary_lchans; return &ts->lchan[lch_idx]; } @@ -200,7 +264,7 @@ void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason) bool trx_is_usable(const struct gsm_bts_trx *trx) { /* FIXME: How does this behave for BS-11 ? */ - if (is_ipaccess_bts(trx->bts)) { + if (is_ipa_abisip_bts(trx->bts)) { if (!nm_is_running(&trx->mo.nm_state) || !nm_is_running(&trx->bb_transc.mo.nm_state)) return false; @@ -223,54 +287,6 @@ void gsm_trx_all_ts_dispatch(struct gsm_bts_trx *trx, uint32_t ts_ev, void *data } } -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; -} - bool trx_has_valid_pchan_config(const struct gsm_bts_trx *trx) { bool combined = false; @@ -310,6 +326,23 @@ bool trx_has_valid_pchan_config(const struct gsm_bts_trx *trx) } break; + case GSM_PCHAN_PDCH: + if (is_ericsson_bts(trx->bts)) { + /* NOTE: A static PDCH is usually handled by the BTS/PCU internally, the BSC + * will not actively manage this channel. It will just keep the timeslot + * unused so that it is free for the BTS/PCU to use it as PDCH. Not all BTSs + * work well in this scheme. Ericsson RBS BTSs support dynamic channels natively + * and require a channel activation on RSL level before the PDCH can be used. + * One could work around this by activating the PDCH once on startup and + * leave it on indefinetly but we decided not to do so. Users of Ericsson RBS + * BTSs must configure a dynamic PDCH channel. */ + LOGP(DNM, LOGL_ERROR, "%s is not allowed, because Ericsson RBS does" + "not support static PDCH (use TCH/F_TCH/H_SDCCH8_PDCH)\n", + gsm_pchan_name(ts->pchan_from_config)); + result = false; + } + break; + default: /* CCCH on TS0 is mandatory for C0 */ if (trx->bts->c0 == trx && i == 0) { @@ -317,6 +350,20 @@ bool trx_has_valid_pchan_config(const struct gsm_bts_trx *trx) result = false; } } + + if (trx->bts->features_known) { + const struct bitvec *ft = &trx->bts->features; + + if (ts->hopping.enabled && !osmo_bts_has_feature(ft, BTS_FEAT_HOPPING)) { + LOGP(DNM, LOGL_ERROR, "TS%d has freq. hopping enabled, but BTS does not support it\n", i); + result = false; + } + + if (ts->tsc != -1 && !osmo_bts_has_feature(ft, BTS_FEAT_MULTI_TSC)) { + LOGP(DNM, LOGL_ERROR, "TS%d has TSC != BCC, but BTS does not support it\n", i); + result = false; + } + } } return result; @@ -361,7 +408,7 @@ static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len) /* set all system information types for a TRX */ int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx) { - int i, rc; + int rc; struct gsm_bts *bts = trx->bts; uint8_t gen_si[_MAX_SYSINFO_TYPE], n_si = 0, n; int si_len[_MAX_SYSINFO_TYPE]; @@ -399,42 +446,52 @@ int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx) /* Second, we generate the selected SI via RSL */ for (n = 0; n < n_si; n++) { - i = gen_si[n]; + const enum osmo_sysinfo_type si_type = gen_si[n]; + /* Only generate SI if this SI is not in "static" (user-defined) mode */ - if (!(bts->si_mode_static & (1 << i))) { + if (!(bts->si_mode_static & (1 << si_type))) { /* Set SI as being valid. gsm_generate_si() might unset * it, if SI is not required. */ - bts->si_valid |= (1 << i); - rc = gsm_generate_si(bts, i); + bts->si_valid |= (1 << si_type); + rc = gsm_generate_si(bts, si_type); if (rc < 0) goto err_out; - si_len[i] = rc; + si_len[si_type] = rc; } else { - if (i == SYSINFO_TYPE_5 || i == SYSINFO_TYPE_5bis - || i == SYSINFO_TYPE_5ter) - si_len[i] = 18; - else if (i == SYSINFO_TYPE_6) - si_len[i] = 11; - else - si_len[i] = 23; + switch (si_type) { + case SYSINFO_TYPE_5: + case SYSINFO_TYPE_5bis: + case SYSINFO_TYPE_5ter: + si_len[si_type] = 18; + break; + case SYSINFO_TYPE_6: + si_len[si_type] = 11; + break; + case SYSINFO_TYPE_10: + si_len[si_type] = 21; + break; + default: + si_len[si_type] = GSM_MACBLOCK_LEN; + } } } /* Third, we send the selected SI via RSL */ for (n = 0; n < n_si; n++) { - i = gen_si[n]; + const enum osmo_sysinfo_type si_type = gen_si[n]; + /* 3GPP TS 08.58 §8.5.1 BCCH INFORMATION. If we don't currently * have this SI, we send a zero-length RSL BCCH FILLING / * SACCH FILLING in order to deactivate the SI, in case it * might have previously been active */ - if (!GSM_BTS_HAS_SI(bts, i)) { + if (!GSM_BTS_HAS_SI(bts, si_type)) { if (bts->si_unused_send_empty) - rc = rsl_si(trx, i, 0); + rc = rsl_si(trx, si_type, 0); else rc = 0; /* some nanoBTS fw don't like receiving empty unsupported SI */ } else - rc = rsl_si(trx, i, si_len[i]); + rc = rsl_si(trx, si_type, si_len[si_type]); if (rc < 0) return rc; } @@ -447,6 +504,6 @@ int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx) err_out: LOGP(DRR, LOGL_ERROR, "Cannot generate SI%s for BTS %u: error <%s>, " "most likely a problem with neighbor cell list generation\n", - get_value_string(osmo_sitype_strs, i), bts->nr, strerror(-rc)); + get_value_string(osmo_sitype_strs, gen_si[n]), bts->nr, strerror(-rc)); return rc; } diff --git a/src/osmo-bsc/bts_trx_ctrl.c b/src/osmo-bsc/bts_trx_ctrl.c new file mode 100644 index 000000000..1e30e8518 --- /dev/null +++ b/src/osmo-bsc/bts_trx_ctrl.c @@ -0,0 +1,157 @@ +/* + * (C) 2013-2015 by Holger Hans Peter Freyther + * (C) 2013-2022 by sysmocom s.f.m.c. GmbH + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/ctrl/control_cmd.h> + +#include <osmocom/bsc/ctrl.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/abis_nm.h> + +static int get_trx_rf_locked(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + /* Return rf_locked = 1 only if it is explicitly locked. If it is in shutdown or null state, do not "trick" the + * caller into thinking that sending "rf_locked 0" is necessary to bring the TRX up. */ + cmd->reply = (trx->mo.nm_state.administrative == NM_STATE_LOCKED) ? "1" : "0"; + return CTRL_CMD_REPLY; +} + +static int set_trx_rf_locked(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + int locked; + if (osmo_str_to_int(&locked, cmd->value, 10, 0, 1)) { + cmd->reply = "Invalid value"; + return CTRL_CMD_ERROR; + } + + gsm_trx_lock_rf(trx, locked, "ctrl"); + + /* Let's not assume the nm FSM has already switched its state, just return the intended rf_locked value. */ + cmd->reply = locked ? "1" : "0"; + return CTRL_CMD_REPLY; +} + +static int verify_trx_rf_locked(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return osmo_str_to_int(NULL, value, 10, 0, 1); +} +CTRL_CMD_DEFINE(trx_rf_locked, "rf_locked"); + +/* TRX related commands below here */ +CTRL_HELPER_GET_INT(trx_max_power, struct gsm_bts_trx, max_power_red); +static int verify_trx_max_power(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int tmp = atoi(value); + + if (tmp < 0 || tmp > 22) { + cmd->reply = "Value must be between 0 and 22"; + return -1; + } + + if (tmp & 1) { + cmd->reply = "Value must be even"; + return -1; + } + + return 0; +} +CTRL_CMD_DEFINE_RANGE(trx_arfcn, "arfcn", struct gsm_bts_trx, arfcn, 0, 1023); + +static int set_trx_max_power(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_bts_trx *trx = cmd->node; + int old_power; + + /* remember the old value, set the new one */ + old_power = trx->max_power_red; + trx->max_power_red = atoi(cmd->value); + + /* Maybe update the value */ + if (old_power != trx->max_power_red) { + LOGP(DCTRL, LOGL_NOTICE, + "%s updating max_pwr_red(%d)\n", + gsm_trx_name(trx), trx->max_power_red); + abis_nm_update_max_power_red(trx); + } + + return get_trx_max_power(cmd, _data); +} +CTRL_CMD_DEFINE(trx_max_power, "max-power-reduction"); + +char *trx_lchan_dump_full_ctrl(const void *t, struct gsm_bts_trx *trx) +{ + int ts_nr; + bool first_ts = true; + char *ts_dump, *dump; + + dump = talloc_strdup(t, ""); + if (!dump) + return NULL; + + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts_dump = ts_lchan_dump_full_ctrl(t, &trx->ts[ts_nr]); + if (!ts_dump) + return NULL; + if (!strlen(ts_dump)) + continue; + dump = talloc_asprintf_append(dump, first_ts ? "%s" : "\n%s", ts_dump); + if (!dump) + return NULL; + first_ts = false; + } + + return dump; +} + +/* Return full information about all logical channels in a TRX. + * format: bts.<0-255>.trx.<0-255>.show-lchan.full + * result format: New line delimited list of <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>, + * <ms power>,<interference dbm>, <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>, + * <ipa bound conn id>,<ipa conn ip>,<ipa conn port>,<ipa conn speech mode> + */ +static int get_trx_show_lchan_full(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + + cmd->reply = trx_lchan_dump_full_ctrl(cmd, trx); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(trx_show_lchan_full, "show-lchan full"); + +int bsc_bts_trx_ctrl_cmds_install(void) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_max_power); + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_arfcn); + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_rf_locked); + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_show_lchan_full); + + rc |= bsc_bts_trx_ts_ctrl_cmds_install(); + + return rc; +} diff --git a/src/osmo-bsc/bts_trx_ts_ctrl.c b/src/osmo-bsc/bts_trx_ts_ctrl.c new file mode 100644 index 000000000..a1a17f0d2 --- /dev/null +++ b/src/osmo-bsc/bts_trx_ts_ctrl.c @@ -0,0 +1,151 @@ +/* + * (C) 2013-2015 by Holger Hans Peter Freyther + * (C) 2013-2022 by sysmocom s.f.m.c. GmbH + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/ctrl/control_cmd.h> + +#include <osmocom/bsc/ctrl.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/system_information.h> + +static int verify_ts_hopping_arfcn_add(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int64_t arfcn; + enum gsm_band unused; + if (osmo_str_to_int64(&arfcn, value, 10, 0, 1024) < 0) + return 1; + if (gsm_arfcn2band_rc(arfcn, &unused) < 0) + return 1; + return 0; +} +static int set_ts_hopping_arfcn_add(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx_ts *ts = cmd->node; + int arfcn = atoi(cmd->value); + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ONE); + + /* Update Cell Allocation (list of all the frequencies allocated to a cell) */ + if (generate_cell_chan_alloc(ts->trx->bts) != 0) { + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ZERO); /* roll-back */ + cmd->reply = "Failed to re-generate Cell Allocation"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} +/* Parameter format: "<arfcn>" */ +CTRL_CMD_DEFINE_WO(ts_hopping_arfcn_add, "hopping-arfcn-add"); + +static int verify_ts_hopping_arfcn_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int64_t arfcn; + enum gsm_band unused; + if (strcmp(value, "all") == 0) + return 0; + if (osmo_str_to_int64(&arfcn, value, 10, 0, 1024) < 0) + return 1; + if (gsm_arfcn2band_rc(arfcn, &unused) < 0) + return 1; + return 0; +} +static int set_ts_hopping_arfcn_del(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx_ts *ts = cmd->node; + bool all = (strcmp(cmd->value, "all") == 0); + int arfcn; + + if (all) { + bitvec_zero(&ts->hopping.arfcns); + } else { + arfcn = atoi(cmd->value); + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ZERO); + } + + /* Update Cell Allocation (list of all the frequencies allocated to a cell) */ + if (generate_cell_chan_alloc(ts->trx->bts) != 0) { + if (!all) + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ONE); /* roll-back */ + cmd->reply = "Failed to re-generate Cell Allocation"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} +/* Parameter format: "(<arfcn>|all)" */ +CTRL_CMD_DEFINE_WO(ts_hopping_arfcn_del, "hopping-arfcn-del"); + +char *ts_lchan_dump_full_ctrl(const void *t, struct gsm_bts_trx_ts *ts) +{ + bool first_lchan = true; + char *lchan_dump, *dump; + struct gsm_lchan *lchan; + + dump = talloc_strdup(t, ""); + if (!dump) + return NULL; + + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { + lchan_dump = lchan_dump_full_ctrl(t, lchan); + if (!lchan_dump) + return NULL; + dump = talloc_asprintf_append(dump, first_lchan ? "%s" : "\n%s", lchan_dump); + if (!dump) + return NULL; + first_lchan = false; + } + + return dump; +} + +/* Return full information about all logical channels in a timeslot. + * format: bts.<0-255>.trx.<0-255>.ts.<0-8>.show-lchan.full + * result format: New line delimited list of <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>, + * <ms power>,<interference dbm>, <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>, + * <ipa bound conn id>,<ipa conn ip>,<ipa conn port>,<ipa conn speech mode> + */ +static int get_ts_show_lchan_full(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx_ts *ts = cmd->node; + + cmd->reply = ts_lchan_dump_full_ctrl(cmd, ts); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(ts_show_lchan_full, "show-lchan full"); + +int bsc_bts_trx_ts_ctrl_cmds_install(void) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_TS, &cmd_ts_hopping_arfcn_add); + rc |= ctrl_cmd_install(CTRL_NODE_TS, &cmd_ts_hopping_arfcn_del); + rc |= ctrl_cmd_install(CTRL_NODE_TS, &cmd_ts_show_lchan_full); + + rc |= bsc_bts_trx_ts_lchan_ctrl_cmds_install(); + + return rc; +} diff --git a/src/osmo-bsc/bts_trx_ts_lchan_ctrl.c b/src/osmo-bsc/bts_trx_ts_lchan_ctrl.c new file mode 100644 index 000000000..be5e755cb --- /dev/null +++ b/src/osmo-bsc/bts_trx_ts_lchan_ctrl.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 by sysmocom s.f.m.c. GmbH + * + * 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 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/ctrl/control_cmd.h> + +#include <osmocom/bsc/ctrl.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/system_information.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/lchan_fsm.h> + +static int verify_lchan_ms_power(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int ms_power = atoi(cmd->value); + + if (ms_power < 0 || ms_power > 40) { + cmd->reply = "Value is out of range"; + return 1; + } + + return 0; +} + +/* power control management: Get lchan's ms power in dBm + * format: bts.<0-255>.trx.<0-255>.ts.<0-8>.lchan.<0-8>.ms-power */ +static int get_lchan_ms_power(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_lchan *lchan = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%u", ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power)); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +/* power control management: Set lchan's ms power in dBm. + * For static ms power control it will change the ms tx power. + * For dynamic ms power control it will limit the maximum power level. + * format: bts.<0-255>.trx.<0-255>.ts.<0-8>.lchan.<0-8>.ms-power <ms power> + * ms power is in range 0..40 */ +static int set_lchan_ms_power(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_lchan *lchan = cmd->node; + + lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, atoi(cmd->value)); + rsl_chan_ms_power_ctrl(lchan); + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(lchan_ms_power, "ms-power"); + + +char *lchan_dump_full_ctrl(const void *t, struct gsm_lchan *lchan) +{ + struct in_addr ia; + char *interference = ",", *tmsi = "", *ipa_bound = ",,", *ipa_conn = ",,"; + + if (lchan->interf_dbm != INTERF_DBM_UNKNOWN) { + interference = talloc_asprintf(t, "%d,%u", lchan->interf_dbm, lchan->interf_band); + if (!interference) + return NULL; + } + + if (lchan->conn && lchan->conn->bsub && lchan->conn->bsub->tmsi != GSM_RESERVED_TMSI) { + tmsi = talloc_asprintf(t, "0x%08x", lchan->conn->bsub->tmsi); + if (!tmsi) + return NULL; + } + + if (is_ipa_abisip_bts(lchan->ts->trx->bts) && lchan->abis_ip.bound_ip) { + ia.s_addr = htonl(lchan->abis_ip.bound_ip); + ipa_bound = talloc_asprintf(t, "%s,%u,%u", inet_ntoa(ia), lchan->abis_ip.bound_port, + lchan->abis_ip.conn_id); + if (!ipa_bound) + return NULL; + } + + if (is_ipa_abisip_bts(lchan->ts->trx->bts) && lchan->abis_ip.connect_ip) { + ia.s_addr = htonl(lchan->abis_ip.connect_ip); + ipa_conn = talloc_asprintf(t, "%s,%u,0x%02x", inet_ntoa(ia), lchan->abis_ip.connect_port, + lchan->abis_ip.speech_mode); + if (!ipa_conn) + return NULL; + } + + return talloc_asprintf(t, "%u,%u,%u,%u,%s,%u,%s,%s,%u,%u,%s,%s,%s,%s,%s,%s", + lchan->ts->trx->bts->nr, + lchan->ts->trx->nr, + lchan->ts->nr, + lchan->nr, + gsm_chan_t_name(lchan->type), + lchan->conn ? 1 : 0, lchan_state_name(lchan), + lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? lchan->last_error : "", + lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red - lchan->bs_power_db, + ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), + interference, + gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), + lchan->conn && lchan->conn->bsub && strlen(lchan->conn->bsub->imsi) ? lchan->conn->bsub->imsi : "", + tmsi, + ipa_bound, + ipa_conn + ); +} + +/* Return full information about a logical channel. + * format: bts.<0-255>.trx.<0-255>.ts.<0-8>.lchan.<0-8>.show.full + * result format: <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>,<ms power>,<interference dbm>, + * <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>,<ipa bound conn id>,<ipa conn ip>, + * <ipa conn port>,<ipa conn speech mode> + */ +static int get_lchan_show_full(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_lchan *lchan = cmd->node; + cmd->reply = lchan_dump_full_ctrl(cmd, lchan); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(lchan_show_full, "show full"); + + +int bsc_bts_trx_ts_lchan_ctrl_cmds_install(void) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_LCHAN, &cmd_lchan_ms_power); + rc |= ctrl_cmd_install(CTRL_NODE_LCHAN, &cmd_lchan_show_full); + + return rc; +} diff --git a/src/osmo-bsc/bts_trx_vty.c b/src/osmo-bsc/bts_trx_vty.c new file mode 100644 index 000000000..9a58089a4 --- /dev/null +++ b/src/osmo-bsc/bts_trx_vty.c @@ -0,0 +1,881 @@ +/* OsmoBSC interface to quagga VTY, TRX (and TS) node */ +/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <time.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/stats.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/misc.h> + +#include <osmocom/bsc/vty.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/bsc/system_information.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/timeslot_fsm.h> +#include <osmocom/bsc/lchan.h> +#include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/lchan_select.h> +#include <osmocom/bsc/bts.h> + +#include <inttypes.h> + +#include "../../bscconfig.h" + +#define X(x) (1 << x) + +static struct cmd_node trx_node = { + TRX_NODE, + "%s(config-net-bts-trx)# ", + 1, +}; + +static struct cmd_node ts_node = { + TS_NODE, + "%s(config-net-bts-trx-ts)# ", + 1, +}; + +/* utility functions */ +void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line, + const char *ts, const char *ss) +{ + e1_link->e1_nr = atoi(line); + e1_link->e1_ts = atoi(ts); + if (!strcmp(ss, "full")) + e1_link->e1_ts_ss = E1_SUBSLOT_FULL; + else + e1_link->e1_ts_ss = atoi(ss); +} + +#define TRX_TEXT "Radio Transceiver\n" + +/* per TRX configuration */ +DEFUN_ATTR(cfg_trx, + cfg_trx_cmd, + "trx <0-255>", + TRX_TEXT + "Select a TRX to configure\n", + CMD_ATTR_IMMEDIATE) +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts *bts = vty->index; + struct gsm_bts_trx *trx; + + if (trx_nr > bts->num_trx) { + vty_out(vty, "%% The next unused TRX number in this BTS is %u%s", + bts->num_trx, VTY_NEWLINE); + return CMD_WARNING; + } else if (trx_nr == bts->num_trx) { + /* we need to allocate a new one */ + trx = gsm_bts_trx_alloc(bts); + } else + trx = gsm_bts_trx_num(bts, trx_nr); + + if (!trx) + return CMD_WARNING; + + vty->index = trx; + vty->node = TRX_NODE; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_trx_arfcn, + cfg_trx_arfcn_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "arfcn <0-1023>", + "Set the ARFCN for this TRX\n" + "Absolute Radio Frequency Channel Number\n") +{ + enum gsm_band unused; + struct gsm_bts_trx *trx = vty->index; + int arfcn = atoi(argv[0]); + + if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { + vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); + return CMD_WARNING; + } + + /* FIXME: check if this ARFCN is supported by this TRX */ + + trx->arfcn = arfcn; + + /* Update Cell Allocation (list of all the frequencies allocated to a cell) */ + if (generate_cell_chan_alloc(trx->bts) != 0) { + vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* FIXME: patch ARFCN into SYSTEM INFORMATION */ + /* FIXME: use OML layer to update the ARFCN */ + /* FIXME: use RSL layer to update SYSTEM INFORMATION */ + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_trx_nominal_power, + cfg_trx_nominal_power_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "nominal power <-20-100>", + "Nominal TRX RF Power in dBm\n" + "Nominal TRX RF Power in dBm\n" + "Nominal TRX RF Power in dBm\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->nominal_power = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_trx_max_power_red, + cfg_trx_max_power_red_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "max_power_red <0-100>", + "Reduction of maximum BS RF Power (relative to nominal power)\n" + "Reduction of maximum BS RF Power in dB\n") +{ + int maxpwr_r = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + int upper_limit = 24; /* default 12.21 max power red. */ + + /* FIXME: check if our BTS type supports more than 12 */ + if (maxpwr_r < 0 || maxpwr_r > upper_limit) { + vty_out(vty, "%% Power %d dB is not in the valid range%s", + maxpwr_r, VTY_NEWLINE); + return CMD_WARNING; + } + if (maxpwr_r & 1) { + vty_out(vty, "%% Power %d dB is not an even value%s", + maxpwr_r, VTY_NEWLINE); + return CMD_WARNING; + } + + trx->max_power_red = maxpwr_r; + + /* FIXME: make sure we update this using OML */ + + return CMD_SUCCESS; +} + +/* NOTE: This requires a full restart as bsc_network_configure() is executed + * only once on startup from osmo_bsc_main.c */ +DEFUN(cfg_trx_rsl_e1, + cfg_trx_rsl_e1_cmd, + "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + "RSL Parameters\n" + "E1/T1 interface to be used for RSL\n" + "E1/T1 interface to be used for RSL\n" + "E1/T1 Line Number to be used for RSL\n" + "E1/T1 Timeslot to be used for RSL\n" + "E1/T1 Timeslot to be used for RSL\n" + "E1/T1 Sub-slot to be used for RSL\n" + "E1/T1 Sub-slot 0 is to be used for RSL\n" + "E1/T1 Sub-slot 1 is to be used for RSL\n" + "E1/T1 Sub-slot 2 is to be used for RSL\n" + "E1/T1 Sub-slot 3 is to be used for RSL\n" + "E1/T1 full timeslot is to be used for RSL\n") +{ + struct gsm_bts_trx *trx = vty->index; + + parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_trx_rsl_e1_tei, + cfg_trx_rsl_e1_tei_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "rsl e1 tei <0-63>", + "RSL Parameters\n" + "Set the TEI to be used for RSL\n" + "Set the TEI to be used for RSL\n" + "TEI to be used for RSL\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->rsl_tei_primary = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_trx_rf_locked, + cfg_trx_rf_locked_cmd, + "rf_locked (0|1)", + "Set or unset the RF Locking (Turn off RF of the TRX)\n" + "TRX is NOT RF locked (active)\n" + "TRX is RF locked (turned off)\n", + CMD_ATTR_IMMEDIATE) +{ + int locked = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + gsm_trx_lock_rf(trx, locked, "vty"); + return CMD_SUCCESS; +} + +/* per TS configuration */ +DEFUN_ATTR(cfg_ts, + cfg_ts_cmd, + "timeslot <0-7>", + "Select a Timeslot to configure\n" + "Timeslot number\n", + CMD_ATTR_IMMEDIATE) +{ + int ts_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + struct gsm_bts_trx_ts *ts; + + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s", + TRX_NR_TS, VTY_NEWLINE); + return CMD_WARNING; + } + + ts = &trx->ts[ts_nr]; + + vty->index = ts; + vty->node = TS_NODE; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_ts_pchan, + cfg_ts_pchan_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "phys_chan_config PCHAN", /* dynamically generated! */ + "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int pchanc; + + pchanc = gsm_pchan_parse(argv[0]); + if (pchanc < 0) + return CMD_WARNING; + + ts->pchan_from_config = pchanc; + + return CMD_SUCCESS; +} + +/* used for backwards compatibility with old config files that still + * have uppercase pchan type names. Also match older names for existing types. */ +DEFUN_HIDDEN(cfg_ts_pchan_compat, + cfg_ts_pchan_compat_cmd, + "phys_chan_config PCHAN", + "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int pchanc; + + pchanc = gsm_pchan_parse(argv[0]); + if (pchanc < 0) { + if (strcasecmp(argv[0], "tch/f_tch/h_pdch") == 0) { + pchanc = GSM_PCHAN_OSMO_DYN; + } else { + vty_out(vty, "Unknown physical channel name '%s'%s", argv[0], VTY_NEWLINE); + return CMD_ERR_NO_MATCH; + } + } + + ts->pchan_from_config = pchanc; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_ts_tsc, + cfg_ts_tsc_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "training_sequence_code <0-7>", + "Training Sequence Code of the Timeslot\n" "TSC\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + const struct gsm_bts *bts = ts->trx->bts; + + if (bts->features_known && !osmo_bts_has_feature(&bts->features, BTS_FEAT_MULTI_TSC)) { + vty_out(vty, "%% This BTS does not support a TSC != BCC, " + "falling back to BCC%s", VTY_NEWLINE); + ts->tsc = -1; + return CMD_WARNING; + } + + ts->tsc = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define HOPPING_STR "Configure frequency hopping\n" + +DEFUN_USRATTR(cfg_ts_hopping, + cfg_ts_hopping_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "hopping enabled (0|1)", + HOPPING_STR "Enable or disable frequency hopping\n" + "Disable frequency hopping\n" "Enable frequency hopping\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + const struct gsm_bts *bts = ts->trx->bts; + int enabled = atoi(argv[0]); + + if (enabled && bts->features_known && !osmo_bts_has_feature(&bts->features, BTS_FEAT_HOPPING)) { + vty_out(vty, "%% BTS does not support freq. hopping%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (enabled && bts->imm_ass_time != IMM_ASS_TIME_POST_CHAN_ACK) { + vty_out(vty, + "%% ERROR: 'hopping enabled 1' works only with 'immediate-assignment post-chan-ack'%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + ts->hopping.enabled = enabled; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_ts_hsn, + cfg_ts_hsn_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "hopping sequence-number <0-63>", + HOPPING_STR + "Which hopping sequence to use for this channel\n" + "Hopping Sequence Number (HSN)\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + ts->hopping.hsn = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_ts_maio, + cfg_ts_maio_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "hopping maio <0-63>", + HOPPING_STR + "Which hopping MAIO to use for this channel\n" + "Mobile Allocation Index Offset (MAIO)\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + ts->hopping.maio = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_ts_arfcn_add, + cfg_ts_arfcn_add_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "hopping arfcn add <0-1023>", + HOPPING_STR "Configure hopping ARFCN list\n" + "Add an entry to the hopping ARFCN list\n" "ARFCN\n") +{ + enum gsm_band unused; + struct gsm_bts_trx_ts *ts = vty->index; + int arfcn = atoi(argv[0]); + + if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { + vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bitvec_get_bit_pos(&ts->hopping.arfcns, arfcn) == ONE) { + vty_out(vty, "%% ARFCN %" PRIu16 " is already set%s", arfcn, VTY_NEWLINE); + return CMD_SUCCESS; + } + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1); + + /* Update Cell Allocation (list of all the frequencies allocated to a cell) */ + if (generate_cell_chan_alloc(ts->trx->bts) != 0) { + vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE); + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ZERO); /* roll-back */ + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_ts_arfcn_del, + cfg_ts_arfcn_del_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "hopping arfcn del <0-1023>", + HOPPING_STR "Configure hopping ARFCN list\n" + "Delete an entry to the hopping ARFCN list\n" "ARFCN\n") +{ + enum gsm_band unused; + struct gsm_bts_trx_ts *ts = vty->index; + int arfcn = atoi(argv[0]); + + if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { + vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bitvec_get_bit_pos(&ts->hopping.arfcns, arfcn) != ONE) { + vty_out(vty, "%% ARFCN %" PRIu16 " is not set%s", arfcn, VTY_NEWLINE); + return CMD_WARNING; + } + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0); + + /* Update Cell Allocation (list of all the frequencies allocated to a cell) */ + if (generate_cell_chan_alloc(ts->trx->bts) != 0) { + vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE); + /* It's unlikely to happen on removal, so we don't roll-back */ + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_ts_arfcn_del_all, + cfg_ts_arfcn_del_all_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "hopping arfcn del-all", + HOPPING_STR "Configure hopping ARFCN list\n" + "Delete all previously configured entries\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + bitvec_zero(&ts->hopping.arfcns); + + /* Update Cell Allocation (list of all the frequencies allocated to a cell) */ + if (generate_cell_chan_alloc(ts->trx->bts) != 0) { + vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE); + /* It's unlikely to happen on removal, so we don't roll-back */ + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* NOTE: This will have an effect on newly created voice lchans since the E1 + * voice channels are handled by osmo-mgw and the information put in e1_link + * here is only used to generate the MGCP messages for the mgw. */ +DEFUN_ATTR(cfg_ts_e1_subslot, + cfg_ts_e1_subslot_cmd, + "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + "E1/T1 channel connected to this on-air timeslot\n" + "E1/T1 channel connected to this on-air timeslot\n" + "E1/T1 line connected to this on-air timeslot\n" + "E1/T1 timeslot connected to this on-air timeslot\n" + "E1/T1 timeslot connected to this on-air timeslot\n" + "E1/T1 sub-slot connected to this on-air timeslot\n" + "E1/T1 sub-slot 0 connected to this on-air timeslot\n" + "E1/T1 sub-slot 1 connected to this on-air timeslot\n" + "E1/T1 sub-slot 2 connected to this on-air timeslot\n" + "E1/T1 sub-slot 3 connected to this on-air timeslot\n" + "Full E1/T1 timeslot connected to this on-air timeslot\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts_trx_ts *ts = vty->index; + + parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + +/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots. + * Don't do anything if the ts is not dynamic. */ +static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config target; + if (ts_is_pchan_switching(ts, &target)) { + vty_out(vty, " switching %s -> %s", gsm_pchan_name(ts->pchan_is), + gsm_pchan_name(target)); + } else if (ts->pchan_is != ts->pchan_on_init) { + vty_out(vty, " as %s", gsm_pchan_name(ts->pchan_is)); + } +} + +static void vty_out_dyn_ts_details(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + /* show dyn TS details, if applicable */ + switch (ts->pchan_on_init) { + case GSM_PCHAN_OSMO_DYN: + vty_out(vty, " Osmocom Dyn TS:"); + vty_out_dyn_ts_status(vty, ts); + vty_out(vty, VTY_NEWLINE); + break; + case GSM_PCHAN_TCH_F_PDCH: + vty_out(vty, " IPACC Dyn PDCH TS:"); + vty_out_dyn_ts_status(vty, ts); + vty_out(vty, VTY_NEWLINE); + break; + default: + /* no dyn ts */ + break; + } +} + +static void meas_rep_dump_uni_vty(struct vty *vty, + struct gsm_meas_rep_unidir *mru, + const char *prefix, + const char *dir) +{ + vty_out(vty, "%s RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ", + prefix, dir, rxlev2dbm(mru->full.rx_lev), + dir, rxlev2dbm(mru->sub.rx_lev)); + vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s", + dir, mru->full.rx_qual, dir, mru->sub.rx_qual, + VTY_NEWLINE); +} + +static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr, + const char *prefix) +{ + vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE); + vty_out(vty, "%s Flags: %s%s%s%s%s", prefix, + mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "", + mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "", + mr->flags & MEAS_REP_F_FPC ? "FPC " : "", + mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ", + VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_MS_TO) + vty_out(vty, "%s MS Timing Offset: %d%s", prefix, mr->ms_timing_offset, VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_MS_L1) + vty_out(vty, "%s L1 MS Power: %u dBm, Timing Advance: %u%s", + prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_DL_VALID) + meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl"); + meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul"); +} + +void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + int idx; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + lchan->nr, gsm_chan_t_name(lchan->type), VTY_NEWLINE); + + if (lchan->activate.concluded) { + vty_out(vty, " Active for: %s seconds%s", + osmo_int_to_float_str_c(OTC_SELECT, gsm_lchan_active_duration_ms(lchan), 3), + VTY_NEWLINE); + } + + vty_out_dyn_ts_details(vty, lchan->ts); + vty_out(vty, " Connection: %u, State: %s%s%s%s", + lchan->conn ? 1: 0, lchan_state_name(lchan), + lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? " Error reason: " : "", + lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? lchan->last_error : "", + VTY_NEWLINE); + vty_out(vty, " BS Power: %u dBm, MS Power: %u dBm%s", + lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red + - lchan->bs_power_db, + ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), + VTY_NEWLINE); + + vty_out(vty, " Interference Level: "); + if (lchan->interf_dbm == INTERF_DBM_UNKNOWN) + vty_out(vty, "unknown"); + else + vty_out(vty, "%d dBm (%u)", lchan->interf_dbm, lchan->interf_band); + vty_out(vty, "%s", VTY_NEWLINE); + + vty_out(vty, " Channel Mode / Codec: %s%s", + gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), + VTY_NEWLINE); + if (!lchan_state_is(lchan, LCHAN_ST_UNUSED)) + vty_out(vty, " Training Sequence: Set %d Code %u%s", (lchan->tsc_set > 0 ? lchan->tsc_set : 1), lchan->tsc, VTY_NEWLINE); + if (lchan->vamos.enabled) + vty_out(vty, " VAMOS: enabled%s", VTY_NEWLINE); + if (lchan->conn && lchan->conn->bsub) { + vty_out(vty, " Subscriber:%s", VTY_NEWLINE); + bsc_subscr_dump_vty(vty, lchan->conn->bsub); + } else { + vty_out(vty, " No Subscriber%s", VTY_NEWLINE); + } + if (is_ipa_abisip_bts(lchan->ts->trx->bts)) { + struct in_addr ia; + if (lchan->abis_ip.bound_ip) { + ia.s_addr = htonl(lchan->abis_ip.bound_ip); + vty_out(vty, " Bound IP: %s Port %u CONN_ID=%u", + inet_ntoa(ia), lchan->abis_ip.bound_port, + lchan->abis_ip.conn_id); + if (lchan->abis_ip.osmux.use) + vty_out(vty, " Osmux_CID=%u%s", lchan->abis_ip.osmux.local_cid, VTY_NEWLINE); + else + vty_out(vty, " RTP_TYPE2=%u%s", lchan->abis_ip.rtp_payload2, VTY_NEWLINE); + } + if (lchan->abis_ip.connect_ip) { + ia.s_addr = htonl(lchan->abis_ip.connect_ip); + vty_out(vty, " Conn. IP: %s Port %u SPEECH_MODE=0x%02x", + inet_ntoa(ia), lchan->abis_ip.connect_port, + lchan->abis_ip.speech_mode); + if (lchan->abis_ip.osmux.use) { + if (lchan->abis_ip.osmux.remote_cid_present) + vty_out(vty, " Osmux_CID=%u%s", lchan->abis_ip.osmux.remote_cid, VTY_NEWLINE); + else + vty_out(vty, " Osmux_CID=?%s", VTY_NEWLINE); + } else { + vty_out(vty, " RTP_TYPE=%u%s", lchan->abis_ip.rtp_payload, VTY_NEWLINE); + } + } + } + + /* we want to report the last measurement report */ + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, 1); + meas_rep_dump_vty(vty, &lchan->meas_rep[idx], " "); +} + +void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + struct gsm_meas_rep *mr; + int idx; + + /* we want to report the last measurement report */ + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, 1); + mr = &lchan->meas_rep[idx]; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + gsm_pchan_name(lchan->ts->pchan_on_init)); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, ", Lchan %u", lchan->nr); + + if (lchan_state_is(lchan, LCHAN_ST_UNUSED)) { + vty_out(vty, ", Type %s, State %s - Interference Level: ", + gsm_pchan_name(lchan->ts->pchan_is), + lchan_state_name(lchan)); + if (lchan->interf_dbm == INTERF_DBM_UNKNOWN) + vty_out(vty, "unknown"); + else + vty_out(vty, "%d dBm (%u)", lchan->interf_dbm, lchan->interf_band); + vty_out(vty, "%s", VTY_NEWLINE); + return; + } + + vty_out(vty, ", Type %s%s TSC-s%dc%u, State %s - L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s", + gsm_chan_t_name(lchan->type), + lchan->vamos.enabled ? " (VAMOS)" : "", + lchan->tsc_set > 0 ? lchan->tsc_set : 1, + lchan->tsc, + lchan_state_name(lchan), + mr->ms_l1.pwr, + rxlev2dbm(mr->dl.full.rx_lev), + rxlev2dbm(mr->ul.full.rx_lev), + VTY_NEWLINE); +} + +void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s (active %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan_on_init), + gsm_pchan_name(ts->pchan_is)); + if (ts->pchan_is != ts->pchan_on_init) + vty_out(vty, " (%s mode)", gsm_pchan_name(ts->pchan_is)); + vty_out(vty, ", TSC %u%s NM State: ", gsm_ts_tsc(ts), VTY_NEWLINE); + vty_out_dyn_ts_details(vty, ts); + net_dump_nmstate(vty, &ts->mo.nm_state); + if (!is_ipa_abisip_bts(ts->trx->bts)) + vty_out(vty, " E1 Line %u, Timeslot %u, Subslot %u%s", + ts->e1_link.e1_nr, ts->e1_link.e1_ts, + ts->e1_link.e1_ts_ss, VTY_NEWLINE); +} + +void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l) +{ + struct e1inp_line *line; + + if (!e1l) { + vty_out(vty, " None%s", VTY_NEWLINE); + return; + } + + line = e1l->ts->line; + + vty_out(vty, " E1 Line %u, Type %s: Timeslot %u, Mode %s%s", + line->num, line->driver->name, e1l->ts->num, + e1inp_signtype_name(e1l->type), VTY_NEWLINE); + vty_out(vty, " E1 TEI %u, SAPI %u%s", + e1l->tei, e1l->sapi, VTY_NEWLINE); +} + +/*! Dump the IP addresses and ports of the input signal link's timeslot. + * This only makes sense for links connected with ipaccess. + * Example output: "(r=10.1.42.1:55416<->l=10.1.42.123:3003)" */ +void e1isl_dump_vty_tcp(struct vty *vty, const struct e1inp_sign_link *e1l) +{ + if (e1l) { + char *name = osmo_sock_get_name(NULL, e1l->ts->driver.ipaccess.fd.fd); + vty_out(vty, "%s", name); + talloc_free(name); + } + vty_out(vty, "%s", VTY_NEWLINE); +} + +void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx, bool print_rsl, bool show_connected) +{ + if (show_connected && !trx->rsl_link_primary) + return; + + if (!show_connected && trx->rsl_link_primary) + return; + + vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s", + trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE); + vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, " + "resulting BS power: %d dBm%s", + trx->nominal_power, trx->max_power_red, + trx->nominal_power - trx->max_power_red, VTY_NEWLINE); + vty_out(vty, " Radio Carrier NM State: "); + net_dump_nmstate(vty, &trx->mo.nm_state); + if (print_rsl) + vty_out(vty, " RSL State: %s%s", trx->rsl_link_primary? "connected" : "disconnected", VTY_NEWLINE); + + vty_out(vty, " %sBaseband Transceiver NM State: ", is_ericsson_bts(trx->bts) ? "[Virtual] " : ""); + net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state); + + if (is_ipa_abisip_bts(trx->bts)) { + vty_out(vty, " IPA Abis/IP stream ID: 0x%02x ", trx->rsl_tei_primary); + e1isl_dump_vty_tcp(vty, trx->rsl_link_primary); + } else { + vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); + e1isl_dump_vty(vty, trx->rsl_link_primary); + } + + const struct load_counter *ll = &trx->lchan_load; + vty_out(vty, " Channel load: %u%%%s", + ll->total ? ll->used * 100 / ll->total : 0, + VTY_NEWLINE); +} + +void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link, + const char *prefix) +{ + if (!e1_link->e1_ts) + return; + + if (e1_link->e1_ts_ss == E1_SUBSLOT_FULL) + vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s", + prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE); + else + vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s", + prefix, e1_link->e1_nr, e1_link->e1_ts, + e1_link->e1_ts_ss, VTY_NEWLINE); +} + + +static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + vty_out(vty, " timeslot %u%s", ts->nr, VTY_NEWLINE); + if (ts->tsc != -1) + vty_out(vty, " training_sequence_code %u%s", ts->tsc, VTY_NEWLINE); + if (ts->pchan_from_config != GSM_PCHAN_NONE) + vty_out(vty, " phys_chan_config %s%s", + gsm_pchan_name(ts->pchan_from_config), VTY_NEWLINE); + vty_out(vty, " hopping enabled %u%s", + ts->hopping.enabled, VTY_NEWLINE); + if (ts->hopping.enabled) { + unsigned int i; + vty_out(vty, " hopping sequence-number %u%s", + ts->hopping.hsn, VTY_NEWLINE); + vty_out(vty, " hopping maio %u%s", + ts->hopping.maio, VTY_NEWLINE); + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i)) + continue; + vty_out(vty, " hopping arfcn add %u%s", + i, VTY_NEWLINE); + } + } + config_write_e1_link(vty, &ts->e1_link, " "); + + if (ts->trx->bts->model->config_write_ts) + ts->trx->bts->model->config_write_ts(vty, ts); +} + +void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx) +{ + int i; + + vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE); + vty_out(vty, " rf_locked %u%s", + trx->mo.force_rf_lock ? 1 : 0, + VTY_NEWLINE); + vty_out(vty, " arfcn %u%s", trx->arfcn, VTY_NEWLINE); + vty_out(vty, " nominal power %u%s", trx->nominal_power, VTY_NEWLINE); + vty_out(vty, " max_power_red %u%s", trx->max_power_red, VTY_NEWLINE); + config_write_e1_link(vty, &trx->rsl_e1_link, " rsl "); + vty_out(vty, " rsl e1 tei %u%s", trx->rsl_tei_primary, VTY_NEWLINE); + + if (trx->bts->model->config_write_trx) + trx->bts->model->config_write_trx(vty, trx); + + for (i = 0; i < TRX_NR_TS; i++) + config_write_ts_single(vty, &trx->ts[i]); +} + +int bts_trx_vty_init(void) +{ + cfg_ts_pchan_cmd.string = + vty_cmd_string_from_valstr(tall_bsc_ctx, + gsm_pchant_names, + "phys_chan_config (", "|", ")", + VTY_DO_LOWER); + cfg_ts_pchan_cmd.doc = + vty_cmd_string_from_valstr(tall_bsc_ctx, + gsm_pchant_descs, + "Physical Channel Combination\n", + "\n", "", 0); + + install_element(BTS_NODE, &cfg_trx_cmd); + install_node(&trx_node, dummy_config_write); + install_element(TRX_NODE, &cfg_trx_arfcn_cmd); + install_element(TRX_NODE, &cfg_description_cmd); + install_element(TRX_NODE, &cfg_no_description_cmd); + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + install_element(TRX_NODE, &cfg_trx_max_power_red_cmd); + install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd); + install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd); + install_element(TRX_NODE, &cfg_trx_rf_locked_cmd); + + install_element(TRX_NODE, &cfg_ts_cmd); + install_node(&ts_node, dummy_config_write); + install_element(TS_NODE, &cfg_ts_pchan_cmd); + install_element(TS_NODE, &cfg_ts_pchan_compat_cmd); + install_element(TS_NODE, &cfg_ts_tsc_cmd); + install_element(TS_NODE, &cfg_ts_hopping_cmd); + install_element(TS_NODE, &cfg_ts_hsn_cmd); + install_element(TS_NODE, &cfg_ts_maio_cmd); + install_element(TS_NODE, &cfg_ts_arfcn_add_cmd); + install_element(TS_NODE, &cfg_ts_arfcn_del_cmd); + install_element(TS_NODE, &cfg_ts_arfcn_del_all_cmd); + install_element(TS_NODE, &cfg_ts_e1_subslot_cmd); + + return 0; +} diff --git a/src/osmo-bsc/bts_vty.c b/src/osmo-bsc/bts_vty.c new file mode 100644 index 000000000..24224f64f --- /dev/null +++ b/src/osmo-bsc/bts_vty.c @@ -0,0 +1,5090 @@ +/* OsmoBSC interface to quagga VTY, BTS node */ +/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org> + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <time.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/stats.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/misc.h> +#include <osmocom/vty/tdef_vty.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/gprs/gprs_ns.h> + +#include <osmocom/bsc/vty.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/meas_rep.h> +#include <osmocom/bsc/system_information.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/paging.h> +#include <osmocom/bsc/pcu_if.h> +#include <osmocom/bsc/handover_vty.h> +#include <osmocom/bsc/gsm_04_08_rr.h> +#include <osmocom/bsc/neighbor_ident.h> +#include <osmocom/bsc/smscb.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bsc_stats.h> + +#include <inttypes.h> + +#include "../../bscconfig.h" + +#define X(x) (1 << x) + +/* FIXME: this should go to some common file */ +static const struct value_string gprs_ns_timer_strs[] = { + { 0, "tns-block" }, + { 1, "tns-block-retries" }, + { 2, "tns-reset" }, + { 3, "tns-reset-retries" }, + { 4, "tns-test" }, + { 5, "tns-alive" }, + { 6, "tns-alive-retries" }, + { 0, NULL } +}; + +static const struct value_string gprs_bssgp_cfg_strs[] = { + { 0, "blocking-timer" }, + { 1, "blocking-retries" }, + { 2, "unblocking-retries" }, + { 3, "reset-timer" }, + { 4, "reset-retries" }, + { 5, "suspend-timer" }, + { 6, "suspend-retries" }, + { 7, "resume-timer" }, + { 8, "resume-retries" }, + { 9, "capability-update-timer" }, + { 10, "capability-update-retries" }, + { 0, NULL } +}; + +static const struct value_string bts_neigh_mode_strs[] = { + { NL_MODE_AUTOMATIC, "automatic" }, + { NL_MODE_MANUAL, "manual" }, + { NL_MODE_MANUAL_SI5SEP, "manual-si5" }, + { 0, NULL } +}; + +static struct cmd_node bts_node = { + BTS_NODE, + "%s(config-net-bts)# ", + 1, +}; + +static struct cmd_node power_ctrl_node = { + POWER_CTRL_NODE, + "%s(config-power-ctrl)# ", + 1, +}; + +/* per-BTS configuration */ +DEFUN_ATTR(cfg_bts, + cfg_bts_cmd, + "bts <0-255>", + "Select a BTS to configure\n" + BTS_NR_STR, + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + int bts_nr = atoi(argv[0]); + struct gsm_bts *bts; + + if (bts_nr > gsmnet->num_bts) { + vty_out(vty, "%% BTS number %d not valid (next BTS number must be %u)%s", + bts_nr, gsmnet->num_bts, VTY_NEWLINE); + return CMD_WARNING; + } else if (bts_nr == gsmnet->num_bts) { + /* allocate a new one */ + bts = bsc_bts_alloc_register(gsmnet, GSM_BTS_TYPE_UNKNOWN, + HARDCODED_BSIC); + } else + bts = gsm_bts_num(gsmnet, bts_nr); + + if (!bts) { + vty_out(vty, "%% Unable to allocate BTS %u%s", + gsmnet->num_bts, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->index = bts; + vty->index_sub = &bts->description; + vty->node = BTS_NODE; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_type, + cfg_bts_type_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "type TYPE", /* dynamically created */ + "Set the BTS type\n" "Type\n") +{ + struct gsm_bts *bts = vty->index; + int rc; + + rc = gsm_set_bts_type(bts, str2btstype(argv[0])); + if (rc == -EBUSY) + vty_out(vty, "%% Changing the type of an existing BTS is not supported.%s", + VTY_NEWLINE); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_bts_type_sysmobts, + cfg_bts_type_sysmobts_cmd, + "type sysmobts", + "Set the BTS type\n" + "Deprecated alias for 'osmo-bts'\n") +{ + const char *args[] = { "osmo-bts" }; + + vty_out(vty, "%% BTS type 'sysmobts' is deprecated, " + "use 'type osmo-bts' instead.%s", VTY_NEWLINE); + + return cfg_bts_type(self, vty, 1, args); +} + +DEFUN_USRATTR(cfg_bts_band, + cfg_bts_band_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "band BAND", + "Set the frequency band of this BTS\n" "Frequency band\n") +{ + struct gsm_bts *bts = vty->index; + int band = gsm_band_parse(argv[0]); + + if (band < 0) { + vty_out(vty, "%% BAND %d is not a valid GSM band%s", + band, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->band = band; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_dtxu, + cfg_bts_dtxu_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "dtx uplink [force]", + "Configure discontinuous transmission\n" + "Enable Uplink DTX for this BTS\n" + "MS 'shall' use DTXu instead of 'may' use (might not be supported by " + "older phones).\n") +{ + struct gsm_bts *bts = vty->index; + + bts->dtxu = (argc > 0) ? GSM48_DTX_SHALL_BE_USED : GSM48_DTX_MAY_BE_USED; + if (!is_ipa_abisip_bts(bts)) + vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration " + "neither supported nor tested!%s", VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_no_dtxu, + cfg_bts_no_dtxu_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "no dtx uplink", + NO_STR "Configure discontinuous transmission\n" + "Disable Uplink DTX for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_dtxd, + cfg_bts_dtxd_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "dtx downlink", + "Configure discontinuous transmission\n" + "Enable Downlink DTX for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->dtxd = true; + if (!is_ipa_abisip_bts(bts)) + vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration " + "neither supported nor tested!%s", VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_no_dtxd, + cfg_bts_no_dtxd_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "no dtx downlink", + NO_STR "Configure discontinuous transmission\n" + "Disable Downlink DTX for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->dtxd = false; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_ci, + cfg_bts_ci_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "cell_identity <0-65535>", + "Set the Cell identity of this BTS\n" "Cell Identity\n") +{ + struct gsm_bts *bts = vty->index; + int ci = atoi(argv[0]); + + if (ci < 0 || ci > 0xffff) { + vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s", + ci, VTY_NEWLINE); + return CMD_WARNING; + } + bts->cell_identity = ci; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_lac, + cfg_bts_lac_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "location_area_code (<0-65535>|<0x0000-0xffff>)", + "Set the Location Area Code (LAC) of this BTS\n" + "LAC in decimal format\n" + "LAC in hexadecimal format\n") +{ + struct gsm_bts *bts = vty->index; + int lac; + if (osmo_str_to_int(&lac, argv[0], 0, 0, 0xffff) < 0) + return CMD_WARNING; + + if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) { + vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s", + lac, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->location_area_code = lac; + + return CMD_SUCCESS; +} + + +/* compatibility wrapper for old config files */ +DEFUN_HIDDEN(cfg_bts_tsc, + cfg_bts_tsc_cmd, + "training_sequence_code <0-7>", + "Set the Training Sequence Code (TSC) of this BTS\n" "TSC\n") +{ + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_bsic, + cfg_bts_bsic_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "base_station_id_code <0-63>", + "Set the Base Station Identity Code (BSIC) of this BTS\n" + "BSIC of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int bsic = atoi(argv[0]); + + if (bsic < 0 || bsic > 0x3f) { + vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s", + bsic, VTY_NEWLINE); + return CMD_WARNING; + } + bts->bsic = bsic; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_unit_id, + cfg_bts_unit_id_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "ipa unit-id <0-65534> <0-255>", + "Abis/IP specific options\n" + "Set the IPA BTS Unit ID\n" + "Unit ID (Site)\n" + "Unit ID (BTS)\n") +{ + struct gsm_bts *bts = vty->index; + int site_id = atoi(argv[0]); + int bts_id = atoi(argv[1]); + + if (!is_ipa_abisip_bts(bts)) { + vty_out(vty, "%% BTS is not of IPA Abis/IP type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->ip_access.site_id = site_id; + bts->ip_access.bts_id = bts_id; + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_bts_unit_id, + cfg_bts_deprecated_unit_id_cmd, + "ip.access unit_id <0-65534> <0-255>", + "Abis/IP specific options\n" + "Set the IPA BTS Unit ID\n" + "Unit ID (Site)\n" + "Unit ID (BTS)\n"); + +DEFUN_USRATTR(cfg_bts_rsl_ip, + cfg_bts_rsl_ip_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "ipa rsl-ip A.B.C.D", + "Abis/IP specific options\n" + "Set the IPA RSL IP Address of the BSC\n" + "Destination IP address for RSL connection\n") +{ + struct gsm_bts *bts = vty->index; + struct in_addr ia; + + if (!is_ipa_abisip_bts(bts)) { + vty_out(vty, "%% BTS is not of IPA Abis/IP type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + inet_aton(argv[0], &ia); + bts->ip_access.rsl_ip = ntohl(ia.s_addr); + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_bts_rsl_ip, + cfg_bts_deprecated_rsl_ip_cmd, + "ip.access rsl-ip A.B.C.D", + "Abis/IP specific options\n" + "Set the IPA RSL IP Address of the BSC\n" + "Destination IP address for RSL connection\n"); + +#define NOKIA_STR "Nokia *Site related commands\n" + +DEFUN_USRATTR(cfg_bts_nokia_site_skip_reset, + cfg_bts_nokia_site_skip_reset_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "nokia_site skip-reset (0|1)", + NOKIA_STR + "Skip the reset step during bootstrap process of this BTS\n" + "Do NOT skip the reset\n" "Skip the reset\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->type != GSM_BTS_TYPE_NOKIA_SITE) { + vty_out(vty, "%% BTS is not of Nokia *Site type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->nokia.skip_reset = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_nokia_site_no_loc_rel_cnf, + cfg_bts_nokia_site_no_loc_rel_cnf_cmd, + "nokia_site no-local-rel-conf (0|1)", + NOKIA_STR + "Do not wait for RELease CONFirm message when releasing channel locally\n" + "Wait for RELease CONFirm\n" "Do not wait for RELease CONFirm\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + if (!is_nokia_bts(bts)) { + vty_out(vty, "%% BTS is not of Nokia *Site type%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + bts->nokia.no_loc_rel_cnf = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_nokia_site_bts_reset_timer_cnf, + cfg_bts_nokia_site_bts_reset_timer_cnf_cmd, + "nokia_site bts-reset-timer <15-100>", + NOKIA_STR + "The amount of time between BTS_RESET is sent " + "and the BTS is being bootstrapped\n" + "Timer value (in seconds, default 15)\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + if (!is_nokia_bts(bts)) { + vty_out(vty, "%% BTS is not of Nokia *Site type%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + bts->nokia.bts_reset_timer_cnf = atoi(argv[0]); + + return CMD_SUCCESS; +} +#define OML_STR "Organization & Maintenance Link\n" +#define IPA_STR "A-bis/IP Specific Options\n" + +DEFUN_USRATTR(cfg_bts_stream_id, + cfg_bts_stream_id_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "oml ipa stream-id <0-255> line E1_LINE", + OML_STR IPA_STR + "Set the ipa Stream ID of the OML link of this BTS\n" "Stream Identifier\n" + "Virtual E1 Line Number\n" "Virtual E1 Line Number\n") +{ + struct gsm_bts *bts = vty->index; + int stream_id = atoi(argv[0]), linenr = atoi(argv[1]); + + if (!is_ipa_abisip_bts(bts)) { + vty_out(vty, "%% BTS is not of IPA Abis/IP type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->oml_tei = stream_id; + /* This is used by e1inp_bind_ops callback for each BTS model. */ + bts->oml_e1_link.e1_nr = linenr; + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_bts_stream_id, + cfg_bts_deprecated_stream_id_cmd, + "oml ip.access stream_id <0-255> line E1_LINE", + OML_STR IPA_STR + "Set the ip.access Stream ID of the OML link of this BTS\n" + "Stream Identifier\n" "Virtual E1 Line Number\n" "Virtual E1 Line Number\n"); + +#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n" + +/* NOTE: This requires a full restart as bsc_network_configure() is executed + * only once on startup from osmo_bsc_main.c */ +DEFUN(cfg_bts_oml_e1, + cfg_bts_oml_e1_cmd, + "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + OML_E1_STR + "E1/T1 line number to be used for OML\n" + "E1/T1 line number to be used for OML\n" + "E1/T1 timeslot to be used for OML\n" + "E1/T1 timeslot to be used for OML\n" + "E1/T1 sub-slot to be used for OML\n" + "Use E1/T1 sub-slot 0\n" + "Use E1/T1 sub-slot 1\n" + "Use E1/T1 sub-slot 2\n" + "Use E1/T1 sub-slot 3\n" + "Use full E1 slot 3\n" + ) +{ + struct gsm_bts *bts = vty->index; + + parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_oml_e1_tei, + cfg_bts_oml_e1_tei_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "oml e1 tei <0-63>", + OML_E1_STR + "Set the TEI to be used for OML\n" + "TEI Number\n") +{ + struct gsm_bts *bts = vty->index; + + bts->oml_tei = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define CHAN_ALLOC_CMD "channel allocator" +#define CHAN_ALLOC_DESC \ + "Channel Allocator\n" \ + "Channel Allocator\n" + +#define CHAN_ALLOC_ASC_DSC "(ascending|descending)" +#define CHAN_ALLOC_ASC_DSC_DESC \ + "Allocate Timeslots and Transceivers in ascending order\n" \ + "Allocate Timeslots and Transceivers in descending order\n" + +DEFUN_ATTR(cfg_bts_challoc_mode_all, + cfg_bts_challoc_mode_all_cmd, + CHAN_ALLOC_CMD " " CHAN_ALLOC_ASC_DSC, + CHAN_ALLOC_DESC CHAN_ALLOC_ASC_DSC_DESC, + CMD_ATTR_IMMEDIATE | CMD_ATTR_DEPRECATED) +{ + bool reverse = !strcmp(argv[0], "descending"); + struct gsm_bts *bts = vty->index; + + bts->chan_alloc_chan_req_reverse = reverse; + bts->chan_alloc_assignment_reverse = reverse; + bts->chan_alloc_handover_reverse = reverse; + bts->chan_alloc_vgcs_reverse = reverse; + bts->chan_alloc_assignment_dynamic = false; + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_challoc_mode, + cfg_bts_challoc_mode_cmd, + CHAN_ALLOC_CMD + " mode (set-all|chan-req|assignment|handover|vgcs-vbs) " + CHAN_ALLOC_ASC_DSC, + CHAN_ALLOC_DESC + "Channel allocation mode\n" + "Set a single mode for all variants\n" + "Channel allocation for CHANNEL REQUEST (RACH)\n" + "Channel allocation for assignment\n" + "Channel allocation for handover\n" + "Channel allocation for VGCS/VBS\n" + CHAN_ALLOC_ASC_DSC_DESC, + CMD_ATTR_IMMEDIATE) +{ + bool reverse = !strcmp(argv[1], "descending"); + bool set_all = !strcmp(argv[0], "set-all"); + struct gsm_bts *bts = vty->index; + + if (set_all || !strcmp(argv[0], "chan-req")) + bts->chan_alloc_chan_req_reverse = reverse; + if (set_all || !strcmp(argv[0], "assignment")) { + bts->chan_alloc_assignment_reverse = reverse; + bts->chan_alloc_assignment_dynamic = false; + } + if (set_all || !strcmp(argv[0], "handover")) + bts->chan_alloc_handover_reverse = reverse; + if (set_all || !strcmp(argv[0], "vgcs-vbs")) + bts->chan_alloc_vgcs_reverse = reverse; + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_challoc_mode_ass_dynamic, + cfg_bts_challoc_mode_ass_dynamic_cmd, + CHAN_ALLOC_CMD " mode assignment dynamic", + CHAN_ALLOC_DESC + "Channel allocation mode\n" + "Channel allocation for assignment\n" + "Dynamic lchan selection based on configured parameters\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + bts->chan_alloc_assignment_dynamic = true; + + return CMD_SUCCESS; +} + +#define CHAN_ALLOC_DYN_PARAM_CMD \ + CHAN_ALLOC_CMD " dynamic-param" +#define CHAN_ALLOC_DYN_PARAM_DESC \ + CHAN_ALLOC_DESC \ + "Parameters for dynamic channel allocation mode\n" + +DEFUN_ATTR(cfg_bts_challoc_dynamic_param_sort_by_trx_power, + cfg_bts_challoc_dynamic_param_sort_by_trx_power_cmd, + CHAN_ALLOC_DYN_PARAM_CMD " sort-by-trx-power (0|1)", + CHAN_ALLOC_DYN_PARAM_DESC + "Whether to sort TRX instances by their respective power levels\n" + "Do not sort, use the same order as in the configuration file\n" + "Sort TRX instances by their power levels in descending order\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + bts->chan_alloc_dyn_params.sort_by_trx_power = (argv[0][0] == '1'); + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_challoc_dynamic_param_ul_rxlev, + cfg_bts_challoc_dynamic_param_ul_rxlev_cmd, + CHAN_ALLOC_DYN_PARAM_CMD " ul-rxlev thresh <0-63> avg-num <1-10>", + CHAN_ALLOC_DYN_PARAM_DESC + "Uplink RxLev\n" + "Uplink RxLev threshold\n" + "Uplink RxLev threshold\n" + "Minimum number of RxLev samples for averaging\n" + "Minimum number of RxLev samples for averaging\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + bts->chan_alloc_dyn_params.ul_rxlev_thresh = atoi(argv[0]); + bts->chan_alloc_dyn_params.ul_rxlev_avg_num = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_challoc_dynamic_param_c0_chan_load, + cfg_bts_challoc_dynamic_param_c0_chan_load_cmd, + CHAN_ALLOC_DYN_PARAM_CMD " c0-chan-load thresh <0-100>", + CHAN_ALLOC_DYN_PARAM_DESC + "C0 (BCCH carrier) channel load\n" + "Channel load threshold\n" + "Channel load threshold (in %)\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + bts->chan_alloc_dyn_params.c0_chan_load_thresh = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_chan_alloc_interf, + cfg_bts_chan_alloc_interf_cmd, + CHAN_ALLOC_CMD " avoid-interference (0|1)", + CHAN_ALLOC_DESC + "Configure whether reported interference levels from RES IND are used in channel allocation\n" + "Ignore interference levels (default). Always assign lchans in a deterministic order.\n" + "In channel allocation, prefer lchans with less interference.\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + if (!strcmp(argv[0], "0")) + bts->chan_alloc_avoid_interf = false; + else + bts->chan_alloc_avoid_interf = true; + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_chan_alloc_tch_signalling_policy, + cfg_bts_chan_alloc_tch_signalling_policy_cmd, + CHAN_ALLOC_CMD " tch-signalling-policy (never|emergency|voice|always)", + CHAN_ALLOC_DESC + "Configure when TCH/H or TCH/F channels can be used to serve signalling if SDCCHs are exhausted\n" + "Never allow TCH for signalling purposes\n" + "Only allow TCH for signalling purposes when establishing an emergency call\n" + "Allow TCH for signalling purposes when establishing any voice call\n" + "Always allow TCH for signalling purposes (default)\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + if (!strcmp(argv[0], "never")) + bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_NEVER; + else if (!strcmp(argv[0], "emergency")) + bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_EMERG; + else if (!strcmp(argv[0], "voice")) + bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_VOICE; + else + bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_ALWAYS; + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_chan_alloc_allow_tch_for_signalling, + cfg_bts_chan_alloc_allow_tch_for_signalling_cmd, + CHAN_ALLOC_CMD " allow-tch-for-signalling (0|1)", + CHAN_ALLOC_DESC + "Configure whether TCH/H or TCH/F channels can be used to serve non-call-related signalling if SDCCHs are exhausted\n" + "Forbid use of TCH for non-call-related signalling purposes\n" + "Allow use of TCH for non-call-related signalling purposes (default)\n", + CMD_ATTR_IMMEDIATE|CMD_ATTR_DEPRECATED) +{ + struct gsm_bts *bts = vty->index; + + vty_out(vty, "%% 'allow-tch-for-signalling' is deprecated, use 'tch-signalling-policy' instead.%s", VTY_NEWLINE); + + if (!strcmp(argv[0], "0")) + bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_VOICE; + else + bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_ALWAYS; + + return CMD_SUCCESS; +} + +#define RACH_STR "Random Access Control Channel\n" + +DEFUN_USRATTR(cfg_bts_rach_tx_integer, + cfg_bts_rach_tx_integer_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "rach tx integer <0-15>", + RACH_STR + "Set the raw tx integer value in RACH Control parameters IE\n" + "Set the raw tx integer value in RACH Control parameters IE\n" + "Raw tx integer value in RACH Control parameters IE\n") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf; + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rach_max_trans, + cfg_bts_rach_max_trans_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "rach max transmission (1|2|4|7)", + RACH_STR + "Set the maximum number of RACH burst transmissions\n" + "Set the maximum number of RACH burst transmissions\n" + "Maximum number of 1 RACH burst transmissions\n" + "Maximum number of 2 RACH burst transmissions\n" + "Maximum number of 4 RACH burst transmissions\n" + "Maximum number of 7 RACH burst transmissions\n") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_rach_max_delay, + cfg_bts_rach_max_delay_cmd, + "rach max-delay <1-127>", + RACH_STR + "Set the max Access Delay IE value to accept in CHANnel ReQuireD\n" + "Maximum Access Delay IE value to accept in CHANnel ReQuireD\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + bts->rach_max_delay = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_rach_expiry_timeout, + cfg_bts_rach_expiry_timeout_cmd, + "rach expiry-timeout <4-64>", + RACH_STR + "Set the timeout for channel requests expiry\n" + "Maximum timeout before dropping channel requests\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + bts->rach_expiry_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define REP_ACCH_STR "FACCH/SACCH repetition\n" + +DEFUN_USRATTR(cfg_bts_rep_dl_facch, + cfg_bts_rep_dl_facch_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "repeat dl-facch (command|all)", + REP_ACCH_STR + "Enable DL-FACCH repetition for this BTS\n" + "command LAPDm frames only\n" + "all LAPDm frames\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% repeated ACCH not supported by BTS %u%s", + bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "command")) { + bts->rep_acch_cap.dl_facch_cmd = true; + bts->rep_acch_cap.dl_facch_all = false; + } else { + bts->rep_acch_cap.dl_facch_cmd = true; + bts->rep_acch_cap.dl_facch_all = true; + } + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rep_no_dl_facch, + cfg_bts_rep_no_dl_facch_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "no repeat dl-facch", + NO_STR REP_ACCH_STR + "Disable DL-FACCH repetition for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->rep_acch_cap.dl_facch_cmd = false; + bts->rep_acch_cap.dl_facch_all = false; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rep_ul_dl_sacch, + cfg_bts_rep_ul_dl_sacch_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "repeat (ul-sacch|dl-sacch)", + REP_ACCH_STR + "Enable UL-SACCH repetition for this BTS\n" + "Enable DL-SACCH repetition for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% repeated ACCH not supported by BTS %u%s", + bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(argv[0], "ul-sacch") == 0) + bts->rep_acch_cap.ul_sacch = true; + else + bts->rep_acch_cap.dl_sacch = true; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rep_no_ul_dl_sacch, + cfg_bts_rep_no_ul_dl_sacch_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "no repeat (ul-sacch|dl-sacch)", + NO_STR REP_ACCH_STR + "Disable UL-SACCH repetition for this BTS\n" + "Disable DL-SACCH repetition for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + if (strcmp(argv[0], "ul-sacch") == 0) + bts->rep_acch_cap.ul_sacch = false; + else + bts->rep_acch_cap.dl_sacch = false; + + return CMD_SUCCESS; +} + +/* See 3GPP TS 45.008, section 8.2.4 */ +#define RXQUAL_THRESH_CMD \ + "rxqual (0|1|2|3|4|5|6|7)" +#define RXQUAL_THRESH_CMD_DESC \ + "Set RxQual (BER) threshold (default 4)\n" \ + "BER >= 0% (always on)\n" \ + "BER >= 0.2%\n" \ + "BER >= 0.4%\n" \ + "BER >= 0.8%\n" \ + "BER >= 1.6% (default)\n" \ + "BER >= 3.2%\n" \ + "BER >= 6.4%\n" \ + "BER >= 12.8%\n" + +DEFUN_USRATTR(cfg_bts_rep_rxqual, + cfg_bts_rep_rxqual_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "repeat " RXQUAL_THRESH_CMD, + REP_ACCH_STR RXQUAL_THRESH_CMD_DESC) +{ + struct gsm_bts *bts = vty->index; + + if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% repeated ACCH not supported by BTS %u%s", + bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + /* See also: GSM 05.08, section 8.2.4 */ + bts->rep_acch_cap.rxqual = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define TOP_ACCH_STR "Temporary ACCH overpower\n" + +DEFUN_USRATTR(cfg_bts_top_dl_acch, + cfg_bts_top_dl_acch_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "overpower (dl-acch|dl-sacch|dl-facch) <1-4>", + TOP_ACCH_STR + "Enable overpower for both SACCH and FACCH\n" + "Enable overpower for SACCH only\n" + "Enable overpower for FACCH only\n" + "Overpower value in dB\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% ACCH overpower is not supported by BTS %u%s", + bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->top_acch_cap.sacch_enable = 0; + bts->top_acch_cap.facch_enable = 0; + + if (!strcmp(argv[0], "dl-acch") || !strcmp(argv[0], "dl-sacch")) + bts->top_acch_cap.sacch_enable = 1; + if (!strcmp(argv[0], "dl-acch") || !strcmp(argv[0], "dl-facch")) + bts->top_acch_cap.facch_enable = 1; + + bts->top_acch_cap.overpower_db = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_top_no_dl_acch, + cfg_bts_top_no_dl_acch_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "no overpower dl-acch", + NO_STR TOP_ACCH_STR + "Disable ACCH overpower for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->top_acch_cap.overpower_db = 0; + bts->top_acch_cap.sacch_enable = 0; + bts->top_acch_cap.facch_enable = 0; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_top_dl_acch_rxqual, + cfg_bts_top_dl_acch_rxqual_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "overpower " RXQUAL_THRESH_CMD, + TOP_ACCH_STR RXQUAL_THRESH_CMD_DESC) +{ + struct gsm_bts *bts = vty->index; + + if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% ACCH overpower is not supported by BTS %u%s", + bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->top_acch_cap.rxqual = atoi(argv[0]); + + return CMD_SUCCESS; +} + +static const struct value_string top_acch_chan_mode_name[] = { + { TOP_ACCH_CHAN_MODE_ANY, "any" }, + { TOP_ACCH_CHAN_MODE_SPEECH_V3, "speech-amr" }, + { 0, NULL } +}; + +DEFUN_USRATTR(cfg_bts_top_dl_acch_chan_mode, + cfg_bts_top_dl_acch_chan_mode_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "overpower chan-mode (speech-amr|any)", + TOP_ACCH_STR + "Allow temporary overpower for specific Channel mode(s)\n" + "Speech channels using AMR codec (default)\n" + "Any kind of channel mode\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% ACCH overpower is not supported by BTS %u%s", + bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->top_acch_chan_mode = get_string_value(top_acch_chan_mode_name, argv[0]); + + return CMD_SUCCESS; +} + +#define CD_STR "Channel Description\n" + +DEFUN_USRATTR(cfg_bts_chan_desc_att, + cfg_bts_chan_desc_att_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "channel-description attach (0|1)", + CD_STR + "Set if attachment is required\n" + "Attachment is NOT required\n" + "Attachment is required (standard)\n") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.chan_desc.att = atoi(argv[0]); + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_bts_chan_desc_att, + cfg_bts_chan_dscr_att_cmd, + "channel-descrption attach (0|1)", + CD_STR + "Set if attachment is required\n" + "Attachment is NOT required\n" + "Attachment is required (standard)\n"); + +DEFUN_USRATTR(cfg_bts_chan_desc_bs_pa_mfrms, + cfg_bts_chan_desc_bs_pa_mfrms_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "channel-description bs-pa-mfrms <2-9>", + CD_STR + "Set number of multiframe periods for paging groups\n" + "Number of multiframe periods for paging groups\n") +{ + struct gsm_bts *bts = vty->index; + int bs_pa_mfrms = atoi(argv[0]); + + bts->si_common.chan_desc.bs_pa_mfrms = bs_pa_mfrms - 2; + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_bts_chan_desc_bs_pa_mfrms, + cfg_bts_chan_dscr_bs_pa_mfrms_cmd, + "channel-descrption bs-pa-mfrms <2-9>", + CD_STR + "Set number of multiframe periods for paging groups\n" + "Number of multiframe periods for paging groups\n"); + +DEFUN_USRATTR(cfg_bts_chan_desc_bs_ag_blks_res, + cfg_bts_chan_desc_bs_ag_blks_res_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "channel-description bs-ag-blks-res <0-7>", + CD_STR + "Set number of blocks reserved for access grant\n" + "Number of blocks reserved for access grant\n") +{ + struct gsm_bts *bts = vty->index; + int bs_ag_blks_res = atoi(argv[0]); + + bts->si_common.chan_desc.bs_ag_blks_res = bs_ag_blks_res; + return CMD_SUCCESS; +} +ALIAS_DEPRECATED(cfg_bts_chan_desc_bs_ag_blks_res, + cfg_bts_chan_dscr_bs_ag_blks_res_cmd, + "channel-descrption bs-ag-blks-res <0-7>", + CD_STR + "Set number of blocks reserved for access grant\n" + "Number of blocks reserved for access grant\n"); + +#define CCCH_STR "Common Control Channel\n" + +DEFUN_USRATTR(cfg_bts_ccch_load_ind_thresh, + cfg_bts_ccch_load_ind_thresh_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "ccch load-indication-threshold <0-100>", + CCCH_STR + "Percentage of CCCH load at which BTS sends RSL CCCH LOAD IND\n" + "CCCH Load Threshold in percent (Default: 10)\n") +{ + struct gsm_bts *bts = vty->index; + bts->ccch_load_ind_thresh = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_ccch_load_ind_period, + cfg_bts_ccch_load_ind_period_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "ccch load-indication-period <0-255>", + CCCH_STR + "Period of time at which BTS sends RSL CCCH LOAD IND\n" + "CCCH Load Indication Period in seconds (Default: 1)\n") +{ + struct gsm_bts *bts = vty->index; + bts->ccch_load_ind_period = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define NM_STR "Network Management\n" + +DEFUN_USRATTR(cfg_bts_rach_nm_b_thresh, + cfg_bts_rach_nm_b_thresh_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "rach nm busy threshold <0-255>", + RACH_STR NM_STR + "Set the NM Busy Threshold\n" + "Set the NM Busy Threshold\n" + "NM Busy Threshold in dB\n") +{ + struct gsm_bts *bts = vty->index; + bts->rach_b_thresh = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rach_nm_ldavg, + cfg_bts_rach_nm_ldavg_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "rach nm load average <0-65535>", + RACH_STR NM_STR + "Set the NM Loadaverage Slots value\n" + "Set the NM Loadaverage Slots value\n" + "NM Loadaverage Slots value\n") +{ + struct gsm_bts *bts = vty->index; + bts->rach_ldavg_slots = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_nch_position, + cfg_bts_nch_position_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "nch-position num-blocks <1-7> first-block <0-6>", + "NCH (Notification Channel) position within CCCH\n" + "Number of blocks reserved for NCH\n" + "Number of blocks reserved for NCH\n" + "First block reserved for NCH\n" + "First block reserved for NCH\n") +{ + struct gsm_bts *bts = vty->index; + int num_blocks = atoi(argv[0]); + int first_block = atoi(argv[1]); + + if (osmo_gsm48_si1ro_nch_pos_encode(num_blocks, first_block)) { + vty_out(vty, "num-blocks %u first-block %u is not permitted by 3GPP TS 44.010 Table 10.5.2.32.1b%s", + num_blocks, first_block, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->nch.num_blocks = num_blocks; + bts->nch.first_block = first_block; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_no_nch_position, + cfg_bts_no_nch_position_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "no nch-position", + NO_STR "Disable NCH in this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->nch.num_blocks = 0; + bts->nch.first_block = 0; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_cell_barred, + cfg_bts_cell_barred_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "cell barred (0|1)", + "Should this cell be barred from access?\n" + "Should this cell be barred from access?\n" + "Cell should NOT be barred\n" + "Cell should be barred\n") + +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.rach_control.cell_bar = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rach_ec_allowed, + cfg_bts_rach_ec_allowed_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "rach emergency call allowed (0|1)", + RACH_STR + "Should this cell allow emergency calls?\n" + "Should this cell allow emergency calls?\n" + "Should this cell allow emergency calls?\n" + "Do NOT allow emergency calls\n" + "Allow emergency calls\n") +{ + struct gsm_bts *bts = vty->index; + + if (atoi(argv[0]) == 0) + bts->si_common.rach_control.t2 |= 0x4; + else + bts->si_common.rach_control.t2 &= ~0x4; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rach_re_allowed, + cfg_bts_rach_re_allowed_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "rach call-reestablishment allowed (0|1)", + RACH_STR + "Resume calls after radio link failure\n" + "Resume calls after radio link failure\n" + "Forbid MS to reestablish calls\n" + "Allow MS to try to reestablish calls\n") +{ + struct gsm_bts *bts = vty->index; + + if (atoi(argv[0]) == 0) + bts->si_common.rach_control.re = 1; + else + bts->si_common.rach_control.re = 0; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rach_ac_class, + cfg_bts_rach_ac_class_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "rach access-control-class (0|1|2|3|4|5|6|7|8|9|11|12|13|14|15) (barred|allowed)", + RACH_STR + "Set access control class\n" + "Access control class 0\n" + "Access control class 1\n" + "Access control class 2\n" + "Access control class 3\n" + "Access control class 4\n" + "Access control class 5\n" + "Access control class 6\n" + "Access control class 7\n" + "Access control class 8\n" + "Access control class 9\n" + "Access control class 11 for PLMN use\n" + "Access control class 12 for security services\n" + "Access control class 13 for public utilities (e.g. water/gas suppliers)\n" + "Access control class 14 for emergency services\n" + "Access control class 15 for PLMN staff\n" + "barred to use access control class\n" + "allowed to use access control class\n") +{ + struct gsm_bts *bts = vty->index; + + uint8_t control_class; + uint8_t allowed = 0; + + if (strcmp(argv[1], "allowed") == 0) + allowed = 1; + + control_class = atoi(argv[0]); + if (control_class < 8) + if (allowed) + bts->si_common.rach_control.t3 &= ~(0x1 << control_class); + else + bts->si_common.rach_control.t3 |= (0x1 << control_class); + else + if (allowed) + bts->si_common.rach_control.t2 &= ~(0x1 << (control_class - 8)); + else + bts->si_common.rach_control.t2 |= (0x1 << (control_class - 8)); + + if (control_class < 10) + acc_mgr_perm_subset_changed(&bts->acc_mgr, &bts->si_common.rach_control); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_ms_max_power, + cfg_bts_ms_max_power_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "ms max power <0-40>", + "MS Options\n" + "Maximum transmit power of the MS\n" + "Maximum transmit power of the MS\n" + "Maximum transmit power of the MS in dBm\n") +{ + struct gsm_bts *bts = vty->index; + + bts->ms_max_power = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define CELL_STR "Cell Parameters\n" + +DEFUN_USRATTR(cfg_bts_cell_resel_hyst, + cfg_bts_cell_resel_hyst_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "cell reselection hysteresis <0-14>", + CELL_STR "Cell re-selection parameters\n" + "Cell Re-Selection Hysteresis in dB\n" + "Cell Re-Selection Hysteresis in dB\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rxlev_acc_min, + cfg_bts_rxlev_acc_min_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "rxlev access min <0-63>", + "Minimum RxLev needed for cell access\n" + "Minimum RxLev needed for cell access\n" + "Minimum RxLev needed for cell access\n" + "Minimum RxLev needed for cell access (better than -110dBm)\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_cell_bar_qualify, + cfg_bts_cell_bar_qualify_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "cell bar qualify (0|1)", + CELL_STR "Cell Bar Qualify\n" "Cell Bar Qualify\n" + "Set CBQ to 0\n" "Set CBQ to 1\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_cell_resel_ofs, + cfg_bts_cell_resel_ofs_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "cell reselection offset <0-126>", + CELL_STR "Cell Re-Selection Parameters\n" + "Cell Re-Selection Offset (CRO) in dB\n" + "Cell Re-Selection Offset (CRO) in dB\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(argv[0])/2; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_temp_ofs, + cfg_bts_temp_ofs_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "temporary offset <0-60>", + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset in dB\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.temp_offs = atoi(argv[0])/10; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_temp_ofs_inf, + cfg_bts_temp_ofs_inf_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "temporary offset infinite", + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset\n" + "Sets cell selection temporary negative offset to infinity\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.temp_offs = 7; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_penalty_time, + cfg_bts_penalty_time_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "penalty time <20-620>", + "Cell selection penalty time\n" + "Cell selection penalty time\n" + "Cell selection penalty time in seconds (by 20s increments)\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.penalty_time = (atoi(argv[0])-20)/20; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_penalty_time_rsvd, + cfg_bts_penalty_time_rsvd_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "penalty time reserved", + "Cell selection penalty time\n" + "Cell selection penalty time\n" + "Set cell selection penalty time to reserved value 31, " + "(indicate that CELL_RESELECT_OFFSET is subtracted from C2 " + "and TEMPORARY_OFFSET is ignored)\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.penalty_time = 31; + + return CMD_SUCCESS; +} + +#define NCC_STR "Network Colour Code\n" +#define NCC_PERMITTED_STR "Set permitted NCCs\n" + +DEFUN_USRATTR(cfg_bts_ncc_permitted_all, + cfg_bts_ncc_permitted_all_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "ncc-permitted all\n", + NCC_PERMITTED_STR + "Permit all NCCs (default)\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.ncc_permitted = 0xff; + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_ncc_permitted, + cfg_bts_ncc_permitted_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "ncc-permitted <1-8> [<1-8>] [<1-8>] [<1-8>] [<1-8>] [<1-8>] [<1-8>]\n", + NCC_PERMITTED_STR + NCC_STR NCC_STR NCC_STR NCC_STR NCC_STR NCC_STR NCC_STR) +{ + struct gsm_bts *bts = vty->index; + int i; + int ncc_prev = -1; + + if (argc == 1 && !strcmp(argv[0], "all")) { + bts->si_common.ncc_permitted = 0xff; + return CMD_SUCCESS; + } + + bts->si_common.ncc_permitted = 0x00; + + /* Check if NCCs are in order (like get_amr_from_arg) */ + for (i = 0; i < argc; i++) { + int ncc = atoi(argv[i]); + if (ncc_prev > ncc) { + vty_out(vty, "%% NCCs must be listed in order%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (ncc_prev == ncc) { + vty_out(vty, "%% NCCs must be unique%s", VTY_NEWLINE); + return CMD_WARNING; + } + ncc_prev = ncc; + } + + for (i = 0; i < argc; i++) + bts->si_common.ncc_permitted |= 1 << (atoi(argv[i]) - 1); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_radio_link_timeout, + cfg_bts_radio_link_timeout_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "radio-link-timeout <4-64>", + "Radio link timeout criterion (BTS side)\n" + "Radio link timeout value (lost SACCH block)\n") +{ + struct gsm_bts *bts = vty->index; + unsigned int radio_link_timeout = atoi(argv[0]); + + /* According to Table 10.5.2.3.1 in TS 144.018 */ + if (radio_link_timeout % 4 != 0) { + vty_out(vty, "%% Radio link timeout must be a multiple of 4%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_bts_set_radio_link_timeout(bts, radio_link_timeout); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_radio_link_timeout_inf, + cfg_bts_radio_link_timeout_inf_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "radio-link-timeout infinite", + "Radio link timeout criterion (BTS side)\n" + "Infinite Radio link timeout value (use only for BTS RF testing)\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% infinite radio link timeout not supported by BTS %u%s", bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "%% INFINITE RADIO LINK TIMEOUT, USE ONLY FOR BTS RF TESTING%s", VTY_NEWLINE); + gsm_bts_set_radio_link_timeout(bts, -1); + + return CMD_SUCCESS; +} + +#define GPRS_TEXT "GPRS Packet Network\n" + +#define GPRS_CHECK_ENABLED(bts) \ + do { \ + if (bts->gprs.mode == BTS_GPRS_NONE) { \ + vty_out(vty, "%% GPRS is not enabled on BTS %u%s", \ + bts->nr, VTY_NEWLINE); \ + return CMD_WARNING; \ + } \ + } while (0) + +DEFUN_USRATTR(cfg_bts_prs_bvci, + cfg_bts_gprs_bvci_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs cell bvci <2-65535>", + GPRS_TEXT + "GPRS Cell Settings\n" + "GPRS BSSGP VC Identifier\n" + "GPRS BSSGP VC Identifier\n") +{ + /* ETSI TS 101 343: values 0 and 1 are reserved for signalling and PTM */ + struct gsm_bts *bts = vty->index; + + GPRS_CHECK_ENABLED(bts); + + bts->gprs.cell.bvci = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_nsei, + cfg_bts_gprs_nsei_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs nsei <0-65535>", + GPRS_TEXT + "GPRS NS Entity Identifier\n" + "GPRS NS Entity Identifier\n") +{ + struct gsm_bts *bts = vty->index; + + GPRS_CHECK_ENABLED(bts); + + bts->site_mgr->gprs.nse.nsei = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \ + "NSVC Logical Number\n" + +DEFUN_USRATTR(cfg_no_bts_gprs_nsvc, + cfg_no_bts_gprs_nsvc_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "no gprs nsvc <0-1>", + NO_STR GPRS_TEXT NSVC_TEXT) +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + GPRS_CHECK_ENABLED(bts); + + bts->site_mgr->gprs.nsvc[idx].enabled = false; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_nsvci, + cfg_bts_gprs_nsvci_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs nsvc <0-1> nsvci <0-65535>", + GPRS_TEXT NSVC_TEXT + "NS Virtual Connection Identifier\n" + "GPRS NS VC Identifier\n") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + GPRS_CHECK_ENABLED(bts); + + bts->site_mgr->gprs.nsvc[idx].nsvci = atoi(argv[1]); + bts->site_mgr->gprs.nsvc[idx].enabled = true; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_nsvc_lport, + cfg_bts_gprs_nsvc_lport_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs nsvc <0-1> local udp port <0-65535>", + GPRS_TEXT NSVC_TEXT + "GPRS NS Local UDP Port\n" + "GPRS NS Local UDP Port\n" + "GPRS NS Local UDP Port\n" + "GPRS NS Local UDP Port Number\n") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + GPRS_CHECK_ENABLED(bts); + + bts->site_mgr->gprs.nsvc[idx].local_port = atoi(argv[1]); + bts->site_mgr->gprs.nsvc[idx].enabled = true; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_nsvc_rport, + cfg_bts_gprs_nsvc_rport_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs nsvc <0-1> remote udp port <0-65535>", + GPRS_TEXT NSVC_TEXT + "GPRS NS Remote UDP Port\n" + "GPRS NS Remote UDP Port\n" + "GPRS NS Remote UDP Port\n" + "GPRS NS Remote UDP Port Number\n") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + GPRS_CHECK_ENABLED(bts); + + /* sockaddr_in and sockaddr_in6 have the port at the same position */ + bts->site_mgr->gprs.nsvc[idx].remote.u.sin.sin_port = htons(atoi(argv[1])); + bts->site_mgr->gprs.nsvc[idx].enabled = true; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_nsvc_rip, + cfg_bts_gprs_nsvc_rip_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs nsvc <0-1> remote ip " VTY_IPV46_CMD, + GPRS_TEXT NSVC_TEXT + "GPRS NS Remote IP Address\n" + "GPRS NS Remote IP Address\n" + "GPRS NS Remote IPv4 Address\n" + "GPRS NS Remote IPv6 Address\n") +{ + struct gsm_bts *bts = vty->index; + struct osmo_sockaddr_str remote; + int idx = atoi(argv[0]); + int ret; + + GPRS_CHECK_ENABLED(bts); + + ret = osmo_sockaddr_str_from_str2(&remote, argv[1]); + if (ret) { + vty_out(vty, "%% Invalid IP address %s%s", argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + /* Can't use osmo_sockaddr_str_to_sockaddr() because the port would be overridden */ + bts->site_mgr->gprs.nsvc[idx].remote.u.sas.ss_family = remote.af; + switch (remote.af) { + case AF_INET: + osmo_sockaddr_str_to_in_addr(&remote, &bts->site_mgr->gprs.nsvc[idx].remote.u.sin.sin_addr); + bts->site_mgr->gprs.nsvc[idx].enabled = true; + break; + case AF_INET6: + osmo_sockaddr_str_to_in6_addr(&remote, &bts->site_mgr->gprs.nsvc[idx].remote.u.sin6.sin6_addr); + bts->site_mgr->gprs.nsvc[idx].enabled = true; + break; + } + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_pag_free, cfg_bts_pag_free_cmd, + "paging free <-1-1024>", + "Paging options\n" + "Only page when having a certain amount of free slots\n" + "amount of required free paging slots. -1 to disable\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + bts->paging.free_chans_need = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_ns_timer, + cfg_bts_gprs_ns_timer_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs ns timer " NS_TIMERS " <0-255>", + GPRS_TEXT "Network Service\n" + "Network Service Timer\n" + NS_TIMERS_HELP "Timer Value\n") +{ + struct gsm_bts *bts = vty->index; + int idx = get_string_value(gprs_ns_timer_strs, argv[0]); + int val = atoi(argv[1]); + + GPRS_CHECK_ENABLED(bts); + + if (idx < 0 || idx >= ARRAY_SIZE(bts->site_mgr->gprs.nse.timer)) + return CMD_WARNING; + + bts->site_mgr->gprs.nse.timer[idx] = val; + + return CMD_SUCCESS; +} + +#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)" +#define BSSGP_TIMERS_HELP \ + "Tbvc-block timeout\n" \ + "Tbvc-block retries\n" \ + "Tbvc-unblock retries\n" \ + "Tbvc-reset timeout\n" \ + "Tbvc-reset retries\n" \ + "Tbvc-suspend timeout\n" \ + "Tbvc-suspend retries\n" \ + "Tbvc-resume timeout\n" \ + "Tbvc-resume retries\n" \ + "Tbvc-capa-update timeout\n" \ + "Tbvc-capa-update retries\n" + +DEFUN_USRATTR(cfg_bts_gprs_cell_timer, + cfg_bts_gprs_cell_timer_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs cell timer " BSSGP_TIMERS " <0-255>", + GPRS_TEXT "Cell / BSSGP\n" + "Cell/BSSGP Timer\n" + BSSGP_TIMERS_HELP "Timer Value\n") +{ + struct gsm_bts *bts = vty->index; + int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]); + int val = atoi(argv[1]); + + GPRS_CHECK_ENABLED(bts); + + if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer)) + return CMD_WARNING; + + bts->gprs.cell.timer[idx] = val; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_rac, + cfg_bts_gprs_rac_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs routing area <0-255>", + GPRS_TEXT + "GPRS Routing Area Code\n" + "GPRS Routing Area Code\n" + "GPRS Routing Area Code\n") +{ + struct gsm_bts *bts = vty->index; + + GPRS_CHECK_ENABLED(bts); + + bts->gprs.rac = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_ctrl_ack, + cfg_bts_gprs_ctrl_ack_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "gprs control-ack-type-rach", + GPRS_TEXT + "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to " + "four access bursts format instead of default RLC/MAC control block\n") +{ + struct gsm_bts *bts = vty->index; + + GPRS_CHECK_ENABLED(bts); + + bts->gprs.ctrl_ack_type_use_block = false; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_ccn_active, + cfg_bts_gprs_ccn_active_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "gprs ccn-active (0|1|default)", + GPRS_TEXT + "Set CCN_ACTIVE in the GPRS Cell Options IE on the BCCH (SI13)\n" + "Disable\n" "Enable\n" "Default based on BTS type support\n") +{ + struct gsm_bts *bts = vty->index; + + bts->gprs.ccn.forced_vty = strcmp(argv[0], "default") != 0; + + if (bts->gprs.ccn.forced_vty) + bts->gprs.ccn.active = argv[0][0] == '1'; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_pwr_ctrl_alpha, + cfg_bts_gprs_pwr_ctrl_alpha_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "gprs power-control alpha <0-10>", + GPRS_TEXT + "GPRS Global Power Control Parameters IE (SI13)\n" + "Set alpha\n" + "alpha for MS output power control in units of 0.1 (defaults to 0)\n") +{ + struct gsm_bts *bts = vty->index; + + bts->gprs.pwr_ctrl.alpha = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_no_bts_gprs_ctrl_ack, + cfg_no_bts_gprs_ctrl_ack_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "no gprs control-ack-type-rach", + NO_STR GPRS_TEXT + "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to " + "default RLC/MAC control block\n") +{ + struct gsm_bts *bts = vty->index; + + GPRS_CHECK_ENABLED(bts); + + bts->gprs.ctrl_ack_type_use_block = true; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_net_ctrl_ord, + cfg_bts_gprs_net_ctrl_ord_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "gprs network-control-order (nc0|nc1|nc2)", + GPRS_TEXT + "GPRS Network Control Order\n" + "MS controlled cell re-selection, no measurement reporting\n" + "MS controlled cell re-selection, MS sends measurement reports\n" + "Network controlled cell re-selection, MS sends measurement reports\n") +{ + struct gsm_bts *bts = vty->index; + + GPRS_CHECK_ENABLED(bts); + + bts->gprs.net_ctrl_ord = atoi(argv[0] + 2); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_mode, + cfg_bts_gprs_mode_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "gprs mode (none|gprs|egprs)", + GPRS_TEXT + "GPRS Mode for this BTS\n" + "GPRS Disabled on this BTS\n" + "GPRS Enabled on this BTS\n" + "EGPRS (EDGE) Enabled on this BTS\n") +{ + struct gsm_bts *bts = vty->index; + enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0], NULL); + + if (bts->features_known && !bts_gprs_mode_is_compat(bts, mode)) { + vty_out(vty, "%% This BTS type does not support %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.mode = mode; + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_bts_gprs_11bit_rach_support_for_egprs, + cfg_bts_gprs_11bit_rach_support_for_egprs_cmd, + "gprs 11bit_rach_support_for_egprs (0|1)", + GPRS_TEXT "EGPRS Packet Channel Request support\n" + "Disable EGPRS Packet Channel Request support\n" + "Enable EGPRS Packet Channel Request support\n") +{ + struct gsm_bts *bts = vty->index; + + vty_out(vty, "%% 'gprs 11bit_rach_support_for_egprs' is now deprecated: " + "use '[no] gprs egprs-packet-channel-request' instead%s", VTY_NEWLINE); + + bts->gprs.egprs_pkt_chan_request = (argv[0][0] == '1'); + + if (bts->gprs.mode == BTS_GPRS_NONE && bts->gprs.egprs_pkt_chan_request) { + vty_out(vty, "%% (E)GPRS is not enabled (see 'gprs mode')%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (bts->gprs.mode != BTS_GPRS_EGPRS) { + vty_out(vty, "%% EGPRS Packet Channel Request support requires " + "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE); + /* Do not return here, keep the old behaviour. */ + } + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_gprs_egprs_pkt_chan_req, + cfg_bts_gprs_egprs_pkt_chan_req_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "gprs egprs-packet-channel-request", + GPRS_TEXT "EGPRS Packet Channel Request support") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode != BTS_GPRS_EGPRS) { + vty_out(vty, "%% EGPRS Packet Channel Request support requires " + "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.egprs_pkt_chan_request = true; + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_no_gprs_egprs_pkt_chan_req, + cfg_bts_no_gprs_egprs_pkt_chan_req_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "no gprs egprs-packet-channel-request", + NO_STR GPRS_TEXT "EGPRS Packet Channel Request support") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode != BTS_GPRS_EGPRS) { + vty_out(vty, "%% EGPRS Packet Channel Request support requires " + "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.egprs_pkt_chan_request = false; + return CMD_SUCCESS; +} + +#define SI_TEXT "System Information Messages\n" +#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)" +#define SI_TYPE_HELP "System Information Type 1\n" \ + "System Information Type 2\n" \ + "System Information Type 3\n" \ + "System Information Type 4\n" \ + "System Information Type 5\n" \ + "System Information Type 6\n" \ + "System Information Type 7\n" \ + "System Information Type 8\n" \ + "System Information Type 9\n" \ + "System Information Type 10\n" \ + "System Information Type 13\n" \ + "System Information Type 16\n" \ + "System Information Type 17\n" \ + "System Information Type 18\n" \ + "System Information Type 19\n" \ + "System Information Type 20\n" \ + "System Information Type 2bis\n" \ + "System Information Type 2ter\n" \ + "System Information Type 2quater\n" \ + "System Information Type 5bis\n" \ + "System Information Type 5ter\n" + +DEFUN_USRATTR(cfg_bts_si_mode, + cfg_bts_si_mode_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "system-information " SI_TYPE_TEXT " mode (static|computed)", + SI_TEXT SI_TYPE_HELP + "System Information Mode\n" + "Static user-specified\n" + "Dynamic, BSC-computed\n") +{ + struct gsm_bts *bts = vty->index; + int type; + + type = get_string_value(osmo_sitype_strs, argv[0]); + if (type < 0) { + vty_out(vty, "%% Error SI Type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[1], "static")) + bts->si_mode_static |= (1 << type); + else + bts->si_mode_static &= ~(1 << type); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_si_static, + cfg_bts_si_static_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "system-information " SI_TYPE_TEXT " static HEXSTRING", + SI_TEXT SI_TYPE_HELP + "Static System Information filling\n" + "Static user-specified SI content in HEX notation\n") +{ + struct gsm_bts *bts = vty->index; + int rc, type; + + type = get_string_value(osmo_sitype_strs, argv[0]); + if (type < 0) { + vty_out(vty, "%% Error SI Type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!(bts->si_mode_static & (1 << type))) { + vty_out(vty, "%% SI Type %s is not configured in static mode%s", + get_value_string(osmo_sitype_strs, type), VTY_NEWLINE); + return CMD_WARNING; + } + + /* Fill buffer with padding pattern */ + memset(GSM_BTS_SI(bts, type), 0x2b, GSM_MACBLOCK_LEN); + + /* Parse the user-specified SI in hex format, [partially] overwriting padding */ + rc = osmo_hexparse(argv[1], GSM_BTS_SI(bts, type), GSM_MACBLOCK_LEN); + if (rc < 0 || rc > GSM_MACBLOCK_LEN) { + vty_out(vty, "%% Error parsing HEXSTRING%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* Mark this SI as present */ + bts->si_valid |= (1 << type); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_si_unused_send_empty, + cfg_bts_si_unused_send_empty_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "system-information unused-send-empty", + SI_TEXT + "Send BCCH Info with empty 'Full BCCH Info' TLV to notify disabled SI. " + "Some nanoBTS fw versions are known to fail upon receival of these messages.\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_unused_send_empty = true; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_no_si_unused_send_empty, + cfg_bts_no_si_unused_send_empty_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "no system-information unused-send-empty", + NO_STR SI_TEXT + "Avoid sending BCCH Info with empty 'Full BCCH Info' TLV to notify disabled SI. " + "Some nanoBTS fw versions are known to fail upon receival of these messages.\n") +{ + struct gsm_bts *bts = vty->index; + + if (!is_ipa_abisip_bts(bts)) { + vty_out(vty, "%% This command is only intended for IPA Abis/IP BTS. See OS#3707.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + bts->si_unused_send_empty = false; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_early_cm, + cfg_bts_early_cm_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "early-classmark-sending (allowed|forbidden)", + "Early Classmark Sending\n" + "Early Classmark Sending is allowed\n" + "Early Classmark Sending is forbidden\n") +{ + struct gsm_bts *bts = vty->index; + + if (!strcmp(argv[0], "allowed")) + bts->early_classmark_allowed = true; + else + bts->early_classmark_allowed = false; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_early_cm_3g, + cfg_bts_early_cm_3g_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "early-classmark-sending-3g (allowed|forbidden)", + "3G Early Classmark Sending\n" + "3G Early Classmark Sending is allowed\n" + "3G Early Classmark Sending is forbidden\n") +{ + struct gsm_bts *bts = vty->index; + + if (!strcmp(argv[0], "allowed")) + bts->early_classmark_allowed_3g = true; + else + bts->early_classmark_allowed_3g = false; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_neigh_mode, + cfg_bts_neigh_mode_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "neighbor-list mode (automatic|manual|manual-si5)", + "Neighbor List\n" "Mode of Neighbor List generation\n" + "Automatically from all BTS in this BSC\n" "Manual\n" + "Manual with different lists for SI2 and SI5\n") +{ + struct gsm_bts *bts = vty->index; + int mode = get_string_value(bts_neigh_mode_strs, argv[0]); + + switch (mode) { + case NL_MODE_MANUAL_SI5SEP: + case NL_MODE_MANUAL: + /* make sure we clear the current list when switching to + * manual mode */ + if (bts->neigh_list_manual_mode == 0) + memset(&bts->si_common.data.neigh_list, 0, + sizeof(bts->si_common.data.neigh_list)); + break; + default: + break; + } + + bts->neigh_list_manual_mode = mode; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_neigh, + cfg_bts_neigh_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "neighbor-list (add|del) arfcn <0-1023>", + "Neighbor List\n" "Add to manual neighbor list\n" + "Delete from manual neighbor list\n" "ARFCN of neighbor\n" + "ARFCN of neighbor\n") +{ + struct gsm_bts *bts = vty->index; + struct bitvec *bv = &bts->si_common.neigh_list; + uint16_t arfcn = atoi(argv[1]); + enum gsm_band unused; + + if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) { + vty_out(vty, "%% Cannot configure neighbor list in " + "automatic mode%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { + vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "add")) + bitvec_set_bit_pos(bv, arfcn, 1); + else + bitvec_set_bit_pos(bv, arfcn, 0); + + return CMD_SUCCESS; +} + +/* help text should be kept in sync with EARFCN_*_INVALID defines */ +DEFUN_USRATTR(cfg_bts_si2quater_neigh_add, + cfg_bts_si2quater_neigh_add_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "si2quater neighbor-list add earfcn <0-65535> thresh-hi <0-31> " + "thresh-lo <0-32> prio <0-8> qrxlv <0-32> meas <0-8>", + "SI2quater Neighbor List\n" "SI2quater Neighbor List\n" + "Add to manual SI2quater neighbor list\n" + "EARFCN of neighbor\n" "EARFCN of neighbor\n" + "threshold high bits\n" "threshold high bits\n" + "threshold low bits\n" "threshold low bits (32 means NA)\n" + "priority\n" "priority (8 means NA)\n" + "QRXLEVMIN\n" "QRXLEVMIN (32 means NA)\n" + "measurement bandwidth\n" "measurement bandwidth (8 means NA)\n") +{ + struct gsm_bts *bts = vty->index; + uint16_t arfcn = atoi(argv[0]); + uint8_t thresh_hi = atoi(argv[1]), thresh_lo = atoi(argv[2]), + prio = atoi(argv[3]), qrx = atoi(argv[4]), meas = atoi(argv[5]); + int r = bts_earfcn_add(bts, arfcn, thresh_hi, thresh_lo, prio, qrx, meas); + + switch (r) { + case 1: + vty_out(vty, "%% Warning: multiple threshold-high are not supported, overriding with %u%s", + thresh_hi, VTY_NEWLINE); + break; + case EARFCN_THRESH_LOW_INVALID: + vty_out(vty, "%% Warning: multiple threshold-low are not supported, overriding with %u%s", + thresh_lo, VTY_NEWLINE); + break; + case EARFCN_QRXLV_INVALID + 1: + vty_out(vty, "%% Warning: multiple QRXLEVMIN are not supported, overriding with %u%s", + qrx, VTY_NEWLINE); + break; + case EARFCN_PRIO_INVALID: + vty_out(vty, "%% Warning: multiple priorities are not supported, overriding with %u%s", + prio, VTY_NEWLINE); + break; + default: + if (r < 0) { + vty_out(vty, "%% Unable to add ARFCN %u: %s%s", arfcn, strerror(-r), VTY_NEWLINE); + return CMD_WARNING; + } + } + + if (si2q_num(bts) <= SI2Q_MAX_NUM) + return CMD_SUCCESS; + + vty_out(vty, "%% Warning: not enough space in SI2quater (%u/%u used) for a given EARFCN %u%s", + bts->si2q_count, SI2Q_MAX_NUM, arfcn, VTY_NEWLINE); + + if (bts_earfcn_del(bts, arfcn) != 0) + vty_out(vty, "%% Failed to roll-back adding EARFCN %u%s", arfcn, VTY_NEWLINE); + + return CMD_WARNING; +} + +DEFUN_USRATTR(cfg_bts_si2quater_neigh_del, + cfg_bts_si2quater_neigh_del_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "si2quater neighbor-list del earfcn <0-65535>", + "SI2quater Neighbor List\n" + "SI2quater Neighbor List\n" + "Delete from SI2quater manual neighbor list\n" + "EARFCN of neighbor\n" + "EARFCN\n") +{ + struct gsm_bts *bts = vty->index; + uint16_t arfcn = atoi(argv[0]); + int r = bts_earfcn_del(bts, arfcn); + if (r < 0) { + vty_out(vty, "%% Unable to delete arfcn %u: %s%s", arfcn, + strerror(-r), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_si2quater_uarfcn_add, + cfg_bts_si2quater_uarfcn_add_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "si2quater neighbor-list add uarfcn <0-16383> <0-511> <0-1>", + "SI2quater Neighbor List\n" + "SI2quater Neighbor List\n" "Add to manual SI2quater neighbor list\n" + "UARFCN of neighbor\n" "UARFCN of neighbor\n" "scrambling code\n" + "diversity bit\n") +{ + struct gsm_bts *bts = vty->index; + uint16_t arfcn = atoi(argv[0]), scramble = atoi(argv[1]); + + switch (bts_uarfcn_add(bts, arfcn, scramble, atoi(argv[2]))) { + case -ENOMEM: + vty_out(vty, "%% Unable to add UARFCN: max number of UARFCNs (%u) reached%s", + MAX_EARFCN_LIST, VTY_NEWLINE); + return CMD_WARNING; + case -ENOSPC: + vty_out(vty, "%% Warning: not enough space in SI2quater for a given UARFCN (%u, %u)%s", + arfcn, scramble, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_si2quater_uarfcn_del, + cfg_bts_si2quater_uarfcn_del_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "si2quater neighbor-list del uarfcn <0-16383> <0-511>", + "SI2quater Neighbor List\n" + "SI2quater Neighbor List\n" + "Delete from SI2quater manual neighbor list\n" + "UARFCN of neighbor\n" + "UARFCN\n" + "scrambling code\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts_uarfcn_del(bts, atoi(argv[0]), atoi(argv[1])) < 0) { + vty_out(vty, "%% Unable to delete uarfcn: pair not found%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_si5_neigh, + cfg_bts_si5_neigh_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK), + "si5 neighbor-list (add|del) arfcn <0-1023>", + "SI5 Neighbor List\n" + "SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n" + "Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n" + "ARFCN of neighbor\n") +{ + enum gsm_band unused; + struct gsm_bts *bts = vty->index; + struct bitvec *bv = &bts->si_common.si5_neigh_list; + uint16_t arfcn = atoi(argv[1]); + + if (!bts->neigh_list_manual_mode) { + vty_out(vty, "%% Cannot configure neighbor list in " + "automatic mode%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (gsm_arfcn2band_rc(arfcn, &unused) < 0) { + vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "add")) + bitvec_set_bit_pos(bv, arfcn, 1); + else + bitvec_set_bit_pos(bv, arfcn, 0); + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_acc_rotate, + cfg_bts_acc_rotate_cmd, + "access-control-class-rotate <0-10>", + "Enable Access Control Class allowed subset rotation\n" + "Size of the rotating allowed ACC 0-9 subset (default=10, no subset)\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + int len_allowed_adm = atoi(argv[0]); + acc_mgr_set_len_allowed_adm(&bts->acc_mgr, len_allowed_adm); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_acc_rotate_quantum, + cfg_bts_acc_rotate_quantum_cmd, + "access-control-class-rotate-quantum <1-65535>", + "Time between rotation of ACC 0-9 generated subsets\n" + "Time in seconds (default=" OSMO_STRINGIFY_VAL(ACC_MGR_QUANTUM_DEFAULT) ")\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + uint32_t rotation_time_sec = (uint32_t)atoi(argv[0]); + acc_mgr_set_rotation_time(&bts->acc_mgr, rotation_time_sec); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_acc_ramping, + cfg_bts_acc_ramping_cmd, + "access-control-class-ramping", + "Enable Access Control Class ramping\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + struct gsm_bts_trx *trx; + + if (!acc_ramp_is_enabled(&bts->acc_ramp)) { + acc_ramp_set_enabled(&bts->acc_ramp, true); + /* Start ramping if at least one TRX is usable */ + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx_is_usable(trx)) { + acc_ramp_trigger(&bts->acc_ramp); + break; + } + } + } + + /* + * ACC ramping takes effect either when the BTS reconnects RSL, + * or when RF administrative state changes to 'unlocked'. + */ + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_no_acc_ramping, + cfg_bts_no_acc_ramping_cmd, + "no access-control-class-ramping", + NO_STR + "Disable Access Control Class ramping\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + if (acc_ramp_is_enabled(&bts->acc_ramp)) { + acc_ramp_abort(&bts->acc_ramp); + acc_ramp_set_enabled(&bts->acc_ramp, false); + if (gsm_bts_set_system_infos(bts) != 0) { + vty_out(vty, "%% Filed to (re)generate System Information " + "messages, check the logs%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_acc_ramping_step_interval, + cfg_bts_acc_ramping_step_interval_cmd, + "access-control-class-ramping-step-interval (<" + OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MIN) "-" + OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MAX) ">|dynamic)", + "Configure Access Control Class ramping step interval\n" + "Set a fixed step interval (in seconds)\n" + "Use dynamic step interval based on BTS channel load (deprecated, don't use, ignored)\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + bool dynamic = (strcmp(argv[0], "dynamic") == 0); + int error; + + if (dynamic) { + vty_out(vty, "%% access-control-class-ramping-step-interval 'dynamic' value is deprecated, ignoring it%s", VTY_NEWLINE); + return CMD_SUCCESS; + } + + error = acc_ramp_set_step_interval(&bts->acc_ramp, atoi(argv[0])); + if (error != 0) { + if (error == -ERANGE) + vty_out(vty, "%% Unable to set ACC ramp step interval: value out of range%s", VTY_NEWLINE); + else + vty_out(vty, "%% Unable to set ACC ramp step interval: unknown error%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_acc_ramping_step_size, + cfg_bts_acc_ramping_step_size_cmd, + "access-control-class-ramping-step-size (<" + OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MIN) "-" + OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MAX) ">)", + "Configure Access Control Class ramping step size\n" + "Set the number of Access Control Classes to enable per ramping step\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + int error; + + error = acc_ramp_set_step_size(&bts->acc_ramp, atoi(argv[0])); + if (error != 0) { + if (error == -ERANGE) + vty_out(vty, "%% Unable to set ACC ramp step size: value out of range%s", VTY_NEWLINE); + else + vty_out(vty, "%% Unable to set ACC ramp step size: unknown error%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_acc_ramping_chan_load, + cfg_bts_acc_ramping_chan_load_cmd, + "access-control-class-ramping-chan-load <0-100> <0-100>", + "Configure Access Control Class ramping channel load thresholds\n" + "Lower Channel load threshold (%) below which subset size of allowed broadcast ACCs can be increased\n" + "Upper channel load threshold (%) above which subset size of allowed broadcast ACCs can be decreased\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + int rc; + + rc = acc_ramp_set_chan_load_thresholds(&bts->acc_ramp, atoi(argv[0]), atoi(argv[1])); + if (rc < 0) { + vty_out(vty, "%% Unable to set ACC channel load thresholds%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n" + +DEFUN_ATTR(cfg_bts_excl_rf_lock, + cfg_bts_excl_rf_lock_cmd, + "rf-lock-exclude", + EXCL_RFLOCK_STR, + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + bts->excl_from_rf_lock = 1; + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_no_excl_rf_lock, + cfg_bts_no_excl_rf_lock_cmd, + "no rf-lock-exclude", + NO_STR EXCL_RFLOCK_STR, + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + bts->excl_from_rf_lock = 0; + return CMD_SUCCESS; +} + +#define FORCE_COMB_SI_STR "Force the generation of a single SI (no ter/bis)\n" + +DEFUN_USRATTR(cfg_bts_force_comb_si, + cfg_bts_force_comb_si_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "force-combined-si", + FORCE_COMB_SI_STR) +{ + struct gsm_bts *bts = vty->index; + bts->force_combined_si = 1; + bts->force_combined_si_set = true; + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_no_force_comb_si, + cfg_bts_no_force_comb_si_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "no force-combined-si", + NO_STR FORCE_COMB_SI_STR) +{ + struct gsm_bts *bts = vty->index; + bts->force_combined_si = 0; + bts->force_combined_si_set = true; + return CMD_SUCCESS; +} + +static void _get_codec_from_arg(struct vty *vty, int argc, const char *argv[]) +{ + struct gsm_bts *bts = vty->index; + struct bts_codec_conf *codec = &bts->codec; + int i; + + codec->hr = 0; + codec->efr = 0; + codec->amr = 0; + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "hr")) + codec->hr = 1; + if (!strcmp(argv[i], "efr")) + codec->efr = 1; + if (!strcmp(argv[i], "amr")) + codec->amr = 1; + } +} + +#define CODEC_PAR_STR " (hr|efr|amr)" +#define CODEC_HELP_STR "Half Rate\n" \ + "Enhanced Full Rate\nAdaptive Multirate\n" + +DEFUN_USRATTR(cfg_bts_codec0, + cfg_bts_codec0_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "codec-support fr", + "Codec Support settings\nFullrate\n") +{ + _get_codec_from_arg(vty, 0, argv); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_codec1, + cfg_bts_codec1_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "codec-support fr" CODEC_PAR_STR, + "Codec Support settings\nFullrate\n" + CODEC_HELP_STR) +{ + _get_codec_from_arg(vty, 1, argv); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_codec2, + cfg_bts_codec2_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR, + "Codec Support settings\nFullrate\n" + CODEC_HELP_STR CODEC_HELP_STR) +{ + _get_codec_from_arg(vty, 2, argv); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_codec3, + cfg_bts_codec3_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR, + "Codec Support settings\nFullrate\n" + CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR) +{ + _get_codec_from_arg(vty, 3, argv); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_codec4, + cfg_bts_codec4_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR, + "Codec Support settings\nFullrate\n" + CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR) +{ + _get_codec_from_arg(vty, 4, argv); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_depends_on, cfg_bts_depends_on_cmd, + "depends-on-bts <0-255>", + "This BTS can only be started if another one is up\n" + BTS_NR_STR, CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + struct gsm_bts *other_bts; + int dep = atoi(argv[0]); + + + if (!is_ipa_abisip_bts(bts)) { + vty_out(vty, "%% This feature is only available for IP systems.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + other_bts = gsm_bts_num(bts->network, dep); + if (!other_bts || !is_ipa_abisip_bts(other_bts)) { + vty_out(vty, "%% This feature is only available for IP systems.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (dep >= bts->nr) { + vty_out(vty, "%% Need to depend on an already declared unit.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + bts_depend_mark(bts, dep); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd, + "no depends-on-bts <0-255>", + NO_STR "This BTS can only be started if another one is up\n" + BTS_NR_STR, CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + int dep = atoi(argv[0]); + + bts_depend_clear(bts, dep); + return CMD_SUCCESS; +} + +#define AMR_TEXT "Adaptive Multi Rate settings\n" +#define AMR_MODE_TEXT "Codec modes to use with AMR codec\n" +#define AMR_START_TEXT "Initial codec mode to use with AMR\n" \ + "Automatically\nFirst mode\nSecond mode\nThird mode\nFourth mode\n" +#define AMR_MS_BTS_TEXT "MS side\nBTS side\n" +#define AMR_TH_TEXT "Lower threshold(s) for switching between codec modes\n" AMR_MS_BTS_TEXT +#define AMR_HY_TEXT "Hysteresis value(s) to obtain the higher threshold(s) " \ + "for switching between codec modes\n" AMR_MS_BTS_TEXT + +static int get_amr_from_arg(struct vty *vty, int argc, const char *argv[], int full) +{ + struct gsm_bts *bts = vty->index; + struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) mr->gsm48_ie; + int i; + int mode; + int mode_prev = -1; + + /* Check if mode parameters are in order */ + for (i = 0; i < argc; i++) { + mode = atoi(argv[i]); + if (mode_prev > mode) { + vty_out(vty, "%% Modes must be listed in order%s", + VTY_NEWLINE); + return -1; + } + + if (mode_prev == mode) { + vty_out(vty, "%% Modes must be unique %s", VTY_NEWLINE); + return -2; + } + mode_prev = mode; + } + + /* Prepare the multirate configuration IE */ + mr->gsm48_ie[1] = 0; + for (i = 0; i < argc; i++) + mr->gsm48_ie[1] |= 1 << atoi(argv[i]); + mr_conf->icmi = 0; + + /* Store actual mode identifier values */ + for (i = 0; i < argc; i++) { + mr->ms_mode[i].mode = atoi(argv[i]); + mr->bts_mode[i].mode = atoi(argv[i]); + } + mr->num_modes = argc; + + /* Trim excess threshold and hysteresis values from previous config */ + for (i = argc - 1; i < 4; i++) { + mr->ms_mode[i].threshold = 0; + mr->bts_mode[i].threshold = 0; + mr->ms_mode[i].hysteresis = 0; + mr->bts_mode[i].hysteresis = 0; + } + return 0; +} + +static void get_amr_th_from_arg(struct vty *vty, int argc, const char *argv[], int full) +{ + struct gsm_bts *bts = vty->index; + struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; + struct amr_mode *modes; + int i; + + modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode; + for (i = 0; i < argc - 1; i++) + modes[i].threshold = atoi(argv[i + 1]); +} + +static void get_amr_hy_from_arg(struct vty *vty, int argc, const char *argv[], int full) +{ + struct gsm_bts *bts = vty->index; + struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; + struct amr_mode *modes; + int i; + + modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode; + for (i = 0; i < argc - 1; i++) + modes[i].hysteresis = atoi(argv[i + 1]); +} + +static void get_amr_start_from_arg(struct vty *vty, const char *argv[], int full) +{ + struct gsm_bts *bts = vty->index; + struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) mr->gsm48_ie; + int num = 0, i; + + for (i = 0; i < ((full) ? 8 : 6); i++) { + if ((mr->gsm48_ie[1] & (1 << i))) { + num++; + } + } + + if (argv[0][0] == 'a' || num == 0) { + mr_conf->icmi = 0; + mr_conf->smod = 0; + } else { + mr_conf->icmi = 1; + if (num < atoi(argv[0])) + mr_conf->smod = num - 1; + else + mr_conf->smod = atoi(argv[0]) - 1; + } +} + +/* Give the current amr configuration a final consistency check by feeding the + * the configuration into the gsm48 multirate IE generator function */ +static int check_amr_config(struct vty *vty) +{ + int rc = 0; + struct amr_multirate_conf *mr; + const struct gsm48_multi_rate_conf *mr_conf; + struct gsm_bts *bts = vty->index; + int vty_rc = CMD_SUCCESS; + + mr = &bts->mr_full; + mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie; + rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes); + if (rc != 0) { + vty_out(vty, + "%% Invalid AMR multirate configuration (tch-f, ms) - check parameters%s", + VTY_NEWLINE); + vty_rc = CMD_WARNING; + } + + rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes); + if (rc != 0) { + vty_out(vty, + "%% Invalid AMR multirate configuration (tch-f, bts) - check parameters%s", + VTY_NEWLINE); + vty_rc = CMD_WARNING; + } + + mr = &bts->mr_half; + mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie; + rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes); + if (rc != 0) { + vty_out(vty, + "%% Invalid AMR multirate configuration (tch-h, ms) - check parameters%s", + VTY_NEWLINE); + vty_rc = CMD_WARNING; + } + + rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes); + if (rc != 0) { + vty_out(vty, + "%% Invalid AMR multirate configuration (tch-h, bts) - check parameters%s", + VTY_NEWLINE); + vty_rc = CMD_WARNING; + } + + return vty_rc; +} + +#define AMR_TCHF_PAR_STR " (0|1|2|3|4|5|6|7)" +#define AMR_TCHF_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" \ + "10,2k\n12,2k\n" + +#define AMR_TCHH_PAR_STR " (0|1|2|3|4|5)" +#define AMR_TCHH_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" + +#define AMR_TH_HELP_STR(a, b) \ + "Threshold between codec mode " a " and " b " (in 0.5 dB steps)\n" +#define AMR_HY_HELP_STR(a, b) \ + "Hysteresis between codec mode " a " and " b " (in 0.5 dB steps)\n" + +DEFUN_USRATTR(cfg_bts_amr_fr_modes1, + cfg_bts_amr_fr_modes1_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f modes" AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR) +{ + if (get_amr_from_arg(vty, 1, argv, 1)) + return CMD_WARNING; + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_modes2, + cfg_bts_amr_fr_modes2_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) +{ + if (get_amr_from_arg(vty, 2, argv, 1)) + return CMD_WARNING; + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_modes3, + cfg_bts_amr_fr_modes3_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) +{ + if (get_amr_from_arg(vty, 3, argv, 1)) + return CMD_WARNING; + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_modes4, + cfg_bts_amr_fr_modes4_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) +{ + if (get_amr_from_arg(vty, 4, argv, 1)) + return CMD_WARNING; + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_start_mode, + cfg_bts_amr_fr_start_mode_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f start-mode (auto|1|2|3|4)", + AMR_TEXT "Full Rate\n" AMR_START_TEXT) +{ + get_amr_start_from_arg(vty, argv, 1); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_thres1, + cfg_bts_amr_fr_thres1_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f threshold (ms|bts) <0-63>", + AMR_TEXT "Full Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR("1", "2")) +{ + get_amr_th_from_arg(vty, 2, argv, 1); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_thres2, + cfg_bts_amr_fr_thres2_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f threshold (ms|bts) <0-63> <0-63>", + AMR_TEXT "Full Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR("1", "2") + AMR_TH_HELP_STR("2", "3")) +{ + get_amr_th_from_arg(vty, 3, argv, 1); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_thres3, + cfg_bts_amr_fr_thres3_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f threshold (ms|bts) <0-63> <0-63> <0-63>", + AMR_TEXT "Full Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR("1", "2") + AMR_TH_HELP_STR("2", "3") + AMR_TH_HELP_STR("3", "4")) +{ + get_amr_th_from_arg(vty, 4, argv, 1); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_hyst1, + cfg_bts_amr_fr_hyst1_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f hysteresis (ms|bts) <0-15>", + AMR_TEXT "Full Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR("1", "2")) +{ + get_amr_hy_from_arg(vty, 2, argv, 1); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_fr_hyst2, + cfg_bts_amr_fr_hyst2_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f hysteresis (ms|bts) <0-15> <0-15>", + AMR_TEXT "Full Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR("1", "2") + AMR_HY_HELP_STR("2", "3")) +{ + get_amr_hy_from_arg(vty, 3, argv, 1); + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_amr_fr_hyst3, + cfg_bts_amr_fr_hyst3_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-f hysteresis (ms|bts) <0-15> <0-15> <0-15>", + AMR_TEXT "Full Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR("1", "2") + AMR_HY_HELP_STR("2", "3") + AMR_HY_HELP_STR("3", "4")) +{ + get_amr_hy_from_arg(vty, 4, argv, 1); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_modes1, + cfg_bts_amr_hr_modes1_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h modes" AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR) +{ + if (get_amr_from_arg(vty, 1, argv, 0)) + return CMD_WARNING; + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_modes2, + cfg_bts_amr_hr_modes2_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) +{ + if (get_amr_from_arg(vty, 2, argv, 0)) + return CMD_WARNING; + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_modes3, + cfg_bts_amr_hr_modes3_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) +{ + if (get_amr_from_arg(vty, 3, argv, 0)) + return CMD_WARNING; + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_modes4, + cfg_bts_amr_hr_modes4_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) +{ + if (get_amr_from_arg(vty, 4, argv, 0)) + return CMD_WARNING; + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_start_mode, + cfg_bts_amr_hr_start_mode_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h start-mode (auto|1|2|3|4)", + AMR_TEXT "Half Rate\n" AMR_START_TEXT) +{ + get_amr_start_from_arg(vty, argv, 0); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_thres1, + cfg_bts_amr_hr_thres1_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h threshold (ms|bts) <0-63>", + AMR_TEXT "Half Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR("1", "2")) +{ + get_amr_th_from_arg(vty, 2, argv, 0); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_thres2, + cfg_bts_amr_hr_thres2_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h threshold (ms|bts) <0-63> <0-63>", + AMR_TEXT "Half Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR("1", "2") + AMR_TH_HELP_STR("2", "3")) +{ + get_amr_th_from_arg(vty, 3, argv, 0); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_thres3, + cfg_bts_amr_hr_thres3_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h threshold (ms|bts) <0-63> <0-63> <0-63>", + AMR_TEXT "Half Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR("1", "2") + AMR_TH_HELP_STR("2", "3") + AMR_TH_HELP_STR("3", "4")) +{ + get_amr_th_from_arg(vty, 4, argv, 0); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_hyst1, + cfg_bts_amr_hr_hyst1_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h hysteresis (ms|bts) <0-15>", + AMR_TEXT "Half Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR("1", "2")) +{ + get_amr_hy_from_arg(vty, 2, argv, 0); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_hyst2, + cfg_bts_amr_hr_hyst2_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h hysteresis (ms|bts) <0-15> <0-15>", + AMR_TEXT "Half Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR("1", "2") + AMR_HY_HELP_STR("2", "3")) +{ + get_amr_hy_from_arg(vty, 3, argv, 0); + return check_amr_config(vty); +} + +DEFUN_USRATTR(cfg_bts_amr_hr_hyst3, + cfg_bts_amr_hr_hyst3_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "amr tch-h hysteresis (ms|bts) <0-15> <0-15> <0-15>", + AMR_TEXT "Half Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR("1", "2") + AMR_HY_HELP_STR("2", "3") + AMR_HY_HELP_STR("3", "4")) +{ + get_amr_hy_from_arg(vty, 4, argv, 0); + return check_amr_config(vty); +} + +#define OSMUX_STR "RTP multiplexing\n" +DEFUN_USRATTR(cfg_bts_osmux, + cfg_bts_osmux_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "osmux (on|off|only)", + OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only use OSMUX\n") +{ + struct gsm_bts *bts = vty->index; + enum osmux_usage use; + + if (strcmp(argv[0], "off") == 0) + use = OSMUX_USAGE_OFF; + else if (strcmp(argv[0], "on") == 0) + use = OSMUX_USAGE_ON; + else if (strcmp(argv[0], "only") == 0) + use = OSMUX_USAGE_ONLY; + else + goto err; + + if (!is_osmobts(bts)) + goto err; + + if (bts->features_known && use != OSMUX_USAGE_OFF && + !osmo_bts_has_feature(&bts->features, BTS_FEAT_OSMUX)) + goto err; + + bts->use_osmux = use; + return CMD_SUCCESS; + +err: + LOGP(DNM, LOGL_ERROR, + "(bts=%u) Unable to set 'osmux %s', BTS does not support Osmux\n", + bts->nr, argv[0]); + return CMD_WARNING; +} + +DEFUN_USRATTR(cfg_bts_mgw_pool_target, + cfg_bts_mgw_pool_target_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "mgw pool-target <0-255> [strict]", + "MGW configuration for this specific BTS\n" + "Pin BTS to use a single MGW in the pool\n" + "Reference Number of the MGW (in the config) to pin to\n" + "Strictly prohibit use of other MGWs if the pinned one is not available\n") +{ + struct gsm_bts *bts = vty->index; + int mgw_nr = atoi(argv[0]); + bool strict = argc > 1; + bts->mgw_pool_target = mgw_nr; + bts->mgw_pool_target_strict = strict; + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_no_mgw_pool_target, + cfg_bts_no_mgw_pool_target_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "no mgw pool-target", + NO_STR "MGW configuration for this specific BTS\n" + "Avoid pinning the BTS to any specific MGW (default)\n") +{ + struct gsm_bts *bts = vty->index; + bts->mgw_pool_target = -1; + bts->mgw_pool_target_strict = false; + return CMD_SUCCESS; +} + +#define TNUM_STR "T-number, optionally preceded by 't' or 'T'\n" +DEFUN_ATTR(cfg_bts_t3113_dynamic, cfg_bts_t3113_dynamic_cmd, + "timer-dynamic TNNNN", + "Calculate T3113 dynamically based on channel config and load (default)\n" + TNUM_STR, + CMD_ATTR_IMMEDIATE) +{ + struct osmo_tdef *d; + struct gsm_bts *bts = vty->index; + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + d = osmo_tdef_vty_parse_T_arg(vty, gsmnet->T_defs, argv[0]); + if (!d) + return CMD_WARNING; + + switch (d->T) { + case 3113: + bts->T3113_dynamic = true; + break; + default: + vty_out(vty, "%% T%d cannot be set to dynamic%s", d->T, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_no_t3113_dynamic, cfg_bts_no_t3113_dynamic_cmd, + "no timer-dynamic TNNNN", + NO_STR + "Set given timer to non-dynamic and use the default or user provided fixed value\n" + TNUM_STR, + CMD_ATTR_IMMEDIATE) +{ + struct osmo_tdef *d; + struct gsm_bts *bts = vty->index; + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + d = osmo_tdef_vty_parse_T_arg(vty, gsmnet->T_defs, argv[0]); + if (!d) + return CMD_WARNING; + + switch (d->T) { + case 3113: + bts->T3113_dynamic = false; + break; + default: + vty_out(vty, "%% T%d already is non-dynamic%s", d->T, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_interf_meas_avg_period, + cfg_bts_interf_meas_avg_period_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "interference-meas avg-period <1-31>", + "Interference measurement parameters\n" + "Averaging period (Intave)\n" + "Number of SACCH multiframes\n") +{ + struct gsm_bts *bts = vty->index; + + bts->interf_meas_params_cfg.avg_period = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_interf_meas_level_bounds, + cfg_bts_interf_meas_level_bounds_cmd, + X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK), + "interference-meas level-bounds " + "<-120-0> <-120-0> <-120-0> <-120-0> <-120-0> <-120-0>", + "Interference measurement parameters\n" + "Interference level Boundaries. 3GPP do not specify whether these should be in ascending or descending" + " order (3GPP TS 48.058 9.3.21 / 3GPP TS 52.021 9.4.25). OsmoBSC supports either ordering, but possibly" + " some BTS models only return meaningful interference levels with one specific ordering.\n" + "Interference boundary 0 (dBm)\n" + "Interference boundary X1 (dBm)\n" + "Interference boundary X2 (dBm)\n" + "Interference boundary X3 (dBm)\n" + "Interference boundary X4 (dBm)\n" + "Interference boundary X5 (dBm)\n") +{ + struct gsm_bts *bts = vty->index; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(bts->interf_meas_params_cfg.bounds_dbm); i++) { + bts->interf_meas_params_cfg.bounds_dbm[i] = abs(atoi(argv[i])); + } + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_srvcc_fast_return, cfg_bts_srvcc_fast_return_cmd, + "srvcc fast-return (allow|forbid)", + "SRVCC Configuration\n" + "Allow or forbid Fast Return to 4G on Channel Release in this BTS\n" + "Allow\n" + "Forbid\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + bts->srvcc_fast_return_allowed = strcmp(argv[0], "allow") == 0; + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_immediate_assignment, cfg_bts_immediate_assignment_cmd, + "immediate-assignment (post-chan-ack|pre-chan-ack|pre-ts-ack)", + "Configure time of Immediate Assignment after ChanRqd RACH (Abis optimization)\n" + "Send the Immediate Assignment after the Channel Activation ACK (normal sequence)\n" + "Send the Immediate Assignment directly after Channel Activation (early), without waiting for the ACK;" + " This may help with double allocations on high latency Abis links\n" + "EXPERIMENTAL: If a dynamic timeslot switch is necessary, send the Immediate Assignment even before the" + " timeslot is switched, i.e. even before the Channel Activation is sent (very early)\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + enum imm_ass_time imm_ass_time; + + if (!strcmp(argv[0], "pre-ts-ack")) + imm_ass_time = IMM_ASS_TIME_PRE_TS_ACK; + else if (!strcmp(argv[0], "pre-chan-ack")) + imm_ass_time = IMM_ASS_TIME_PRE_CHAN_ACK; + else + imm_ass_time = IMM_ASS_TIME_POST_CHAN_ACK; + + if (imm_ass_time != IMM_ASS_TIME_POST_CHAN_ACK) { + struct gsm_bts_trx *trx; + + /* Early-IA does not work with frequency hopping, because the IMM ASS does not convey an ARFCN when + * frequency hopping is in use. Make sure the user knows that. */ + llist_for_each_entry(trx, &bts->trx_list, list) { + int ts_nr; + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + if (ts->hopping.enabled) { + vty_out(vty, "%% ERROR: 'hopping enabled 1' works only with" + " 'immediate-assignment post-chan-ack', see timeslot %s%s", + ts->fi->id, VTY_NEWLINE); + return CMD_WARNING; + } + } + } + } + + bts->imm_ass_time = imm_ass_time; + return CMD_SUCCESS; +} + +#define BS_POWER_CONTROL_CMD \ + "bs-power-control" +#define MS_POWER_CONTROL_CMD \ + "ms-power-control" +#define POWER_CONTROL_CMD \ + "(" BS_POWER_CONTROL_CMD "|" MS_POWER_CONTROL_CMD ")" +#define POWER_CONTROL_DESC \ + "BS (Downlink) power control parameters\n" \ + "MS (Uplink) power control parameters\n" + +#define BTS_POWER_CTRL_PARAMS(bts) \ + (strcmp(argv[0], BS_POWER_CONTROL_CMD) == 0) ? \ + &bts->bs_power_ctrl : &bts->ms_power_ctrl + +DEFUN_USRATTR(cfg_bts_no_power_ctrl, + cfg_bts_no_power_ctrl_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "no " POWER_CONTROL_CMD, + NO_STR POWER_CONTROL_DESC) +{ + struct gsm_power_ctrl_params *params; + struct gsm_bts *bts = vty->index; + + params = BTS_POWER_CTRL_PARAMS(bts); + params->mode = GSM_PWR_CTRL_MODE_NONE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_power_ctrl, + cfg_bts_power_ctrl_cmd, + POWER_CONTROL_CMD, + POWER_CONTROL_DESC) +{ + struct gsm_power_ctrl_params *params; + struct gsm_bts *bts = vty->index; + + params = BTS_POWER_CTRL_PARAMS(bts); + vty->node = POWER_CTRL_NODE; + vty->index = params; + + /* Change the prefix to reflect MS/BS difference */ + if (params->dir == GSM_PWR_CTRL_DIR_UL) + power_ctrl_node.prompt = "%s(config-ms-power-ctrl)# "; + else + power_ctrl_node.prompt = "%s(config-bs-power-ctrl)# "; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_mode, + cfg_power_ctrl_mode_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "mode (static|dyn-bts|dyn-bsc) [reset]", + "Power control mode\n" + "Instruct the MS/BTS to use a static power level\n" + "Power control to be performed dynamically by the BTS itself\n" + "Power control to be performed dynamically at this BSC\n" + "Reset to default parameters for the given mode\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + + /* Do we need to reset? */ + if (argc > 1) { + vty_out(vty, "%% Reset to default parameters%s", VTY_NEWLINE); + power_ctrl_params_def_reset(params, params->dir); + } + + if (strcmp(argv[0], "static") == 0) + params->mode = GSM_PWR_CTRL_MODE_STATIC; + else if (strcmp(argv[0], "dyn-bts") == 0) + params->mode = GSM_PWR_CTRL_MODE_DYN_BTS; + else if (strcmp(argv[0], "dyn-bsc") == 0) { + if (params->dir == GSM_PWR_CTRL_DIR_DL) { + vty_out(vty, "%% mode dyn-bsc not supported for Downlink.%s", VTY_NEWLINE); + return CMD_WARNING; + } + params->mode = GSM_PWR_CTRL_MODE_DYN_BSC; + } + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_bs_power, + cfg_power_ctrl_bs_power_cmd, + X(BSC_VTY_ATTR_NEW_LCHAN), + "bs-power (static|dyn-max) <0-30>", + "BS Power IE value to be sent to the BTS\n" + "Fixed BS Power reduction value (for static mode)\n" + "Maximum BS Power reduction value (for dynamic mode)\n" + "BS Power reduction value (in dB, even numbers only)\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + bool dynamic = !strcmp(argv[0], "dyn-max"); + int value = atoi(argv[1]); + + if (params->dir != GSM_PWR_CTRL_DIR_DL) { + vty_out(vty, "%% This command is only valid for " + "'bs-power-control' node%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (value % 2 != 0) { + vty_out(vty, "%% Incorrect BS Power reduction value, " + "an even number is expected%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (dynamic) /* maximum value */ + params->bs_power_max_db = value; + else /* static (fixed) value */ + params->bs_power_val_db = value; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_ctrl_interval, + cfg_power_ctrl_ctrl_interval_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "ctrl-interval <0-31>", + "Set power control interval (for dynamic mode)\n" + "P_CON_INTERVAL, in units of 2 SACCH periods (0.96 seconds)(default=1)\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + + params->ctrl_interval = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_step_size, + cfg_power_ctrl_step_size_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "step-size inc <2-6> red <2-4>", + "Set power change step size (for dynamic mode)\n" + "Increase step size (default is 4 dB)\n" + "Step size (2, 4, or 6 dB)\n" + "Reduce step size (default is 2 dB)\n" + "Step size (2 or 4 dB)\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + int inc_step_size_db = atoi(argv[0]); + int red_step_size_db = atoi(argv[1]); + + if (inc_step_size_db % 2 || red_step_size_db % 2) { + vty_out(vty, "%% Power change step size must be " + "an even number%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* Recommendation: POW_RED_STEP_SIZE <= POW_INCR_STEP_SIZE */ + if (red_step_size_db > inc_step_size_db) { + vty_out(vty, "%% Increase step size (%d) should be greater " + "than reduce step size (%d), consider changing it%s", + inc_step_size_db, red_step_size_db, VTY_NEWLINE); + } + + /* Recommendation: POW_INCR_STEP_SIZE <= (U_RXLEV_XX_P - L_RXLEV_XX_P) */ + const struct gsm_power_ctrl_meas_params *mp = ¶ms->rxlev_meas; + if (inc_step_size_db > (mp->upper_thresh - mp->lower_thresh)) { + vty_out(vty, "%% Increase step size (%d) should be less or equal " + "than/to the RxLev threshold window (%d, upper - lower), " + "consider changing it%s", inc_step_size_db, + mp->upper_thresh - mp->lower_thresh, VTY_NEWLINE); + } + + params->inc_step_size_db = inc_step_size_db; + params->red_step_size_db = red_step_size_db; + + return CMD_SUCCESS; +} + +#define POWER_CONTROL_MEAS_RXLEV_DESC \ + "RxLev value (signal strength, 0 is worst, 63 is best)\n" +#define POWER_CONTROL_MEAS_RXQUAL_DESC \ + "RxQual value (signal quality, 0 is best, 7 is worst)\n" +#define POWER_CONTROL_MEAS_CI_DESC \ + "C/I value (Carrier-to-Interference (dB), 0 is worst, 30 is best)\n" + +DEFUN_USRATTR(cfg_power_ctrl_rxlev_thresh, + cfg_power_ctrl_rxlev_thresh_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "rxlev-thresh lower <0-63> upper <0-63>", + "Set target RxLev thresholds (for dynamic mode)\n" + "Lower RxLev value (default is 32, i.e. -78 dBm)\n" + "Lower " POWER_CONTROL_MEAS_RXLEV_DESC + "Upper RxLev value (default is 38, i.e. -72 dBm)\n" + "Upper " POWER_CONTROL_MEAS_RXLEV_DESC) +{ + struct gsm_power_ctrl_params *params = vty->index; + int lower = atoi(argv[0]); + int upper = atoi(argv[1]); + + if (lower > upper) { + vty_out(vty, "%% Lower 'rxlev-thresh' (%d) must be less than upper (%d)%s", + lower, upper, VTY_NEWLINE); + return CMD_WARNING; + } + + params->rxlev_meas.lower_thresh = lower; + params->rxlev_meas.upper_thresh = upper; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_rxqual_thresh, + cfg_power_ctrl_rxqual_thresh_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "rxqual-thresh lower <0-7> upper <0-7>", + "Set target RxQual thresholds (for dynamic mode)\n" + "Lower RxQual value (default is 3, i.e. 0.8% <= BER < 1.6%)\n" + "Lower " POWER_CONTROL_MEAS_RXQUAL_DESC + "Upper RxQual value (default is 0, i.e. BER < 0.2%)\n" + "Upper " POWER_CONTROL_MEAS_RXQUAL_DESC) +{ + struct gsm_power_ctrl_params *params = vty->index; + int lower = atoi(argv[0]); + int upper = atoi(argv[1]); + + /* RxQual: 0 is best, 7 is worst, so upper must be less */ + if (upper > lower) { + vty_out(vty, "%% Upper 'rxqual-rxqual' (%d) must be less than lower (%d)%s", + upper, lower, VTY_NEWLINE); + return CMD_WARNING; + } + + params->rxqual_meas.lower_thresh = lower; + params->rxqual_meas.upper_thresh = upper; + + return CMD_SUCCESS; +} + +#define VTY_CMD_CI_TYPE "(fr-efr|hr|amr-fr|amr-hr|sdcch|gprs)" +#define VTY_CMD_CI_OR_ALL_TYPE "(fr-efr|hr|amr-fr|amr-hr|sdcch|gprs|all)" +#define VTY_DESC_CI_TYPE \ + "Channel Type FR/EFR\n" \ + "Channel Type HR\n" \ + "Channel Type AMR FR\n" \ + "Channel Type AMR HR\n" \ + "Channel Type SDCCH\n" \ + "Channel Type (E)GPRS\n" +#define VTY_DESC_CI_OR_ALL_TYPE VTY_DESC_CI_TYPE "All Channel Types\n" + +static struct gsm_power_ctrl_meas_params *ci_thresh_by_conn_type(struct gsm_power_ctrl_params *params, const char *type) +{ + if (!strcmp(type, "fr-efr")) + return ¶ms->ci_fr_meas; + if (!strcmp(type, "hr")) + return ¶ms->ci_hr_meas; + if (!strcmp(type, "amr-fr")) + return ¶ms->ci_amr_fr_meas; + if (!strcmp(type, "amr-hr")) + return ¶ms->ci_amr_hr_meas; + if (!strcmp(type, "sdcch")) + return ¶ms->ci_sdcch_meas; + if (!strcmp(type, "gprs")) + return ¶ms->ci_gprs_meas; + OSMO_ASSERT(false); + return NULL; +} + +DEFUN_USRATTR(cfg_power_ctrl_ci_thresh, + cfg_power_ctrl_ci_thresh_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "ci-thresh " VTY_CMD_CI_TYPE " lower <0-30> upper <0-30>", + "Set target C/I thresholds (for dynamic mode), only available in ms-power-control\n" + VTY_DESC_CI_TYPE + "Lower C/I value\n" + "Lower " POWER_CONTROL_MEAS_CI_DESC + "Upper C/I value\n" + "Upper " POWER_CONTROL_MEAS_CI_DESC) +{ + struct gsm_power_ctrl_params *params = vty->index; + const char *type = argv[0]; + int lower = atoi(argv[1]); + int upper = atoi(argv[2]); + struct gsm_power_ctrl_meas_params *meas_params; + + if (params->mode == GSM_PWR_CTRL_MODE_DYN_BSC) { + vty_out(vty, "%% C/I based power loop not possible in dyn-bsc mode!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (params->dir != GSM_PWR_CTRL_DIR_UL) { + vty_out(vty, "%% C/I based power loop only possible in Uplink!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (lower > upper) { + vty_out(vty, "%% Lower 'rxqual-rxqual' (%d) must be less than upper (%d)%s", + upper, lower, VTY_NEWLINE); + return CMD_WARNING; + } + + meas_params = ci_thresh_by_conn_type(params, type); + + meas_params->lower_thresh = lower; + meas_params->upper_thresh = upper; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_ci_thresh_disable, + cfg_power_ctrl_ci_thresh_disable_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "ci-thresh " VTY_CMD_CI_OR_ALL_TYPE " (enable|disable)", + "Set target C/I thresholds (for dynamic mode), only available in ms-power-control\n" + VTY_DESC_CI_OR_ALL_TYPE + "Enable C/I comparison in control loop\n" + "Disable C/I comparison in control loop\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + + bool enable = strcmp(argv[1], "enable") == 0; + + if (strcmp(argv[0], "all") == 0) { + params->ci_fr_meas.enabled = enable; + params->ci_hr_meas.enabled = enable; + params->ci_amr_fr_meas.enabled = enable; + params->ci_amr_hr_meas.enabled = enable; + params->ci_sdcch_meas.enabled = enable; + params->ci_gprs_meas.enabled = enable; + } else { + struct gsm_power_ctrl_meas_params *meas_params = ci_thresh_by_conn_type(params, argv[0]); + meas_params->enabled = enable; + } + + return CMD_SUCCESS; +} + +#define POWER_CONTROL_MEAS_THRESH_COMP_CMD(meas) \ + meas " lower <0-31> <0-31> upper <0-31> <0-31>" +#define POWER_CONTROL_MEAS_THRESH_COMP_DESC(meas, opt_param, lp, ln, up, un) \ + "Set " meas " threshold comparators (for dynamic mode)\n" \ + opt_param \ + "Lower " meas " threshold comparators (see 3GPP TS 45.008, A.3.2.1)\n" lp ln \ + "Upper " meas " threshold comparators (see 3GPP TS 45.008, A.3.2.1)\n" up un + +DEFUN_USRATTR(cfg_power_ctrl_rxlev_thresh_comp, + cfg_power_ctrl_rxlev_thresh_comp_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + POWER_CONTROL_MEAS_THRESH_COMP_CMD("rxlev-thresh-comp"), + POWER_CONTROL_MEAS_THRESH_COMP_DESC("RxLev", /*empty*/, + "P1 (default 10)\n", "N1 (default 12)\n", + "P2 (default 10)\n", "N2 (default 12)\n")) +{ + struct gsm_power_ctrl_params *params = vty->index; + int lower_cmp_p = atoi(argv[0]); + int lower_cmp_n = atoi(argv[1]); + int upper_cmp_p = atoi(argv[2]); + int upper_cmp_n = atoi(argv[3]); + + if (lower_cmp_p > lower_cmp_n) { + vty_out(vty, "%% Lower RxLev P1 %d must be less than N1 %d%s", + lower_cmp_p, lower_cmp_n, VTY_NEWLINE); + return CMD_WARNING; + } + + if (upper_cmp_p > upper_cmp_n) { + vty_out(vty, "%% Upper RxLev P2 %d must be less than N2 %d%s", + upper_cmp_p, upper_cmp_n, VTY_NEWLINE); + return CMD_WARNING; + } + + params->rxlev_meas.lower_cmp_p = lower_cmp_p; + params->rxlev_meas.lower_cmp_n = lower_cmp_n; + params->rxlev_meas.upper_cmp_p = upper_cmp_p; + params->rxlev_meas.upper_cmp_n = upper_cmp_n; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_rxqual_thresh_comp, + cfg_power_ctrl_rxqual_thresh_comp_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + POWER_CONTROL_MEAS_THRESH_COMP_CMD("rxqual-thresh-comp"), + POWER_CONTROL_MEAS_THRESH_COMP_DESC("RxQual", /*empty*/, + "P3 (default 5)\n", "N3 (default 7)\n", + "P4 (default 15)\n", "N4 (default 18)\n")) +{ + struct gsm_power_ctrl_params *params = vty->index; + int lower_cmp_p = atoi(argv[0]); + int lower_cmp_n = atoi(argv[1]); + int upper_cmp_p = atoi(argv[2]); + int upper_cmp_n = atoi(argv[3]); + + if (lower_cmp_p > lower_cmp_n) { + vty_out(vty, "%% Lower RxQual P3 %d must be less than N3 %d%s", + lower_cmp_p, lower_cmp_n, VTY_NEWLINE); + return CMD_WARNING; + } + + if (upper_cmp_p > upper_cmp_n) { + vty_out(vty, "%% Upper RxQual P4 %d must be less than N4 %d%s", + upper_cmp_p, upper_cmp_n, VTY_NEWLINE); + return CMD_WARNING; + } + + params->rxqual_meas.lower_cmp_p = lower_cmp_p; + params->rxqual_meas.lower_cmp_n = lower_cmp_n; + params->rxqual_meas.upper_cmp_p = upper_cmp_p; + params->rxqual_meas.upper_cmp_n = upper_cmp_n; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_ci_thresh_comp, + cfg_power_ctrl_ci_thresh_comp_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + POWER_CONTROL_MEAS_THRESH_COMP_CMD("ci-thresh-comp " VTY_CMD_CI_TYPE), + POWER_CONTROL_MEAS_THRESH_COMP_DESC("Carrier-to_interference (C/I)", + VTY_DESC_CI_TYPE, + "Lower P (default 5)\n", "Lower N (default 7)\n", + "Upper P (default 15)\n", "Upper N (default 18)\n")) +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *meas_params; + int lower_cmp_p = atoi(argv[1]); + int lower_cmp_n = atoi(argv[2]); + int upper_cmp_p = atoi(argv[3]); + int upper_cmp_n = atoi(argv[4]); + + if (lower_cmp_p > lower_cmp_n) { + vty_out(vty, "%% Lower C/I P %d must be less than N %d%s", + lower_cmp_p, lower_cmp_n, VTY_NEWLINE); + return CMD_WARNING; + } + + if (upper_cmp_p > upper_cmp_n) { + vty_out(vty, "%% Upper C/I P %d must be less than N %d%s", + upper_cmp_p, upper_cmp_n, VTY_NEWLINE); + return CMD_WARNING; + } + + meas_params = ci_thresh_by_conn_type(params, argv[0]); + + meas_params->lower_cmp_p = lower_cmp_p; + meas_params->lower_cmp_n = lower_cmp_n; + meas_params->upper_cmp_p = upper_cmp_p; + meas_params->upper_cmp_n = upper_cmp_n; + + return CMD_SUCCESS; +} + +#define POWER_CONTROL_MEAS_AVG_CMD \ + "(rxlev-avg|rxqual-avg)" +#define POWER_CONTROL_MEAS_AVG_DESC \ + "RxLev (signal strength) measurement averaging (for dynamic mode)\n" \ + "RxQual (signal quality) measurement averaging (for dynamic mode)\n" + +#define POWER_CONTROL_MEAS_AVG_PARAMS(params) \ + (strncmp(argv[0], "rxlev", 5) == 0) ? \ + ¶ms->rxlev_meas : ¶ms->rxqual_meas + +DEFUN_USRATTR(cfg_power_ctrl_no_avg, + cfg_power_ctrl_no_avg_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "no " POWER_CONTROL_MEAS_AVG_CMD, + NO_STR POWER_CONTROL_MEAS_AVG_DESC) +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *avg_params; + + avg_params = POWER_CONTROL_MEAS_AVG_PARAMS(params); + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_avg_params, + cfg_power_ctrl_avg_params_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + POWER_CONTROL_MEAS_AVG_CMD " params hreqave <1-31> hreqt <1-31>", + POWER_CONTROL_MEAS_AVG_DESC "Configure general averaging parameters\n" + "Hreqave: the period over which an average is produced\n" + "Hreqave value (so that Hreqave * Hreqt < 32)\n" + "Hreqt: the number of averaged results that are maintained\n" + "Hreqt value (so that Hreqave * Hreqt < 32)\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *avg_params; + int h_reqave = atoi(argv[1]); + int h_reqt = atoi(argv[2]); + + if (h_reqave * h_reqt > 31) { + vty_out(vty, "%% Hreqave (%d) * Hreqt (%d) = %d must be < 32%s", + h_reqave, h_reqt, h_reqave * h_reqt, VTY_NEWLINE); + return CMD_WARNING; + } + + avg_params = POWER_CONTROL_MEAS_AVG_PARAMS(params); + avg_params->h_reqave = h_reqave; + avg_params->h_reqt = h_reqt; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_avg_algo, + cfg_power_ctrl_avg_algo_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + /* FIXME: add algorithm specific parameters */ + POWER_CONTROL_MEAS_AVG_CMD " algo (unweighted|weighted|mod-median)", + POWER_CONTROL_MEAS_AVG_DESC "Select the averaging algorithm\n" + "Un-weighted average\n" "Weighted average\n" + "Modified median calculation\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *avg_params; + + avg_params = POWER_CONTROL_MEAS_AVG_PARAMS(params); + if (strcmp(argv[1], "unweighted") == 0) + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED; + else if (strcmp(argv[1], "weighted") == 0) + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED; + else if (strcmp(argv[1], "mod-median") == 0) + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_avg_osmo_ewma, + cfg_power_ctrl_avg_osmo_ewma_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + POWER_CONTROL_MEAS_AVG_CMD " algo osmo-ewma beta <1-99>", + POWER_CONTROL_MEAS_AVG_DESC "Select the averaging algorithm\n" + "Exponentially Weighted Moving Average (EWMA)\n" + "Smoothing factor (in %): beta = (100 - alpha)\n" + "1% - lowest smoothing, 99% - highest smoothing\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *avg_params; + const struct gsm_bts *bts; + + if (params->dir == GSM_PWR_CTRL_DIR_UL) + bts = container_of(params, struct gsm_bts, ms_power_ctrl); + else + bts = container_of(params, struct gsm_bts, bs_power_ctrl); + + if (bts->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% EWMA is an OsmoBTS specific algorithm, " + "it's not usable for other BTS types%s", VTY_NEWLINE); + return CMD_WARNING; + } + + avg_params = POWER_CONTROL_MEAS_AVG_PARAMS(params); + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA; + avg_params->ewma.alpha = 100 - atoi(argv[1]); + + return CMD_SUCCESS; +} + +/* C/I related power control measurements */ +#define POWER_CONTROL_CI_MEAS_AVG_DESC \ + "C/I (Carrier-to-Interference) measurement averaging (for dynamic mode)\n" + +DEFUN_USRATTR(cfg_power_ctrl_no_ci_avg, + cfg_power_ctrl_no_ci_avg_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "no ci-avg " VTY_CMD_CI_TYPE, + NO_STR POWER_CONTROL_CI_MEAS_AVG_DESC VTY_DESC_CI_TYPE) +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *avg_params; + + avg_params = ci_thresh_by_conn_type(params, argv[0]); + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_ci_avg_params, + cfg_power_ctrl_ci_avg_params_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "ci-avg " VTY_CMD_CI_TYPE " params hreqave <1-31> hreqt <1-31>", + POWER_CONTROL_CI_MEAS_AVG_DESC VTY_DESC_CI_TYPE + "Configure general averaging parameters\n" + "Hreqave: the period over which an average is produced\n" + "Hreqave value (so that Hreqave * Hreqt < 32)\n" + "Hreqt: the number of averaged results that are maintained\n" + "Hreqt value (so that Hreqave * Hreqt < 32)\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *avg_params; + int h_reqave = atoi(argv[1]); + int h_reqt = atoi(argv[2]); + + if (h_reqave * h_reqt > 31) { + vty_out(vty, "%% Hreqave (%d) * Hreqt (%d) = %d must be < 32%s", + h_reqave, h_reqt, h_reqave * h_reqt, VTY_NEWLINE); + return CMD_WARNING; + } + + avg_params = ci_thresh_by_conn_type(params, argv[0]); + avg_params->h_reqave = h_reqave; + avg_params->h_reqt = h_reqt; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_ci_avg_algo, + cfg_power_ctrl_ci_avg_algo_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + /* FIXME: add algorithm specific parameters */ + "ci-avg " VTY_CMD_CI_TYPE " algo (unweighted|weighted|mod-median)", + POWER_CONTROL_CI_MEAS_AVG_DESC VTY_DESC_CI_TYPE + "Select the averaging algorithm\n" + "Un-weighted average\n" "Weighted average\n" + "Modified median calculation\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *avg_params; + + avg_params = ci_thresh_by_conn_type(params, argv[0]); + if (strcmp(argv[1], "unweighted") == 0) + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED; + else if (strcmp(argv[1], "weighted") == 0) + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED; + else if (strcmp(argv[1], "mod-median") == 0) + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_power_ctrl_ci_avg_osmo_ewma, + cfg_power_ctrl_ci_avg_osmo_ewma_cmd, + X(BSC_VTY_ATTR_VENDOR_SPECIFIC) | + X(BSC_VTY_ATTR_NEW_LCHAN), + "ci-avg " VTY_CMD_CI_TYPE " algo osmo-ewma beta <1-99>", + POWER_CONTROL_CI_MEAS_AVG_DESC VTY_DESC_CI_TYPE + "Select the averaging algorithm\n" + "Exponentially Weighted Moving Average (EWMA)\n" + "Smoothing factor (in %): beta = (100 - alpha)\n" + "1% - lowest smoothing, 99% - highest smoothing\n") +{ + struct gsm_power_ctrl_params *params = vty->index; + struct gsm_power_ctrl_meas_params *avg_params; + const struct gsm_bts *bts; + + if (params->dir == GSM_PWR_CTRL_DIR_UL) + bts = container_of(params, struct gsm_bts, ms_power_ctrl); + else + bts = container_of(params, struct gsm_bts, bs_power_ctrl); + + if (bts->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% EWMA is an OsmoBTS specific algorithm, " + "it's not usable for other BTS types%s", VTY_NEWLINE); + return CMD_WARNING; + } + + avg_params = ci_thresh_by_conn_type(params, argv[0]); + avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA; + avg_params->ewma.alpha = 100 - atoi(argv[1]); + + return CMD_SUCCESS; +} + +static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv) +{ + int count = 0; + int i; + for (i = 0; i < 1024; i++) { + if (!bitvec_get_bit_pos(bv, i)) + continue; + vty_out(vty, " %u", i); + count ++; + } + if (!count) + vty_out(vty, " (none)"); + else + vty_out(vty, " (%d)", count); +} + +static void bts_dump_vty_cbch(struct vty *vty, const struct bts_smscb_chan_state *cstate) +{ + vty_out(vty, " CBCH %s: %u messages, %u pages, %zu-entry sched_arr, %u%% load%s", + bts_smscb_chan_state_name(cstate), llist_count(&cstate->messages), + bts_smscb_chan_page_count(cstate), cstate->sched_arr_size, + bts_smscb_chan_load_percent(cstate), VTY_NEWLINE); +} + + +static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts) +{ + unsigned int i; + bool no_features = true; + vty_out(vty, " Features:%s", VTY_NEWLINE); + + for (i = 0; i < _NUM_BTS_FEAT; i++) { + if (osmo_bts_has_feature(&bts->features, i)) { + vty_out(vty, " %03u ", i); + vty_out(vty, "%-40s%s", osmo_bts_features_desc(i), VTY_NEWLINE); + no_features = false; + } + } + + if (no_features) + vty_out(vty, " (not available)%s", VTY_NEWLINE); +} + +void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) +{ + struct pchan_load pl; + struct gsm_bts_trx *trx; + int ts_hopping_total; + int ts_non_hopping_total; + const struct rate_ctr *activations_tch; + const struct rate_ctr *activations_sdcch; + + vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, " + "BSIC %u (NCC=%u, BCC=%u) and %u TRX%s", + bts->nr, btstype2str(bts->type), gsm_band_name(bts->band), + bts->cell_identity, + bts->location_area_code, bts->bsic, + bts->bsic >> 3, bts->bsic & 7, + bts->num_trx, VTY_NEWLINE); + vty_out(vty, " Description: %s%s", + bts->description ? bts->description : "(null)", VTY_NEWLINE); + + vty_out(vty, " ARFCNs:"); + ts_hopping_total = 0; + ts_non_hopping_total = 0; + llist_for_each_entry(trx, &bts->trx_list, list) { + int ts_nr; + int ts_hopping = 0; + int ts_non_hopping = 0; + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + if (ts->hopping.enabled) + ts_hopping++; + else + ts_non_hopping++; + } + + if (ts_non_hopping) + vty_out(vty, " %u", trx->arfcn); + ts_hopping_total += ts_hopping; + ts_non_hopping_total += ts_non_hopping; + } + if (ts_hopping_total) { + if (ts_non_hopping_total) + vty_out(vty, " / Hopping on %d of %d timeslots", + ts_hopping_total, ts_hopping_total + ts_non_hopping_total); + else + vty_out(vty, " Hopping on all %d timeslots", ts_hopping_total); + } + vty_out(vty, "%s", VTY_NEWLINE); + + if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH)) + vty_out(vty, " PCU version %s connected%s", bts->pcu_version, + VTY_NEWLINE); + vty_out(vty, " BCCH carrier power reduction (maximum): %u dB%s", + bts->c0_max_power_red_db, VTY_NEWLINE); + vty_out(vty, " MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE); + vty_out(vty, " Minimum Rx Level for Access: %i dBm%s", + rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min), + VTY_NEWLINE); + vty_out(vty, " Cell Reselection Hysteresis: %u dBm%s", + bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE); + vty_out(vty, " Access Control Class rotation allow mask: 0x%" PRIx16 "%s", + bts->acc_mgr.allowed_subset_mask, VTY_NEWLINE); + vty_out(vty, " Access Control Class ramping: %senabled%s", + acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "not ", VTY_NEWLINE); + if (acc_ramp_is_enabled(&bts->acc_ramp)) { + vty_out(vty, " Access Control Class ramping step interval: %u seconds%s", + acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE); + vty_out(vty, " Access Control Class channel load thresholds: (%" PRIu8 ", %" PRIu8 ")%s", + bts->acc_ramp.chan_load_lower_threshold, + bts->acc_ramp.chan_load_upper_threshold, VTY_NEWLINE); + vty_out(vty, " enabling %u Access Control Class%s per ramping step%s", + acc_ramp_get_step_size(&bts->acc_ramp), + acc_ramp_get_step_size(&bts->acc_ramp) > 1 ? "es" : "", VTY_NEWLINE); + } + vty_out(vty, " RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer, + VTY_NEWLINE); + vty_out(vty, " RACH Max transmissions: %u%s", + rach_max_trans_raw2val(bts->si_common.rach_control.max_trans), + VTY_NEWLINE); + vty_out(vty, " RACH Max Delay (Max Access Delay IE in CHANnel ReQuireD): %u%s", + bts->rach_max_delay, VTY_NEWLINE); + if (bts->si_common.rach_control.cell_bar) + vty_out(vty, " CELL IS BARRED%s", VTY_NEWLINE); + if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED) + vty_out(vty, " Uplink DTX: %s%s", + (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? + "enabled" : "forced", VTY_NEWLINE); + else + vty_out(vty, " Uplink DTX: not enabled%s", VTY_NEWLINE); + vty_out(vty, " Downlink DTX: %senabled%s", bts->dtxd ? "" : "not ", + VTY_NEWLINE); + vty_out(vty, " Channel Description Attachment: %s%s", + (bts->si_common.chan_desc.att) ? "yes" : "no", VTY_NEWLINE); + vty_out(vty, " Channel Description BS-PA-MFRMS: %u%s", + bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE); + vty_out(vty, " Channel Description BS-AG_BLKS-RES: %u%s", + bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE); + vty_out(vty, " System Information present: 0x%08x, static: 0x%08x%s", + bts->si_valid, bts->si_mode_static, VTY_NEWLINE); + vty_out(vty, " Early Classmark Sending: 2G %s, 3G %s%s%s", + bts->early_classmark_allowed ? "allowed" : "forbidden", + bts->early_classmark_allowed_3g ? "allowed" : "forbidden", + bts->early_classmark_allowed_3g && !bts->early_classmark_allowed ? + " (forbidden by 2G bit)" : "", + VTY_NEWLINE); + if (is_ipa_abisip_bts(bts)) + vty_out(vty, " Unit ID: %u/%u/0, OML Stream ID 0x%02x%s", + bts->ip_access.site_id, bts->ip_access.bts_id, + bts->oml_tei, VTY_NEWLINE); + else if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) + vty_out(vty, " Skip Reset: %d%s", + bts->nokia.skip_reset, VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &bts->mo.nm_state); + vty_out(vty, " Site Mgr NM State: "); + net_dump_nmstate(vty, &bts->site_mgr->mo.nm_state); + + if (bts->gprs.mode != BTS_GPRS_NONE) { + vty_out(vty, " GPRS NSE: "); + net_dump_nmstate(vty, &bts->site_mgr->gprs.nse.mo.nm_state); + vty_out(vty, " GPRS CELL: "); + net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state); + vty_out(vty, " GPRS NSVC0: "); + net_dump_nmstate(vty, &bts->site_mgr->gprs.nsvc[0].mo.nm_state); + vty_out(vty, " GPRS NSVC1: "); + net_dump_nmstate(vty, &bts->site_mgr->gprs.nsvc[1].mo.nm_state); + } else + vty_out(vty, " GPRS: not configured%s", VTY_NEWLINE); + + vty_out(vty, " Paging: %u pending requests, %u free slots%s", + paging_pending_requests_nr(bts), + bts->paging.available_slots, VTY_NEWLINE); + if (is_ipa_abisip_bts(bts)) { + vty_out(vty, " OML Link: "); + e1isl_dump_vty_tcp(vty, bts->oml_link); + bts_dump_vty_oml_link_state(vty, bts); + } else { + vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); + e1isl_dump_vty(vty, bts->oml_link); + } + + vty_out(vty, " Neighbor Cells: "); + switch (bts->neigh_list_manual_mode) { + default: + case NL_MODE_AUTOMATIC: + vty_out(vty, "Automatic"); + /* generate_bcch_chan_list() should populate si_common.neigh_list */ + break; + case NL_MODE_MANUAL: + vty_out(vty, "Manual"); + break; + case NL_MODE_MANUAL_SI5SEP: + vty_out(vty, "Manual/separate SI5"); + break; + } + vty_out(vty, ", ARFCNs:"); + vty_out_neigh_list(vty, &bts->si_common.neigh_list); + if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) { + vty_out(vty, " SI5:"); + vty_out_neigh_list(vty, &bts->si_common.si5_neigh_list); + } + vty_out(vty, "%s", VTY_NEWLINE); + + /* FIXME: chan_desc */ + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); + dump_pchan_load_vty(vty, " ", &pl); + + bts_dump_vty_cbch(vty, &bts->cbch_basic); + bts_dump_vty_cbch(vty, &bts->cbch_extended); + + vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s", + rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_TOTAL)->current, + rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_NO_CHANNEL)->current, + VTY_NEWLINE); + + vty_out(vty, " Channel Activations :%s", VTY_NEWLINE); + activations_tch = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_TCH); + vty_out(vty, " TCH %"PRIu64"", activations_tch->current); + if (activations_tch->intv[RATE_CTR_INTV_HOUR].rate > 0) { + const struct rate_ctr *active_time_tch_ms = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_TCH_ACTIVE_MILLISECONDS_TOTAL); + vty_out(vty, " (avg lifespan %s seconds in last hour)", + osmo_int_to_float_str_c(OTC_SELECT, + active_time_tch_ms->intv[RATE_CTR_INTV_HOUR].rate + / activations_tch->intv[RATE_CTR_INTV_HOUR].rate, 3)); + } + vty_out(vty, "%s", VTY_NEWLINE); + activations_sdcch = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_SDCCH); + vty_out(vty, " SDCCH %"PRIu64"", activations_sdcch->current); + if (activations_sdcch->intv[RATE_CTR_INTV_HOUR].rate > 0) { + const struct rate_ctr *active_time_sdcch_ms = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_SDCCH_ACTIVE_MILLISECONDS_TOTAL); + vty_out(vty, " (avg lifespan %s seconds in last hour)", + osmo_int_to_float_str_c(OTC_SELECT, + active_time_sdcch_ms->intv[RATE_CTR_INTV_HOUR].rate + / activations_sdcch->intv[RATE_CTR_INTV_HOUR].rate, 3)); + } + vty_out(vty, "%s", VTY_NEWLINE); + + vty_out(vty, " Channel Failures : %"PRIu64" rf_failures, %"PRIu64" rll failures%s", + rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_RF_FAIL)->current, + rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_RLL_ERR)->current, + VTY_NEWLINE); + vty_out(vty, " BTS failures : %"PRIu64" OML, %"PRIu64" RSL%s", + rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_BTS_OML_FAIL)->current, + rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_BTS_RSL_FAIL)->current, + VTY_NEWLINE); + + vty_out_stat_item_group(vty, " ", bts->bts_statg); + + bts_dump_vty_features(vty, bts); +} + +void bts_dump_vty_oml_link_state(struct vty *vty, struct gsm_bts *bts) +{ + unsigned long long sec; + + vty_out(vty, " OML Link state: %s", get_model_oml_status(bts)); + if (bts_setup_ramp_active(bts->network)) + vty_out(vty, " BTS Ramping: %s", bts_setup_ramp_get_state_str(bts)); + sec = bts_updowntime(bts); + if (sec) + vty_out(vty, " %llu days %llu hours %llu min. %llu sec.", + OSMO_SEC2DAY(sec), OSMO_SEC2HRS(sec), OSMO_SEC2MIN(sec), sec % 60); + vty_out(vty, "%s", VTY_NEWLINE); +} + +static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts) +{ + unsigned int i; + struct gsm_bts_sm *bts_sm = bts->site_mgr; + vty_out(vty, " gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode), + VTY_NEWLINE); + if (bts->gprs.mode == BTS_GPRS_NONE) + return; + + vty_out(vty, " gprs routing area %u%s", bts->gprs.rac, + VTY_NEWLINE); + vty_out(vty, " gprs network-control-order nc%u%s", + bts->gprs.net_ctrl_ord, VTY_NEWLINE); + if (!bts->gprs.ctrl_ack_type_use_block) + vty_out(vty, " gprs control-ack-type-rach%s", VTY_NEWLINE); + if (bts->gprs.ccn.forced_vty) + vty_out(vty, " gprs ccn-active %d%s", + bts->gprs.ccn.active ? 1 : 0, VTY_NEWLINE); + vty_out(vty, " gprs power-control alpha %u%s", + bts->gprs.pwr_ctrl.alpha, VTY_NEWLINE); + vty_out(vty, " gprs cell bvci %u%s", bts->gprs.cell.bvci, + VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++) + vty_out(vty, " gprs cell timer %s %u%s", + get_value_string(gprs_bssgp_cfg_strs, i), + bts->gprs.cell.timer[i], VTY_NEWLINE); + vty_out(vty, " gprs nsei %u%s", bts_sm->gprs.nse.nsei, + VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nse.timer); i++) + vty_out(vty, " gprs ns timer %s %u%s", + get_value_string(gprs_ns_timer_strs, i), + bts_sm->gprs.nse.timer[i], VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nsvc); i++) { + const struct gsm_gprs_nsvc *nsvc = &bts_sm->gprs.nsvc[i]; + struct osmo_sockaddr_str remote; + + if (!nsvc->enabled) { + vty_out(vty, " no gprs nsvc %u%s", i, VTY_NEWLINE); + continue; + } + + vty_out(vty, " gprs nsvc %u nsvci %u%s", i, + nsvc->nsvci, VTY_NEWLINE); + + vty_out(vty, " gprs nsvc %u local udp port %u%s", i, + nsvc->local_port, VTY_NEWLINE); + + /* Most likely, the remote address is not configured (AF_UNSPEC). + * Printing the port alone makes no sense, so let's just skip both. */ + if (osmo_sockaddr_str_from_sockaddr(&remote, &nsvc->remote.u.sas) != 0) + continue; + + vty_out(vty, " gprs nsvc %u remote ip %s%s", + i, remote.ip, VTY_NEWLINE); + vty_out(vty, " gprs nsvc %u remote udp port %u%s", + i, remote.port, VTY_NEWLINE); + } + + /* EGPRS specific parameters */ + if (bts->gprs.mode == BTS_GPRS_EGPRS) { + if (bts->gprs.egprs_pkt_chan_request) + vty_out(vty, " gprs egprs-packet-channel-request%s", VTY_NEWLINE); + } +} + +/* Write the model data if there is one */ +static void config_write_bts_model(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + if (!bts->model) + return; + + if (bts->model->config_write_bts) + bts->model->config_write_bts(vty, bts); + + llist_for_each_entry(trx, &bts->trx_list, list) + config_write_trx_single(vty, trx); +} + +static void write_amr_modes(struct vty *vty, const char *prefix, + const char *name, struct amr_mode *modes, int num) +{ + int i; + + vty_out(vty, " %s threshold %s", prefix, name); + for (i = 0; i < num - 1; i++) + vty_out(vty, " %d", modes[i].threshold); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " %s hysteresis %s", prefix, name); + for (i = 0; i < num - 1; i++) + vty_out(vty, " %d", modes[i].hysteresis); + vty_out(vty, "%s", VTY_NEWLINE); +} + +static void config_write_bts_amr(struct vty *vty, struct gsm_bts *bts, + struct amr_multirate_conf *mr, int full) +{ + struct gsm48_multi_rate_conf *mr_conf; + const char *prefix = (full) ? "amr tch-f" : "amr tch-h"; + int i, num; + + if (!(mr->gsm48_ie[1])) + return; + + mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie; + + num = 0; + vty_out(vty, " %s modes", prefix); + for (i = 0; i < ((full) ? 8 : 6); i++) { + if ((mr->gsm48_ie[1] & (1 << i))) { + vty_out(vty, " %d", i); + num++; + } + } + vty_out(vty, "%s", VTY_NEWLINE); + if (num > 4) + num = 4; + if (num > 1) { + write_amr_modes(vty, prefix, "ms", mr->ms_mode, num); + write_amr_modes(vty, prefix, "bts", mr->bts_mode, num); + } + vty_out(vty, " %s start-mode ", prefix); + if (mr_conf->icmi) { + num = 0; + for (i = 0; i < ((full) ? 8 : 6) && num < 4; i++) { + if ((mr->gsm48_ie[1] & (1 << i))) + num++; + if (mr_conf->smod == num - 1) { + vty_out(vty, "%d%s", num, VTY_NEWLINE); + break; + } + } + } else + vty_out(vty, "auto%s", VTY_NEWLINE); +} + +/* TODO: generalize and move indentation handling to libosmocore */ +#define cfg_out(fmt, args...) \ + vty_out(vty, "%*s" fmt, indent, "", ##args); + +static void config_write_power_ctrl_meas(struct vty *vty, unsigned int indent, + const struct gsm_power_ctrl_meas_params *mp, + const char *param, const char *param2) +{ + if (strcmp(param, "ci") == 0) { + cfg_out("%s-thresh%s %s%s", + param, param2, mp->enabled ? "enable" : "disable", + VTY_NEWLINE); + } + + cfg_out("%s-thresh%s lower %u upper %u%s", + param, param2, mp->lower_thresh, mp->upper_thresh, + VTY_NEWLINE); + cfg_out("%s-thresh-comp%s lower %u %u upper %u %u%s", + param, param2, mp->lower_cmp_p, mp->lower_cmp_n, + mp->upper_cmp_p, mp->upper_cmp_n, + VTY_NEWLINE); + + switch (mp->algo) { + case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE: + /* Do not print any averaging parameters */ + return; /* we're done */ + case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED: + cfg_out("%s-avg%s algo unweighted%s", param, param2, VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED: + cfg_out("%s-avg%s algo weighted%s", param, param2, VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN: + cfg_out("%s-avg%s algo mod-median%s", param, param2, VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: + cfg_out("%s-avg%s algo osmo-ewma beta %u%s", + param, param2, 100 - mp->ewma.alpha, + VTY_NEWLINE); + break; + } + + cfg_out("%s-avg%s params hreqave %u hreqt %u%s", + param, param2, mp->h_reqave, mp->h_reqt, + VTY_NEWLINE); +} + +static void config_write_power_ctrl(struct vty *vty, unsigned int indent, + const struct gsm_bts *bts, + const struct gsm_power_ctrl_params *cp) +{ + const char *node_name; + + if (cp->dir == GSM_PWR_CTRL_DIR_UL) + node_name = "ms-power-control"; + else + node_name = "bs-power-control"; + + switch (cp->mode) { + case GSM_PWR_CTRL_MODE_NONE: + cfg_out("no %s%s", node_name, VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MODE_STATIC: + cfg_out("%s%s", node_name, VTY_NEWLINE); + cfg_out(" mode static%s", VTY_NEWLINE); + if (cp->dir == GSM_PWR_CTRL_DIR_DL && cp->bs_power_val_db != 0) + cfg_out(" bs-power static %u%s", cp->bs_power_val_db, VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MODE_DYN_BTS: + case GSM_PWR_CTRL_MODE_DYN_BSC: + cfg_out("%s%s", node_name, VTY_NEWLINE); + cfg_out(" mode %s%s", + cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS ? "dyn-bts" : "dyn-bsc", VTY_NEWLINE); + if (cp->dir == GSM_PWR_CTRL_DIR_DL) + cfg_out(" bs-power dyn-max %u%s", cp->bs_power_max_db, VTY_NEWLINE); + + cfg_out(" ctrl-interval %u%s", cp->ctrl_interval, VTY_NEWLINE); + cfg_out(" step-size inc %u red %u%s", + cp->inc_step_size_db, cp->red_step_size_db, + VTY_NEWLINE); + + /* Measurement processing / averaging parameters */ + config_write_power_ctrl_meas(vty, indent + 1, &cp->rxlev_meas, "rxlev", ""); + config_write_power_ctrl_meas(vty, indent + 1, &cp->rxqual_meas, "rxqual", ""); + if (cp->dir == GSM_PWR_CTRL_DIR_UL && is_osmobts(bts) + && cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS) { + config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_fr_meas, "ci", " fr-efr"); + config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_hr_meas, "ci", " hr"); + config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_amr_fr_meas, "ci", " amr-fr"); + config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_amr_hr_meas, "ci", " amr-hr"); + config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_sdcch_meas, "ci", " sdcch"); + config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_gprs_meas, "ci", " gprs"); + } + break; + } +} + +#undef cfg_out + +static void config_write_bts_ncc_permitted(struct vty *vty, const char *prefix, const struct gsm_bts *bts) +{ + int i; + uint8_t ncc_permitted = bts->si_common.ncc_permitted; + + if (ncc_permitted == 0xff) + return; + + vty_out(vty, "%sncc-permitted", prefix); + + for (i = 0; i < 8; i++) { + if ((ncc_permitted & (1 << i))) + vty_out(vty, " %d", i + 1); + } + + vty_out(vty, "%s", VTY_NEWLINE); +} + +static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) +{ + int i; + uint8_t tmp; + + vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE); + vty_out(vty, " type %s%s", btstype2str(bts->type), VTY_NEWLINE); + if (bts->description) + vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE); + vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE); + vty_out(vty, " cell_identity %u%s", bts->cell_identity, VTY_NEWLINE); + vty_out(vty, " location_area_code 0x%04x%s", bts->location_area_code, + VTY_NEWLINE); + if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED) + vty_out(vty, " dtx uplink%s%s", + (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? "" : " force", + VTY_NEWLINE); + if (bts->dtxd) + vty_out(vty, " dtx downlink%s", VTY_NEWLINE); + vty_out(vty, " base_station_id_code %u%s", bts->bsic, VTY_NEWLINE); + vty_out(vty, " ms max power %u%s", bts->ms_max_power, VTY_NEWLINE); + vty_out(vty, " cell reselection hysteresis %u%s", + bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE); + vty_out(vty, " rxlev access min %u%s", + bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE); + + if (bts->si_common.cell_ro_sel_par.present) { + struct osmo_gsm48_si_selection_params *sp; + sp = &bts->si_common.cell_ro_sel_par; + + if (sp->cbq) + vty_out(vty, " cell bar qualify %u%s", + sp->cbq, VTY_NEWLINE); + + if (sp->cell_resel_off) + vty_out(vty, " cell reselection offset %u%s", + sp->cell_resel_off*2, VTY_NEWLINE); + + if (sp->temp_offs == 7) + vty_out(vty, " temporary offset infinite%s", + VTY_NEWLINE); + else if (sp->temp_offs) + vty_out(vty, " temporary offset %u%s", + sp->temp_offs*10, VTY_NEWLINE); + + if (sp->penalty_time == 31) + vty_out(vty, " penalty time reserved%s", + VTY_NEWLINE); + else if (sp->penalty_time) + vty_out(vty, " penalty time %u%s", + (sp->penalty_time*20)+20, VTY_NEWLINE); + } + + if (gsm_bts_get_radio_link_timeout(bts) < 0) + vty_out(vty, " radio-link-timeout infinite%s", VTY_NEWLINE); + else + vty_out(vty, " radio-link-timeout %d%s", + gsm_bts_get_radio_link_timeout(bts), VTY_NEWLINE); + + vty_out(vty, " channel allocator mode chan-req %s%s", + bts->chan_alloc_chan_req_reverse ? "descending" : "ascending", + VTY_NEWLINE); + if (bts->chan_alloc_assignment_dynamic) { + vty_out(vty, " channel allocator mode assignment dynamic%s", + VTY_NEWLINE); + vty_out(vty, " channel allocator dynamic-param sort-by-trx-power %c%s", + bts->chan_alloc_dyn_params.sort_by_trx_power ? '1' : '0', + VTY_NEWLINE); + vty_out(vty, " channel allocator dynamic-param ul-rxlev thresh %u avg-num %u%s", + bts->chan_alloc_dyn_params.ul_rxlev_thresh, + bts->chan_alloc_dyn_params.ul_rxlev_avg_num, + VTY_NEWLINE); + vty_out(vty, " channel allocator dynamic-param c0-chan-load thresh %u%s", + bts->chan_alloc_dyn_params.c0_chan_load_thresh, + VTY_NEWLINE); + } else { + vty_out(vty, " channel allocator mode assignment %s%s", + bts->chan_alloc_assignment_reverse ? "descending" : "ascending", + VTY_NEWLINE); + } + vty_out(vty, " channel allocator mode handover %s%s", + bts->chan_alloc_handover_reverse ? "descending" : "ascending", + VTY_NEWLINE); + vty_out(vty, " channel allocator mode vgcs-vbs %s%s", + bts->chan_alloc_vgcs_reverse ? "descending" : "ascending", + VTY_NEWLINE); + if (bts->chan_alloc_avoid_interf) + vty_out(vty, " channel allocator avoid-interference 1%s", VTY_NEWLINE); + if (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_NEVER) + vty_out(vty, " channel allocator tch-signalling-policy never%s", VTY_NEWLINE); + else if (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_EMERG) + vty_out(vty, " channel allocator tch-signalling-policy emergency%s", VTY_NEWLINE); + else if (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_VOICE) + vty_out(vty, " channel allocator tch-signalling-policy voice%s", VTY_NEWLINE); + vty_out(vty, " rach tx integer %u%s", + bts->si_common.rach_control.tx_integer, VTY_NEWLINE); + vty_out(vty, " rach max transmission %u%s", + rach_max_trans_raw2val(bts->si_common.rach_control.max_trans), + VTY_NEWLINE); + vty_out(vty, " rach max-delay %u%s", bts->rach_max_delay, VTY_NEWLINE); + vty_out(vty, " rach expiry-timeout %u%s", bts->rach_expiry_timeout, VTY_NEWLINE); + + vty_out(vty, " channel-description attach %u%s", + bts->si_common.chan_desc.att, VTY_NEWLINE); + vty_out(vty, " channel-description bs-pa-mfrms %u%s", + bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE); + vty_out(vty, " channel-description bs-ag-blks-res %u%s", + bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE); + if (bts->nch.num_blocks) { + vty_out(vty, " nch-position num-blocks %u first-block %u%s", + bts->nch.num_blocks, bts->nch.first_block, VTY_NEWLINE); + } else { + vty_out(vty, " no nch-position%s", VTY_NEWLINE); + } + + if (bts->ccch_load_ind_thresh != 10) + vty_out(vty, " ccch load-indication-threshold %u%s", + bts->ccch_load_ind_thresh, VTY_NEWLINE); + if (bts->ccch_load_ind_period != 1) + vty_out(vty, " ccch load-indication-period %u%s", + bts->ccch_load_ind_period, VTY_NEWLINE); + if (bts->rach_b_thresh != -1) + vty_out(vty, " rach nm busy threshold %u%s", + bts->rach_b_thresh, VTY_NEWLINE); + if (bts->rach_ldavg_slots != -1) + vty_out(vty, " rach nm load average %u%s", + bts->rach_ldavg_slots, VTY_NEWLINE); + if (bts->si_common.rach_control.cell_bar) + vty_out(vty, " cell barred 1%s", VTY_NEWLINE); + if ((bts->si_common.rach_control.t2 & 0x4) == 0) + vty_out(vty, " rach emergency call allowed 1%s", VTY_NEWLINE); + if (bts->si_common.rach_control.re == 0) + vty_out(vty, " rach call-reestablishment allowed 1%s", VTY_NEWLINE); + if ((bts->si_common.rach_control.t3) != 0) + for (i = 0; i < 8; i++) + if (bts->si_common.rach_control.t3 & (0x1 << i)) + vty_out(vty, " rach access-control-class %d barred%s", i, VTY_NEWLINE); + if ((bts->si_common.rach_control.t2 & 0xfb) != 0) + for (i = 0; i < 8; i++) + if ((i != 2) && (bts->si_common.rach_control.t2 & (0x1 << i))) + vty_out(vty, " rach access-control-class %d barred%s", i+8, VTY_NEWLINE); + if (bts->acc_mgr.len_allowed_adm < 10) + vty_out(vty, " access-control-class-rotate %" PRIu8 "%s", bts->acc_mgr.len_allowed_adm, VTY_NEWLINE); + if (bts->acc_mgr.rotation_time_sec != ACC_MGR_QUANTUM_DEFAULT) + vty_out(vty, " access-control-class-rotate-quantum %" PRIu32 "%s", bts->acc_mgr.rotation_time_sec, VTY_NEWLINE); + vty_out(vty, " %saccess-control-class-ramping%s", acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "no ", VTY_NEWLINE); + if (acc_ramp_is_enabled(&bts->acc_ramp)) { + vty_out(vty, " access-control-class-ramping-step-interval %u%s", + acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE); + vty_out(vty, " access-control-class-ramping-step-size %u%s", acc_ramp_get_step_size(&bts->acc_ramp), + VTY_NEWLINE); + vty_out(vty, " access-control-class-ramping-chan-load %u %u%s", + bts->acc_ramp.chan_load_lower_threshold, bts->acc_ramp.chan_load_upper_threshold, VTY_NEWLINE); + } + if (!bts->si_unused_send_empty) + vty_out(vty, " no system-information unused-send-empty%s", VTY_NEWLINE); + for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) { + if (bts->si_mode_static & (1 << i)) { + vty_out(vty, " system-information %s mode static%s", + get_value_string(osmo_sitype_strs, i), VTY_NEWLINE); + vty_out(vty, " system-information %s static %s%s", + get_value_string(osmo_sitype_strs, i), + osmo_hexdump_nospc(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN), + VTY_NEWLINE); + } + } + vty_out(vty, " early-classmark-sending %s%s", + bts->early_classmark_allowed ? "allowed" : "forbidden", VTY_NEWLINE); + vty_out(vty, " early-classmark-sending-3g %s%s", + bts->early_classmark_allowed_3g ? "allowed" : "forbidden", VTY_NEWLINE); + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + vty_out(vty, " ipa unit-id %u %u%s", + bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE); + if (bts->ip_access.rsl_ip) { + struct in_addr ia; + ia.s_addr = htonl(bts->ip_access.rsl_ip); + vty_out(vty, " ipa rsl-ip %s%s", inet_ntoa(ia), + VTY_NEWLINE); + } + vty_out(vty, " oml ipa stream-id %u line %u%s", + bts->oml_tei, bts->oml_e1_link.e1_nr, VTY_NEWLINE); + break; + case GSM_BTS_TYPE_NOKIA_SITE: + vty_out(vty, " nokia_site skip-reset %d%s", bts->nokia.skip_reset, VTY_NEWLINE); + vty_out(vty, " nokia_site no-local-rel-conf %d%s", + bts->nokia.no_loc_rel_cnf, VTY_NEWLINE); + vty_out(vty, " nokia_site bts-reset-timer %d%s", bts->nokia.bts_reset_timer_cnf, VTY_NEWLINE); + /* fall through: Nokia requires "oml e1" parameters also */ + default: + config_write_e1_link(vty, &bts->oml_e1_link, " oml "); + vty_out(vty, " oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE); + break; + } + + /* if we have a limit, write it */ + if (bts->paging.free_chans_need >= 0) + vty_out(vty, " paging free %d%s", bts->paging.free_chans_need, VTY_NEWLINE); + if (!bts->T3113_dynamic) + vty_out(vty, " no timer-dynamic T3113%s", VTY_NEWLINE); + + vty_out(vty, " neighbor-list mode %s%s", + get_value_string(bts_neigh_mode_strs, bts->neigh_list_manual_mode), VTY_NEWLINE); + if (bts->neigh_list_manual_mode != NL_MODE_AUTOMATIC) { + for (i = 0; i < 1024; i++) { + if (bitvec_get_bit_pos(&bts->si_common.neigh_list, i)) + vty_out(vty, " neighbor-list add arfcn %u%s", + i, VTY_NEWLINE); + } + } + if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) { + for (i = 0; i < 1024; i++) { + if (bitvec_get_bit_pos(&bts->si_common.si5_neigh_list, i)) + vty_out(vty, " si5 neighbor-list add arfcn %u%s", + i, VTY_NEWLINE); + } + } + + for (i = 0; i < MAX_EARFCN_LIST; i++) { + struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; + if (e->arfcn[i] != OSMO_EARFCN_INVALID) { + vty_out(vty, " si2quater neighbor-list add earfcn %u " + "thresh-hi %u", e->arfcn[i], e->thresh_hi); + + vty_out(vty, " thresh-lo %u", + e->thresh_lo_valid ? e->thresh_lo : 32); + + vty_out(vty, " prio %u", + e->prio_valid ? e->prio : 8); + + vty_out(vty, " qrxlv %u", + e->qrxlm_valid ? e->qrxlm : 32); + + tmp = e->meas_bw[i]; + vty_out(vty, " meas %u", + (tmp != OSMO_EARFCN_MEAS_INVALID) ? tmp : 8); + + vty_out(vty, "%s", VTY_NEWLINE); + } + } + + for (i = 0; i < bts->si_common.uarfcn_length; i++) { + vty_out(vty, " si2quater neighbor-list add uarfcn %u %u %u%s", + bts->si_common.data.uarfcn_list[i], + bts->si_common.data.scramble_list[i] & ~(1 << 9), + (bts->si_common.data.scramble_list[i] >> 9) & 1, + VTY_NEWLINE); + } + + neighbor_ident_vty_write_bts(vty, " ", bts); + + vty_out(vty, " codec-support fr"); + if (bts->codec.hr) + vty_out(vty, " hr"); + if (bts->codec.efr) + vty_out(vty, " efr"); + if (bts->codec.amr) + vty_out(vty, " amr"); + vty_out(vty, "%s", VTY_NEWLINE); + + config_write_bts_amr(vty, bts, &bts->mr_full, 1); + config_write_bts_amr(vty, bts, &bts->mr_half, 0); + + if (bts->use_osmux != OSMUX_USAGE_OFF) { + vty_out(vty, " osmux %s%s", bts->use_osmux == OSMUX_USAGE_ON ? "on" : "only", + VTY_NEWLINE); + } + + if (bts->mgw_pool_target > -1) { + vty_out(vty, " mgw pool-target %u%s%s", + bts->mgw_pool_target, + bts->mgw_pool_target_strict ? " strict" : "", + VTY_NEWLINE); + } + + config_write_bts_gprs(vty, bts); + + if (bts->excl_from_rf_lock) + vty_out(vty, " rf-lock-exclude%s", VTY_NEWLINE); + + if (bts->force_combined_si_set) + vty_out(vty, " %sforce-combined-si%s", + bts->force_combined_si ? "" : "no ", VTY_NEWLINE); + + for (i = 0; i < ARRAY_SIZE(bts->depends_on); ++i) { + int j; + + if (bts->depends_on[i] == 0) + continue; + + for (j = 0; j < sizeof(bts->depends_on[i]) * 8; ++j) { + int bts_nr; + + if ((bts->depends_on[i] & (1<<j)) == 0) + continue; + + bts_nr = (i * sizeof(bts->depends_on[i]) * 8) + j; + vty_out(vty, " depends-on-bts %d%s", bts_nr, VTY_NEWLINE); + } + } + + ho_vty_write_bts(vty, bts); + + if (bts->top_acch_cap.overpower_db > 0) { + const struct abis_rsl_osmo_temp_ovp_acch_cap *top = \ + &bts->top_acch_cap; + const char *mode = NULL; + + if (top->sacch_enable && top->facch_enable) + mode = "dl-acch"; + else if (top->sacch_enable) + mode = "dl-sacch"; + else if (top->facch_enable) + mode = "dl-facch"; + else /* shall not happen */ + OSMO_ASSERT(0); + + vty_out(vty, " overpower %s %u%s", + mode, top->overpower_db, VTY_NEWLINE); + vty_out(vty, " overpower rxqual %u%s", + top->rxqual, VTY_NEWLINE); + vty_out(vty, " overpower chan-mode %s%s", + get_value_string(top_acch_chan_mode_name, + bts->top_acch_chan_mode), + VTY_NEWLINE); + } + + if (bts->rep_acch_cap.dl_facch_all) + vty_out(vty, " repeat dl-facch all%s", VTY_NEWLINE); + else if (bts->rep_acch_cap.dl_facch_cmd) + vty_out(vty, " repeat dl-facch command%s", VTY_NEWLINE); + if (bts->rep_acch_cap.dl_sacch) + vty_out(vty, " repeat dl-sacch%s", VTY_NEWLINE); + if (bts->rep_acch_cap.ul_sacch) + vty_out(vty, " repeat ul-sacch%s", VTY_NEWLINE); + if (bts->rep_acch_cap.ul_sacch + || bts->rep_acch_cap.dl_facch_cmd + || bts->rep_acch_cap.dl_facch_cmd) + vty_out(vty, " repeat rxqual %u%s", bts->rep_acch_cap.rxqual, VTY_NEWLINE); + + if (bts->interf_meas_params_cfg.avg_period != interf_meas_params_def.avg_period) { + vty_out(vty, " interference-meas avg-period %u%s", + bts->interf_meas_params_cfg.avg_period, + VTY_NEWLINE); + } + if (memcmp(bts->interf_meas_params_cfg.bounds_dbm, + interf_meas_params_def.bounds_dbm, + sizeof(interf_meas_params_def.bounds_dbm))) { + vty_out(vty, " interference-meas level-bounds " + "%d %d %d %d %d %d%s", + -1 * bts->interf_meas_params_cfg.bounds_dbm[0], + -1 * bts->interf_meas_params_cfg.bounds_dbm[1], + -1 * bts->interf_meas_params_cfg.bounds_dbm[2], + -1 * bts->interf_meas_params_cfg.bounds_dbm[3], + -1 * bts->interf_meas_params_cfg.bounds_dbm[4], + -1 * bts->interf_meas_params_cfg.bounds_dbm[5], + VTY_NEWLINE); + } + + if (!bts->srvcc_fast_return_allowed) + vty_out(vty, " srvcc fast-return forbid%s", VTY_NEWLINE); + + switch (bts->imm_ass_time) { + default: + case IMM_ASS_TIME_POST_CHAN_ACK: + /* default value */ + break; + case IMM_ASS_TIME_PRE_CHAN_ACK: + vty_out(vty, " immediate-assignment pre-chan-ack%s", VTY_NEWLINE); + break; + case IMM_ASS_TIME_PRE_TS_ACK: + vty_out(vty, " immediate-assignment pre-ts-ack%s", VTY_NEWLINE); + break; + } + + /* BS/MS Power Control parameters */ + config_write_power_ctrl(vty, 2, bts, &bts->bs_power_ctrl); + config_write_power_ctrl(vty, 2, bts, &bts->ms_power_ctrl); + + config_write_bts_ncc_permitted(vty, " ", bts); + + config_write_bts_model(vty, bts); +} + +int config_write_bts(struct vty *v) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(v); + struct gsm_bts *bts; + + llist_for_each_entry(bts, &gsmnet->bts_list, list) + config_write_bts_single(v, bts); + + return CMD_SUCCESS; +} + +int bts_vty_init(void) +{ + cfg_bts_type_cmd.string = + vty_cmd_string_from_valstr(tall_bsc_ctx, + bts_type_names, + "type (", "|", ")", + VTY_DO_LOWER); + cfg_bts_type_cmd.doc = + vty_cmd_string_from_valstr(tall_bsc_ctx, + bts_type_descs, + "BTS Vendor/Type\n", + "\n", "", 0); + + install_element(GSMNET_NODE, &cfg_bts_cmd); + install_node(&bts_node, config_write_bts); + install_element(BTS_NODE, &cfg_bts_type_cmd); + install_element(BTS_NODE, &cfg_bts_type_sysmobts_cmd); + install_element(BTS_NODE, &cfg_description_cmd); + install_element(BTS_NODE, &cfg_no_description_cmd); + install_element(BTS_NODE, &cfg_bts_band_cmd); + install_element(BTS_NODE, &cfg_bts_ci_cmd); + install_element(BTS_NODE, &cfg_bts_dtxu_cmd); + install_element(BTS_NODE, &cfg_bts_dtxd_cmd); + install_element(BTS_NODE, &cfg_bts_no_dtxu_cmd); + install_element(BTS_NODE, &cfg_bts_no_dtxd_cmd); + install_element(BTS_NODE, &cfg_bts_lac_cmd); + install_element(BTS_NODE, &cfg_bts_tsc_cmd); + install_element(BTS_NODE, &cfg_bts_bsic_cmd); + install_element(BTS_NODE, &cfg_bts_unit_id_cmd); + install_element(BTS_NODE, &cfg_bts_deprecated_unit_id_cmd); + install_element(BTS_NODE, &cfg_bts_rsl_ip_cmd); + install_element(BTS_NODE, &cfg_bts_deprecated_rsl_ip_cmd); + install_element(BTS_NODE, &cfg_bts_nokia_site_skip_reset_cmd); + install_element(BTS_NODE, &cfg_bts_nokia_site_no_loc_rel_cnf_cmd); + install_element(BTS_NODE, &cfg_bts_nokia_site_bts_reset_timer_cnf_cmd); + install_element(BTS_NODE, &cfg_bts_stream_id_cmd); + install_element(BTS_NODE, &cfg_bts_deprecated_stream_id_cmd); + install_element(BTS_NODE, &cfg_bts_oml_e1_cmd); + install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd); + install_element(BTS_NODE, &cfg_bts_challoc_mode_cmd); + install_element(BTS_NODE, &cfg_bts_challoc_mode_all_cmd); + install_element(BTS_NODE, &cfg_bts_challoc_mode_ass_dynamic_cmd); + install_element(BTS_NODE, &cfg_bts_challoc_dynamic_param_sort_by_trx_power_cmd); + install_element(BTS_NODE, &cfg_bts_challoc_dynamic_param_ul_rxlev_cmd); + install_element(BTS_NODE, &cfg_bts_challoc_dynamic_param_c0_chan_load_cmd); + install_element(BTS_NODE, &cfg_bts_chan_alloc_interf_cmd); + install_element(BTS_NODE, &cfg_bts_chan_alloc_tch_signalling_policy_cmd); + install_element(BTS_NODE, &cfg_bts_chan_alloc_allow_tch_for_signalling_cmd); + install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd); + install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd); + install_element(BTS_NODE, &cfg_bts_rach_max_delay_cmd); + install_element(BTS_NODE, &cfg_bts_rach_expiry_timeout_cmd); + install_element(BTS_NODE, &cfg_bts_chan_desc_att_cmd); + install_element(BTS_NODE, &cfg_bts_chan_dscr_att_cmd); + install_element(BTS_NODE, &cfg_bts_chan_desc_bs_pa_mfrms_cmd); + install_element(BTS_NODE, &cfg_bts_chan_dscr_bs_pa_mfrms_cmd); + install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_cmd); + install_element(BTS_NODE, &cfg_bts_chan_dscr_bs_ag_blks_res_cmd); + install_element(BTS_NODE, &cfg_bts_ccch_load_ind_thresh_cmd); + install_element(BTS_NODE, &cfg_bts_ccch_load_ind_period_cmd); + install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd); + install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd); + install_element(BTS_NODE, &cfg_bts_cell_barred_cmd); + install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd); + install_element(BTS_NODE, &cfg_bts_rach_re_allowed_cmd); + install_element(BTS_NODE, &cfg_bts_rach_ac_class_cmd); + install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd); + install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd); + install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd); + install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd); + install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd); + install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd); + install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd); + install_element(BTS_NODE, &cfg_bts_penalty_time_cmd); + install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd); + install_element(BTS_NODE, &cfg_bts_radio_link_timeout_cmd); + install_element(BTS_NODE, &cfg_bts_radio_link_timeout_inf_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_11bit_rach_support_for_egprs_cmd); + install_element(BTS_NODE, &cfg_bts_no_gprs_egprs_pkt_chan_req_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_egprs_pkt_chan_req_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_net_ctrl_ord_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_ctrl_ack_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_ccn_active_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_pwr_ctrl_alpha_cmd); + install_element(BTS_NODE, &cfg_no_bts_gprs_ctrl_ack_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd); + install_element(BTS_NODE, &cfg_no_bts_gprs_nsvc_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd); + install_element(BTS_NODE, &cfg_bts_pag_free_cmd); + install_element(BTS_NODE, &cfg_bts_si_mode_cmd); + install_element(BTS_NODE, &cfg_bts_si_static_cmd); + install_element(BTS_NODE, &cfg_bts_si_unused_send_empty_cmd); + install_element(BTS_NODE, &cfg_bts_no_si_unused_send_empty_cmd); + install_element(BTS_NODE, &cfg_bts_early_cm_cmd); + install_element(BTS_NODE, &cfg_bts_early_cm_3g_cmd); + install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd); + install_element(BTS_NODE, &cfg_bts_neigh_cmd); + install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd); + install_element(BTS_NODE, &cfg_bts_si2quater_neigh_add_cmd); + install_element(BTS_NODE, &cfg_bts_si2quater_neigh_del_cmd); + install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_add_cmd); + install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_del_cmd); + install_element(BTS_NODE, &cfg_bts_excl_rf_lock_cmd); + install_element(BTS_NODE, &cfg_bts_no_excl_rf_lock_cmd); + install_element(BTS_NODE, &cfg_bts_force_comb_si_cmd); + install_element(BTS_NODE, &cfg_bts_no_force_comb_si_cmd); + install_element(BTS_NODE, &cfg_bts_codec0_cmd); + install_element(BTS_NODE, &cfg_bts_codec1_cmd); + install_element(BTS_NODE, &cfg_bts_codec2_cmd); + install_element(BTS_NODE, &cfg_bts_codec3_cmd); + install_element(BTS_NODE, &cfg_bts_codec4_cmd); + install_element(BTS_NODE, &cfg_bts_depends_on_cmd); + install_element(BTS_NODE, &cfg_bts_no_depends_on_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_modes1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_modes2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_modes3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_modes4_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_thres1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_thres2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_thres3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_hyst1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_hyst2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_hyst3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_start_mode_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_modes1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_modes2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_modes3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_modes4_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_thres1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_thres2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_thres3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_hyst1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_hyst2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_hyst3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_start_mode_cmd); + install_element(BTS_NODE, &cfg_bts_osmux_cmd); + install_element(BTS_NODE, &cfg_bts_mgw_pool_target_cmd); + install_element(BTS_NODE, &cfg_bts_no_mgw_pool_target_cmd); + install_element(BTS_NODE, &cfg_bts_acc_rotate_cmd); + install_element(BTS_NODE, &cfg_bts_acc_rotate_quantum_cmd); + install_element(BTS_NODE, &cfg_bts_acc_ramping_cmd); + install_element(BTS_NODE, &cfg_bts_no_acc_ramping_cmd); + install_element(BTS_NODE, &cfg_bts_acc_ramping_step_interval_cmd); + install_element(BTS_NODE, &cfg_bts_acc_ramping_step_size_cmd); + install_element(BTS_NODE, &cfg_bts_acc_ramping_chan_load_cmd); + install_element(BTS_NODE, &cfg_bts_t3113_dynamic_cmd); + install_element(BTS_NODE, &cfg_bts_no_t3113_dynamic_cmd); + install_element(BTS_NODE, &cfg_bts_rep_dl_facch_cmd); + install_element(BTS_NODE, &cfg_bts_rep_no_dl_facch_cmd); + install_element(BTS_NODE, &cfg_bts_rep_ul_dl_sacch_cmd); + install_element(BTS_NODE, &cfg_bts_rep_no_ul_dl_sacch_cmd); + install_element(BTS_NODE, &cfg_bts_rep_rxqual_cmd); + install_element(BTS_NODE, &cfg_bts_top_dl_acch_cmd); + install_element(BTS_NODE, &cfg_bts_top_no_dl_acch_cmd); + install_element(BTS_NODE, &cfg_bts_top_dl_acch_rxqual_cmd); + install_element(BTS_NODE, &cfg_bts_top_dl_acch_chan_mode_cmd); + install_element(BTS_NODE, &cfg_bts_interf_meas_avg_period_cmd); + install_element(BTS_NODE, &cfg_bts_interf_meas_level_bounds_cmd); + install_element(BTS_NODE, &cfg_bts_srvcc_fast_return_cmd); + install_element(BTS_NODE, &cfg_bts_immediate_assignment_cmd); + install_element(BTS_NODE, &cfg_bts_nch_position_cmd); + install_element(BTS_NODE, &cfg_bts_no_nch_position_cmd); + install_element(BTS_NODE, &cfg_bts_ncc_permitted_all_cmd); + install_element(BTS_NODE, &cfg_bts_ncc_permitted_cmd); + + neighbor_ident_vty_init(); + /* See also handover commands added on bts level from handover_vty.c */ + + install_element(BTS_NODE, &cfg_bts_power_ctrl_cmd); + install_element(BTS_NODE, &cfg_bts_no_power_ctrl_cmd); + install_node(&power_ctrl_node, dummy_config_write); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_mode_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_bs_power_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ctrl_interval_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_step_size_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_rxlev_thresh_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_rxqual_thresh_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_thresh_disable_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_thresh_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_rxlev_thresh_comp_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_rxqual_thresh_comp_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_thresh_comp_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_no_avg_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_avg_params_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_avg_algo_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_avg_osmo_ewma_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_no_ci_avg_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_avg_params_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_avg_algo_cmd); + install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_avg_osmo_ewma_cmd); + + + return bts_trx_vty_init(); +} diff --git a/src/osmo-bsc/cbch_scheduler.c b/src/osmo-bsc/cbch_scheduler.c index 802180419..d699e7ede 100644 --- a/src/osmo-bsc/cbch_scheduler.c +++ b/src/osmo-bsc/cbch_scheduler.c @@ -60,6 +60,9 @@ static int bts_smscb_sched_add_before(struct bts_smscb_page **sched_arr, int sch OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page)); OSMO_ASSERT(smscb->num_pages >= 1); + if (last_idx >= sched_arr_size) + return -ERANGE; + for (i = smscb->num_pages - 1; i >= 0; i--) { while (sched_arr[arr_idx]) { arr_idx--; @@ -132,7 +135,7 @@ int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smsc } last_page = rc; - while (last_page < cstate->sched_arr_size) { + while (last_page + smscb->input.rep_period < cstate->sched_arr_size) { /* store further instances in a way that the last block of the N+1th instance * happens no later than "interval" after the last block of the Nth instance */ rc = bts_smscb_sched_add_before(arr, arr_size, @@ -243,7 +246,7 @@ static void bts_cbch_send_one(struct bts_smscb_chan_state *cstate) bts_smscb_page_done(cstate, page); } -static void bts_cbch_timer(void *data) +void bts_cbch_timer_cb(void *data) { struct gsm_bts *bts = (struct gsm_bts *)data; @@ -256,7 +259,6 @@ static void bts_cbch_timer(void *data) /* There is one SMSCB message (page) per eight 51-multiframes, i.e. 1.882 seconds */ void bts_cbch_timer_schedule(struct gsm_bts *bts) { - osmo_timer_setup(&bts->cbch_timer, &bts_cbch_timer, bts); osmo_timer_schedule(&bts->cbch_timer, 1, 882920); } diff --git a/src/osmo-bsc/cbsp_link.c b/src/osmo-bsc/cbsp_link.c index 492679942..9e17ef373 100644 --- a/src/osmo-bsc/cbsp_link.c +++ b/src/osmo-bsc/cbsp_link.c @@ -21,7 +21,6 @@ #include <osmocom/bsc/gsm_data.h> -#include <osmocom/bsc/vty.h> #include <osmocom/bsc/debug.h> #include <osmocom/bsc/smscb.h> #include <osmocom/bsc/bsc_msc_data.h> @@ -55,7 +54,6 @@ const struct osmo_sockaddr_str bsc_cbc_default_server_local_addr = { static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn) { struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn); - //struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); LOGP(DCBS, LOGL_NOTICE, "CBSP Server lost connection from %s\n", cbc->server.sock_name); talloc_free(cbc->server.sock_name); @@ -64,29 +62,28 @@ static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn) return 0; } -static int cbsp_srv_cb(struct osmo_stream_srv *conn) +static int cbsp_srv_read_cb(struct osmo_stream_srv *conn, int res, struct msgb *msg) { struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn); - struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); struct osmo_cbsp_decoded *decoded; - struct msgb *msg; - int rc; - /* READ */ - rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->server.msg); - if (rc <= 0) { - if (rc == -EAGAIN || rc == -EINTR) { - /* more data needs to be read */ + if (res <= 0) { + if (res == -EAGAIN || res == -EINTR) { + msgb_free(msg); return 0; - } else if (rc == -EPIPE || rc == -ECONNRESET) { - /* lost connection */ - } else if (rc == 0) { - /* connection closed */ } + /* + if (rc == -EPIPE || rc == -ECONNRESET) { + // lost connection + } else if (rc == 0) { + // connection closed + } */ + msgb_free(msg); osmo_stream_srv_destroy(conn); cbc->server.srv = NULL; return -EBADF; } + OSMO_ASSERT(msg); decoded = osmo_cbsp_decode(conn, msg); if (decoded) { @@ -118,12 +115,15 @@ static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd) return -1; } - srv = osmo_stream_srv_create(cbc, link, fd, cbsp_srv_cb, cbsp_srv_closed_cb, cbc); + srv = osmo_stream_srv_create2(cbc, link, fd, cbc); if (!srv) { LOGP(DCBS, LOGL_ERROR, "Unable to create stream server for %s\n", osmo_sock_get_name2(fd)); return -1; } + osmo_stream_srv_set_read_cb(srv, cbsp_srv_read_cb); + osmo_stream_srv_set_closed_cb(srv, cbsp_srv_closed_cb); + osmo_stream_srv_set_segmentation_cb(srv, osmo_cbsp_segmentation_cb); cbc->server.srv = srv; if (cbc->server.sock_name) @@ -142,11 +142,10 @@ static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd) static int cbsp_client_connect_cb(struct osmo_stream_cli *cli) { struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli); - struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli); if (cbc->client.sock_name) talloc_free(cbc->client.sock_name); - cbc->client.sock_name = osmo_sock_get_name(cbc, ofd->fd); + cbc->client.sock_name = osmo_sock_get_name(cbc, osmo_stream_cli_get_fd(cli)); LOGP(DCBS, LOGL_NOTICE, "CBSP Client connected to CBC: %s\n", cbc->client.sock_name); @@ -166,28 +165,27 @@ static int cbsp_client_disconnect_cb(struct osmo_stream_cli *cli) return 0; } -static int cbsp_client_read_cb(struct osmo_stream_cli *cli) +static int cbsp_client_read_cb(struct osmo_stream_cli *cli, int res, struct msgb *msg) { struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli); - struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli); struct osmo_cbsp_decoded *decoded; - struct msgb *msg = NULL; - int rc; - - /* READ */ - rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->client.msg); - if (rc <= 0) { - if (rc == -EAGAIN || rc == -EINTR) { - /* more data needs to be read */ + + if (res <= 0) { + if (res == -EAGAIN || res == -EINTR) { + msgb_free(msg); return 0; - } else if (rc == -EPIPE || rc == -ECONNRESET) { - /* lost connection */ - } else if (rc == 0) { - /* connection closed */ } + /* + if (rc == -EPIPE || rc == -ECONNRESET) { + // lost connection + } else if (rc == 0) { + // connection closed + } */ + msgb_free(msg); osmo_stream_cli_reconnect(cli); return -EBADF; } + OSMO_ASSERT(msg); decoded = osmo_cbsp_decode(cli, msg); if (decoded) { @@ -245,7 +243,8 @@ int bsc_cbc_link_restart(void) osmo_stream_cli_set_data(cbc->client.cli, cbc); osmo_stream_cli_set_connect_cb(cbc->client.cli, cbsp_client_connect_cb); osmo_stream_cli_set_disconnect_cb(cbc->client.cli, cbsp_client_disconnect_cb); - osmo_stream_cli_set_read_cb(cbc->client.cli, cbsp_client_read_cb); + osmo_stream_cli_set_read_cb2(cbc->client.cli, cbsp_client_read_cb); + osmo_stream_cli_set_segmentation_cb(cbc->client.cli, osmo_cbsp_segmentation_cb); } /* CBC side */ osmo_stream_cli_set_addr(cbc->client.cli, cbc->client.remote_addr.ip); @@ -301,6 +300,13 @@ int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *cbsp) { struct msgb *msg; + if (!cbc->client.cli && !cbc->server.srv) { + LOGP(DCBS, LOGL_INFO, "Discarding Tx CBSP Message Type %s, link is down\n", + get_value_string(cbsp_msg_type_names, cbsp->msg_type)); + talloc_free(cbsp); + return 0; + } + msg = osmo_cbsp_encode(cbc, cbsp); if (!msg) { LOGP(DCBS, LOGL_ERROR, "Unable to encode CBSP Message Type %s: %s\n", @@ -312,349 +318,7 @@ int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *cbsp) osmo_stream_cli_send(cbc->client.cli, msg); else if (cbc->server.srv) osmo_stream_srv_send(cbc->server.srv, msg); - else { - LOGP(DCBS, LOGL_ERROR, "Discarding CBSP Message, link is down: %s\n", msgb_hexdump(msg)); - msgb_free(msg); - } talloc_free(cbsp); return 0; } - -static struct bsc_cbc_link *vty_cbc_data(struct vty *vty) -{ - return bsc_gsmnet->cbc; -} - -/********************************************************************************* - * VTY Interface (Configuration + Introspection) - *********************************************************************************/ - -DEFUN(cfg_cbc, cfg_cbc_cmd, - "cbc", "Configure CBSP Link to Cell Broadcast Centre\n") -{ - vty->node = CBC_NODE; - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_mode, cfg_cbc_mode_cmd, - "mode (server|client|disabled)", - "Set OsmoBSC as CBSP server or client\n" - "CBSP Server: listen for inbound TCP connections from a remote Cell Broadcast Centre\n" - "CBSP Client: establish outbound TCP connection to a remote Cell Broadcast Centre\n" - "Disable CBSP link\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - cbc->mode = get_string_value(bsc_cbc_link_mode_names, argv[0]); - OSMO_ASSERT(cbc->mode >= 0); - - /* Immediately restart/stop CBSP only when coming from a telnet session. The settings from the config file take - * effect in osmo_bsc_main.c's invocation of bsc_cbc_link_restart(). */ - if (vty->type != VTY_FILE) - bsc_cbc_link_restart(); - - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_server, cfg_cbc_server_cmd, - "server", "Configure OsmoBSC's CBSP server role\n") -{ - vty->node = CBC_SERVER_NODE; - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_server_local_ip, cfg_cbc_server_local_ip_cmd, - "local-ip " VTY_IPV46_CMD, - "Set IP Address to listen on for inbound CBSP from a Cell Broadcast Centre\n" - "IPv4 address\n" "IPv6 address\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - osmo_sockaddr_str_from_str(&cbc->server.local_addr, argv[0], cbc->server.local_addr.port); - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_server_local_port, cfg_cbc_server_local_port_cmd, - "local-port <1-65535>", - "Set TCP port to listen on for inbound CBSP from a Cell Broadcast Centre\n" - "CBSP port number (Default: " OSMO_STRINGIFY_VAL(CBSP_TCP_PORT) ")\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - cbc->server.local_addr.port = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_client, cfg_cbc_client_cmd, - "client", "Configure OsmoBSC's CBSP client role\n") -{ - vty->node = CBC_CLIENT_NODE; - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_client_remote_ip, cfg_cbc_client_remote_ip_cmd, - "remote-ip " VTY_IPV46_CMD, - "Set IP Address of the Cell Broadcast Centre, to establish CBSP link to\n" - "IPv4 address\n" "IPv6 address\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - osmo_sockaddr_str_from_str(&cbc->client.remote_addr, argv[0], cbc->client.remote_addr.port); - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_client_remote_port, cfg_cbc_client_remote_port_cmd, - "remote-port <1-65535>", - "Set TCP port of the Cell Broadcast Centre, to establish CBSP link to\n" - "CBSP port number (Default: " OSMO_STRINGIFY_VAL(CBSP_TCP_PORT) ")\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - cbc->client.remote_addr.port = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_client_local_ip, cfg_cbc_client_local_ip_cmd, - "local-ip " VTY_IPV46_CMD, - "Set local bind address for the outbound CBSP link to the Cell Broadcast Centre\n" - "IPv4 address\n" "IPv6 address\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - osmo_sockaddr_str_from_str(&cbc->client.local_addr, argv[0], cbc->client.local_addr.port); - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_client_local_port, cfg_cbc_client_local_port_cmd, - "local-port <1-65535>", - "Set local bind port for the outbound CBSP link to the Cell Broadcast Centre\n" - "port number\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - cbc->client.local_addr.port = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_client_no_local_ip, cfg_cbc_client_no_local_ip_cmd, - "no local-ip", - NO_STR "Remove local IP address bind config for the CBSP client mode\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - cbc->client.local_addr = (struct osmo_sockaddr_str){ .port = cbc->client.local_addr.port }; - return CMD_SUCCESS; -} - -DEFUN(cfg_cbc_client_no_local_port, cfg_cbc_client_no_local_port_cmd, - "no local-port", - NO_STR "Remove local TCP port bind config for the CBSP client mode\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - cbc->client.local_addr.port = 0; - return CMD_SUCCESS; -} - -static struct cmd_node cbc_node = { - CBC_NODE, - "%s(config-cbc)# ", - 1, -}; - -static struct cmd_node cbc_server_node = { - CBC_SERVER_NODE, - "%s(config-cbc-server)# ", - 1, -}; - -static struct cmd_node cbc_client_node = { - CBC_CLIENT_NODE, - "%s(config-cbc-client)# ", - 1, -}; - -static int config_write_cbc(struct vty *vty) -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - - bool default_server_local; - bool default_client_remote; - bool default_client_local; - - default_server_local = !osmo_sockaddr_str_cmp(&cbc->server.local_addr, - &bsc_cbc_default_server_local_addr); - default_client_remote = !osmo_sockaddr_str_is_set(&cbc->client.remote_addr); - default_client_local = !osmo_sockaddr_str_is_set(&cbc->client.local_addr); - - /* If all reflects default values, skip the 'cbc' section */ - if (cbc->mode == BSC_CBC_LINK_MODE_DISABLED - && default_server_local - && default_client_remote && default_client_local) - return 0; - - vty_out(vty, "cbc%s", VTY_NEWLINE); - vty_out(vty, " mode %s%s", bsc_cbc_link_mode_name(cbc->mode), VTY_NEWLINE); - - if (!default_server_local) { - vty_out(vty, " server%s", VTY_NEWLINE); - - if (strcmp(cbc->server.local_addr.ip, bsc_cbc_default_server_local_addr.ip)) - vty_out(vty, " local-ip %s%s", cbc->server.local_addr.ip, VTY_NEWLINE); - if (cbc->server.local_addr.port != bsc_cbc_default_server_local_addr.port) - vty_out(vty, " local-port %u%s", cbc->server.local_addr.port, VTY_NEWLINE); - } - - if (!(default_client_remote && default_client_local)) { - vty_out(vty, " client%s", VTY_NEWLINE); - - if (osmo_sockaddr_str_is_set(&cbc->client.remote_addr)) { - vty_out(vty, " remote-ip %s%s", cbc->client.remote_addr.ip, VTY_NEWLINE); - if (cbc->client.remote_addr.port != CBSP_TCP_PORT) - vty_out(vty, " remote-port %u%s", cbc->client.remote_addr.port, VTY_NEWLINE); - } - - if (cbc->client.local_addr.ip[0]) - vty_out(vty, " local-ip %s%s", cbc->client.local_addr.ip, VTY_NEWLINE); - if (cbc->client.local_addr.port) - vty_out(vty, " local-port %u%s", cbc->client.local_addr.port, VTY_NEWLINE); - } - - return 0; -} - -DEFUN(show_cbc, show_cbc_cmd, - "show cbc", - SHOW_STR "Display state of CBC / CBSP\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - - switch (cbc->mode) { - case BSC_CBC_LINK_MODE_DISABLED: - vty_out(vty, "CBSP link is disabled%s", VTY_NEWLINE); - break; - - case BSC_CBC_LINK_MODE_SERVER: - vty_out(vty, "OsmoBSC is configured as CBSP Server on " OSMO_SOCKADDR_STR_FMT "%s", - OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->server.local_addr), VTY_NEWLINE); - vty_out(vty, "CBSP Server Connection: %s%s", - cbc->server.sock_name ? cbc->server.sock_name : "Disconnected", VTY_NEWLINE); - break; - - case BSC_CBC_LINK_MODE_CLIENT: - vty_out(vty, "OsmoBSC is configured as CBSP Client to remote CBC at " OSMO_SOCKADDR_STR_FMT "%s", - OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->client.remote_addr), VTY_NEWLINE); - vty_out(vty, "CBSP Client Connection: %s%s", - cbc->client.sock_name ? cbc->client.sock_name : "Disconnected", VTY_NEWLINE); - break; - } - return CMD_SUCCESS; -} - -/* --- Deprecated 'cbc' commands for backwards compat --- */ - -DEFUN_DEPRECATED(cfg_cbc_remote_ip, cfg_cbc_remote_ip_cmd, - "remote-ip A.B.C.D", - "IP Address of the Cell Broadcast Centre\n" - "IP Address of the Cell Broadcast Centre\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - vty_out(vty, "%% cbc/remote-ip config is deprecated, instead use cbc/client/remote-ip and cbc/ mode%s", - VTY_NEWLINE); - osmo_sockaddr_str_from_str(&cbc->client.remote_addr, argv[0], cbc->client.remote_addr.port); - cbc->mode = BSC_CBC_LINK_MODE_CLIENT; - if (vty->type != VTY_FILE) - bsc_cbc_link_restart(); - return CMD_SUCCESS; -} -DEFUN_DEPRECATED(cfg_cbc_no_remote_ip, cfg_cbc_no_remote_ip_cmd, - "no remote-ip", - NO_STR "Remove IP address of CBC; disables outbound CBSP connections\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - vty_out(vty, "%% cbc/remote-ip config is deprecated, instead use cbc/client/remote-ip and cbc/mode%s", - VTY_NEWLINE); - if (cbc->mode == BSC_CBC_LINK_MODE_CLIENT) { - cbc->mode = BSC_CBC_LINK_MODE_DISABLED; - if (vty->type != VTY_FILE) - bsc_cbc_link_restart(); - } - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_cbc_remote_port, cfg_cbc_remote_port_cmd, - "remote-port <1-65535>", - "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n" - "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - vty_out(vty, "%% cbc/remote-port config is deprecated, instead use cbc/client/remote-port%s", - VTY_NEWLINE); - cbc->client.remote_addr.port = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_cbc_listen_port, cfg_cbc_listen_port_cmd, - "listen-port <1-65535>", - "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n" - "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - vty_out(vty, "%% cbc/listen-port config is deprecated, instead use cbc/server/local-port and cbc/mode%s", - VTY_NEWLINE); - cbc->mode = BSC_CBC_LINK_MODE_SERVER; - cbc->server.local_addr.port = atoi(argv[0]); - if (vty->type != VTY_FILE) - bsc_cbc_link_restart(); - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_cbc_no_listen_port, cfg_cbc_no_listen_port_cmd, - "no listen-port", - NO_STR "Remove CBSP Listen Port; disables inbound CBSP connections\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - vty_out(vty, "%% cbc/listen-port config is deprecated, instead use cbc/server/local-port and cbc/mode%s", - VTY_NEWLINE); - if (cbc->mode == BSC_CBC_LINK_MODE_SERVER) { - cbc->mode = BSC_CBC_LINK_MODE_DISABLED; - if (vty->type != VTY_FILE) - bsc_cbc_link_restart(); - } - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_cbc_listen_ip, cfg_cbc_listen_ip_cmd, - "listen-ip A.B.C.D", - "Local IP Address where BSC listens for incoming CBC connections (Default: 127.0.0.1)\n" - "Local IP Address where BSC listens for incoming CBC connections\n") -{ - struct bsc_cbc_link *cbc = vty_cbc_data(vty); - vty_out(vty, "%% cbc/listen-ip config is deprecated, instead use cbc/server/local-ip%s", - VTY_NEWLINE); - osmo_sockaddr_str_from_str(&cbc->server.local_addr, argv[0], cbc->server.local_addr.port); - return CMD_SUCCESS; -} - -void cbc_vty_init(void) -{ - install_element_ve(&show_cbc_cmd); - - install_element(CONFIG_NODE, &cfg_cbc_cmd); - install_node(&cbc_node, config_write_cbc); - install_element(CBC_NODE, &cfg_cbc_mode_cmd); - - install_element(CBC_NODE, &cfg_cbc_server_cmd); - install_node(&cbc_server_node, NULL); - install_element(CBC_SERVER_NODE, &cfg_cbc_server_local_ip_cmd); - install_element(CBC_SERVER_NODE, &cfg_cbc_server_local_port_cmd); - - install_element(CBC_NODE, &cfg_cbc_client_cmd); - install_node(&cbc_client_node, NULL); - install_element(CBC_CLIENT_NODE, &cfg_cbc_client_remote_ip_cmd); - install_element(CBC_CLIENT_NODE, &cfg_cbc_client_remote_port_cmd); - install_element(CBC_CLIENT_NODE, &cfg_cbc_client_local_ip_cmd); - install_element(CBC_CLIENT_NODE, &cfg_cbc_client_local_port_cmd); - install_element(CBC_CLIENT_NODE, &cfg_cbc_client_no_local_ip_cmd); - install_element(CBC_CLIENT_NODE, &cfg_cbc_client_no_local_port_cmd); - - /* Deprecated, for backwards compat */ - install_element(CBC_NODE, &cfg_cbc_remote_ip_cmd); - install_element(CBC_NODE, &cfg_cbc_no_remote_ip_cmd); - install_element(CBC_NODE, &cfg_cbc_remote_port_cmd); - install_element(CBC_NODE, &cfg_cbc_listen_port_cmd); - install_element(CBC_NODE, &cfg_cbc_no_listen_port_cmd); - install_element(CBC_NODE, &cfg_cbc_listen_ip_cmd); -} diff --git a/src/osmo-bsc/chan_alloc.c b/src/osmo-bsc/chan_alloc.c index 3569d4eaa..cc8da3daf 100644 --- a/src/osmo-bsc/chan_alloc.c +++ b/src/osmo-bsc/chan_alloc.c @@ -44,8 +44,12 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts) struct gsm_bts_trx *trx; llist_for_each_entry(trx, &bts->trx_list, list) { + struct load_counter *ll = &trx->lchan_load; int i; + /* init per-TRX load counters */ + memset(ll, 0, sizeof(*ll)); + /* skip administratively deactivated transceivers */ if (!trx_is_usable(trx)) continue; @@ -59,38 +63,32 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts) if (!nm_is_running(&ts->mo.nm_state)) continue; - /* Dynamic timeslots have to be counted separately - * when not in TCH/F or TCH/H mode because they don't - * have an lchan's allocated to them. At the same time, - * dynamic timeslots in NONE and PDCH modes are same - * as in UNUSED mode from the CS channel load perspective - * beause they can be switched to TCH mode at any moment. - * I.e. they are "available" for TCH. */ - if ((ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH || + /* A dynamic timeslot currently in PDCH mode are available as TCH or SDCCH8, because they can be switched + * to TCH or SDCCH mode at any moment. Count TCH/F_TCH/H_SDCCH8_PDCH as one total timeslot, even though it may + * be switched to TCH/H and would then count as two -- hence opt for pessimistic load. */ + if ((ts->pchan_on_init == GSM_PCHAN_OSMO_DYN || ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH) && (ts->pchan_is == GSM_PCHAN_NONE || ts->pchan_is == GSM_PCHAN_PDCH)) { + ll->total++; pl->total++; + /* Below loop would not count this timeslot, since in PDCH mode it has no usable + * timeslots. But let's make it clear that the timeslot must not be counted again: */ + continue; } - /* Count allocated logical channels. - * Note: A GSM_PCHAN_TCH_F_TCH_H_PDCH can be switched - * to a single TCH/F or to two TCH/H. So when it's in - * the TCH/H mode, total number of available channels - * is 1 more than when it's in the TCH/F mode. - * I.e. "total" count will fluctuate depending on - * whether GSM_PCHAN_TCH_F_TCH_H_PDCH timeslot is - * in TCH/F or TCH/H (or in NONE/PDCH) mode. */ - ts_for_each_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) { /* don't even count CBCH slots in total */ if (lchan->type == GSM_LCHAN_CBCH) continue; + ll->total++; pl->total++; /* lchans under a BORKEN TS should be counted * as used just as BORKEN lchans under a normal TS */ if (ts->fi->state == TS_ST_BORKEN) { + ll->used++; pl->used++; continue; } @@ -99,6 +97,7 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts) case LCHAN_ST_UNUSED: break; default: + ll->used++; pl->used++; break; } @@ -129,36 +128,36 @@ static void chan_load_stat_set(enum gsm_phys_chan_config pchan, case GSM_PCHAN_UNKNOWN: break; case GSM_PCHAN_CCCH_SDCCH4: - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_CCCH_SDCCH4_USED], lc->used); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL], lc->total); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_USED), lc->used); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL), lc->total); break; case GSM_PCHAN_TCH_F: - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_USED], lc->used); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_TOTAL], lc->total); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_USED), lc->used); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_TOTAL), lc->total); break; case GSM_PCHAN_TCH_H: - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_H_USED], lc->used); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_H_TOTAL], lc->total); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_H_USED), lc->used); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_H_TOTAL), lc->total); break; case GSM_PCHAN_SDCCH8_SACCH8C: - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_SDCCH8_USED], lc->used); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_SDCCH8_TOTAL], lc->total); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_USED), lc->used); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_TOTAL), lc->total); break; case GSM_PCHAN_TCH_F_PDCH: - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_PDCH_USED], lc->used); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_PDCH_TOTAL], lc->total); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_PDCH_USED), lc->used); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_PDCH_TOTAL), lc->total); break; case GSM_PCHAN_CCCH_SDCCH4_CBCH: - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_USED], lc->used); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL], lc->total); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_USED), lc->used); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL), lc->total); break; case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_SDCCH8_CBCH_USED], lc->used); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL], lc->total); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_CBCH_USED), lc->used); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL), lc->total); break; - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_TCH_H_PDCH_USED], lc->used); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_TCH_H_PDCH_TOTAL], lc->total); + case GSM_PCHAN_OSMO_DYN: + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_OSMO_DYN_USED), lc->used); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_OSMO_DYN_TOTAL), lc->total); break; default: LOG_BTS(bts, DRLL, LOGL_NOTICE, "Unknown channel type %d\n", pchan); @@ -240,7 +239,7 @@ bts_update_t3122_chan_load(struct gsm_bts *bts) (load & 0xffffff00) >> 8, (load & 0xff) / 10); bts->chan_load_avg = ((load & 0xffffff00) >> 8); OSMO_ASSERT(bts->chan_load_avg <= 100); - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_LOAD_AVERAGE], bts->chan_load_avg); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_LOAD_AVERAGE), bts->chan_load_avg); /* Calculate new T3122 wait indicator. */ wait_ind = ((used / total) * max_wait_ind); @@ -252,5 +251,5 @@ bts_update_t3122_chan_load(struct gsm_bts *bts) LOG_BTS(bts, DRLL, LOGL_DEBUG, "T3122 wait indicator set to %"PRIu64" seconds\n", wait_ind); bts->T3122 = (uint8_t)wait_ind; - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_T3122], wait_ind); + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_T3122), wait_ind); } diff --git a/src/osmo-bsc/chan_counts.c b/src/osmo-bsc/chan_counts.c new file mode 100644 index 000000000..bf863688d --- /dev/null +++ b/src/osmo-bsc/chan_counts.c @@ -0,0 +1,310 @@ +/* count total, allocated and free channels of all types. + * + * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr <nhofmeyr@sysmocom.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/gsm/gsm_utils.h> + +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bts_trx.h> +#include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/chan_counts.h> +#include <osmocom/bsc/bsc_stats.h> +#include <osmocom/bsc/signal.h> + +static const unsigned int lchans_per_pchan[_GSM_PCHAN_MAX][_GSM_LCHAN_MAX] = { + [GSM_PCHAN_NONE] = {0}, + [GSM_PCHAN_CCCH] = { [GSM_LCHAN_CCCH] = 1, }, + [GSM_PCHAN_PDCH] = { [GSM_LCHAN_PDTCH] = 1, }, + [GSM_PCHAN_CCCH_SDCCH4] = { + [GSM_LCHAN_CCCH] = 1, + [GSM_LCHAN_SDCCH] = 3, + }, + [GSM_PCHAN_TCH_F] = { [GSM_LCHAN_TCH_F] = 1, }, + [GSM_PCHAN_TCH_H] = { [GSM_LCHAN_TCH_H] = 2, }, + [GSM_PCHAN_SDCCH8_SACCH8C] = { [GSM_LCHAN_SDCCH] = 8, }, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = { + [GSM_LCHAN_CCCH] = 1, + [GSM_LCHAN_SDCCH] = 3, + [GSM_LCHAN_CBCH] = 1, + }, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = { + [GSM_LCHAN_SDCCH] = 8, + [GSM_LCHAN_CBCH] = 1, + }, + [GSM_PCHAN_OSMO_DYN] = { + [GSM_LCHAN_TCH_F] = 1, + [GSM_LCHAN_TCH_H] = 2, + [GSM_LCHAN_SDCCH] = 8, + [GSM_LCHAN_PDTCH] = 1, + }, + [GSM_PCHAN_TCH_F_PDCH] = { + [GSM_LCHAN_TCH_F] = 1, + [GSM_LCHAN_PDTCH] = 1, + }, +}; + +static inline void chan_counts_per_pchan_add(struct chan_counts *dst, + enum chan_counts_dim1 dim1, enum chan_counts_dim2 dim2, + enum gsm_phys_chan_config pchan) +{ + int i; + for (i = 0; i < _GSM_LCHAN_MAX; i++) + dst->val[dim1][dim2][i] += lchans_per_pchan[pchan][i]; +} + +static const char *chan_counts_dim1_name[_CHAN_COUNTS1_NUM] = { + [CHAN_COUNTS1_ALL] = "all", + [CHAN_COUNTS1_STATIC] = "static", + [CHAN_COUNTS1_DYNAMIC] = "dynamic", +}; + +static const char *chan_counts_dim2_name[_CHAN_COUNTS2_NUM] = { + [CHAN_COUNTS2_MAX_TOTAL] = "max", + [CHAN_COUNTS2_CURRENT_TOTAL] = "current", + [CHAN_COUNTS2_ALLOCATED] = "alloc", + [CHAN_COUNTS2_FREE] = "free", +}; + +int chan_counts_to_str_buf(char *buf, size_t buflen, const struct chan_counts *c) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + int i1, i2, i3; + OSMO_STRBUF_PRINTF(sb, "{"); + for (i1 = 0; i1 < _CHAN_COUNTS1_NUM; i1++) { + for (i2 = 0; i2 < _CHAN_COUNTS2_NUM; i2++) { + bool p12 = false; + + for (i3 = 0; i3 < _GSM_LCHAN_MAX; i3++) { + + int v = c->val[i1][i2][i3]; + if (v) { + if (!p12) { + p12 = true; + OSMO_STRBUF_PRINTF(sb, " %s.%s{", chan_counts_dim1_name[i1], + chan_counts_dim2_name[i2]); + } + OSMO_STRBUF_PRINTF(sb, " %s=%d", gsm_chan_t_name(i3), v); + } + } + + if (p12) + OSMO_STRBUF_PRINTF(sb, " }"); + } + } + OSMO_STRBUF_PRINTF(sb, " }"); + return sb.chars_needed; +} + +char *chan_counts_to_str_c(void *ctx, const struct chan_counts *c) +{ + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", chan_counts_to_str_buf, c) +} + +void chan_counts_for_ts(struct chan_counts *ts_counts, const struct gsm_bts_trx_ts *ts) +{ + const struct gsm_lchan *lchan; + bool ts_is_dynamic; + + chan_counts_zero(ts_counts); + + if (!ts_is_usable(ts)) + return; + + /* Count the full potential nr of lchans for dynamic TS */ + chan_counts_per_pchan_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_MAX_TOTAL, ts->pchan_on_init); + + switch (ts->pchan_on_init) { + case GSM_PCHAN_TCH_F_PDCH: + case GSM_PCHAN_OSMO_DYN: + ts_is_dynamic = true; + break; + default: + ts_is_dynamic = false; + break; + } + + if (ts_is_dynamic && ts->pchan_is == GSM_PCHAN_PDCH) { + /* Dynamic timeslots in PDCH mode can become TCH or SDCCH immediately, + * so set CURRENT_TOTAL = MAX_TOTAL. */ + chan_counts_dim3_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL, + ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_MAX_TOTAL); + } else { + /* Static TS, or dyn TS that are currently fixed on a specific pchan: count lchans for the + * current pchan mode. */ + chan_counts_per_pchan_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL, ts->pchan_is); + } + + /* Count currently allocated lchans */ + ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) { + if (!lchan_state_is(lchan, LCHAN_ST_UNUSED)) + ts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_ALLOCATED][lchan->type]++; + } + + chan_counts_dim3_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_FREE, + ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL); + chan_counts_dim3_sub(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_FREE, + ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_ALLOCATED); + + if (ts_is_dynamic) + chan_counts_dim2_add(ts_counts, CHAN_COUNTS1_DYNAMIC, ts_counts, CHAN_COUNTS1_ALL); + else + chan_counts_dim2_add(ts_counts, CHAN_COUNTS1_STATIC, ts_counts, CHAN_COUNTS1_ALL); +} + +static void chan_counts_diff(struct chan_counts *diff, const struct chan_counts *left, const struct chan_counts *right) +{ + chan_counts_zero(diff); + chan_counts_add(diff, right); + chan_counts_sub(diff, left); +} + +static void _chan_counts_ts_update(struct gsm_bts_trx_ts *ts, const struct chan_counts *ts_new_counts) +{ + struct chan_counts diff; + + chan_counts_diff(&diff, &ts->chan_counts, ts_new_counts); + if (chan_counts_is_zero(&diff)) + return; + + ts->chan_counts = *ts_new_counts; + chan_counts_add(&ts->trx->chan_counts, &diff); + chan_counts_add(&ts->trx->bts->chan_counts, &diff); + chan_counts_add(&bsc_gsmnet->chan_counts, &diff); + + all_allocated_update_bts(ts->trx->bts); + all_allocated_update_bsc(); + + LOGP(DLGLOBAL, LOGL_DEBUG, "change in channel counts: ts %u-%u-%u: %s\n", + ts->trx->bts->nr, ts->trx->nr, ts->nr, chan_counts_to_str_c(OTC_SELECT, &diff)); + LOGP(DLGLOBAL, LOGL_DEBUG, "bsc channel counts: %s\n", + chan_counts_to_str_c(OTC_SELECT, &bsc_gsmnet->chan_counts)); +} + +/* Re-count this TS, and update ts->chan_counts. If the new ts->chan_counts differ, propagate the difference to + * trx->chan_counts, bts->chan_counts and gsm_network->chan_counts. */ +void chan_counts_ts_update(struct gsm_bts_trx_ts *ts) +{ + struct chan_counts ts_new_counts; + chan_counts_for_ts(&ts_new_counts, ts); + _chan_counts_ts_update(ts, &ts_new_counts); +} + +void chan_counts_ts_clear(struct gsm_bts_trx_ts *ts) +{ + struct chan_counts ts_new_counts = {0}; + _chan_counts_ts_update(ts, &ts_new_counts); +} + +void chan_counts_trx_update(struct gsm_bts_trx *trx) +{ + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + chan_counts_ts_update(ts); + } +} + +static int chan_counts_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) +{ + struct nm_running_chg_signal_data *nsd; + struct gsm_bts_trx *trx; + if (signal != S_NM_RUNNING_CHG) + return 0; + nsd = signal_data; + switch (nsd->obj_class) { + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *)nsd->obj; + break; + case NM_OC_BASEB_TRANSC: + trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj); + break; + default: + return 0; + } + chan_counts_trx_update(trx); + return 0; +} + +void chan_counts_sig_init(void) +{ + osmo_signal_register_handler(SS_NM, chan_counts_sig_cb, NULL); +} + +void chan_counts_bsc_verify(void) +{ + struct gsm_bts *bts; + struct chan_counts bsc_counts = {0}; + struct chan_counts diff; + + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + struct gsm_bts_trx *trx; + struct chan_counts bts_counts = {0}; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct chan_counts trx_counts = {0}; + int i; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct chan_counts ts_counts; + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + chan_counts_for_ts(&ts_counts, ts); + + chan_counts_diff(&diff, &ts->chan_counts, &ts_counts); + if (!chan_counts_is_zero(&diff)) { + LOGP(DLGLOBAL, LOGL_ERROR, + "internal error in channel counts, on bts-trx-ts %u-%u-%u, fixing." + " diff: %s\n", + bts->nr, trx->nr, ts->nr, + chan_counts_to_str_c(OTC_SELECT, &diff)); + ts->chan_counts = ts_counts; + } + + chan_counts_add(&trx_counts, &ts_counts); + } + + chan_counts_diff(&diff, &trx->chan_counts, &trx_counts); + if (!chan_counts_is_zero(&diff)) { + LOGP(DLGLOBAL, LOGL_ERROR, "internal error in channel counts, on bts-trx %u-%u, fixing." + " diff: %s\n", + bts->nr, trx->nr, chan_counts_to_str_c(OTC_SELECT, &diff)); + trx->chan_counts = trx_counts; + } + + chan_counts_add(&bts_counts, &trx_counts); + } + + chan_counts_diff(&diff, &bts->chan_counts, &bts_counts); + if (!chan_counts_is_zero(&diff)) { + LOGP(DLGLOBAL, LOGL_ERROR, "internal error in channel counts, on bts %u, fixing. diff: %s\n", + bts->nr, chan_counts_to_str_c(OTC_SELECT, &diff)); + bts->chan_counts = bts_counts; + } + + chan_counts_add(&bsc_counts, &bts_counts); + } + + chan_counts_diff(&diff, &bsc_gsmnet->chan_counts, &bsc_counts); + if (!chan_counts_is_zero(&diff)) { + LOGP(DLGLOBAL, LOGL_ERROR, "internal error in overall channel counts, fixing. diff: %s\n", + chan_counts_to_str_c(OTC_SELECT, &diff)); + bsc_gsmnet->chan_counts = bsc_counts; + } +} diff --git a/src/osmo-bsc/codec_pref.c b/src/osmo-bsc/codec_pref.c index 58a7867c5..454b00bea 100644 --- a/src/osmo-bsc/codec_pref.c +++ b/src/osmo-bsc/codec_pref.c @@ -61,8 +61,7 @@ static int full_rate_from_perm_spch(bool * full_rate, return 0; } -/* Helper function for match_codec_pref(), looks up a matching chan mode for - * a given permitted speech value */ +/* Look 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) { @@ -88,8 +87,7 @@ static enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech spe } } -/* Helper function for match_codec_pref(), looks up a matching permitted speech - * value for a given msc audio codec pref */ +/* Look 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) { @@ -125,10 +123,9 @@ static enum gsm0808_permitted_speech audio_support_to_gsm88(const struct gsm_aud } } -/* Helper function for 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) */ +/* Test 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_speech_codec **sc_match, const struct gsm0808_speech_codec_list *scl, const struct gsm0808_channel_type *ct, @@ -177,37 +174,48 @@ static bool test_codec_pref(const struct gsm0808_speech_codec **sc_match, return false; } -/* Helper function to check if the given permitted speech value is supported - * by the BTS. (vty option bts->codec-support). */ -static bool test_codec_support_bts(const struct gsm_bts *bts, uint8_t perm_spch) +static bool test_codec_support_bts_rate(const struct gsm_bts *bts, const bool full_rate) { + unsigned int i; struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + for (i = 0; i < TRX_NR_TS; i++) { + switch (trx->ts[i].pchan_from_config) { + case GSM_PCHAN_OSMO_DYN: + return true; + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_F_PDCH: + if (full_rate) + return true; + break; + case GSM_PCHAN_TCH_H: + if (!full_rate) + return true; + break; + default: + continue; + } + } + } + + return false; +} + +/* Check if the given permitted speech value is supported by the BTS + * (vty option bts->codec-support). */ +static bool test_codec_support_bts(const struct gsm_bts *bts, uint8_t perm_spch) +{ const struct bts_codec_conf *bts_codec = &bts->codec; - unsigned int i; bool full_rate; int rc; - enum gsm_phys_chan_config pchan; - bool rate_match = false; /* Check if the BTS provides a physical channel that matches the * bandwidth of the desired codec. */ rc = full_rate_from_perm_spch(&full_rate, perm_spch); if (rc < 0) return false; - llist_for_each_entry(trx, &bts->trx_list, list) { - for (i = 0; i < TRX_NR_TS; i++) { - pchan = trx->ts[i].pchan_from_config; - if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) - rate_match = true; - else if (full_rate && pchan == GSM_PCHAN_TCH_F) - rate_match = true; - else if (full_rate && pchan == GSM_PCHAN_TCH_F_PDCH) - rate_match = true; - else if (!full_rate && pchan == GSM_PCHAN_TCH_H) - rate_match = true; - } - } - if (!rate_match) + if (!test_codec_support_bts_rate(bts, full_rate)) return false; /* Check codec support */ @@ -217,21 +225,12 @@ static bool test_codec_support_bts(const struct gsm_bts *bts, uint8_t perm_spch) * selectively disable GSM-RF per BTS via VTY. */ return true; case GSM0808_PERM_FR2: - if (bts_codec->efr) - return true; - break; + return (bool)bts_codec->efr; case GSM0808_PERM_FR3: - if (bts_codec->amr) - return true; - break; - case GSM0808_PERM_HR1: - if (bts_codec->hr) - return true; - break; case GSM0808_PERM_HR3: - if (bts_codec->amr) - return true; - break; + return (bool)bts_codec->amr; + case GSM0808_PERM_HR1: + return (bool)bts_codec->hr; default: return false; } @@ -308,8 +307,8 @@ static int match_amr_s15_s0(struct channel_mode_and_rate *ch_mode_rate, const st return 0; } -/*! Match the codec preferences from local config with a received codec preferences IEs received from the - * MSC and the BTS' codec configuration. +/*! Match the codec preferences from local config with codec preference IEs + * received from the MSC and the BTS' codec configuration. * \param[out] ch_mode_rate resulting codec and rate information * \param[in] ct GSM 08.08 channel type received from MSC. * \param[in] scl GSM 08.08 speech codec list received from MSC (optional). @@ -336,7 +335,7 @@ int match_codec_pref(struct channel_mode_and_rate *ch_mode_rate, * indeed available with the current BTS and MSC configuration */ for (i = 0; i < msc->audio_length; i++) { /* Pick a permitted speech value from the global codec configuration list */ - perm_spch = audio_support_to_gsm88(msc->audio_support[i]); + perm_spch = audio_support_to_gsm88(&msc->audio_support[i]); /* Determine if the result is a half or full rate codec */ rc = full_rate_from_perm_spch(&full_rate, perm_spch); @@ -376,7 +375,7 @@ int match_codec_pref(struct channel_mode_and_rate *ch_mode_rate, break; } - /* Exit without result, in case no match can be deteched */ + /* Exit without result, in case no match can be detected */ if (!match) { ch_mode_rate->chan_mode = GSM48_CMODE_SIGN; ch_mode_rate->chan_rate = CH_RATE_SDCCH; @@ -407,7 +406,7 @@ void gen_bss_supported_codec_list(struct gsm0808_speech_codec_list *scl, for (i = 0; i < msc->audio_length; i++) { /* Pick a permitted speech value from the global codec configuration list */ - perm_spch = audio_support_to_gsm88(msc->audio_support[i]); + perm_spch = audio_support_to_gsm88(&msc->audio_support[i]); /* Check this permitted speech value against the BTS specific parameters. * if the BTS does not support the codec, try the next one */ @@ -422,8 +421,8 @@ void gen_bss_supported_codec_list(struct gsm0808_speech_codec_list *scl, /* AMR (HR/FR version 3) is the only codec that requires a codec * configuration (S0-S15). Determine the current configuration and update * the cfg flag. */ - if (msc->audio_support[i]->ver == 3) - scl->codec[scl->len].cfg = gen_bss_supported_amr_s15_s0(msc, bts, msc->audio_support[i]->hr); + if (msc->audio_support[i].ver == 3) + scl->codec[scl->len].cfg = gen_bss_supported_amr_s15_s0(msc, bts, msc->audio_support[i].hr); scl->len++; } @@ -482,22 +481,28 @@ int check_codec_pref(struct llist_head *mscs) rc = -1; } - bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_full.gsm48_ie; - rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie); - if (rc_rate < 0) { - LOGP(DMSC, LOGL_FATAL, - "network amr tch-f mode config of BTS %u does not intersect with amr-config of MSC %u\n", - bts->nr, msc->nr); - rc = -1; + /* Full rate codec check, only if any full rate TS is configured. */ + if (test_codec_support_bts_rate(bts, true)) { + bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_full.gsm48_ie; + rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie); + if (rc_rate < 0) { + LOGP(DMSC, LOGL_FATAL, + "network amr tch-f mode config of BTS %u does not intersect with amr-config of MSC %u\n", + bts->nr, msc->nr); + rc = -1; + } } - bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_half.gsm48_ie; - rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie); - if (rc_rate < 0) { - LOGP(DMSC, LOGL_FATAL, - "network amr tch-h mode config of BTS %u does not intersect with amr-config of MSC %u\n", - bts->nr, msc->nr); - rc = -1; + /* Half rate codec check, only if any half rate TS is configured. */ + if (test_codec_support_bts_rate(bts, false)) { + bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_half.gsm48_ie; + rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie); + if (rc_rate < 0) { + LOGP(DMSC, LOGL_FATAL, + "network amr tch-h mode config of BTS %u does not intersect with amr-config of MSC %u\n", + bts->nr, msc->nr); + rc = -1; + } } } } diff --git a/src/osmo-bsc/data_rate_pref.c b/src/osmo-bsc/data_rate_pref.c new file mode 100644 index 000000000..44a733fb2 --- /dev/null +++ b/src/osmo-bsc/data_rate_pref.c @@ -0,0 +1,165 @@ +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Oliver Smith + * + * 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/gsm/protocol/gsm_08_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/bsc/data_rate_pref.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/lchan.h> + +static int gsm0808_data_rate_transp_to_gsm0858(enum gsm0808_data_rate_transp rate) +{ + switch (rate) { + case GSM0808_DATA_RATE_TRANSP_32k0: + return RSL_CMOD_CSD_T_32k0; + case GSM0808_DATA_RATE_TRANSP_28k8: + return RSL_CMOD_CSD_T_29k0; + case GSM0808_DATA_RATE_TRANSP_14k4: + return RSL_CMOD_CSD_T_14k4; + case GSM0808_DATA_RATE_TRANSP_9k6: + return RSL_CMOD_CSD_T_9k6; + case GSM0808_DATA_RATE_TRANSP_4k8: + return RSL_CMOD_CSD_T_4k8; + case GSM0808_DATA_RATE_TRANSP_2k4: + return RSL_CMOD_CSD_T_2k4; + case GSM0808_DATA_RATE_TRANSP_1k2: + return RSL_CMOD_CSD_T_1k2; + case GSM0808_DATA_RATE_TRANSP_600: + return RSL_CMOD_CSD_T_600; + case GSM0808_DATA_RATE_TRANSP_1200_75: + return RSL_CMOD_CSD_T_1200_75; + default: + LOGP(DMSC, LOGL_ERROR, "Unsupported transparent data rate 0x%x\n", rate); + return -1; + } +} + +static int gsm0808_data_rate_transp_to_gsm0408(enum gsm0808_data_rate_transp rate) +{ + switch (rate) { + case GSM0808_DATA_RATE_TRANSP_14k4: + return GSM48_CMODE_DATA_14k5; + case GSM0808_DATA_RATE_TRANSP_9k6: + return GSM48_CMODE_DATA_12k0; + case GSM0808_DATA_RATE_TRANSP_4k8: + return GSM48_CMODE_DATA_6k0; + case GSM0808_DATA_RATE_TRANSP_2k4: + case GSM0808_DATA_RATE_TRANSP_1k2: + case GSM0808_DATA_RATE_TRANSP_600: + case GSM0808_DATA_RATE_TRANSP_1200_75: + return GSM48_CMODE_DATA_3k6; + default: + LOGP(DMSC, LOGL_ERROR, "Unsupported transparent data rate 0x%x\n", rate); + return -1; + } +} + +static int gsm0808_data_rate_non_transp_to_gsm0408(enum gsm0808_data_rate_non_transp rate, bool full_rate) +{ + switch (rate) { + case GSM0808_DATA_RATE_NON_TRANSP_12000_6000: + if (full_rate) + return GSM48_CMODE_DATA_12k0; + return GSM48_CMODE_DATA_6k0; + case GSM0808_DATA_RATE_NON_TRANSP_14k5: + return GSM48_CMODE_DATA_14k5; + case GSM0808_DATA_RATE_NON_TRANSP_12k0: + return GSM48_CMODE_DATA_12k0; + case GSM0808_DATA_RATE_NON_TRANSP_6k0: + return GSM48_CMODE_DATA_6k0; + default: + LOGP(DMSC, LOGL_ERROR, "Unsupported non-transparent data rate 0x%x\n", rate); + return -1; + } +} + +static int gsm0808_data_rate_non_transp_to_gsm0858(enum gsm0808_data_rate_non_transp rate, bool full_rate) +{ + switch (rate) { + case GSM0808_DATA_RATE_NON_TRANSP_12000_6000: + if (full_rate) + return RSL_CMOD_CSD_NT_12k0; + return RSL_CMOD_CSD_NT_6k0; + case GSM0808_DATA_RATE_NON_TRANSP_14k5: + return RSL_CMOD_CSD_NT_14k5; + case GSM0808_DATA_RATE_NON_TRANSP_12k0: + return RSL_CMOD_CSD_NT_12k0; + case GSM0808_DATA_RATE_NON_TRANSP_6k0: + return RSL_CMOD_CSD_NT_6k0; + case GSM0808_DATA_RATE_NON_TRANSP_43k5: + return RSL_CMOD_CSD_NT_43k5; + case GSM0808_DATA_RATE_NON_TRANSP_29k0: + return RSL_CMOD_CSD_NT_28k8; + default: + LOGP(DMSC, LOGL_ERROR, "Unsupported non-transparent data rate 0x%x\n", rate); + return -1; + } +} + +static enum gsm48_chan_mode match_non_transp_data_rate(const struct gsm0808_channel_type *ct, bool full_rate) +{ + /* FIXME: Handle ct->data_rate_allowed too if it is set. Find the best + * match by comparing the preferred ct->data_rate + all allowed + * ct->data_rate_allowed against what's most suitable for the BTS. */ + + return gsm0808_data_rate_non_transp_to_gsm0858(ct->data_rate, full_rate); +} + +/*! Match the GSM 08.08 channel type received from the MSC to suitable data for + * the BTS, the GSM 04.08 channel mode, channel rate (FR/HR) and GSM 08.58 + * data rate. + * \param[out] ch_mode_rate resulting channel rate, channel mode and data rate + * \param[in] ct GSM 08.08 channel type received from MSC. + * \param[in] full_rate true means FR is preferred, false means HR + * \returns 0 on success, -1 in case no match was found */ +int match_data_rate_pref(struct channel_mode_and_rate *ch_mode_rate, + const struct gsm0808_channel_type *ct, + const bool full_rate) +{ + int rc; + *ch_mode_rate = (struct channel_mode_and_rate){}; + ch_mode_rate->chan_rate = full_rate ? CH_RATE_FULL : CH_RATE_HALF; + ch_mode_rate->data_transparent = ct->data_transparent; + + if (ct->data_transparent) { + rc = gsm0808_data_rate_transp_to_gsm0858(ct->data_rate); + if (rc == -1) + return -1; + ch_mode_rate->data_rate.t = rc; + + rc = gsm0808_data_rate_transp_to_gsm0408(ct->data_rate); + if (rc == -1) + return -1; + ch_mode_rate->chan_mode = rc; + } else { + rc = match_non_transp_data_rate(ct, full_rate); + if (rc == -1) + return -1; + ch_mode_rate->data_rate.nt = rc; + + rc = gsm0808_data_rate_non_transp_to_gsm0408(ct->data_rate, full_rate); + if (rc == -1) + return -1; + ch_mode_rate->chan_mode = rc; + } + + return 0; +} diff --git a/src/osmo-bsc/e1_config.c b/src/osmo-bsc/e1_config.c index 9ccbbfd70..dbea3e9e0 100644 --- a/src/osmo-bsc/e1_config.c +++ b/src/osmo-bsc/e1_config.c @@ -90,7 +90,7 @@ int e1_reconfig_trx(struct gsm_bts_trx *trx) if (trx->bts->type == GSM_BTS_TYPE_RBS2000) { struct e1inp_sign_link *oml_link; oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, trx, - trx->rsl_tei, SAPI_OML); + trx->rsl_tei_primary, SAPI_OML); if (!oml_link) { LOG_TRX(trx, DLINP, LOGL_ERROR, "TRX OML link creation failed\n"); return -ENOMEM; @@ -100,14 +100,14 @@ int e1_reconfig_trx(struct gsm_bts_trx *trx) trx->oml_link = oml_link; } rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, - trx, trx->rsl_tei, SAPI_RSL); + trx, trx->rsl_tei_primary, SAPI_RSL); if (!rsl_link) { LOG_TRX(trx, DLINP, LOGL_ERROR, "TRX RSL link creation failed\n"); return -ENOMEM; } - if (trx->rsl_link) - e1inp_sign_link_destroy(trx->rsl_link); - trx->rsl_link = rsl_link; + if (trx->rsl_link_primary) + e1inp_sign_link_destroy(trx->rsl_link_primary); + trx->rsl_link_primary = rsl_link; for (i = 0; i < TRX_NR_TS; i++) e1_reconfig_ts(&trx->ts[i]); @@ -196,8 +196,8 @@ int e1_reconfig_bts(struct gsm_bts *bts) if (bts->oml_link) e1inp_sign_link_destroy(bts->oml_link); bts->oml_link = oml_link; - rc = clock_gettime(CLOCK_MONOTONIC, &tp); - bts->uptime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */ + rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp); + bts->updowntime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */ llist_for_each_entry(trx, &bts->trx_list, list) e1_reconfig_trx(trx); diff --git a/src/osmo-bsc/gsm_04_08_rr.c b/src/osmo-bsc/gsm_04_08_rr.c index a44812618..194432125 100644 --- a/src/osmo-bsc/gsm_04_08_rr.c +++ b/src/osmo-bsc/gsm_04_08_rr.c @@ -46,17 +46,26 @@ #include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/system_information.h> #include <osmocom/bsc/bts.h> - +#include <osmocom/bsc/lchan.h> int gsm48_sendmsg(struct msgb *msg) { if (msg->lchan) - msg->dst = msg->lchan->ts->trx->rsl_link; + msg->dst = rsl_chan_link(msg->lchan); msg->l3h = msg->data; return rsl_data_request(msg, 0); } +int gsm48_sendmsg_unit(struct msgb *msg) +{ + if (msg->lchan) + msg->dst = rsl_chan_link(msg->lchan); + + msg->l3h = msg->data; + return rsl_unit_data_request(msg, 0); +} + /* Section 9.1.8 / Table 9.9 */ struct chreq { uint8_t val; @@ -232,11 +241,11 @@ int get_reason_by_chreq(uint8_t ra, int neci) return GSM_CHREQ_REASON_OTHER; } -static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg) +static int put_mr_config_for_ms(struct msgb *msg, const struct gsm48_multi_rate_conf *mr_conf_filtered, + const struct amr_multirate_conf *mr_modes) { - if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) - msgb_tlv_put(msg, GSM48_IE_MUL_RATE_CFG, lchan->mr_ms_lv[0], - lchan->mr_ms_lv + 1); + msgb_put_u8(msg, GSM48_IE_MUL_RATE_CFG); + return gsm48_multirate_config(msg, mr_conf_filtered, mr_modes->ms_mode, mr_modes->num_modes); } #define CELL_SEL_IND_AFTER_REL_EARCFN_ENTRY (1+16+4+1+1) @@ -244,7 +253,7 @@ static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg) #define CELL_SEL_IND_AFTER_REL_MAX_BYTES OSMO_BYTES_FOR_BITS(CELL_SEL_IND_AFTER_REL_MAX_BITS) /* Generate a CSN.1 encoded "Cell Selection Indicator after release of all TCH and SDCCH" - * as per TF 44.018 version 15.3.0 Table 10.5.2.1e.1. This only generates the "value" + * as per TS 44.018 version 15.3.0 Table 10.5.2.1e.1. This only generates the "value" * part of the IE, not the tag+length wrapper */ static int generate_cell_sel_ind_after_rel(uint8_t *out, unsigned int out_len, const struct gsm_bts *bts) { @@ -299,12 +308,15 @@ static int generate_cell_sel_ind_after_rel(uint8_t *out, unsigned int out_len, c } } +#define REPEAT_RR_RELEASE_UI 3 + /* 7.1.7 and 9.1.7: RR CHANnel RELease */ -int gsm48_send_rr_release(struct gsm_lchan *lchan) +int gsm48_send_rr_release(struct gsm_lchan *lchan, bool ui) { - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL"); + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL"), *msgc; struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); uint8_t *cause; + int n; msg->lchan = lchan; gh->proto_discr = GSM48_PDISC_RR; @@ -313,10 +325,10 @@ int gsm48_send_rr_release(struct gsm_lchan *lchan) cause = msgb_put(msg, 1); cause[0] = lchan->release.rr_cause; - if (lchan->release.is_csfb) { + if (lchan->release.last_eutran_plmn_valid) { uint8_t buf[CELL_SEL_IND_AFTER_REL_MAX_BYTES]; int len; - + /* FIXME: so far we assume all configured neigbhors match last_eutran_plmn */ len = generate_cell_sel_ind_after_rel(buf, sizeof(buf), lchan->ts->trx->bts); if (len == 0) { LOGPLCHAN(lchan, DRR, LOGL_NOTICE, "MSC indicated CSFB Fast Return, but " @@ -325,11 +337,21 @@ int gsm48_send_rr_release(struct gsm_lchan *lchan) msgb_tlv_put(msg, GSM48_IE_CELL_SEL_IND_AFTER_REL, len, buf); } - DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d RR-Cause: 0x%x '%s'\n", - lchan->nr, lchan->type, lchan->release.rr_cause, rr_cause_name(lchan->release.rr_cause)); + DEBUGP(DRR, "%s Tx Channel Release (cause=0x%02x '%s')\n", + gsm_lchan_name(lchan), lchan->release.rr_cause, + rr_cause_name(lchan->release.rr_cause)); - /* Send actual release request to MS */ - return gsm48_sendmsg(msg); + /* Send actual release request to MS (dedicated channel) */ + if (!ui) + return gsm48_sendmsg(msg); + + /* Send actual release request to MS (VGCS channel) */ + for (n = 1; n < REPEAT_RR_RELEASE_UI; n++) { + msgc = msgb_copy(msg, "CHAN RELEASE copy"); + msgc->lchan = lchan; + gsm48_sendmsg_unit(msgc); + } + return gsm48_sendmsg_unit(msg); } int send_siemens_mrpci(struct gsm_lchan *lchan, @@ -358,7 +380,8 @@ int gsm48_send_rr_classmark_enquiry(struct gsm_lchan *lchan) gh->proto_discr = GSM48_PDISC_RR; gh->msg_type = GSM48_MT_RR_CLSM_ENQ; - DEBUGP(DRR, "%s TX CLASSMARK ENQUIRY %u\n", gsm_lchan_name(lchan), msgb_length(msg)); + DEBUGP(DRR, "%s Tx CLASSMARK ENQUIRY (len=%u)\n", + gsm_lchan_name(lchan), msgb_length(msg)); return gsm48_sendmsg(msg); } @@ -368,16 +391,14 @@ int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv) { struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CIPH"); struct gsm48_hdr *gh; - uint8_t ciph_mod_set; + uint8_t ciph_mod_set = 0x00; msg->lchan = lchan; - DEBUGP(DRR, "TX CIPHERING MODE CMD\n"); + DEBUGP(DRR, "%s Tx CIPHERING MODE CMD\n", gsm_lchan_name(lchan)); - if (lchan->encr.alg_id <= RSL_ENC_ALG_A5(0)) - ciph_mod_set = 0; - else - ciph_mod_set = (lchan->encr.alg_id-2)<<1 | 1; + if (lchan->encr.alg_a5_n > 0) + ciph_mod_set = (lchan->encr.alg_a5_n - 1) << 1 | 0x01; gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); gh->proto_discr = GSM48_PDISC_RR; @@ -397,12 +418,12 @@ static void gsm48_cell_desc(struct gsm48_cell_desc *cd, } /*! \brief Encode a TS 04.08 multirate config LV according to 10.5.2.21aa. - * \param[out] lv caller-allocated buffer of 7 bytes. First octet is is length. + * \param[out] msg msgb to append to. * \param[in] mr_conf multi-rate configuration to encode (selected modes). * \param[in] modes array describing the AMR modes. * \param[in] num_modes length of the modes array. * \returns 0 on success, -EINVAL on failure. */ -int gsm48_multirate_config(uint8_t *lv, +int gsm48_multirate_config(struct msgb *msg, const struct gsm48_multi_rate_conf *mr_conf, const struct amr_mode *modes, unsigned int num_modes) { @@ -413,15 +434,17 @@ int gsm48_multirate_config(uint8_t *lv, bool mode_valid; uint8_t *gsm48_ie = (uint8_t *) mr_conf; const struct amr_mode *modes_selected[4]; + uint8_t *len; + uint8_t *data; /* Check if modes for consistency (order and duplicates) */ - for (i = 0; i < num_modes; i++) { - if (i > 0 && modes[i - 1].mode > modes[i].mode) { + for (i = 1; i < num_modes; i++) { + if (modes[i - 1].mode > modes[i].mode) { LOGP(DRR, LOGL_ERROR, "BUG: Multirate codec with inconsistent config (mode order).\n"); return -EINVAL; } - if (i > 0 && modes[i - 1].mode == modes[i].mode) { + if (modes[i - 1].mode == modes[i].mode) { LOGP(DRR, LOGL_ERROR, "BUG: Multirate codec with inconsistent config (duplicate modes).\n"); return -EINVAL; @@ -482,52 +505,91 @@ int gsm48_multirate_config(uint8_t *lv, /* When the caller is not interested in any result, skip the actual * composition of the IE (dry run) */ - if (!lv) + if (!msg) return 0; /* Compose output buffer */ - lv[0] = (num == 1) ? 2 : (num + 2); - memcpy(lv + 1, gsm48_ie, 2); + /* length */ + len = msgb_put(msg, 1); + + /* Write octet 3 (Multirate speech version, NSCB, ICMI, spare, Start mode) + * and octet 4 (Set of AMR codec modes) */ + data = msgb_put(msg, 2); + memcpy(data, gsm48_ie, 2); if (num == 1) - return 0; + goto return_msg; - lv[3] = modes_selected[0]->threshold & 0x3f; - lv[4] = modes_selected[0]->hysteresis << 4; + /* more than 1 mode: write octet 5 and 6: threshold 1 and hysteresis 1 */ + data = msgb_put(msg, 2); + data[0] = modes_selected[0]->threshold & 0x3f; + data[1] = modes_selected[0]->hysteresis << 4; if (num == 2) - return 0; - lv[4] |= (modes_selected[1]->threshold & 0x3f) >> 2; - lv[5] = modes_selected[1]->threshold << 6; - lv[5] |= (modes_selected[1]->hysteresis & 0x0f) << 2; + goto return_msg; + + /* more than 2 modes: complete octet 6 and add octet 7: threshold 2 and hysteresis 2. + * Threshold 2 starts in octet 6. */ + data[1] |= (modes_selected[1]->threshold & 0x3f) >> 2; + /* octet 7 */ + data = msgb_put(msg, 1); + data[0] = modes_selected[1]->threshold << 6; + data[0] |= (modes_selected[1]->hysteresis & 0x0f) << 2; if (num == 3) - return 0; - lv[5] |= (modes_selected[2]->threshold & 0x3f) >> 4; - lv[6] = modes_selected[2]->threshold << 4; - lv[6] |= modes_selected[2]->hysteresis & 0x0f; - + goto return_msg; + + /* four modes: complete octet 7 and add octet 8: threshold 3 and hysteresis 3. + * Threshold 3 starts in octet 7. */ + data[0] |= (modes_selected[2]->threshold & 0x3f) >> 4; + /* octet 8 */ + data = msgb_put(msg, 1); + data[0] = modes_selected[2]->threshold << 4; + data[0] |= modes_selected[2]->hysteresis & 0x0f; + +return_msg: + /* Place written len in the IE length field. msg->tail points one byte after the last data octet, len points at + * the L octet of the TLV. */ + *len = (msg->tail - 1) - len; return 0; } #define GSM48_HOCMD_CCHDESC_LEN 16 /* Chapter 9.1.15: Handover Command */ -struct msgb *gsm48_make_ho_cmd(struct gsm_lchan *new_lchan, uint8_t power_command, uint8_t ho_ref) +struct msgb *gsm48_make_ho_cmd(const struct gsm_lchan *new_lchan, + enum handover_scope ho_scope, bool async, + uint8_t power_command, uint8_t ho_ref) { struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 HO CMD"); struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); struct gsm48_ho_cmd *ho = (struct gsm48_ho_cmd *) msgb_put(msg, sizeof(*ho)); + const struct gsm_bts *bts = new_lchan->ts->trx->bts; gh->proto_discr = GSM48_PDISC_RR; gh->msg_type = GSM48_MT_RR_HANDO_CMD; /* mandatory bits */ - gsm48_cell_desc(&ho->cell_desc, new_lchan->ts->trx->bts); - gsm48_lchan2chan_desc(&ho->chan_desc, new_lchan); + gsm48_cell_desc(&ho->cell_desc, bts); + if (gsm48_lchan2chan_desc(&ho->chan_desc, new_lchan, gsm_ts_tsc(new_lchan->ts), false)) { + msgb_free(msg); + return NULL; + } ho->ho_ref = ho_ref; ho->power_command = power_command; + /* Synchronization Indication, TV (see 3GPP TS 44.018, 9.1.15.1). + * In the case of inter-RAT handover, always include this IE for the sake of + * explicitness. In the case of intra-RAT handover, include this IE only for + * the synchronized handover. If omitted, non-synchronized handover is assumed. */ + if (!async || (ho_scope & HO_INTER_BSC_IN)) { + /* Only the SI field (Non-synchronized/Synchronized) is present. + * TODO: ROT (Report Observed Time Difference), currently 0. + * TODO: NCI (Normal cell indication), currently 0. */ + const uint8_t sync_ind = async ? 0x00 : 0x01; + /* T (4 bit) + V (4 bit), see 3GPP TS 44.018, 10.5.2.39 */ + msgb_v_put(msg, (GSM48_IE_SYNC_IND_HO << 4) | (sync_ind & 0x0f)); + } + if (new_lchan->ts->hopping.enabled) { - struct gsm_bts *bts = new_lchan->ts->trx->bts; struct gsm48_system_information_type_1 *si1; si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1); @@ -536,9 +598,8 @@ struct msgb *gsm48_make_ho_cmd(struct gsm_lchan *new_lchan, uint8_t power_comman GSM48_HOCMD_CCHDESC_LEN, si1->cell_channel_description); } - /* FIXME: optional bits for type of synchronization? */ - msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->tch_mode); + msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->current_ch_mode_rate.chan_mode); /* Mobile Allocation (after time), TLV (see 3GPP TS 44.018, 10.5.2.21) */ if (new_lchan->ts->hopping.enabled) { @@ -547,35 +608,45 @@ struct msgb *gsm48_make_ho_cmd(struct gsm_lchan *new_lchan, uint8_t power_comman new_lchan->ts->hopping.ma_data); } + /* (O) Cipher Mode Setting, TV (see 3GPP TS 44.018, 9.1.15.10). + * Omitted in the case of intra-RAT (GERAN-to-GERAN) handover. + * Shall be included in the case of inter-RAT handover. */ + if (ho_scope & HO_INTER_BSC_IN) { + uint8_t cms = 0x00; + if (new_lchan->encr.alg_a5_n > 0) + cms = (new_lchan->encr.alg_a5_n - 1) << 1 | 1; + /* T (4 bit) + V (4 bit), see 3GPP TS 44.018, 10.5.2.9 */ + msgb_v_put(msg, (GSM48_IE_CIP_MODE_SET_HO << 4) | (cms & 0x0f)); + } + /* in case of multi rate we need to attach a config */ - if (new_lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) - msgb_tlv_put(msg, GSM48_IE_MUL_RATE_CFG, new_lchan->mr_ms_lv[0], - new_lchan->mr_ms_lv + 1); + if (gsm48_chan_mode_to_non_vamos(new_lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) { + if (put_mr_config_for_ms(msg, &new_lchan->current_mr_conf, + (new_lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half)) { + LOG_LCHAN(new_lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n"); + msgb_free(msg); + return NULL; + } + } return msg; } -int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan, - uint8_t power_command, uint8_t ho_ref) -{ - struct msgb *msg = gsm48_make_ho_cmd(new_lchan, power_command, ho_ref); - if (!msg) - return -EINVAL; - msg->lchan = old_lchan; - return gsm48_sendmsg(msg); -} - /* Chapter 9.1.2: Assignment Command */ -int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_command) +int gsm48_send_rr_ass_cmd(struct gsm_lchan *current_lchan, struct gsm_lchan *new_lchan, uint8_t power_command) { + int rc; struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ASS CMD"); struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); struct gsm48_ass_cmd *ass = (struct gsm48_ass_cmd *) msgb_put(msg, sizeof(*ass)); + struct gsm_bts *bts = new_lchan->ts->trx->bts; - DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode); + DEBUGP(DRR, "%s Tx ASSIGNMENT COMMAND (tch_mode=0x%02x)\n", + gsm_lchan_name(current_lchan), + new_lchan->current_ch_mode_rate.chan_mode); - msg->lchan = dest_lchan; + msg->lchan = current_lchan; gh->proto_discr = GSM48_PDISC_RR; gh->msg_type = GSM48_MT_RR_ASS_CMD; @@ -587,26 +658,45 @@ int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, * the chan_desc. But as long as multi-slot configurations * are not used we seem to be fine. */ - gsm48_lchan2chan_desc(&ass->chan_desc, lchan); + rc = gsm48_lchan2chan_desc(&ass->chan_desc, new_lchan, new_lchan->tsc, false); + if (rc) { + msgb_free(msg); + return rc; + } ass->power_command = power_command; /* Cell Channel Description (freq. hopping), TV (see 3GPP TS 44.018, 10.5.2.1b) */ - if (lchan->ts->hopping.enabled) { - uint8_t *chan_desc = msgb_put(msg, 1 + 16); /* tag + fixed length */ - generate_cell_chan_list(chan_desc + 1, dest_lchan->ts->trx->bts); - chan_desc[0] = GSM48_IE_CELL_CH_DESC; + if (new_lchan->ts->hopping.enabled) { + const struct gsm48_system_information_type_1 *si1 = GSM_BTS_SI(bts, 1); + msgb_tv_fixed_put(msg, GSM48_IE_CELL_CH_DESC, + sizeof(si1->cell_channel_description), + si1->cell_channel_description); } - msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode); + msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->current_ch_mode_rate.chan_mode); /* Mobile Allocation (freq. hopping), TLV (see 3GPP TS 44.018, 10.5.2.21) */ - if (lchan->ts->hopping.enabled) { - msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len, - lchan->ts->hopping.ma_data); + if (new_lchan->ts->hopping.enabled) { + msgb_tlv_put(msg, GSM48_IE_MA_AFTER, new_lchan->ts->hopping.ma_len, + new_lchan->ts->hopping.ma_data); } /* in case of multi rate we need to attach a config */ - mr_config_for_ms(lchan, msg); + if (gsm48_chan_mode_to_non_vamos(new_lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) { + int rc = put_mr_config_for_ms(msg, &new_lchan->current_mr_conf, + (new_lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half); + if (rc) { + LOG_LCHAN(current_lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n"); + msgb_free(msg); + return rc; + } + } + + /* For VAMOS, include the TSC Set number in the Extended TSC Set IE. + * We don't put any PS domain related values, only the lowest two CS domain bits. + * Convert from spec conforming "human readable" TSC Set 1-4 to 0-3 on the wire. */ + if (new_lchan->vamos.enabled && new_lchan->tsc_set > 0) + msgb_tv_put(msg, GSM48_IE_EXTENDED_TSC_SET, new_lchan->tsc_set - 1); return gsm48_sendmsg(msg); } @@ -636,29 +726,107 @@ int gsm48_send_rr_app_info(struct gsm_lchan *lchan, uint8_t apdu_id, uint8_t apd /* 9.1.5 Channel mode modify: Modify the mode on the MS side */ int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t mode) { + int rc; struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CHN MOD"); struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); struct gsm48_chan_mode_modify *cmm = (struct gsm48_chan_mode_modify *) msgb_put(msg, sizeof(*cmm)); + struct gsm_bts *bts = lchan->ts->trx->bts; - DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", mode); + DEBUGP(DRR, "%s Tx CHANNEL MODE MODIFY (mode=0x%02x)\n", + gsm_lchan_name(lchan), mode); - lchan->tch_mode = mode; msg->lchan = lchan; gh->proto_discr = GSM48_PDISC_RR; gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF; - /* fill the channel information element, this code - * should probably be shared with rsl_rx_chan_rqd() */ - gsm48_lchan2chan_desc(&cmm->chan_desc, lchan); + rc = gsm48_lchan2chan_desc(&cmm->chan_desc, lchan, lchan->modify.tsc, false); + if (rc) { + msgb_free(msg); + return rc; + } cmm->mode = mode; /* in case of multi rate we need to attach a config */ - mr_config_for_ms(lchan, msg); + if (gsm48_chan_mode_to_non_vamos(lchan->modify.ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) { + int rc = put_mr_config_for_ms(msg, &lchan->modify.mr_conf_filtered, + (lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half); + if (rc) { + LOG_LCHAN(lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n"); + msgb_free(msg); + return rc; + } + } + + if (lchan->modify.info.type_for == LCHAN_TYPE_FOR_VAMOS && lchan->modify.tsc_set > 0) { + /* Add the Extended TSC Set IE. So far we only need a TSC Set sent for VAMOS. + * Convert from spec conforming "human readable" TSC Set 1-4 to 0-3 on the wire */ + msgb_tv_put(msg, GSM48_IE_EXTENDED_TSC_SET, (lchan->modify.tsc_set - 1) & 0x3); + } return gsm48_sendmsg(msg); } +/* TS 44.018 section 9.1.48 */ +int gsm48_send_uplink_release(struct gsm_lchan *lchan, uint8_t cause) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 UL RELEASE"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + msg->lchan = lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_UPLINK_RELEASE; + + msgb_put_u8(msg, cause); + + return gsm48_sendmsg(msg); +} + +/* TS 44.018 section 9.1.46 */ +int gsm48_send_uplink_busy(struct gsm_lchan *lchan) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 UL BUSY"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + msg->lchan = lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_UPLINK_BUSY; + + return gsm48_sendmsg_unit(msg); +} + +/* TS 44.018 section 9.1.47 */ +int gsm48_send_uplink_free(struct gsm_lchan *lchan, uint8_t acc_bit, uint8_t *uic) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 UL FREE"); + struct gsm48_hdr_sh *sh = (struct gsm48_hdr_sh *) msgb_put(msg, sizeof(*sh) + 22); + struct bitvec *bv = bitvec_alloc(22, NULL); + + msg->lchan = lchan; + sh->rr_short_pd = GSM48_PDISC_SH_RR; + sh->msg_type = GSM48_MT_RR_SH_UL_FREE; + sh->l2_header = 0; + + /* < Uplink Access Request bit > */ + bitvec_set_bit(bv, (acc_bit) ? H : L); + + /* { L | H <Uplink Identity Code bit(6) > } */ + if (uic) { + bitvec_set_bit(bv, H); + sh->data[0] = 0x40 | *uic; + } else + bitvec_set_bit(bv, L); + + /* Emergency mode not supported */ + + /* Padding the rest with 0x2B and apply to msg. */ + bitvec_spare_padding(bv, 175); + bitvec_pack(bv, sh->data); + bitvec_free(bv); + + return gsm48_sendmsg_unit(msg); +} + int gsm48_rx_rr_modif_ack(struct msgb *msg) { struct gsm48_hdr *gh = msgb_l3(msg); @@ -668,33 +836,64 @@ int gsm48_rx_rr_modif_ack(struct msgb *msg) LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CHANNEL MODE MODIFY ACK for %s\n", gsm48_chan_mode_name(mod->mode)); - if (mod->mode != msg->lchan->tch_mode) { + if (mod->mode != msg->lchan->modify.ch_mode_rate.chan_mode) { LOG_LCHAN(msg->lchan, LOGL_ERROR, "CHANNEL MODE MODIFY ACK has wrong mode: Wanted: %s Got: %s\n", - gsm48_chan_mode_name(msg->lchan->tch_mode), + gsm48_chan_mode_name(msg->lchan->modify.ch_mode_rate.chan_mode), gsm48_chan_mode_name(mod->mode)); return -1; } - /* update the channel type */ - switch (mod->mode) { - case GSM48_CMODE_SIGN: - msg->lchan->rsl_cmode = RSL_CMOD_SPD_SIGN; - break; - case GSM48_CMODE_SPEECH_V1: - case GSM48_CMODE_SPEECH_EFR: - case GSM48_CMODE_SPEECH_AMR: - msg->lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; - break; - case GSM48_CMODE_DATA_14k5: - case GSM48_CMODE_DATA_12k0: - case GSM48_CMODE_DATA_6k0: - case GSM48_CMODE_DATA_3k6: - msg->lchan->rsl_cmode = RSL_CMOD_SPD_DATA; - break; + return 0; +} + +/* Get the ARFCN from the BCCH channel list by the index "BCCH-FREQ-NCELL i" + * (idx) as described in 3GPP TS 144.018 § 10.5.2.20. The BCCH channel list is + * split into two sub lists, each ascendingly ordered by ARFCN > 0: + * 1) SI* and SI*bis entries + * 2) SI*ter entries (if available) + * ARFCN 0 is at the end of each sub list. + * Both sub lists are stored in one bitvec (nbv), iterate twice through it. */ +int neigh_list_get_arfcn(struct gsm_bts *bts, const struct bitvec *nbv, unsigned int idx) +{ + unsigned int arfcn, i = 0; + + /* First sub list, ARFCN > 0 */ + for (arfcn = 1; arfcn < nbv->data_len * 8; arfcn++) { + if (bitvec_get_bit_pos(nbv, arfcn) == ZERO) + continue; + /* Skip SI*ter */ + if (!band_compatible(bts, arfcn)) + continue; + if (i == idx) + return arfcn; + i++; } - return 0; + /* First sub list, ARFCN == 0 (last position) */ + if (bitvec_get_bit_pos(nbv, 0) == ONE && band_compatible(bts, 0)) { + if (i == idx) + return 0; + i++; + } + + /* Second sub list, ARFCN > 0 */ + for (arfcn = 1; arfcn < nbv->data_len * 8; arfcn++) { + if (bitvec_get_bit_pos(nbv, arfcn) == ZERO) + continue; + /* Require SI*ter */ + if (band_compatible(bts, arfcn)) + continue; + if (i == idx) + return arfcn; + i++; + } + + /* Second sub list, ARFCN == 0 (last position) */ + if (bitvec_get_bit_pos(nbv, 0) == ONE && !band_compatible(bts, 0) && i == idx) + return 0; + + return -EINVAL; } int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) @@ -702,8 +901,9 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) struct gsm48_hdr *gh = msgb_l3(msg); uint8_t *data = gh->data; struct gsm_bts *bts = msg->lchan->ts->trx->bts; - struct bitvec *nbv = &bts->si_common.neigh_list; + struct bitvec *nbv; struct gsm_meas_rep_cell *mrc; + int rc; if (gh->msg_type != GSM48_MT_RR_MEAS_REP) return -EINVAL; @@ -727,11 +927,21 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) return 0; } + /* If the phone reports BA-IND 1 this is a report for the SI5* set. + * If we have generated SI5* with manual SI5 neighbor list, the measurements refer to it. */ + if ((rep->flags & MEAS_REP_F_BA1) && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) + nbv = &bts->si_common.si5_neigh_list; + else + nbv = &bts->si_common.neigh_list; + /* an encoding nightmare in perfection */ mrc = &rep->cell[0]; mrc->rxlev = data[3] & 0x3f; mrc->neigh_idx = data[4] >> 3; - mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx); + if (rc < 0) + goto error; + mrc->arfcn = rc; mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5); if (rep->num_cell < 2) return 0; @@ -739,7 +949,10 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) mrc = &rep->cell[1]; mrc->rxlev = ((data[5] & 0x1f) << 1) | (data[6] >> 7); mrc->neigh_idx = (data[6] >> 2) & 0x1f; - mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx); + if (rc < 0) + goto error; + mrc->arfcn = rc; mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4); if (rep->num_cell < 3) return 0; @@ -747,7 +960,10 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) mrc = &rep->cell[2]; mrc->rxlev = ((data[7] & 0x0f) << 2) | (data[8] >> 6); mrc->neigh_idx = (data[8] >> 1) & 0x1f; - mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx); + if (rc < 0) + goto error; + mrc->arfcn = rc; mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3); if (rep->num_cell < 4) return 0; @@ -755,7 +971,10 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) mrc = &rep->cell[3]; mrc->rxlev = ((data[9] & 0x07) << 3) | (data[10] >> 5); mrc->neigh_idx = data[10] & 0x1f; - mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx); + if (rc < 0) + goto error; + mrc->arfcn = rc; mrc->bsic = data[11] >> 2; if (rep->num_cell < 5) return 0; @@ -763,7 +982,10 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) mrc = &rep->cell[4]; mrc->rxlev = ((data[11] & 0x03) << 4) | (data[12] >> 4); mrc->neigh_idx = ((data[12] & 0xf) << 1) | (data[13] >> 7); - mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx); + if (rc < 0) + goto error; + mrc->arfcn = rc; mrc->bsic = (data[13] >> 1) & 0x3f; if (rep->num_cell < 6) return 0; @@ -771,10 +993,18 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) mrc = &rep->cell[5]; mrc->rxlev = ((data[13] & 0x01) << 5) | (data[14] >> 3); mrc->neigh_idx = ((data[14] & 0x07) << 2) | (data[15] >> 6); - mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx); + if (rc < 0) + goto error; + mrc->arfcn = rc; mrc->bsic = data[15] & 0x3f; return 0; + +error: + LOGP(DRR, LOGL_ERROR, "Invalid BCCH channel list index %d in measurement report\n", mrc->neigh_idx); + rep->num_cell = 0; + return 0; } /* 9.1.29 RR Status */ @@ -946,7 +1176,7 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn, osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_RR_HO_FAIL, msg); break; case GSM48_MT_RR_CIPH_M_COMPL: - bsc_cipher_mode_compl(conn, msg, conn->lchan->encr.alg_id); + bsc_cipher_mode_compl(conn, msg, conn->lchan->encr.alg_a5_n); break; case GSM48_MT_RR_ASS_COMPL: if (conn->assignment.fi) @@ -974,11 +1204,20 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn, case GSM48_MT_RR_CLSM_CHG: handle_classmark_chg(conn, msg); break; + case GSM48_MT_RR_UTRAN_CLSM_CHG: + /* TODO: forward to the MSC? */ + break; case GSM48_MT_RR_APP_INFO: /* Passing RR APP INFO to MSC, not quite * according to spec */ bsc_dtap(conn, link_id, msg); break; + case GSM48_MT_RR_UPLINK_RELEASE: + /* When the calling phone releases the uplink before it has been assigned to the group + * channel, it will send an UPLINK RELEASE message on the dedicated channel. The MSC + * has to take care of it. (assigning the phone to the group channel) */ + bsc_dtap(conn, link_id, msg); + break; default: /* Drop unknown RR message */ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown RR message: %s\n", @@ -992,15 +1231,18 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn, * VTY configuration does not permit. */ if (msg_type == GSM48_MT_CC_EMERG_SETUP) { if (msg->lchan->ts->trx->bts->si_common.rach_control.t2 & 0x4) { - LOG_LCHAN(msg->lchan, LOGL_NOTICE, "MS attempts EMERGENCY SETUP although EMERGENCY CALLS" - " are not allowed in sysinfo (spec violation by MS!)\n"); - lchan_release(msg->lchan, true, true, GSM48_RR_CAUSE_PREMPTIVE_REL); + LOG_LCHAN(msg->lchan, LOGL_ERROR, "MS attempts EMERGENCY SETUP although EMERGENCY CALLS" + " are not allowed in sysinfo (cfg: network / bts / rach emergency call allowed 0)\n"); + lchan_release(msg->lchan, true, true, GSM48_RR_CAUSE_PROT_ERROR_UNSPC, + gscon_last_eutran_plmn(msg->lchan->conn)); break; } if (!conn->sccp.msc->allow_emerg) { - LOG_LCHAN(msg->lchan, LOGL_NOTICE, "MS attempts EMERGENCY SETUP, but EMERGENCY CALLS are" - " denied on this BSC (check BTS config!)\n"); - lchan_release(msg->lchan, true, true, GSM48_RR_CAUSE_PREMPTIVE_REL); + LOG_LCHAN(msg->lchan, LOGL_ERROR, "MS attempts EMERGENCY SETUP, but EMERGENCY CALLS are" + " denied on MSC %d (cfg: msc %d / allow-emergency deny)\n", + conn->sccp.msc->nr, conn->sccp.msc->nr); + lchan_release(msg->lchan, true, true, GSM48_RR_CAUSE_PROT_ERROR_UNSPC, + gscon_last_eutran_plmn(msg->lchan->conn)); break; } } diff --git a/src/osmo-bsc/gsm_08_08.c b/src/osmo-bsc/gsm_08_08.c index cd8b77f79..692e35cea 100644 --- a/src/osmo-bsc/gsm_08_08.c +++ b/src/osmo-bsc/gsm_08_08.c @@ -26,6 +26,7 @@ #include <osmocom/bsc/gsm_08_08.h> #include <osmocom/bsc/codec_pref.h> #include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/bsc_stats.h> #include <osmocom/bsc/gsm_04_08_rr.h> #include <osmocom/bsc/a_reset.h> @@ -34,6 +35,7 @@ #include <osmocom/bsc/lcs_loc_req.h> #include <osmocom/gsm/protocol/gsm_08_08.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/gsm0808.h> #include <osmocom/gsm/mncc.h> #include <osmocom/gsm/gsm48.h> @@ -69,21 +71,23 @@ void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, { int rc; struct msgb *resp; + struct gsm_bts *bts; if (!msc_connected(conn)) return; - LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT (dlci=0x%02x, cause='%s')\n", - dlci, gsm0808_cause_name(cause)); + bts = conn_get_bts(conn); + LOG_BTS(bts, DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT (dlci=0x%02x, cause='%s')\n", + dlci, gsm0808_cause_name(cause)); resp = gsm0808_create_sapi_reject_cause(dlci, cause); - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_SAPI_N_REJECT]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_SAPI_N_REJECT)); rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); if (rc != 0) msgb_free(resp); } /*! MS->MSC: Tell MSC that ciphering has been enabled. */ -void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_encr) +void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_a5_n) { int rc; struct msgb *resp; @@ -92,30 +96,13 @@ void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb * return; LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n"); - resp = gsm0808_create_cipher_complete(msg, chosen_encr); - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CIPHER_COMPLETE]); + resp = gsm0808_create_cipher_complete(msg, ALG_A5_NR_TO_BSSAP(chosen_a5_n)); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CIPHER_COMPLETE)); rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); if (rc != 0) msgb_free(resp); } -/* 9.2.5 CM service accept */ -int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERV ACK"); - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - - msg->lchan = conn->lchan; - - gh->proto_discr = GSM48_PDISC_MM; - gh->msg_type = GSM48_MT_MM_CM_SERV_ACC; - - DEBUGP(DMM, "-> CM SERVICE ACK\n"); - - gscon_submit_rsl_dtap(conn, msg, 0, 0); - return 0; -} - static bool is_cm_service_for_emerg(struct msgb *msg) { struct gsm48_service_request *cm; @@ -195,7 +182,7 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, struct bsc_msc_data *msc_target = NULL; struct bsc_msc_data *msc_round_robin_next = NULL; struct bsc_msc_data *msc_round_robin_first = NULL; - uint8_t round_robin_next_nr; + unsigned int round_robin_next_nr; int16_t nri_v = -1; bool is_null_nri = false; @@ -232,7 +219,7 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, if (nri_matches_msc) { LOG_NRI(LOGL_DEBUG, "matches msc %d, but this MSC is currently not connected\n", msc->nr); - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_ATTACH_LOST]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_ATTACH_LOST)); } continue; } @@ -244,10 +231,10 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, msc->nr); } else { LOG_NRI(LOGL_DEBUG, "matches msc %d\n", msc->nr); - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_KNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_KNOWN)); if (is_emerg) { - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_MSCPOOL_EMERG_FORWARDED]); - rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_MSCPOOL_EMERG_FORWARDED]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_MSCPOOL_EMERG_FORWARDED)); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_EMERG_FORWARDED)); } return msc; } @@ -282,9 +269,9 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, * them are usable -- wrap to the start. */ msc_target = msc_round_robin_next ? : msc_round_robin_first; if (!msc_target) { - rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_MSCPOOL_SUBSCR_NO_MSC]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_SUBSCR_NO_MSC)); if (is_emerg) - rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_MSCPOOL_EMERG_LOST]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_EMERG_LOST)); return NULL; } @@ -292,13 +279,13 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, osmo_mobile_identity_to_str_c(OTC_SELECT, mi), msc_target->nr); if (is_null_nri) - rate_ctr_inc(&msc_target->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_REATTACH]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc_target->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_REATTACH)); else - rate_ctr_inc(&msc_target->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_NEW]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc_target->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_NEW)); if (is_emerg) { - rate_ctr_inc(&msc_target->msc_ctrs->ctr[MSC_CTR_MSCPOOL_EMERG_FORWARDED]); - rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_MSCPOOL_EMERG_FORWARDED]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc_target->msc_ctrs, MSC_CTR_MSCPOOL_EMERG_FORWARDED)); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_EMERG_FORWARDED)); } /* An MSC was picked by round-robin, so update the next round-robin nr to pick */ @@ -364,6 +351,19 @@ static void parse_powercap(struct gsm_subscriber_connection *conn, struct msgb * conn_update_ms_power_class(conn, rc8); } +static struct gsm_subscriber_connection *bsc_conn_by_bsub(const struct bsc_subscr *bsub) +{ + struct gsm_subscriber_connection *conn; + if (!bsub) + return NULL; + + llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) { + if (conn->bsub == bsub) + return conn; + } + return NULL; +} + /*! MS->MSC: New MM context with L3 payload. */ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_channel) { @@ -402,11 +402,10 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan if (osmo_mobile_identity_decode_from_l3(&mi, msg, false)) { LOG_COMPL_L3(pdisc, mtype, LOGL_ERROR, "Cannot extract Mobile Identity: %s\n", msgb_hexdump_c(OTC_SELECT, msg)); - /* Likely this is an invalid Complete Layer 3 message that deserves to be rejected. However, the current - * state of our ttcn3 tests does send invalid Layer 3 Info in some tests and expects osmo-bsc to not - * care about that. So, changing the behavior to rejecting on missing MI causes test failure and, if at - * all, should happen in a separate patch. - * See e.g. BSC_Tests.TC_chan_rel_rll_rel_ind: "dt := * f_est_dchan('23'O, 23, '00010203040506'O);" + /* Likely this is an invalid Complete Layer 3 message that deserves to be rejected. However, the BSC is + * not expected to look at this layer so it's duty of the MSC to reject it. + * Hence, keep on going with the conn without an assigned bsc_subscr, forwarding the L3 to the MSC and + * letting it take decision on the matter. */ } else { bsub = bsc_subscr_find_or_create_by_mi(bsc_gsmnet->bsc_subscribers, &mi, __func__); @@ -414,8 +413,13 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan /* If this Mobile Identity already has an active bsc_subscr, look whether there also is an active A-interface * conn for this subscriber. This may be the case during a Perform Location Request (LCS) from the MSC that - * started on an IDLE MS, and now the MS is becoming active. Associate with the existing conn. */ - if (bsub) + * started on an IDLE MS, and now the MS is becoming active. Associate with the existing conn. + * + * However, for a CM Re-Establishment Request, we must *not* re-use the existing conn, but allocate a second + * conn for the same bsub. The previous conn will be Clear'ed by the MSC as soon as it receives the L3 Complete + * message == the CM Re-Establishment Request. + */ + if (bsub && !(pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_REEST_REQ)) conn = bsc_conn_by_bsub(bsub); if (!conn) { @@ -448,7 +452,10 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan paged_from_msc = NULL; paging_reasons = BSC_PAGING_NONE; if (pdisc == GSM48_PDISC_RR && mtype == GSM48_MT_RR_PAG_RESP) { - paging_request_stop(&paged_from_msc, &paging_reasons, bts, conn->bsub); + /* It only makes sense to attempt to find a pending paging request if the subscriber from the + * Paging Response can be identified (bsub != NULL). */ + if (conn->bsub) + paging_request_stop(&paged_from_msc, &paging_reasons, bts, conn->bsub); if (!paged_from_msc) { /* This looks like an unsolicited Paging Response. It is required to pick any MSC, because any * MT-CSFB calls were Paged by the MSC via SGs, and hence are not listed in the BSC. */ @@ -456,12 +463,12 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan "%s Unsolicited Paging Response, possibly an MT-CSFB call.\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_NO_ACTIVE_PAGING]); - rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_PAGING_NO_ACTIVE_PAGING]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_NO_ACTIVE_PAGING)); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_PAGING_NO_ACTIVE_PAGING)); } else if (is_msc_usable(paged_from_msc, is_emerg)) { LOG_COMPL_L3(pdisc, mtype, LOGL_DEBUG, "%s matches earlier Paging from msc %d\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi), paged_from_msc->nr); - rate_ctr_inc(&paged_from_msc->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_PAGED]); + rate_ctr_inc(rate_ctr_group_get_ctr(paged_from_msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_PAGED)); } else { LOG_COMPL_L3(pdisc, mtype, LOGL_DEBUG, "%s matches earlier Paging from msc %d, but this MSC is not connected\n", @@ -540,20 +547,12 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan early_exit: if (release_lchan) - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + lchan_release(lchan, true, true, RSL_ERR_EQUIPMENT_FAIL, + gscon_last_eutran_plmn(conn)); log_set_context(LOG_CTX_BSC_SUBSCR, NULL); return rc; } -/* Data Link Connection Identifier (DLCI) is defined in 3GPP TS 48.006, section 9.3.2. - * .... .SSS - SAPI value used on the radio link; - * CC.. .... - control channel identification: - * 00.. .... - indicates that the control channel is not further specified, - * 10.. .... - represents the FACCH or the SDCCH, - * 11.. .... - represents the SACCH, - * other values are reserved. */ -#define RSL_LINK_ID2DLCI(link_id) \ - (link_id & 0x40 ? 0xc0 : 0x80) | (link_id & 0x07) /*! MS->BSC/MSC: Um L3 message. */ void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) @@ -602,12 +601,33 @@ void bsc_cm_update(struct gsm_subscriber_connection *conn, } conn_update_ms_power_class(conn, rc8); + if (cm3 != NULL && cm3_len > 0) { + rc = gsm48_decode_classmark3(&conn->cm3, cm3, cm3_len); + if (rc < 0) { + LOGP(DMSC, LOGL_NOTICE, "Unable to decode classmark3 during CM Update.\n"); + memset(&conn->cm3, 0, sizeof(conn->cm3)); + conn->cm3_valid = false; + } else + conn->cm3_valid = true; + } + if (!msc_connected(conn)) return; - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CLASSMARK_UPDATE]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CLASSMARK_UPDATE)); resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len); rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); if (rc != 0) msgb_free(resp); } + +bool bsc_chan_ind_requires_rtp_stream(enum gsm0808_chan_indicator ch_indctr) +{ + switch (ch_indctr) { + case GSM0808_CHAN_SPEECH: + case GSM0808_CHAN_DATA: + return true; + default: + return false; + } +} diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c index 22616f37d..b11607e3f 100644 --- a/src/osmo-bsc/gsm_data.c +++ b/src/osmo-bsc/gsm_data.c @@ -62,16 +62,13 @@ void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr, 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); - + llist_for_each_entry(bts, &net->bts_list, list) { if (skip) { if (start_bts == bts) skip = 0; @@ -110,19 +107,21 @@ struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_typ uint8_t bsic) { struct gsm_bts_model *model = bts_model_find(type); + struct gsm_bts_sm *bts_sm; struct gsm_bts *bts; if (!model && type != GSM_BTS_TYPE_UNKNOWN) return NULL; - bts = gsm_bts_alloc(net, net->num_bts); - if (!bts) + bts_sm = gsm_bts_sm_alloc(net, net->num_bts); + if (!bts_sm) return NULL; + bts = bts_sm->bts[0]; net->num_bts++; bts->type = type; - bts->model = model; + gsm_set_bts_model(bts, model); bts->bsic = bsic; llist_add_tail(&bts->list, &net->bts_list); @@ -170,7 +169,7 @@ void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts, 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_CALL, "call (re-)establishment" }, { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" }, { GSM_CHREQ_REASON_PDCH, "one phase packet access" }, { GSM_CHREQ_REASON_OTHER, "other" }, @@ -185,31 +184,22 @@ const struct value_string gsm_pchant_names[] = { { 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_TCH_F_PDCH, "DYNAMIC/IPACCESS" }, { 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" }, + { GSM_PCHAN_OSMO_DYN, "DYNAMIC/OSMOCOM" }, + /* make get_string_value() return GSM_PCHAN_TCH_F_PDCH for both "DYNAMIC/IPACCESS" and "TCH/F_PDCH" */ + { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" }, + /* make get_string_value() return GSM_PCHAN_OSMO_DYN for both "DYNAMIC/OSMOCOM" and "TCH/F_TCH/H_SDCCH8_PDCH" */ + { GSM_PCHAN_OSMO_DYN, "TCH/F_TCH/H_SDCCH8_PDCH" }, + /* When adding items here, you must also add matching items to gsm_pchant_descs[]! */ { 0, NULL } }; -const struct value_string gsm_pchant_descs[13] = { +/* VTY command descriptions. These have to be in the same order as gsm_pchant_names[], so that the automatic VTY command + * composition in bts_trx_vty_init() works out. */ +const struct value_string gsm_pchant_descs[] = { { GSM_PCHAN_NONE, "Physical Channel not configured" }, { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" }, { GSM_PCHAN_CCCH_SDCCH4, @@ -218,14 +208,24 @@ const struct value_string gsm_pchant_descs[13] = { { 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_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" + " (dynamic/ipaccess is an alias for tch/f_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" }, + { GSM_PCHAN_OSMO_DYN, "Dynamic TCH/F or TCH/H or SDCCH/8 or GPRS PDCH" + " (dynamic/osmocom is an alias for tch/f_tch/h_sdcch8_pdch)" }, + /* These duplicate entries are needed to provide a description for both the DYNAMIC/... aliases and their + * explicit versions 'TCH/F_PDCH' / 'TCH/F_TCH/H_SDCCH8_PDCH', see bts_trx_vty_init() */ + { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" + " (dynamic/ipaccess is an alias for tch/f_pdch)" }, + { GSM_PCHAN_OSMO_DYN, "Dynamic TCH/F or TCH/H or SDCCH/8 or GPRS PDCH" + " (dynamic/osmocom is an alias for tch/f_tch/h_sdcch8_pdch)" }, { 0, NULL } }; +osmo_static_assert(ARRAY_SIZE(gsm_pchant_names) == ARRAY_SIZE(gsm_pchant_descs), _pchan_vty_docs); + const char *gsm_pchan_name(enum gsm_phys_chan_config c) { return get_value_string(gsm_pchant_names, c); @@ -236,12 +236,6 @@ 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" }, @@ -332,96 +326,57 @@ char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts) 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_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; + return &bts->mo; 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; + return trx != NULL ? &trx->mo : NULL; 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; + return trx != NULL ? &trx->bb_transc.mo : NULL; 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; + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + return trx != NULL ? &trx->ts[obj_inst->ts_nr].mo : NULL; case NM_OC_SITE_MANAGER: - mo = &bts->site_mgr.mo; - break; + return &bts->site_mgr->mo; case NM_OC_BS11: switch (obj_inst->bts_nr) { case BS11_OBJ_CCLK: - mo = &bts->bs11.cclk.mo; - break; + return &bts->bs11.cclk.mo; 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; + return trx != NULL ? &trx->bs11.bbsig.mo : NULL; 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; + return trx != NULL ? &trx->bs11.pa.mo : NULL; } break; case NM_OC_BS11_RACK: - mo = &bts->bs11.rack.mo; - break; + return &bts->bs11.rack.mo; 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; + return &bts->bs11.envabtse[obj_inst->trx_nr].mo; case NM_OC_GPRS_NSE: - mo = &bts->gprs.nse.mo; - break; + return &bts->site_mgr->gprs.nse.mo; case NM_OC_GPRS_CELL: - mo = &bts->gprs.cell.mo; - break; + return &bts->gprs.cell.mo; case NM_OC_GPRS_NSVC: - if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->site_mgr->gprs.nsvc)) return NULL; - mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo; - break; + return &bts->site_mgr->gprs.nsvc[obj_inst->trx_nr].mo; } - return mo; + + return NULL; } /* obtain the gsm_nm_state data structure for a given object instance */ @@ -474,43 +429,51 @@ gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class, obj = &trx->ts[obj_inst->ts_nr]; break; case NM_OC_SITE_MANAGER: - obj = &bts->site_mgr; + obj = bts->site_mgr; break; case NM_OC_GPRS_NSE: - obj = &bts->gprs.nse; + obj = &bts->site_mgr->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)) + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->site_mgr->gprs.nsvc)) return NULL; - obj = &bts->gprs.nsvc[obj_inst->trx_nr]; + obj = &bts->site_mgr->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) +int gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, + uint8_t ts_nr, uint8_t lchan_nr, bool vamos_is_secondary) { 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; + if (lchan_nr != 0) + return -EINVAL; + if (vamos_is_secondary) + cbits = ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs; + else + cbits = ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs; break; case GSM_PCHAN_PDCH: - OSMO_ASSERT(lchan_nr == 0); - cbits = RSL_CHAN_OSMO_PDCH >> 3; + if (lchan_nr != 0) + return -EINVAL; + cbits = ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH; break; case GSM_PCHAN_TCH_H: - OSMO_ASSERT(lchan_nr < 2); - cbits = 0x02; - cbits += lchan_nr; + if (lchan_nr >= 2) + return -EINVAL; + if (vamos_is_secondary) + cbits = ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(lchan_nr); + else + cbits = ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(lchan_nr); break; case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: @@ -520,22 +483,22 @@ uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, * 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; + lchan_nr = 0; + else if (lchan_nr > 4) + return -EINVAL; + cbits = ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(lchan_nr); break; case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: - OSMO_ASSERT(lchan_nr < 8); - cbits = 0x08; - cbits += lchan_nr; + if (lchan_nr >= 8) + return -EINVAL; + cbits = ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(lchan_nr); break; default: case GSM_PCHAN_CCCH: - OSMO_ASSERT(lchan_nr == 0); - cbits = 0x10; + if (lchan_nr != 0) + return -EINVAL; + cbits = ABIS_RSL_CHAN_NR_CBITS_BCCH; break; } @@ -544,11 +507,43 @@ uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, return chan_nr; } -uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan) +/* For RSL, to talk to osmo-bts, we introduce Osmocom specific channel number cbits to indicate VAMOS secondary lchans. + * However, in RR, which is sent to the MS, these special cbits must not be sent, but their "normal" equivalent; for RR + * messages, pass allow_osmo_cbits = false. */ +int gsm_lchan_and_pchan2chan_nr(const struct gsm_lchan *lchan, enum gsm_phys_chan_config pchan, bool allow_osmo_cbits) { - /* 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); + int rc; + uint8_t lchan_nr = lchan->nr; + + /* Take care that we never send Osmocom specific cbits to non-Osmo BTS. */ + if (allow_osmo_cbits && lchan->vamos.is_secondary + && lchan->ts->trx->bts->model->type != GSM_BTS_TYPE_OSMOBTS) { + LOG_LCHAN(lchan, LOGL_ERROR, "Cannot address VAMOS shadow lchan on this BTS type: %s\n", + get_value_string(bts_type_names, lchan->ts->trx->bts->model->type)); + return -ENOTSUP; + } + if (allow_osmo_cbits && lchan->ts->trx->bts->model->type != GSM_BTS_TYPE_OSMOBTS) + allow_osmo_cbits = false; + + /* The VAMOS lchans are behind the primary ones in the ts->lchan[] array. They keep their lchan->nr as in the + * array, but on the wire they are the "shadow" lchans for the primary lchans. For example, for TCH/F, there is + * a primary ts->lchan[0] and a VAMOS ts->lchan[1]. Still, the VAMOS lchan should send chan_nr = 0. */ + if (lchan->vamos.is_secondary) + lchan_nr -= lchan->ts->max_primary_lchans; + rc = gsm_pchan2chan_nr(pchan, lchan->ts->nr, lchan_nr, + allow_osmo_cbits ? lchan->vamos.is_secondary : false); + /* Log an error so that we don't need to add logging to each caller of this function */ + if (rc < 0) + LOG_LCHAN(lchan, LOGL_ERROR, + "Error encoding Channel Number: pchan %s ts %u ss %u%s\n", + gsm_pchan_name(lchan->ts->pchan_from_config), lchan->ts->nr, lchan_nr, + lchan->vamos.is_secondary ? " (VAMOS shadow)" : ""); + return rc; +} + +int gsm_lchan2chan_nr(const struct gsm_lchan *lchan, bool allow_osmo_cbits) +{ + return gsm_lchan_and_pchan2chan_nr(lchan, lchan->ts->pchan_is, allow_osmo_cbits); } static const uint8_t subslots_per_pchan[] = { @@ -562,12 +557,12 @@ static const uint8_t subslots_per_pchan[] = { [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_OSMO_DYN] = 8, [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. */ +/*! Return the maximum number of logical channels that may be used in a timeslot of the given physical channel + * configuration. */ uint8_t pchan_subslots(enum gsm_phys_chan_config pchan) { if (pchan < 0 || pchan >= ARRAY_SIZE(subslots_per_pchan)) @@ -575,6 +570,30 @@ uint8_t pchan_subslots(enum gsm_phys_chan_config pchan) return subslots_per_pchan[pchan]; } +static const uint8_t subslots_per_pchan_vamos[] = { + [GSM_PCHAN_NONE] = 0, + [GSM_PCHAN_CCCH] = 0, + [GSM_PCHAN_PDCH] = 0, + [GSM_PCHAN_CCCH_SDCCH4] = 0, + /* VAMOS: on a TCH/F, there may be a TCH/H shadow */ + [GSM_PCHAN_TCH_F] = 2, + [GSM_PCHAN_TCH_H] = 2, + [GSM_PCHAN_SDCCH8_SACCH8C] = 0, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 0, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 0, + [GSM_PCHAN_OSMO_DYN] = 0, + [GSM_PCHAN_TCH_F_PDCH] = 2, +}; + +/* Return the maximum number of VAMOS secondary lchans that may be used in a timeslot of the given physical channel + * configuration. */ +uint8_t pchan_subslots_vamos(enum gsm_phys_chan_config pchan) +{ + if (pchan < 0 || pchan >= ARRAY_SIZE(subslots_per_pchan_vamos)) + return 0; + return subslots_per_pchan_vamos[pchan]; +} + static bool pchan_is_tch(enum gsm_phys_chan_config pchan) { switch (pchan) { @@ -597,17 +616,18 @@ struct gsm_bts *conn_get_bts(struct gsm_subscriber_connection *conn) { return conn->lchan->ts->trx->bts; } -static void _chan_desc_fill_tail(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan) +static void _chan_desc_fill_tail(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan, + uint8_t tsc) { if (!lchan->ts->hopping.enabled) { uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff; - cd->h0.tsc = gsm_ts_tsc(lchan->ts); + cd->h0.tsc = tsc; cd->h0.h = 0; cd->h0.spare = 0; cd->h0.arfcn_high = arfcn >> 8; cd->h0.arfcn_low = arfcn & 0xff; } else { - cd->h1.tsc = gsm_ts_tsc(lchan->ts); + cd->h1.tsc = tsc; cd->h1.h = 1; cd->h1.maio_high = lchan->ts->hopping.maio >> 2; cd->h1.maio_low = lchan->ts->hopping.maio & 0x03; @@ -615,21 +635,30 @@ static void _chan_desc_fill_tail(struct gsm48_chan_desc *cd, const struct gsm_lc } } -void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, - const struct gsm_lchan *lchan) +int gsm48_lchan_and_pchan2chan_desc(struct gsm48_chan_desc *cd, + const struct gsm_lchan *lchan, + enum gsm_phys_chan_config pchan, + uint8_t tsc, bool allow_osmo_cbits) { - cd->chan_nr = gsm_lchan2chan_nr(lchan); - _chan_desc_fill_tail(cd, lchan); + int chan_nr = gsm_lchan_and_pchan2chan_nr(lchan, pchan, allow_osmo_cbits); + if (chan_nr < 0) { + /* Log an error so that we don't need to add logging to each caller of this function */ + LOG_LCHAN(lchan, LOGL_ERROR, + "Error encoding Channel Number: pchan %s ts %u ss %u%s (rc = %d)\n", + gsm_pchan_name(pchan), lchan->ts->nr, lchan->nr, + lchan->vamos.is_secondary ? " (VAMOS shadow)" : "", chan_nr); + return chan_nr; + } + cd->chan_nr = chan_nr; + _chan_desc_fill_tail(cd, lchan, tsc); + return 0; } -/* 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) +int gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, + const struct gsm_lchan *lchan, + uint8_t tsc, bool allow_osmo_cbits) { - cd->chan_nr = gsm_pchan2chan_nr(lchan->ts->pchan_from_config, lchan->ts->nr, lchan->nr); - _chan_desc_fill_tail(cd, lchan); + return gsm48_lchan_and_pchan2chan_desc(cd, lchan, lchan->ts->pchan_is, tsc, allow_osmo_cbits); } uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts) @@ -643,7 +672,7 @@ uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts) 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)) + if (s->availability != NM_AVSTATE_OK) return false; if (s->administrative != NM_STATE_UNLOCKED) return false; @@ -675,6 +704,8 @@ enum gsm_phys_chan_config gsm_pchan_by_lchan_type(enum gsm_chan_t type) return GSM_PCHAN_TCH_F; case GSM_LCHAN_TCH_H: return GSM_PCHAN_TCH_H; + case GSM_LCHAN_SDCCH: + return GSM_PCHAN_SDCCH8_SACCH8C; case GSM_LCHAN_NONE: case GSM_LCHAN_PDTCH: /* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only @@ -686,6 +717,22 @@ enum gsm_phys_chan_config gsm_pchan_by_lchan_type(enum gsm_chan_t type) } } +enum channel_rate chan_t_to_chan_rate(enum gsm_chan_t chan_t) +{ + switch (chan_t) { + case GSM_LCHAN_SDCCH: + return CH_RATE_SDCCH; + case GSM_LCHAN_TCH_F: + return CH_RATE_FULL; + case GSM_LCHAN_TCH_H: + return CH_RATE_HALF; + default: + /* For other channel types, the channel_rate value is never used. It is fine to return an invalid value, + * and callers don't actually need to check for this. */ + return -1; + } +} + /* 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) { @@ -699,11 +746,12 @@ bool ts_is_capable_of_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config return false; } - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: switch (pchan) { case GSM_PCHAN_TCH_F: case GSM_PCHAN_TCH_H: case GSM_PCHAN_PDCH: + case GSM_PCHAN_SDCCH8_SACCH8C: return true; default: return false; @@ -771,11 +819,12 @@ bool ts_is_capable_of_lchant(struct gsm_bts_trx_ts *ts, enum gsm_chan_t type) return false; } - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: switch (type) { case GSM_LCHAN_TCH_F: case GSM_LCHAN_TCH_H: case GSM_LCHAN_PDTCH: + case GSM_LCHAN_SDCCH: return true; default: return false; @@ -817,10 +866,8 @@ bool ts_is_capable_of_lchant(struct gsm_bts_trx_ts *ts, enum gsm_chan_t type) 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)); + if (!trx_is_usable(ts->trx)) return false; - } if (!ts->fi) return false; @@ -839,6 +886,11 @@ bool ts_is_usable(const struct gsm_bts_trx_ts *ts) void conn_update_ms_power_class(struct gsm_subscriber_connection *conn, uint8_t power_class) { struct gsm_bts *bts = conn_get_bts(conn); + + /* MS Power class remains the same => do nothing */ + if (power_class == conn->ms_power_class) + return; + LOGP(DRLL, LOGL_DEBUG, "MS Power class update: %" PRIu8 " -> %" PRIu8 "\n", conn->ms_power_class, power_class); @@ -850,65 +902,28 @@ void conn_update_ms_power_class(struct gsm_subscriber_connection *conn, uint8_t 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; +const struct value_string lchan_activate_mode_names[] = { + OSMO_VALUE_STRING(ACTIVATE_FOR_NONE), + OSMO_VALUE_STRING(ACTIVATE_FOR_MS_CHANNEL_REQUEST), + OSMO_VALUE_STRING(ACTIVATE_FOR_ASSIGNMENT), + OSMO_VALUE_STRING(ACTIVATE_FOR_HANDOVER), + OSMO_VALUE_STRING(ACTIVATE_FOR_VGCS_CHANNEL), + OSMO_VALUE_STRING(ACTIVATE_FOR_VTY), + {} +}; - if (send_pwr_ctrl_msg) - rsl_chan_ms_power_ctrl(lchan); -} +const struct value_string lchan_modify_for_names[] = { + OSMO_VALUE_STRING(MODIFY_FOR_NONE), + OSMO_VALUE_STRING(MODIFY_FOR_ASSIGNMENT), + OSMO_VALUE_STRING(MODIFY_FOR_VTY), + {} +}; -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), +const struct value_string assign_for_names[] = { + OSMO_VALUE_STRING(ASSIGN_FOR_NONE), + OSMO_VALUE_STRING(ASSIGN_FOR_BSSMAP_REQ), + OSMO_VALUE_STRING(ASSIGN_FOR_CONGESTION_RESOLUTION), + OSMO_VALUE_STRING(ASSIGN_FOR_VTY), {} }; @@ -964,3 +979,50 @@ enum gsm48_rr_cause bsc_gsm48_rr_cause_from_rsl_cause(uint8_t c) return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; } } + +/* Default Interference Measurement Parameters */ +const struct gsm_interf_meas_params interf_meas_params_def = { + .avg_period = 6, /* 6 SACCH periods */ + .bounds_dbm = { + 115, /* 0: -115 dBm */ + 109, /* X1: -109 dBm */ + 103, /* X2: -103 dBm */ + 97, /* X3: -97 dBm */ + 91, /* X4: -91 dBm */ + 85, /* X5: -85 dBm */ + }, +}; + +enum rsl_cmod_spd chan_mode_to_rsl_cmod_spd(enum gsm48_chan_mode chan_mode) +{ + switch (gsm48_chan_mode_to_non_vamos(chan_mode)) { + case GSM48_CMODE_SIGN: + return RSL_CMOD_SPD_SIGN; + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + return RSL_CMOD_SPD_SPEECH; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + return RSL_CMOD_SPD_DATA; + default: + return -EINVAL; + } +} + +int gsm_audio_support_cmp(const struct gsm_audio_support *a, const struct gsm_audio_support *b) +{ + int rc; + if (a == b) + return 0; + if (!a) + return -1; + if (!b) + return 1; + rc = OSMO_CMP(a->hr, b->hr); + if (rc) + return rc; + return OSMO_CMP(a->ver, b->ver); +} diff --git a/src/osmo-bsc/handover_ctrl.c b/src/osmo-bsc/handover_ctrl.c new file mode 100644 index 000000000..139d0382b --- /dev/null +++ b/src/osmo-bsc/handover_ctrl.c @@ -0,0 +1,216 @@ +/* OsmoBSC handover control interface implementation */ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * Author: Philipp Maier <pmaier@sysmocom.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 <stdbool.h> +#include <talloc.h> +#include <osmocom/bsc/vty.h> +#include <osmocom/bsc/handover_cfg.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/handover_decision_2.h> +#include <osmocom/ctrl/control_cmd.h> + +/* In handover_cfg.h the config items are described in VTY syntax. To be able to + * use those here in the CTRL interface, we parse the config arguments like the + * VTY would. (the value specification may be in the form of "<from-to>" or + * "A|B|C|..." */ +static bool verify_vty_cmd_arg(void *ctx, const char *range, const char *value) +{ + bool success; + char *range_tok; + char *valid_val; + + /* "default" value is always a valid value */ + if (strcmp(value, "default") == 0) + return true; + + /* Try to check for a range first since it is the most common case */ + if (range[0] == '<') { + if (vty_cmd_range_match(range, value)) + return true; + else + return false; + } + + /* Try to tokenize the string to check for distintinct values */ + success = false; + range_tok = talloc_zero_size(ctx, strlen(range) + 1); + memcpy(range_tok, range, strlen(range)); + valid_val = strtok(range_tok, "|"); + while (valid_val != NULL) { + if (strcmp(valid_val, value) == 0) { + success = true; + break; + } + valid_val = strtok(NULL, "|"); + } + + talloc_free(range_tok); + return success; +} + +/* NOTE: The following macro scheme has been designed for using it in the VTY + * code. However, for the most part it also works for CTRL interface code as + * well. */ +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY_CMD_PREFIX, VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL, VTY_WRITE_FMT, VTY_WRITE_CONV, VTY6) \ +CTRL_CMD_DEFINE(NAME, VTY_CMD_PREFIX VTY_CMD); \ +static int get_##NAME(struct ctrl_cmd *cmd, void *_data) \ +{ \ + struct gsm_network *net = cmd->node; \ + struct handover_cfg *ho = net->ho; \ + TYPE val; \ + if (ho_isset_##NAME(ho)) { \ + val = ho_get_##NAME(ho); \ + cmd->reply = talloc_asprintf(cmd, VTY_WRITE_FMT, VTY_WRITE_CONV(val)); \ + } else \ + cmd->reply = talloc_asprintf(cmd, "%s", #DEFAULT_VAL); \ + return CTRL_CMD_REPLY; \ +} \ +static int set_##NAME(struct ctrl_cmd *cmd, void *_data) \ +{ \ + struct gsm_network *net = cmd->node; \ + struct handover_cfg *ho = net->ho; \ + TYPE value; \ + if (strcmp(cmd->value, "default") == 0) \ + value = VTY_ARG_EVAL(#DEFAULT_VAL); \ + else \ + value = VTY_ARG_EVAL(cmd->value); \ + ho_set_##NAME(ho, value); \ + return get_##NAME(cmd, _data); \ +} \ +static int verify_##NAME(struct ctrl_cmd *cmd, const char *value, void *_data) \ +{ \ + if (verify_vty_cmd_arg(cmd, VTY_CMD_ARG, value) != true) \ + return -1; \ + return 0; \ +} \ +CTRL_CMD_DEFINE(bts_##NAME, VTY_CMD_PREFIX VTY_CMD); \ +static int get_bts_##NAME(struct ctrl_cmd *cmd, void *_data) \ +{ \ + struct gsm_bts *bts = cmd->node; \ + struct handover_cfg *ho = bts->ho; \ + TYPE val; \ + if (ho_isset_##NAME(ho)) { \ + val = ho_get_##NAME(ho); \ + cmd->reply = talloc_asprintf(cmd, VTY_WRITE_FMT, VTY_WRITE_CONV(val)); \ + } else { \ + cmd->reply = talloc_asprintf(cmd, "%s", #DEFAULT_VAL); \ + } \ + return CTRL_CMD_REPLY; \ +} \ +static int set_bts_##NAME(struct ctrl_cmd *cmd, void *_data) \ +{ \ + struct gsm_bts *bts = cmd->node; \ + struct handover_cfg *ho = bts->ho; \ + TYPE value; \ + if (strcmp(cmd->value, "default") == 0) \ + value = VTY_ARG_EVAL(#DEFAULT_VAL); \ + else \ + value = VTY_ARG_EVAL(cmd->value); \ + ho_set_##NAME(ho, value); \ + return get_bts_##NAME(cmd, _data); \ +} \ +static int verify_bts_##NAME(struct ctrl_cmd *cmd, const char *value, void *_data) \ +{ \ + return verify_##NAME(cmd, value, _data); \ +} \ + +/* Expand the above macro using the definitions from handover_cfg.h */ +HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER + +CTRL_CMD_DEFINE(congestion_check_interval, "handover2 congestion-check"); +static int get_congestion_check_interval(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + if (net->hodec2.congestion_check_interval_s > 0) + cmd->reply = talloc_asprintf(cmd, "%u", net->hodec2.congestion_check_interval_s); + else + cmd->reply = "disabled"; + return CTRL_CMD_REPLY; +} + +static int set_congestion_check_interval(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + int value; + + /* Trigger congestion check and leave without changing anything */ + if (strcmp(cmd->value, "now") == 0) { + hodec2_congestion_check(net); + return get_congestion_check_interval(cmd, _data); + } + + if (strcmp(cmd->value, "disabled") == 0) + value = 0; + else + value = atoi(cmd->value); + hodec2_on_change_congestion_check_interval(net, value); + return get_congestion_check_interval(cmd, _data); +} + +static int verify_congestion_check_interval(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (strcmp(value, "disabled") == 0) + return 0; + if (strcmp(value, "now") == 0) + return 0; + if (verify_vty_cmd_arg(cmd, "<1-999>", value)) + return 0; + return -1; +} + +/* Filter name member in cmd for illegal '/' characters */ +static struct ctrl_cmd_element *filter_name(void *ctx, + struct ctrl_cmd_element *cmd) +{ + unsigned int i; + char *name; + + if (osmo_separated_identifiers_valid(cmd->name, " -")) + return cmd; + + name = talloc_strdup(ctx, cmd->name); + for (i = 0; i < strlen(name); i++) { + if (name[i] == '/') + name[i] = '-'; + } + + cmd->name = name; + return cmd; +} + +int bsc_ho_ctrl_cmds_install(void *ctx) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_congestion_check_interval); + +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY_ARG_EVAL, VTY4, VTY5, VTY6) \ + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, filter_name(ctx, &cmd_##NAME)); \ + rc |= ctrl_cmd_install(CTRL_NODE_BTS, filter_name(ctx, &cmd_bts_##NAME)); \ + +HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER + + return rc; +} diff --git a/src/osmo-bsc/handover_decision.c b/src/osmo-bsc/handover_decision.c index 7eb8f31e0..2fb466cbe 100644 --- a/src/osmo-bsc/handover_decision.c +++ b/src/osmo-bsc/handover_decision.c @@ -201,8 +201,7 @@ static void attempt_handover(struct gsm_meas_rep *mr) req = (struct handover_out_req){ .from_hodec_id = HODEC1, .old_lchan = mr->lchan, - .target_nik = { - .from_bts = bts->nr, + .target_cell_ab = { .arfcn = best_cell->arfcn, .bsic = best_cell->bsic, }, @@ -215,7 +214,6 @@ static void attempt_handover(struct gsm_meas_rep *mr) static void on_measurement_report(struct gsm_meas_rep *mr) { struct gsm_bts *bts = mr->lchan->ts->trx->bts; - enum meas_rep_field dlev, dqual; int av_rxlev; unsigned int pwr_interval; @@ -232,24 +230,16 @@ static void on_measurement_report(struct gsm_meas_rep *mr) return; } - if (mr->flags & MEAS_REP_F_DL_DTX) { - dlev = MEAS_REP_DL_RXLEV_SUB; - dqual = MEAS_REP_DL_RXQUAL_SUB; - } else { - dlev = MEAS_REP_DL_RXLEV_FULL; - dqual = MEAS_REP_DL_RXQUAL_FULL; - } - /* parse actual neighbor cell info */ if (mr->num_cell > 0 && mr->num_cell < 7) process_meas_neigh(mr); - av_rxlev = get_meas_rep_avg(mr->lchan, dlev, + av_rxlev = get_meas_rep_avg(mr->lchan, TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_AUTO, ho_get_hodec1_rxlev_avg_win(bts->ho)); /* Interference HO */ if (rxlev2dbm(av_rxlev) > -85 && - meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5)) { + meas_rep_n_out_of_m_be(mr->lchan, TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_AUTO, 3, 4, 5)) { LOGPC(DHO, LOGL_INFO, "HO cause: Interference HO av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev)); attempt_handover(mr); @@ -257,7 +247,7 @@ static void on_measurement_report(struct gsm_meas_rep *mr) } /* Bad Quality */ - if (meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5)) { + if (meas_rep_n_out_of_m_be(mr->lchan, TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_AUTO, 3, 4, 5)) { LOGPC(DHO, LOGL_INFO, "HO cause: Bad Quality av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev)); attempt_handover(mr); return; diff --git a/src/osmo-bsc/handover_decision_2.c b/src/osmo-bsc/handover_decision_2.c index c818dbbde..073e013d9 100644 --- a/src/osmo-bsc/handover_decision_2.c +++ b/src/osmo-bsc/handover_decision_2.c @@ -24,9 +24,12 @@ #include <stdbool.h> #include <errno.h> +#include <limits.h> +#include <math.h> #include <osmocom/bsc/debug.h> #include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/assignment_fsm.h> #include <osmocom/bsc/handover_fsm.h> #include <osmocom/bsc/handover_decision.h> #include <osmocom/bsc/handover_decision_2.h> @@ -38,6 +41,8 @@ #include <osmocom/bsc/neighbor_ident.h> #include <osmocom/bsc/timeslot_fsm.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/lchan_select.h> +#include <osmocom/bsc/chan_counts.h> #define LOGPHOBTS(bts, level, fmt, args...) \ LOGP(DHODEC, level, "(BTS %u) " fmt, bts->nr, ## args) @@ -48,8 +53,8 @@ lchan->ts->trx->nr, \ lchan->ts->nr, \ lchan->nr, \ - gsm_lchant_name(lchan->type), \ - gsm48_chan_mode_name(lchan->tch_mode), \ + gsm_chan_t_name(lchan->type), \ + gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \ bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ ## args) @@ -59,8 +64,8 @@ lchan->ts->trx->nr, \ lchan->ts->nr, \ lchan->nr, \ - gsm_lchant_name(lchan->type), \ - gsm48_chan_mode_name(lchan->tch_mode), \ + gsm_chan_t_name(lchan->type), \ + gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \ new_bts->nr, \ bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ ## args) @@ -71,18 +76,18 @@ lchan->ts->trx->nr, \ lchan->ts->nr, \ lchan->nr, \ - gsm_lchant_name(lchan->type), \ - gsm48_chan_mode_name(lchan->tch_mode), \ + gsm_chan_t_name(lchan->type), \ + gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \ gsm0808_cell_id_list_name(remote_cil), \ bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ ## args) #define LOGPHOCAND(candidate, level, fmt, args...) do {\ - if ((candidate)->bts) \ - LOGPHOLCHANTOBTS((candidate)->lchan, (candidate)->bts, level, fmt, ## args); \ - else if ((candidate)->cil) \ - LOGPHOLCHANTOREMOTE((candidate)->lchan, (candidate)->cil, level, fmt, ## args); \ - } while(0) + if ((candidate)->target.bts) \ + LOGPHOLCHANTOBTS((candidate)->current.lchan, (candidate)->target.bts, level, fmt, ## args); \ + else if ((candidate)->target.cell_ids.id_list_len) \ + LOGPHOLCHANTOREMOTE((candidate)->current.lchan, &(candidate)->target.cell_ids, level, fmt, ## args); \ + } while (0) #define REQUIREMENT_A_TCHF 0x01 @@ -98,12 +103,39 @@ #define REQUIREMENT_C_MASK (REQUIREMENT_C_TCHF | REQUIREMENT_C_TCHH) struct ho_candidate { - struct gsm_lchan *lchan; /* candidate for whom */ - struct neighbor_ident_key nik; /* neighbor ARFCN+BSIC */ - struct gsm_bts *bts; /* target BTS in local BSS */ - const struct gsm0808_cell_id_list2 *cil; /* target cells in remote BSS */ uint8_t requirements; /* what is fulfilled */ - int avg; /* average RX level */ + struct { + struct gsm_lchan *lchan; + struct gsm_bts *bts; + int rxlev; + /* free/min-free for the current TCH kind, same as either free_tch_f or free_tch_h below */ + int free_tch; + int min_free_tch; + /* free/min-free for the two TCH kinds, to calculate F<->H cross effects for dynamic timeslots */ + int free_tchf; + int min_free_tchf; + int free_tchh; + int min_free_tchh; + /* Effects of freeing a dynamic timeslot, i.e. turning it into PDCH mode and making available more free + * TCH: */ + int lchan_frees_tchf; + int lchan_frees_tchh; + } current; + struct { + struct cell_ab ab; /* neighbor ARFCN+BSIC */ + struct gsm0808_cell_id_list2 cell_ids; /* target cells in remote BSS */ + struct gsm_bts *bts; + int rxlev; + int rxlev_afs_bias; + int free_tchf; + int min_free_tchf; + int free_tchh; + int min_free_tchh; + /* Effects of occupying a dynamic timeslot, i.e. turning from PDCH into a specific TCH kind, and + * reducing the number of free TCH for both TCH/F and TCH/H: */ + int next_tchf_reduces_tchh; + int next_tchh_reduces_tchf; + } target; }; enum ho_reason { @@ -136,6 +168,24 @@ static enum ho_reason global_ho_reason; static void congestion_check_cb(void *arg); +static bool is_upgrade_to_tchf(const struct ho_candidate *c, uint8_t for_requirement) +{ + return c->current.lchan + && (c->current.lchan->type == GSM_LCHAN_TCH_H) + && ((c->requirements & for_requirement) & REQUIREMENT_TCHF_MASK); +} + +static unsigned int ts_usage_count(struct gsm_bts_trx_ts *ts) +{ + struct gsm_lchan *lchan; + unsigned int count = 0; + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { + if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED)) + count++; + } + return count; +} + /* This function gets called on ho2 init, whenever the congestion check interval is changed, and also * when the timer has fired to trigger again after the next congestion check timeout. */ static void reinit_congestion_timer(struct gsm_network *net) @@ -174,49 +224,6 @@ void hodec2_on_change_congestion_check_interval(struct gsm_network *net, unsigne reinit_congestion_timer(net); } -static void _conn_penalty_time_add(struct gsm_subscriber_connection *conn, - const void *for_object, - int penalty_time) -{ - if (!for_object) { - LOGP(DHODEC, LOGL_ERROR, "%s Unable to set Handover-2 penalty timer:" - " no target cell pointer\n", - bsc_subscr_name(conn->bsub)); - return; - } - - if (!conn->hodec2.penalty_timers) { - conn->hodec2.penalty_timers = penalty_timers_init(conn); - OSMO_ASSERT(conn->hodec2.penalty_timers); - } - - penalty_timers_add(conn->hodec2.penalty_timers, for_object, penalty_time); -} - -static void nik_penalty_time_add(struct gsm_subscriber_connection *conn, - struct neighbor_ident_key *nik, - int penalty_time) -{ - _conn_penalty_time_add(conn, - neighbor_ident_get(conn->network->neighbor_bss_cells, nik), - penalty_time); -} - -static void bts_penalty_time_add(struct gsm_subscriber_connection *conn, - struct gsm_bts *bts, - int penalty_time) -{ - _conn_penalty_time_add(conn, bts, penalty_time); -} - -static unsigned int conn_penalty_time_remaining(struct gsm_subscriber_connection *conn, - const void *for_object) -{ - if (!conn->hodec2.penalty_timers) - return 0; - return penalty_timers_remaining(conn->hodec2.penalty_timers, for_object); -} - /* did we get a RXLEV for a given cell in the given report? Mark matches as MRC_F_PROCESSED. */ static struct gsm_meas_rep_cell *cell_in_rep(struct gsm_meas_rep *mr, uint16_t arfcn, uint8_t bsic) { @@ -235,6 +242,35 @@ static struct gsm_meas_rep_cell *cell_in_rep(struct gsm_meas_rep *mr, uint16_t a return NULL; } +static int current_rxlev(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + return get_meas_rep_avg(lchan, TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_DL, ho_get_hodec2_tdma_meas_set(bts->ho), + ho_get_hodec2_rxlev_avg_win(bts->ho)); +} + +static int current_rxqual(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + return get_meas_rep_avg(lchan, TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, ho_get_hodec2_tdma_meas_set(bts->ho), + ho_get_hodec2_rxqual_avg_win(bts->ho)); +} + +static bool is_low_rxlev(int rxlev_current, struct handover_cfg *neigh_cfg) +{ + return rxlev_current >= 0 + && rxlev2dbm(rxlev_current) < ho_get_hodec2_min_rxlev(neigh_cfg); +} + +static bool is_low_rxqual(int rxqual_current, struct handover_cfg *neigh_cfg) +{ + /* min_rxqual is actually a bit of a misnomer, low quality is a high number. So the "min" refers to the minimum + * acceptable level of quality, and "min or better" here means "rxqual number must be SMALLER-or-equal than the + * min-rxqual setting". */ + return rxqual_current >= 0 + && rxqual_current > ho_get_hodec2_min_rxqual(neigh_cfg); +} + /* obtain averaged rxlev for given neighbor */ static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) { @@ -364,6 +400,26 @@ static bool codec_type_is_supported(struct gsm_subscriber_connection *conn, return false; } +#define LOAD_PRECISION 6 + +/* Return a number representing overload, i.e. the fraction of lchans used above the congestion threshold. + * Think of it as a percentage of used lchans above congestion, just represented in a fixed-point fraction with N + * decimal digits of fractional part. If there is no congestion (free_tch >= min_free_tch), return 0. + */ +static int32_t load_above_congestion(int free_tch, int min_free_tch) +{ + int32_t v; + OSMO_ASSERT(free_tch >= 0); + /* Avoid division by zero when no congestion threshold is set, and return zero overload when there is no + * congestion. */ + if (free_tch >= min_free_tch) + return 0; + v = min_free_tch - free_tch; + v *= pow(10, LOAD_PRECISION); + v /= min_free_tch; + return v; +} + /* * Check what requirements the given cell fulfills. * A bit mask of fulfilled requirements is returned. @@ -416,113 +472,116 @@ static bool codec_type_is_supported(struct gsm_subscriber_connection *conn, * * The number of free slots are checked for TCH/F and TCH/H slot types * individually. */ -static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts, int tchf_count, int tchh_count) +static void check_requirements(struct ho_candidate *c) { - int count; uint8_t requirement = 0; unsigned int penalty_time; - struct gsm_bts *current_bts = lchan->ts->trx->bts; + int32_t current_overbooked; + struct gsm0808_cell_id target_cell_id; + c->requirements = 0; /* Requirement A */ /* the handover/assignment must not be disabled */ - if (current_bts == bts) { - if (!ho_get_hodec2_as_active(bts->ho)) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, "Assignment disabled\n"); - return 0; + if (c->current.bts == c->target.bts) { + if (!ho_get_hodec2_as_active(c->target.bts->ho)) { + LOGPHOLCHAN(c->current.lchan, LOGL_DEBUG, "Assignment disabled\n"); + return; } } else { - if (!ho_get_ho_active(bts->ho)) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + if (!ho_get_ho_active(c->target.bts->ho)) { + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, handover is disabled in target BTS\n"); - return 0; + return; } } /* the handover penalty timer must not run for this bts */ - penalty_time = conn_penalty_time_remaining(lchan->conn, bts); + gsm_bts_cell_id(&target_cell_id, c->target.bts); + penalty_time = penalty_timers_remaining(&c->current.lchan->conn->hodec2.penalty_timers, &target_cell_id); if (penalty_time) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, target BTS still in penalty time" + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, target BTS still in penalty time" " (%u seconds left)\n", penalty_time); - return 0; + return; } /* compatibility check for codecs. * if so, the candidates for full rate and half rate are selected */ - switch (lchan->tch_mode) { + switch (gsm48_chan_mode_to_non_vamos(c->current.lchan->current_ch_mode_rate.chan_mode)) { case GSM48_CMODE_SPEECH_V1: - switch (lchan->type) { + switch (c->current.lchan->type) { case GSM_LCHAN_TCH_F: /* mandatory */ requirement |= REQUIREMENT_A_TCHF; break; case GSM_LCHAN_TCH_H: - if (!bts->codec.hr) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + if (!c->target.bts->codec.hr) { + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "tch_mode='%s' type='%s' not supported\n", get_value_string(gsm48_chan_mode_names, - lchan->tch_mode), - gsm_lchant_name(lchan->type)); + c->current.lchan->current_ch_mode_rate.chan_mode), + gsm_chan_t_name(c->current.lchan->type)); break; } - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR1)) + if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_HR1)) requirement |= REQUIREMENT_A_TCHH; break; default: - LOGPHOLCHAN(lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n", - get_value_string(gsm48_chan_mode_names, lchan->tch_mode)); - return 0; + LOGPHOLCHAN(c->current.lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n", + get_value_string(gsm48_chan_mode_names, + c->current.lchan->current_ch_mode_rate.chan_mode)); + return; } break; case GSM48_CMODE_SPEECH_EFR: - if (!bts->codec.efr) { - LOGPHOBTS(bts, LOGL_DEBUG, "EFR not supported\n"); + if (!c->target.bts->codec.efr) { + LOGPHOBTS(c->target.bts, LOGL_DEBUG, "EFR not supported\n"); break; } - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR2)) + if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_FR2)) requirement |= REQUIREMENT_A_TCHF; break; case GSM48_CMODE_SPEECH_AMR: - if (!bts->codec.amr) { - LOGPHOBTS(bts, LOGL_DEBUG, "AMR not supported\n"); + if (!c->target.bts->codec.amr) { + LOGPHOBTS(c->target.bts, LOGL_DEBUG, "AMR not supported\n"); break; } - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3)) + if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_FR3)) requirement |= REQUIREMENT_A_TCHF; - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR3)) + if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_HR3)) requirement |= REQUIREMENT_A_TCHH; break; default: - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n"); + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n"); /* FIXME: should allow handover of non-speech lchans */ - return 0; + return; } /* no candidate, because new cell is incompatible */ if (!requirement) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, because codec of MS and BTS are incompatible\n"); - return 0; + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, because codec of MS and BTS are incompatible\n"); + return; } /* remove slot types that are not available */ - if (!tchf_count && requirement & REQUIREMENT_A_TCHF) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + if (!c->target.free_tchf && (requirement & REQUIREMENT_A_TCHF)) { + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "removing TCH/F, since all TCH/F lchans are in use\n"); requirement &= ~(REQUIREMENT_A_TCHF); } - if (!tchh_count && requirement & REQUIREMENT_A_TCHH) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + if (!c->target.free_tchh && (requirement & REQUIREMENT_A_TCHH)) { + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "removing TCH/H, since all TCH/H lchans are in use\n"); requirement &= ~(REQUIREMENT_A_TCHH); } if (!requirement) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, because no suitable slots available\n"); - return 0; + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, because no suitable slots available\n"); + return; } /* omit same channel type on same BTS (will not change anything) */ - if (bts == current_bts) { - switch (lchan->type) { + if (c->target.bts == c->current.bts) { + switch (c->current.lchan->type) { case GSM_LCHAN_TCH_F: requirement &= ~(REQUIREMENT_A_TCHF); break; @@ -534,9 +593,9 @@ static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts, } if (!requirement) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, + LOGPHOLCHAN(c->current.lchan, LOGL_DEBUG, "Reassignment within cell not an option, no differing channel types available\n"); - return 0; + return; } } @@ -544,26 +603,26 @@ static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts, // This was useful in osmo-nitb. We're in osmo-bsc now and have no idea whether the osmo-msc does // internal or external call control. Maybe a future config switch wants to add this behavior? /* Built-in call control requires equal codec rates. Remove rates that are not equal. */ - if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR - && current_bts->network->mncc_recv != mncc_sock_from_cc) { - switch (lchan->type) { + if (gsm48_chan_mode_to_non_vamos(c->current.lchan->tch_mode) == GSM48_CMODE_SPEECH_AMR + && c->current.bts->network->mncc_recv != mncc_sock_from_cc) { + switch (c->current.lchan->type) { case GSM_LCHAN_TCH_F: if ((requirement & REQUIREMENT_A_TCHF) - && !!memcmp(¤t_bts->mr_full, &bts->mr_full, + && !!memcmp(&c->current.bts->mr_full, &c->target.bts->mr_full, sizeof(struct amr_multirate_conf))) requirement &= ~(REQUIREMENT_A_TCHF); if ((requirement & REQUIREMENT_A_TCHH) - && !!memcmp(¤t_bts->mr_full, &bts->mr_half, + && !!memcmp(&c->current.bts->mr_full, &c->target.bts->mr_half, sizeof(struct amr_multirate_conf))) requirement &= ~(REQUIREMENT_A_TCHH); break; case GSM_LCHAN_TCH_H: if ((requirement & REQUIREMENT_A_TCHF) - && !!memcmp(¤t_bts->mr_half, &bts->mr_full, + && !!memcmp(&c->current.bts->mr_half, &c->target.bts->mr_full, sizeof(struct amr_multirate_conf))) requirement &= ~(REQUIREMENT_A_TCHF); if ((requirement & REQUIREMENT_A_TCHH) - && !!memcmp(¤t_bts->mr_half, &bts->mr_half, + && !!memcmp(&c->current.bts->mr_half, &c->target.bts->mr_half, sizeof(struct amr_multirate_conf))) requirement &= ~(REQUIREMENT_A_TCHH); break; @@ -572,20 +631,20 @@ static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts, } if (!requirement) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, cannot provide identical codec rate\n"); - return 0; + return; } } #endif /* the maximum number of unsynchronized handovers must no be exceeded */ - if (current_bts != bts - && bts_handover_count(bts, HO_SCOPE_ALL) >= ho_get_hodec2_ho_max(bts->ho)) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + if (c->current.bts != c->target.bts + && bts_handover_count(c->target.bts, HO_SCOPE_ALL) >= ho_get_hodec2_ho_max(c->target.bts->ho)) { + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, number of allowed handovers (%d) would be exceeded\n", - ho_get_hodec2_ho_max(bts->ho)); - return 0; + ho_get_hodec2_ho_max(c->target.bts->ho)); + return; } /* Requirement B */ @@ -593,88 +652,177 @@ static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts, /* the minimum free timeslots that are defined for this cell must * be maintained _after_ handover/assignment */ if (requirement & REQUIREMENT_A_TCHF) { - if (tchf_count - 1 >= ho_get_hodec2_tchf_min_slots(bts->ho)) + if ((c->target.free_tchf - 1) >= c->target.min_free_tchf + && (!c->target.next_tchf_reduces_tchh + || (c->target.free_tchh - c->target.next_tchf_reduces_tchh) >= c->target.min_free_tchh)) requirement |= REQUIREMENT_B_TCHF; } if (requirement & REQUIREMENT_A_TCHH) { - if (tchh_count - 1 >= ho_get_hodec2_tchh_min_slots(bts->ho)) + if ((c->target.free_tchh - 1) >= c->target.min_free_tchh + && (!c->target.next_tchh_reduces_tchf + || (c->target.free_tchf - c->target.next_tchh_reduces_tchf) >= c->target.min_free_tchf)) requirement |= REQUIREMENT_B_TCHH; } /* Requirement C */ - /* the nr of free timeslots of the target cell must be >= the - * free slots of the current cell _after_ handover/assignment */ - count = bts_count_free_ts(current_bts, - (lchan->type == GSM_LCHAN_TCH_H) ? - GSM_PCHAN_TCH_H : GSM_PCHAN_TCH_F); + /* the load percentage above congestion on the target cell *after* HO must be < the load percentage above + * congestion on the current cell, hence the - 1 on the target. */ + current_overbooked = load_above_congestion(c->current.free_tch, c->current.min_free_tch); if (requirement & REQUIREMENT_A_TCHF) { - if (tchf_count - 1 >= count + 1) + bool ok; + int32_t target_overbooked; + int target_free_tchf_after_ho; + + /* To evaluate whether a handover improves or worsens congestion on TCH/F, first figure out how many + * TCH/F lchans will be occupied on the target after the handover. If the target is a different cell, + * then we obviously reduce by one TCH/F. If source and target cell are the same (re-assignment), then + * the source lchan may also free a TCH/F at the same time. Add up all of these effects to figure out + * the congestion percentages before and after handover. */ + target_free_tchf_after_ho = c->target.free_tchf - 1; + if (c->current.bts == c->target.bts) + target_free_tchf_after_ho += c->current.lchan_frees_tchf; + target_overbooked = load_above_congestion(target_free_tchf_after_ho, c->target.min_free_tchf); + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, + "current overbooked = %s%%, TCH/F target overbooked after HO = %s%%\n", + osmo_int_to_float_str_c(OTC_SELECT, current_overbooked, LOAD_PRECISION - 2), + osmo_int_to_float_str_c(OTC_SELECT, target_overbooked, LOAD_PRECISION - 2)); + ok = target_overbooked < current_overbooked; + /* Look at dynamic timeslot effects on TCH/H: */ + if (ok && c->target.next_tchf_reduces_tchh) { + /* Looking at the current TCH type and the target cell's TCH/F alone, congestion balancing + * should happen. However, what if the target TCH/F is a dynamic timeslot -- would that cause + * congestion on TCH/H above the current cell's TCH/H congestion? */ + int32_t current_tchh_overbooked = load_above_congestion(c->current.free_tchh, + c->current.min_free_tchh); + int32_t target_tchh_overbooked; + int target_free_tchh_after_ho = c->target.free_tchh - c->target.next_tchf_reduces_tchh; + /* If this is a re-assignment within the same cell, and if the current candidate would free a + * dynamic timeslot, then the target-overbooking after HO is reduced again by the freed dynamic + * TS. */ + if (c->current.bts == c->target.bts) + target_free_tchh_after_ho += c->current.lchan_frees_tchh; + target_tchh_overbooked = load_above_congestion(target_free_tchh_after_ho, + c->target.min_free_tchh); + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, + "dyn TS: current TCH/H overbooked = %s%%, TCH/H target overbooked after HO = %s%%\n", + osmo_int_to_float_str_c(OTC_SELECT, current_tchh_overbooked, LOAD_PRECISION - 2), + osmo_int_to_float_str_c(OTC_SELECT, target_tchh_overbooked, LOAD_PRECISION - 2)); + /* For the current TCH kind, a handover should only happen if things actually get better + * (condition is '<'). For dynamic timeslot cross effects TCH/F->TCH/H, it is fine to not make + * it worse. Hence the smaller-or-equal condition. */ + ok = target_tchh_overbooked <= current_tchh_overbooked; + } + if (ok) requirement |= REQUIREMENT_C_TCHF; } if (requirement & REQUIREMENT_A_TCHH) { - if (tchh_count - 1 >= count + 1) + bool ok; + int32_t target_overbooked; + int target_free_tchh_after_ho; + + /* To evaluate whether a handover improves or worsens congestion on TCH/H, first figure out how many + * TCH/H lchans will be occupied on the target after the handover. If the target is a different cell, + * then we obviously reduce by one TCH/H. If source and target cell are the same (re-assignment), then + * the source lchan may also free one or two TCH/H at the same time. Add up all of these effects to + * figure out the congestion percentages before and after handover. */ + target_free_tchh_after_ho = c->target.free_tchh - 1; + if (c->current.bts == c->target.bts) + target_free_tchh_after_ho += c->current.lchan_frees_tchh; + target_overbooked = load_above_congestion(target_free_tchh_after_ho, c->target.min_free_tchh); + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, + "current overbooked = %s%%, TCH/H target overbooked after HO = %s%%\n", + osmo_int_to_float_str_c(OTC_SELECT, current_overbooked, LOAD_PRECISION - 2), + osmo_int_to_float_str_c(OTC_SELECT, target_overbooked, LOAD_PRECISION - 2)); + ok = target_overbooked < current_overbooked; + /* Look at dynamic timeslot effects on TCH/F: */ + if (ok && c->target.next_tchh_reduces_tchf) { + /* Looking at the current TCH type and the target cell's TCH/H alone, congestion balancing + * should happen. However, what if the target TCH/H is a dynamic timeslot -- would that cause + * congestion on TCH/F above the current cell's TCH/F congestion? */ + int32_t current_tchf_overbooked = load_above_congestion(c->current.free_tchf, + c->current.min_free_tchf); + int32_t target_tchf_overbooked; + int target_free_tchf_after_ho = c->target.free_tchf - c->target.next_tchh_reduces_tchf; + /* If this is a re-assignment within the same cell, and if the current candidate would free a + * dynamic timeslot, then the target-overbooking after HO is reduced again by the freed dynamic + * TS. */ + if (c->current.bts == c->target.bts) + target_free_tchf_after_ho += c->current.lchan_frees_tchf; + target_tchf_overbooked = load_above_congestion(target_free_tchf_after_ho, + c->target.min_free_tchf); + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, + "dyn TS: current TCH/F overbooked = %s%%, TCH/F target overbooked after HO = %s%%\n", + osmo_int_to_float_str_c(OTC_SELECT, current_tchf_overbooked, LOAD_PRECISION - 2), + osmo_int_to_float_str_c(OTC_SELECT, target_tchf_overbooked, LOAD_PRECISION - 2)); + /* For the current TCH kind, a handover should only happen if things actually get better + * (condition is '<'). For dynamic timeslot cross effects TCH/H->TCH/F, it is fine to not make + * it worse. Hence the smaller-or-equal condition. */ + ok = target_tchf_overbooked <= current_tchf_overbooked; + } + if (ok) requirement |= REQUIREMENT_C_TCHH; } /* return mask of fulfilled requirements */ - return requirement; + c->requirements = requirement; } -static uint8_t check_requirements_remote_bss(struct gsm_lchan *lchan, - const struct gsm0808_cell_id_list2 *cil) +static void check_requirements_remote_bss(struct ho_candidate *c) { uint8_t requirement = 0; unsigned int penalty_time; + c->requirements = 0; /* Requirement A */ /* the handover penalty timer must not run for this bts */ - penalty_time = conn_penalty_time_remaining(lchan->conn, cil); + penalty_time = penalty_timers_remaining_list(&c->current.lchan->conn->hodec2.penalty_timers, &c->target.cell_ids); if (penalty_time) { - LOGPHOLCHANTOREMOTE(lchan, cil, LOGL_DEBUG, + LOGPHOLCHANTOREMOTE(c->current.lchan, &c->target.cell_ids, LOGL_DEBUG, "not a candidate, target BSS still in penalty time" " (%u seconds left)\n", penalty_time); - return 0; + return; } /* compatibility check for codecs -- we have no notion of what the remote BSS supports. We can * only assume that a handover would work, and use only the local requirements. */ - switch (lchan->tch_mode) { + switch (gsm48_chan_mode_to_non_vamos(c->current.lchan->current_ch_mode_rate.chan_mode)) { case GSM48_CMODE_SPEECH_V1: - switch (lchan->type) { + switch (c->current.lchan->type) { case GSM_LCHAN_TCH_F: /* mandatory */ requirement |= REQUIREMENT_A_TCHF; break; case GSM_LCHAN_TCH_H: - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR1)) + if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_HR1)) requirement |= REQUIREMENT_A_TCHH; break; default: - LOGPHOLCHAN(lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n", - get_value_string(gsm48_chan_mode_names, lchan->tch_mode)); - return 0; + LOGPHOLCHAN(c->current.lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n", + get_value_string(gsm48_chan_mode_names, + c->current.lchan->current_ch_mode_rate.chan_mode)); + return; } break; case GSM48_CMODE_SPEECH_EFR: - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR2)) + if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_FR2)) requirement |= REQUIREMENT_A_TCHF; break; case GSM48_CMODE_SPEECH_AMR: - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3)) + if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_FR3)) requirement |= REQUIREMENT_A_TCHF; - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR3)) + if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_HR3)) requirement |= REQUIREMENT_A_TCHH; break; default: - LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n"); + LOGPHOLCHAN(c->current.lchan, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n"); /* FIXME: should allow handover of non-speech lchans */ - return 0; + return; } if (!requirement) { - LOGPHOLCHAN(lchan, LOGL_ERROR, "lchan doesn't fit its own requirements??\n"); - return 0; + LOGPHOLCHAN(c->current.lchan, LOGL_ERROR, "lchan doesn't fit its own requirements??\n"); + return; } /* Requirement B and C */ @@ -687,38 +835,34 @@ static uint8_t check_requirements_remote_bss(struct gsm_lchan *lchan, requirement |= REQUIREMENT_B_TCHH | REQUIREMENT_C_TCHH; /* return mask of fulfilled requirements */ - return requirement; + c->requirements = requirement; } /* Trigger handover or assignment depending on the target BTS */ static int trigger_local_ho_or_as(struct ho_candidate *c, uint8_t requirements) { - struct gsm_lchan *lchan = c->lchan; - struct gsm_bts *new_bts = c->bts; - struct handover_out_req req; - struct gsm_bts *current_bts = lchan->ts->trx->bts; int afs_bias = 0; bool full_rate = false; /* afs_bias becomes > 0, if AFS is used and is improved */ - if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) - afs_bias = ho_get_hodec2_afs_bias_rxlev(new_bts->ho); + if (gsm48_chan_mode_to_non_vamos(c->current.lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) + afs_bias = ho_get_hodec2_afs_bias_rxlev(c->target.bts->ho); /* select TCH rate, prefer TCH/F if AFS is improved */ - switch (lchan->type) { + switch (c->current.lchan->type) { case GSM_LCHAN_TCH_F: /* keep on full rate, if TCH/F is a candidate */ if ((requirements & REQUIREMENT_TCHF_MASK)) { - if (current_bts == new_bts) { - LOGPHOLCHAN(lchan, LOGL_INFO, "Not performing assignment: Already on target type\n"); - return 0; + if (c->current.bts == c->target.bts) { + LOGPHOLCHAN(c->current.lchan, LOGL_INFO, "Not performing assignment: Already on target type\n"); + return -EALREADY; } full_rate = true; break; } /* change to half rate */ if (!(requirements & REQUIREMENT_TCHH_MASK)) { - LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_ERROR, "neither TCH/F nor TCH/H requested, aborting ho/as\n"); return -EINVAL; } @@ -737,38 +881,53 @@ static int trigger_local_ho_or_as(struct ho_candidate *c, uint8_t requirements) } /* keep on half rate */ if (!(requirements & REQUIREMENT_TCHH_MASK)) { - LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_ERROR, "neither TCH/F nor TCH/H requested, aborting ho/as\n"); return -EINVAL; } - if (current_bts == new_bts) { - LOGPHOLCHAN(lchan, LOGL_INFO, "Not performing assignment: Already on target type\n"); - return 0; + if (c->current.bts == c->target.bts) { + LOGPHOLCHAN(c->current.lchan, LOGL_INFO, "Not performing assignment: Already on target type\n"); + return -EALREADY; } break; default: - LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, "lchan is neither TCH/F nor TCH/H, aborting ho/as\n"); + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_ERROR, "c->current.lchan is neither TCH/F nor TCH/H, aborting ho/as\n"); return -EINVAL; } /* trigger handover or assignment */ - if (current_bts == new_bts) - LOGPHOLCHAN(lchan, LOGL_NOTICE, "Triggering assignment to %s, due to %s\n", + if (c->current.bts == c->target.bts) { + LOGPHOLCHAN(c->current.lchan, LOGL_NOTICE, "Triggering assignment to %s, due to %s\n", full_rate ? "TCH/F" : "TCH/H", ho_reason_name(global_ho_reason)); - else - LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_INFO, + return reassignment_request_to_chan_type(ASSIGN_FOR_CONGESTION_RESOLUTION, c->current.lchan, + full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H); + } else { + struct handover_out_req req = { + .from_hodec_id = HODEC2, + .old_lchan = c->current.lchan, + .new_lchan_type = full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H, + }; + bts_cell_ab(&req.target_cell_ab, c->target.bts); + LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_INFO, "Triggering handover to %s, due to %s\n", full_rate ? "TCH/F" : "TCH/H", ho_reason_name(global_ho_reason)); - - req = (struct handover_out_req){ - .from_hodec_id = HODEC2, - .old_lchan = lchan, - .target_nik = *bts_ident_key(new_bts), - .new_lchan_type = full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H, - }; - handover_request(&req); + handover_request(&req); + + /* Apply penalty timer hodec2_penalty_low_rxqual_ho */ + if (global_ho_reason == HO_REASON_INTERFERENCE + || global_ho_reason == HO_REASON_BAD_QUALITY) { + struct gsm0808_cell_id bts_id; + struct gsm_subscriber_connection *conn = c->current.lchan->conn; + int timeout = ho_get_hodec2_penalty_low_rxqual_ho(c->current.bts->ho); + gsm_bts_cell_id(&bts_id, c->current.bts); + LOGPHOCAND(c, LOGL_DEBUG, "Applying penalty-time low-rxqual-ho %d s on bts %u (%s), reason: %s\n", + timeout, c->current.bts->nr, gsm0808_cell_id_name_c(OTC_SELECT, &bts_id), + ho_reason_name(global_ho_reason)); + penalty_timers_add(conn, &conn->hodec2.penalty_timers, &bts_id, timeout); + } + } return 0; } @@ -776,14 +935,14 @@ static int trigger_remote_bss_ho(struct ho_candidate *c, uint8_t requirements) { struct handover_out_req req; - LOGPHOLCHANTOREMOTE(c->lchan, c->cil, LOGL_INFO, + LOGPHOLCHANTOREMOTE(c->current.lchan, &c->target.cell_ids, LOGL_INFO, "Triggering inter-BSC handover, due to %s\n", ho_reason_name(global_ho_reason)); req = (struct handover_out_req){ .from_hodec_id = HODEC2, - .old_lchan = c->lchan, - .target_nik = c->nik, + .old_lchan = c->current.lchan, + .target_cell_ab = c->target.ab, }; handover_request(&req); return 0; @@ -791,7 +950,7 @@ static int trigger_remote_bss_ho(struct ho_candidate *c, uint8_t requirements) static int trigger_ho(struct ho_candidate *c, uint8_t requirements) { - if (c->bts) + if (c->target.bts) return trigger_local_ho_or_as(c, requirements); else return trigger_remote_bss_ho(c, requirements); @@ -812,59 +971,134 @@ static int trigger_ho(struct ho_candidate *c, uint8_t requirements) " less-or-equal congestion")) /* verbosely log about a handover candidate */ -static inline void debug_candidate(struct ho_candidate *candidate, - int8_t rxlev, int tchf_count, int tchh_count) +static inline void debug_candidate(struct ho_candidate *candidate) { - struct gsm_lchan *lchan = candidate->lchan; - #define HO_CANDIDATE_FMT(tchx, TCHX) "TCH/" #TCHX "={free %d (want %d), " REQUIREMENTS_FMT "}" #define HO_CANDIDATE_ARGS(tchx, TCHX) \ - tch##tchx##_count, ho_get_hodec2_tch##tchx##_min_slots(candidate->bts->ho), \ + candidate->target.free_tch##tchx, candidate->target.min_free_tch##tchx, \ REQUIREMENTS_ARGS(candidate->requirements, TCHX) - if (!candidate->bts && !candidate->cil) - LOGPHOLCHAN(lchan, LOGL_DEBUG, "Empty candidate\n"); - if (candidate->bts && candidate->cil) - LOGPHOLCHAN(lchan, LOGL_ERROR, "Invalid candidate: both local- and remote-BSS target\n"); + if (!candidate->target.bts && !candidate->target.cell_ids.id_list_len) + LOGPHOLCHAN(candidate->current.lchan, LOGL_DEBUG, "Empty candidate\n"); + if (candidate->target.bts && candidate->target.cell_ids.id_list_len) + LOGPHOLCHAN(candidate->current.lchan, LOGL_ERROR, "Invalid candidate: both local- and remote-BSS target\n"); - if (candidate->cil) - LOGPHOLCHANTOREMOTE(lchan, candidate->cil, LOGL_DEBUG, - "RX level %d -> %d\n", - rxlev2dbm(rxlev), rxlev2dbm(candidate->avg)); + if (candidate->target.cell_ids.id_list_len) + LOGPHOLCHANTOREMOTE(candidate->current.lchan, &candidate->target.cell_ids, LOGL_DEBUG, + "RX level %d dBm -> %d dBm\n", + rxlev2dbm(candidate->current.rxlev), rxlev2dbm(candidate->target.rxlev)); - if (candidate->bts == lchan->ts->trx->bts) - LOGPHOLCHANTOBTS(lchan, candidate->bts, LOGL_DEBUG, - "RX level %d; " + if (candidate->target.bts == candidate->current.bts) + LOGPHOLCHANTOBTS(candidate->current.lchan, candidate->target.bts, LOGL_DEBUG, + "RX level %d dBm; " HO_CANDIDATE_FMT(f, F) "; " HO_CANDIDATE_FMT(h, H) "\n", - rxlev2dbm(candidate->avg), + rxlev2dbm(candidate->current.rxlev), HO_CANDIDATE_ARGS(f, F), HO_CANDIDATE_ARGS(h, H)); - else if (candidate->bts) - LOGPHOLCHANTOBTS(lchan, candidate->bts, LOGL_DEBUG, - "RX level %d -> %d; " + else if (candidate->target.bts) + LOGPHOLCHANTOBTS(candidate->current.lchan, candidate->target.bts, LOGL_DEBUG, + "RX level %d dBm -> %d dBm; " HO_CANDIDATE_FMT(f, F) "; " HO_CANDIDATE_FMT(h, H) "\n", - rxlev2dbm(rxlev), rxlev2dbm(candidate->avg), + rxlev2dbm(candidate->current.rxlev), rxlev2dbm(candidate->target.rxlev), HO_CANDIDATE_ARGS(f, F), HO_CANDIDATE_ARGS(h, H)); } +static void candidate_set_free_tch(struct ho_candidate *c) +{ + struct chan_counts *bts_counts; + struct gsm_lchan *next_lchan; + + bts_counts = &c->current.bts->chan_counts; + c->current.free_tchf = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]; + c->current.min_free_tchf = ho_get_hodec2_tchf_min_slots(c->current.bts->ho); + c->current.free_tchh = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]; + c->current.min_free_tchh = ho_get_hodec2_tchh_min_slots(c->current.bts->ho); + + switch (c->current.lchan->ts->pchan_is) { + case GSM_PCHAN_TCH_F: + c->current.free_tch = c->current.free_tchf; + c->current.min_free_tch = c->current.min_free_tchf; + c->current.lchan_frees_tchf = 1; + if (c->current.lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) + c->current.lchan_frees_tchh = 2; + else + c->current.lchan_frees_tchh = 0; + break; + case GSM_PCHAN_TCH_H: + c->current.free_tch = c->current.free_tchh; + c->current.min_free_tch = c->current.min_free_tchh; + c->current.lchan_frees_tchh = 1; + /* Freeing one of two TCH/H does not free a dyn TS and would not free a TCH/F. It has to be the last + * TCH/H of a dynamic timeslot that is freed to get a new TCH/F in the current cell from the handover. + * Hence the ts_usage_count() condition. */ + if (c->current.lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN + && ts_usage_count(c->current.lchan->ts) == 1) + c->current.lchan_frees_tchf = 1; + else + c->current.lchan_frees_tchf = 0; + break; + default: + break; + } + + /* For inter-BSC handover, the target BTS is in a different BSC and hence NULL here. */ + if (c->target.bts) { + bts_counts = &c->target.bts->chan_counts; + c->target.free_tchf = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]; + c->target.min_free_tchf = ho_get_hodec2_tchf_min_slots(c->target.bts->ho); + c->target.free_tchh = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]; + c->target.min_free_tchh = ho_get_hodec2_tchh_min_slots(c->target.bts->ho); + + /* Would the next TCH/F lchan occupy a dynamic timeslot that currently counts for free TCH/H timeslots? + */ + next_lchan = lchan_avail_by_type(c->target.bts, GSM_LCHAN_TCH_F, + SELECT_FOR_HANDOVER, NULL, false); + if (next_lchan && next_lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) + c->target.next_tchf_reduces_tchh = 2; + else + c->target.next_tchf_reduces_tchh = 0; + + /* Would the next TCH/H lchan occupy a dynamic timeslot that currently counts for free TCH/F timeslots? + * Note that a dyn TS already in TCH/H mode (half occupied) would not reduce free TCH/F. */ + next_lchan = lchan_avail_by_type(c->target.bts, GSM_LCHAN_TCH_H, + SELECT_FOR_HANDOVER, NULL, false); + if (next_lchan && next_lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN + && next_lchan->ts->pchan_is != GSM_PCHAN_TCH_H) + c->target.next_tchh_reduces_tchf = 1; + else + c->target.next_tchh_reduces_tchf = 0; + } else { + + c->target.free_tchf = 0; + c->target.min_free_tchf = 0; + c->target.next_tchh_reduces_tchf = 0; + c->target.free_tchh = 0; + c->target.min_free_tchh = 0; + c->target.next_tchf_reduces_tchh = 0; + } +} + /* add candidate for re-assignment within the current cell */ static void collect_assignment_candidate(struct gsm_lchan *lchan, struct ho_candidate *clist, - unsigned int *candidates, int av_rxlev) + unsigned int *candidates, int rxlev_current) { struct gsm_bts *bts = lchan->ts->trx->bts; - int tchf_count, tchh_count; struct ho_candidate c; - tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F); - tchh_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_H); - c = (struct ho_candidate){ - .lchan = lchan, - .bts = bts, - .requirements = check_requirements(lchan, bts, tchf_count, tchh_count), - .avg = av_rxlev, + .current = { + .lchan = lchan, + .bts = bts, + .rxlev = rxlev_current, + }, + .target = { + .bts = bts, + .rxlev = rxlev_current, /* same cell, same rxlev */ + }, }; + candidate_set_free_tch(&c); + check_requirements(&c); - debug_candidate(&c, 0, tchf_count, tchh_count); + debug_candidate(&c); if (!c.requirements) return; @@ -876,22 +1110,17 @@ static void collect_assignment_candidate(struct gsm_lchan *lchan, struct ho_cand /* add candidates for handover to all neighbor cells */ static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_meas_proc *nmp, struct ho_candidate *clist, unsigned int *candidates, - bool include_weaker_rxlev, int av_rxlev, + bool include_weaker_rxlev, int rxlev_current, int *neighbors_count) { struct gsm_bts *bts = lchan->ts->trx->bts; - int tchf_count = 0; - int tchh_count = 0; struct gsm_bts *neighbor_bts; - const struct gsm0808_cell_id_list2 *neighbor_cil; - struct neighbor_ident_key ni = { - .from_bts = bts->nr, + struct gsm0808_cell_id_list2 neighbor_cil; + struct cell_ab target_ab = { .arfcn = nmp->arfcn, .bsic = nmp->bsic, }; - int avg; struct ho_candidate c; - int min_rxlev; struct handover_cfg *neigh_cfg; /* skip empty slots */ @@ -910,9 +1139,9 @@ static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_mea } find_handover_target_cell(&neighbor_bts, &neighbor_cil, - lchan->conn, &ni, false); + lchan->conn, &target_ab, false); - if (!neighbor_bts && !neighbor_cil) { + if (!neighbor_bts && !neighbor_cil.id_list_len) { LOGPHOBTS(bts, LOGL_DEBUG, "no neighbor ARFCN %u BSIC %u configured for this cell\n", nmp->arfcn, nmp->bsic); return; @@ -928,51 +1157,51 @@ static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_mea * instead assume the local BTS' config to apply. */ neigh_cfg = (neighbor_bts ? : bts)->ho; - /* calculate average rxlev for this cell over the window */ - avg = neigh_meas_avg(nmp, ho_get_hodec2_rxlev_neigh_avg_win(bts->ho)); - c = (struct ho_candidate){ - .lchan = lchan, - .avg = avg, - .nik = ni, - .bts = neighbor_bts, - .cil = neighbor_cil, + .current = { + .lchan = lchan, + .bts = bts, + .rxlev = rxlev_current, + }, + .target = { + .ab = target_ab, + .bts = neighbor_bts, + .cell_ids = neighbor_cil, + .rxlev = neigh_meas_avg(nmp, ho_get_hodec2_rxlev_neigh_avg_win(bts->ho)), + }, }; + candidate_set_free_tch(&c); /* Heed rxlev hysteresis only if the RXLEV/RXQUAL/TA levels of the MS aren't critically bad and * we're just looking for an improvement. If levels are critical, we desperately need a handover * and thus skip the hysteresis check. */ if (!include_weaker_rxlev) { - unsigned int pwr_hyst = ho_get_hodec2_pwr_hysteresis(bts->ho); - if (avg <= (av_rxlev + pwr_hyst)) { + int pwr_hyst = ho_get_hodec2_pwr_hysteresis(bts->ho); + if ((c.target.rxlev - c.current.rxlev) <= pwr_hyst) { LOGPHOCAND(&c, LOGL_DEBUG, - "Not a candidate, because RX level (%d) is lower" - " or equal than current RX level (%d) + hysteresis (%d)\n", - rxlev2dbm(avg), rxlev2dbm(av_rxlev), pwr_hyst); + "Not a candidate, because RX level (%d dBm) is lower" + " or equal than current RX level (%d dBm) + hysteresis (%d)\n", + rxlev2dbm(c.target.rxlev), rxlev2dbm(c.current.rxlev), pwr_hyst); return; } } /* if the minimum level is not reached. * In case of a remote-BSS, use the current BTS' configuration. */ - min_rxlev = ho_get_hodec2_min_rxlev(neigh_cfg); - if (rxlev2dbm(avg) < min_rxlev) { + if (is_low_rxlev(c.target.rxlev, neigh_cfg)) { LOGPHOCAND(&c, LOGL_DEBUG, - "Not a candidate, because RX level (%d) is lower" - " than the minimum required RX level (%d)\n", - rxlev2dbm(avg), min_rxlev); + "Not a candidate, because RX level (%d dBm) is lower" + " than the minimum required RX level (%d dBm)\n", + rxlev2dbm(c.target.rxlev), ho_get_hodec2_min_rxlev(neigh_cfg)); return; } if (neighbor_bts) { - tchf_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_F); - tchh_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_H); - c.requirements = check_requirements(lchan, neighbor_bts, tchf_count, - tchh_count); + check_requirements(&c); } else - c.requirements = check_requirements_remote_bss(lchan, neighbor_cil); + check_requirements_remote_bss(&c); - debug_candidate(&c, av_rxlev, tchf_count, tchh_count); + debug_candidate(&c); if (!c.requirements) return; @@ -983,45 +1212,45 @@ static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_mea static void collect_candidates_for_lchan(struct gsm_lchan *lchan, struct ho_candidate *clist, unsigned int *candidates, - int *_av_rxlev, bool include_weaker_rxlev) + int *_rxlev_current, bool include_weaker_rxlev) { struct gsm_bts *bts = lchan->ts->trx->bts; - int av_rxlev; + int rxlev_current; bool assignment; bool handover; int neighbors_count = 0; - unsigned int rxlev_avg_win = ho_get_hodec2_rxlev_avg_win(bts->ho); OSMO_ASSERT(candidates); - /* calculate average rxlev for this cell over the window */ - av_rxlev = get_meas_rep_avg(lchan, - ho_get_hodec2_full_tdma(bts->ho) ? - MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB, - rxlev_avg_win); - if (_av_rxlev) - *_av_rxlev = av_rxlev; + rxlev_current = current_rxlev(lchan); + if (_rxlev_current) + *_rxlev_current = rxlev_current; /* in case there is no measurement report (yet) */ - if (av_rxlev < 0) { + if (rxlev_current < 0) { LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not collecting candidates, not enough measurements" " (got %d, want %u)\n", - lchan->meas_rep_count, rxlev_avg_win); + lchan->meas_rep_count, ho_get_hodec2_rxlev_avg_win(bts->ho)); return; } assignment = ho_get_hodec2_as_active(bts->ho); handover = ho_get_ho_active(bts->ho); - if (assignment) - collect_assignment_candidate(lchan, clist, candidates, av_rxlev); + /* See if re-assignment within the same cell can resolve congestion. + * But: when TCH/F has low rxlev, do not re-assign. If a low rxlev TCH/F were re-assigned to TCH/H, we would + * subsequently oscillate back to TCH/F due to low rxlev. So skip TCH/F with low rxlev. */ + if (assignment + && !(lchan->type == GSM_LCHAN_TCH_F + && (is_low_rxlev(rxlev_current, bts->ho) || is_low_rxqual(current_rxqual(lchan), bts->ho)))) + collect_assignment_candidate(lchan, clist, candidates, rxlev_current); if (handover) { int i; for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) { collect_handover_candidate(lchan, &lchan->neigh_meas[i], clist, candidates, - include_weaker_rxlev, av_rxlev, &neighbors_count); + include_weaker_rxlev, rxlev_current, &neighbors_count); } } } @@ -1077,12 +1306,12 @@ static void collect_candidates_for_lchan(struct gsm_lchan *lchan, * If minimum RXLEV, minimum RXQUAL or maximum TA are exceeded, the caller should pass * include_weaker_rxlev=true so that handover is performed despite congestion. */ -static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_rxlev) +static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_rxlev, bool request_upgrade_to_tch_f) { struct gsm_bts *bts = lchan->ts->trx->bts; - int ahs = (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + int ahs = (gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR && lchan->type == GSM_LCHAN_TCH_H); - int av_rxlev; + int rxlev_current; struct ho_candidate clist[1 + ARRAY_SIZE(lchan->neigh_meas)]; unsigned int candidates = 0; int i; @@ -1098,7 +1327,7 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r return 0; } - collect_candidates_for_lchan(lchan, clist, &candidates, &av_rxlev, include_weaker_rxlev); + collect_candidates_for_lchan(lchan, clist, &candidates, &rxlev_current, include_weaker_rxlev); /* If assignment is disabled and no neighbor cell report exists, or no neighbor cell qualifies, * we may not even have any candidates. */ @@ -1117,14 +1346,15 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r continue; /* Only consider Local-BSS cells */ - if (!clist[i].bts) + if (!clist[i].target.bts) continue; - better = clist[i].avg - av_rxlev; - /* Apply AFS bias? */ + better = clist[i].target.rxlev - clist[i].current.rxlev; + /* Apply AFS bias? Skip AFS bias for all intra-cell candidates. */ afs_bias = 0; - if (ahs && (clist[i].requirements & REQUIREMENT_B_TCHF)) - afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + if (clist[i].target.bts != bts + && ahs && (clist[i].requirements & REQUIREMENT_B_TCHF)) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].target.bts->ho); better += afs_bias; if (better > best_better_db) { best_cand = &clist[i]; @@ -1136,7 +1366,7 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r /* perform handover, if there is a candidate */ if (best_cand) { LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate, RX level %d%s\n", - rxlev2dbm(best_cand->avg), + rxlev2dbm(best_cand->target.rxlev), best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); return trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_B_MASK); } @@ -1150,14 +1380,15 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r continue; /* Only consider Local-BSS cells */ - if (!clist[i].bts) + if (!clist[i].target.bts) continue; - better = clist[i].avg - av_rxlev; - /* Apply AFS bias? */ + better = clist[i].target.rxlev - clist[i].current.rxlev; + /* Apply AFS bias? Skip AFS bias for all intra-cell candidates. */ afs_bias = 0; - if (ahs && (clist[i].requirements & REQUIREMENT_C_TCHF)) - afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + if (clist[i].target.bts != bts + && ahs && (clist[i].requirements & REQUIREMENT_C_TCHF)) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].target.bts->ho); better += afs_bias; if (better > best_better_db) { best_cand = &clist[i]; @@ -1169,7 +1400,7 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r /* perform handover, if there is a candidate */ if (best_cand) { LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate, RX level %d%s\n", - rxlev2dbm(best_cand->avg), + rxlev2dbm(best_cand->target.rxlev), best_applied_afs_bias? " (applied AHS -> AFS rxlev bias)" : ""); return trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_C_MASK); } @@ -1190,15 +1421,19 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r if (!(clist[i].requirements & REQUIREMENT_A_MASK)) continue; - better = clist[i].avg - av_rxlev; - /* Apply AFS bias? + better = clist[i].target.rxlev - clist[i].current.rxlev; + /* Apply AFS bias? Skip AFS bias for all intra-cell candidates. * (never to remote-BSS neighbors, since we will not change the lchan type for those.) */ afs_bias = 0; if (ahs && (clist[i].requirements & REQUIREMENT_A_TCHF) - && clist[i].bts) - afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + && clist[i].target.bts && clist[i].target.bts != bts) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].target.bts->ho); better += afs_bias; - if (better > best_better_db) { + if (better > best_better_db + || (better >= best_better_db /* Upgrade from TCH/H to TCH/F: allow for equal rxlev */ + && request_upgrade_to_tch_f + && is_upgrade_to_tchf(&clist[i], REQUIREMENT_A_MASK))) { + best_cand = &clist[i]; best_better_db = better; best_applied_afs_bias = afs_bias? true : false; @@ -1207,10 +1442,22 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r /* perform handover, if there is a candidate */ if (best_cand) { + int rc; LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate: RX level %d%s\n", - rxlev2dbm(best_cand->avg), + rxlev2dbm(best_cand->target.rxlev), best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); - return trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_A_MASK); + rc = trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_A_MASK); + + /* After upgrading TCH/H to TCH/F due to bad RxQual, start penalty timer to avoid re-assignment within + * the same cell again, to avoid oscillation from RxQual noise combined with congestion resolution. */ + if (!rc && best_cand->target.bts == best_cand->current.bts + && is_upgrade_to_tchf(best_cand, REQUIREMENT_A_MASK)) { + struct gsm0808_cell_id bts_id; + gsm_bts_cell_id(&bts_id, best_cand->target.bts); + penalty_timers_add(lchan->conn, &lchan->conn->hodec2.penalty_timers, &bts_id, + ho_get_hodec2_penalty_low_rxqual_as(bts->ho)); + } + return rc; } /* Damn, all is congested, has too low RXLEV or cannot service the voice call due to codec @@ -1276,14 +1523,8 @@ static void on_measurement_report(struct gsm_meas_rep *mr) } /* get average levels. if not enough measurements yet, value is < 0 */ - av_rxlev = get_meas_rep_avg(lchan, - ho_get_hodec2_full_tdma(bts->ho) ? - MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB, - ho_get_hodec2_rxlev_avg_win(bts->ho)); - av_rxqual = get_meas_rep_avg(lchan, - ho_get_hodec2_full_tdma(bts->ho) ? - MEAS_REP_DL_RXQUAL_FULL : MEAS_REP_DL_RXQUAL_SUB, - ho_get_hodec2_rxqual_avg_win(bts->ho)); + av_rxlev = current_rxlev(lchan); + av_rxqual = current_rxqual(lchan); if (av_rxlev < 0 && av_rxqual < 0) { LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Not enough recent measurements\n"); return; @@ -1291,7 +1532,7 @@ static void on_measurement_report(struct gsm_meas_rep *mr) /* improve levels in case of AFS, if defined */ if (lchan->type == GSM_LCHAN_TCH_F - && lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + && gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) { int av_rxlev_was = av_rxlev; int av_rxqual_was = av_rxqual; int rxlev_bias = ho_get_hodec2_afs_bias_rxlev(bts->ho); @@ -1323,31 +1564,34 @@ static void on_measurement_report(struct gsm_meas_rep *mr) global_ho_reason = HO_REASON_BAD_QUALITY; LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment due to bad quality\n"); } - find_alternative_lchan(lchan, true); + find_alternative_lchan(lchan, true, true); return; } /* Low Level */ - if (av_rxlev >= 0 && rxlev2dbm(av_rxlev) < ho_get_hodec2_min_rxlev(bts->ho)) { + if (is_low_rxlev(av_rxlev, bts->ho)) { global_ho_reason = HO_REASON_LOW_RXLEVEL; LOGPHOLCHAN(lchan, LOGL_NOTICE, "RX level is TOO LOW: %d < %d\n", rxlev2dbm(av_rxlev), ho_get_hodec2_min_rxlev(bts->ho)); - find_alternative_lchan(lchan, true); + find_alternative_lchan(lchan, true, true); return; } /* Max Distance */ if (lchan->meas_rep_count > 0 - && lchan->rqd_ta > ho_get_hodec2_max_distance(bts->ho)) { + && lchan->last_ta > ho_get_hodec2_max_distance(bts->ho)) { + struct gsm0808_cell_id bts_id; global_ho_reason = HO_REASON_MAX_DISTANCE; LOGPHOLCHAN(lchan, LOGL_NOTICE, "TA is TOO HIGH: %u > %d\n", - lchan->rqd_ta, ho_get_hodec2_max_distance(bts->ho)); + lchan->last_ta, ho_get_hodec2_max_distance(bts->ho)); /* start penalty timer to prevent coming back too * early. it must be started before selecting a better cell, * so there is no assignment selected, due to running * penalty timer. */ - bts_penalty_time_add(lchan->conn, bts, ho_get_hodec2_penalty_max_dist(bts->ho)); - find_alternative_lchan(lchan, true); + gsm_bts_cell_id(&bts_id, bts); + penalty_timers_add(lchan->conn, &lchan->conn->hodec2.penalty_timers, &bts_id, + ho_get_hodec2_penalty_max_dist(bts->ho)); + find_alternative_lchan(lchan, true, false); return; } @@ -1358,10 +1602,123 @@ static void on_measurement_report(struct gsm_meas_rep *mr) /* try handover to a better cell */ if (av_rxlev >= 0 && (mr->nr % pwr_interval) == 0) { global_ho_reason = HO_REASON_BETTER_CELL; - find_alternative_lchan(lchan, false); + find_alternative_lchan(lchan, false, false); } } +static bool lchan_is_on_dynamic_ts(struct gsm_lchan *lchan) +{ + return lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN + || lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH; +} + +/* Given two candidates, pick the one that should rather be moved during handover. + * Return the better candidate in out-parameters best_cand and best_avg_db. + */ +static struct ho_candidate *pick_better_lchan_to_move(struct ho_candidate *a, + struct ho_candidate *b, + uint8_t for_requirement) +{ + int a_rxlev_change; + int b_rxlev_change; + struct ho_candidate *ret = a; + + if (!a) + return b; + if (!b) + return a; + + a_rxlev_change = a->target.rxlev - a->current.rxlev; + b_rxlev_change = b->target.rxlev - b->current.rxlev; + + /* Typically, a congestion related handover reduces RXLEV. If there is a candidate that actually improves RXLEV, + * prefer that, because it pre-empts a likely handover due to measurement results later. Also favor unchanged + * RXLEV over a loss of RXLEV (favor staying within the same cell over moving to a worse cell). */ + if (a_rxlev_change >= 0 && a_rxlev_change > b_rxlev_change) + return a; + if (b_rxlev_change >= 0 && b_rxlev_change > a_rxlev_change) + return b; + + if (a_rxlev_change < 0 && b_rxlev_change < 0) { + /* For handover that reduces RXLEV, favor the highest resulting RXLEV, AFS bias applied. */ + int a_rxlev = a->target.rxlev + a->target.rxlev_afs_bias; + int b_rxlev = b->target.rxlev + b->target.rxlev_afs_bias; + + if (a_rxlev > b_rxlev) + return a; + if (b_rxlev > a_rxlev) + return b; + /* There is no target RXLEV difference between the two candidates. Let other factors influence the + * choice. */ + } + + /* Prefer picking a dynamic timeslot: free PDCH and allow more timeslot type flexibility for further + * congestion resolution. */ + if (lchan_is_on_dynamic_ts(b->current.lchan)) { + unsigned int ac, bc; + + if (!lchan_is_on_dynamic_ts(a->current.lchan)) + return b; + + /* Both are dynamic timeslots. Prefer one that completely (or to a higher degree) frees its + * timeslot. */ + ac = ts_usage_count(a->current.lchan->ts); + bc = ts_usage_count(b->current.lchan->ts); + if (bc < ac) + return b; + if (ac < bc) + return a; + /* (If both are dynamic timeslots, favor moving the later dynamic timeslot. That is a vague preference + * for later dynamic TS to become PDCH and join up with plain PDCH that follow it -- not actually clear + * whether that helps, and depends on user's TS config. No harm done either way.) */ + ret = b; + } + + /* When upgrading TCH/H to TCH/F, favor moving a TCH/H with lower current rxlev, because presumably that + * one benefits more from a higher bandwidth. */ + if (is_upgrade_to_tchf(a, for_requirement) && is_upgrade_to_tchf(b, for_requirement)) { + if (b->current.rxlev < a->current.rxlev) + return b; + if (a->current.rxlev < b->current.rxlev) + return a; + } + + return ret; +} + +static struct ho_candidate *pick_best_candidate(struct ho_candidate *clist, int clist_len, + uint8_t for_requirement) +{ + struct ho_candidate *result = NULL; + int i; + + for (i = 0; i < clist_len; i++) { + struct ho_candidate *c = &clist[i]; + + /* For multiple passes of congestion resolution, already handovered candidates are marked by lchan = + * NULL. (though at the time of writing, multiple passes of congestion resolution are DISABLED.) */ + if (!c->current.lchan) + continue; + + /* Omit remote BSS */ + if (!c->target.bts) + continue; + + if (!(c->requirements & for_requirement)) + continue; + + /* improve AHS */ + if (is_upgrade_to_tchf(c, for_requirement)) + c->target.rxlev_afs_bias = ho_get_hodec2_afs_bias_rxlev(c->target.bts->ho); + else + c->target.rxlev_afs_bias = 0; + + result = pick_better_lchan_to_move(result, c, for_requirement); + } + + return result; +} + /* * Handover/assignment check after timer timeout: * @@ -1419,13 +1776,9 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int int i, j; struct ho_candidate *clist; unsigned int candidates; - struct ho_candidate *best_cand = NULL, *worst_cand = NULL; - struct gsm_lchan *delete_lchan = NULL; - unsigned int best_avg_db, worst_avg_db; - int avg; + struct ho_candidate *best_cand = NULL; int rc = 0; int any_ho = 0; - int is_improved = 0; if (tchf_congestion < 0) tchf_congestion = 0; @@ -1511,70 +1864,27 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int LOGPHOCAND(&clist[i], LOGL_DEBUG, "#%d: req={TCH/F:" REQUIREMENTS_FMT ", TCH/H:" REQUIREMENTS_FMT "} avg-rxlev=%d dBm\n", i, REQUIREMENTS_ARGS(clist[i].requirements, F), REQUIREMENTS_ARGS(clist[i].requirements, H), - rxlev2dbm(clist[i].avg)); + rxlev2dbm(clist[i].target.rxlev)); } } #if 0 next_b1: #endif - /* select best candidate that fulfills requirement B, - * omit change from AHS to AFS */ - best_avg_db = 0; - for (i = 0; i < candidates; i++) { - /* delete subscriber that just have handovered */ - if (clist[i].lchan == delete_lchan) - clist[i].lchan = NULL; - /* omit all subscribers that are handovered */ - if (!clist[i].lchan) - continue; - - /* Do not resolve congestion towards remote BSS, which would cause oscillation if the - * remote BSS is also congested. */ - /* TODO: attempt inter-BSC HO if no local cells qualify, and rely on the remote BSS to - * deny receiving the handover if it also considers itself congested. Maybe do that only - * when the cell is absolutely full, i.e. not only min-free-slots. (x) */ - if (!clist[i].bts) - continue; - - if (!(clist[i].requirements & REQUIREMENT_B_MASK)) - continue; - /* omit assignment from AHS to AFS */ - if (clist[i].lchan->ts->trx->bts == clist[i].bts - && clist[i].lchan->type == GSM_LCHAN_TCH_H - && (clist[i].requirements & REQUIREMENT_B_TCHF)) - continue; - /* omit candidates that will not solve/reduce congestion */ - if (clist[i].lchan->type == GSM_LCHAN_TCH_F - && tchf_congestion <= 0) - continue; - if (clist[i].lchan->type == GSM_LCHAN_TCH_H - && tchh_congestion <= 0) - continue; - - avg = clist[i].avg; - /* improve AHS */ - if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR - && clist[i].lchan->type == GSM_LCHAN_TCH_H - && (clist[i].requirements & REQUIREMENT_B_TCHF)) { - avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); - is_improved = 1; - } else - is_improved = 0; - LOGPHOCAND(&clist[i], LOGL_DEBUG, "candidate %d: avg=%d best_avg_db=%d\n", - i, avg, best_avg_db); - if (avg > best_avg_db) { - best_cand = &clist[i]; - best_avg_db = avg; - } - } - - /* perform handover, if there is a candidate */ + /* select best candidate that does not cause congestion in the target. + * Do not resolve congestion towards remote BSS, which would cause oscillation if the remote BSS is also + * congested. + * Treating specially below: upgrading TCH/H to TCH/F within the same cell, so omit here. + */ + /* TODO: attempt inter-BSC HO if no local cells qualify, and rely on the remote BSS to + * deny receiving the handover if it also considers itself congested. Maybe do that only + * when the cell is absolutely full, i.e. not only min-free-slots. (x) */ + best_cand = pick_best_candidate(clist, candidates, REQUIREMENT_B_MASK); if (best_cand) { any_ho = 1; LOGPHOCAND(best_cand, LOGL_DEBUG, "Best candidate: RX level %d%s\n", - rxlev2dbm(best_cand->avg), - is_improved ? " (applied AHS->AFS bias)" : ""); + rxlev2dbm(best_cand->target.rxlev), + best_cand->target.rxlev_afs_bias ? " (applied AHS->AFS bias)" : ""); trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_B_MASK); #if 0 /* if there is still congestion, mark lchan as deleted @@ -1598,132 +1908,17 @@ next_b1: } #if 0 -next_b2: -#endif - /* select worst candidate that fulfills requirement B, - * select candidates that change from AHS to AFS only */ - if (tchh_congestion > 0) { - /* since this will only check half rate channels, it will - * only need to be checked, if tchh is congested */ - worst_avg_db = 999; - for (i = 0; i < candidates; i++) { - /* delete subscriber that just have handovered */ - if (clist[i].lchan == delete_lchan) - clist[i].lchan = NULL; - /* omit all subscribers that are handovered */ - if (!clist[i].lchan) - continue; - - /* Do not resolve congestion towards remote BSS, which would cause oscillation if - * the remote BSS is also congested. */ - /* TODO: see (x) above */ - if (!clist[i].bts) - continue; - - if (!(clist[i].requirements & REQUIREMENT_B_MASK)) - continue; - /* omit all but assignment from AHS to AFS */ - if (clist[i].lchan->ts->trx->bts != clist[i].bts - || clist[i].lchan->type != GSM_LCHAN_TCH_H - || !(clist[i].requirements & REQUIREMENT_B_TCHF)) - continue; - - avg = clist[i].avg; - /* improve AHS */ - if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR - && clist[i].lchan->type == GSM_LCHAN_TCH_H) { - avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); - is_improved = 1; - } else - is_improved = 0; - if (avg < worst_avg_db) { - worst_cand = &clist[i]; - worst_avg_db = avg; - } - } - } - - /* perform handover, if there is a candidate */ - if (worst_cand) { - any_ho = 1; - LOGPHOCAND(worst_cand, LOGL_INFO, "Worst candidate: RX level %d from TCH/H -> TCH/F%s\n", - rxlev2dbm(worst_cand->avg), - is_improved ? " (applied AHS -> AFS rxlev bias)" : ""); - trigger_ho(worst_cand, worst_cand->requirements & REQUIREMENT_B_MASK); -#if 0 - /* if there is still congestion, mark lchan as deleted - * and redo this process */ - tchh_congestion--; - if (tchh_congestion > 0) { - delete_lchan = worst_cand->lchan; - best_cand = NULL; - goto next_b2; - } -#else - /* must exit here, because triggering handover/assignment - * will cause change in requirements. more check for this - * bts is performed in the next iteration. - */ -#endif - goto exit; - } - -#if 0 next_c1: #endif - /* select best candidate that fulfills requirement C, - * omit change from AHS to AFS */ - best_avg_db = 0; - for (i = 0; i < candidates; i++) { - /* delete subscriber that just have handovered */ - if (clist[i].lchan == delete_lchan) - clist[i].lchan = NULL; - /* omit all subscribers that are handovered */ - if (!clist[i].lchan) - continue; - - /* Do not resolve congestion towards remote BSS, which would cause oscillation if - * the remote BSS is also congested. */ - /* TODO: see (x) above */ - if (!clist[i].bts) - continue; - - if (!(clist[i].requirements & REQUIREMENT_C_MASK)) - continue; - /* omit assignment from AHS to AFS */ - if (clist[i].lchan->ts->trx->bts == clist[i].bts - && clist[i].lchan->type == GSM_LCHAN_TCH_H - && (clist[i].requirements & REQUIREMENT_C_TCHF)) - continue; - /* omit candidates that will not solve/reduce congestion */ - if (clist[i].lchan->type == GSM_LCHAN_TCH_F - && tchf_congestion <= 0) - continue; - if (clist[i].lchan->type == GSM_LCHAN_TCH_H - && tchh_congestion <= 0) - continue; - - avg = clist[i].avg; - /* improve AHS */ - if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR - && clist[i].lchan->type == GSM_LCHAN_TCH_H - && (clist[i].requirements & REQUIREMENT_C_TCHF)) { - avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); - is_improved = 1; - } else - is_improved = 0; - if (avg > best_avg_db) { - best_cand = &clist[i]; - best_avg_db = avg; - } - } - - /* perform handover, if there is a candidate */ + /* Select best candidate that balances congestion. + * Again no remote BSS. + * Again no TCH/H -> F upgrades within the same cell. */ + best_cand = pick_best_candidate(clist, candidates, REQUIREMENT_C_MASK); if (best_cand) { any_ho = 1; LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate: RX level %d%s\n", - rxlev2dbm(best_cand->avg), - is_improved ? " (applied AHS -> AFS rxlev bias)" : ""); + rxlev2dbm(best_cand->target.rxlev), + best_cand->target.rxlev_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_C_MASK); #if 0 /* if there is still congestion, mark lchan as deleted @@ -1746,84 +1941,7 @@ next_c1: goto exit; } - LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a best candidate that fulfills requirement C" - " (omitting change from AHS to AFS)\n"); - -#if 0 -next_c2: -#endif - /* select worst candidate that fulfills requirement C, - * select candidates that change from AHS to AFS only */ - if (tchh_congestion > 0) { - /* since this will only check half rate channels, it will - * only need to be checked, if tchh is congested */ - worst_avg_db = 999; - for (i = 0; i < candidates; i++) { - /* delete subscriber that just have handovered */ - if (clist[i].lchan == delete_lchan) - clist[i].lchan = NULL; - /* omit all subscribers that are handovered */ - if (!clist[i].lchan) - continue; - - /* Do not resolve congestion towards remote BSS, which would cause oscillation if - * the remote BSS is also congested. */ - /* TODO: see (x) above */ - if (!clist[i].bts) - continue; - - if (!(clist[i].requirements & REQUIREMENT_C_MASK)) - continue; - /* omit all but assignment from AHS to AFS */ - if (clist[i].lchan->ts->trx->bts != clist[i].bts - || clist[i].lchan->type != GSM_LCHAN_TCH_H - || !(clist[i].requirements & REQUIREMENT_C_TCHF)) - continue; - - avg = clist[i].avg; - /* improve AHS */ - if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR - && clist[i].lchan->type == GSM_LCHAN_TCH_H) { - avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); - is_improved = 1; - } else - is_improved = 0; - LOGP(DHODEC, LOGL_DEBUG, "candidate %d: avg=%d worst_avg_db=%d\n", i, avg, - worst_avg_db); - if (avg < worst_avg_db) { - worst_cand = &clist[i]; - worst_avg_db = avg; - } - } - } - - /* perform handover, if there is a candidate */ - if (worst_cand) { - any_ho = 1; - LOGPHOCAND(worst_cand, LOGL_INFO, "Worst candidate: RX level %d from TCH/H -> TCH/F%s\n", - rxlev2dbm(worst_cand->avg), - is_improved ? " (applied AHS -> AFS rxlev bias)" : ""); - trigger_ho(worst_cand, worst_cand->requirements & REQUIREMENT_C_MASK); -#if 0 - /* if there is still congestion, mark lchan as deleted - * and redo this process */ - tchh_congestion--; - if (tchh_congestion > 0) { - delete_lchan = worst_cand->lchan; - worst_cand = NULL; - goto next_c2; - } -#else - /* must exit here, because triggering handover/assignment - * will cause change in requirements. more check for this - * bts is performed in the next iteration. - */ -#endif - goto exit; - } - LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a worst candidate that fulfills requirement C," - " selecting candidates that change from AHS to AFS only\n"); - + LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a best candidate that fulfills requirement C\n"); exit: /* free array */ @@ -1843,8 +1961,9 @@ exit: static void bts_congestion_check(struct gsm_bts *bts) { + struct chan_counts *bts_counts; int min_free_tchf, min_free_tchh; - int tchf_count, tchh_count; + int free_tchf, free_tchh; global_ho_reason = HO_REASON_CONGESTION; @@ -1870,19 +1989,20 @@ static void bts_congestion_check(struct gsm_bts *bts) return; } - tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F); - tchh_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_H); + bts_counts = &bts->chan_counts; + free_tchf = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]; + free_tchh = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]; LOGPHOBTS(bts, LOGL_INFO, "Congestion check: (free/want-free) TCH/F=%d/%d TCH/H=%d/%d\n", - tchf_count, min_free_tchf, tchh_count, min_free_tchh); + free_tchf, min_free_tchf, free_tchh, min_free_tchh); /* only check BTS if congested */ - if (tchf_count >= min_free_tchf && tchh_count >= min_free_tchh) { + if (free_tchf >= min_free_tchf && free_tchh >= min_free_tchh) { LOGPHOBTS(bts, LOGL_DEBUG, "Not congested\n"); return; } LOGPHOBTS(bts, LOGL_DEBUG, "Attempting to resolve congestion...\n"); - bts_resolve_congestion(bts, min_free_tchf - tchf_count, min_free_tchh - tchh_count); + bts_resolve_congestion(bts, min_free_tchf - free_tchf, min_free_tchh - free_tchh); } void hodec2_congestion_check(struct gsm_network *net) @@ -1903,7 +2023,6 @@ static void congestion_check_cb(void *arg) static void on_handover_end(struct gsm_subscriber_connection *conn, enum handover_result result) { struct gsm_bts *old_bts = NULL; - struct gsm_bts *new_bts = NULL; int penalty; struct handover *ho = &conn->ho; @@ -1913,8 +2032,6 @@ static void on_handover_end(struct gsm_subscriber_connection *conn, enum handove if (conn->lchan) old_bts = conn->lchan->ts->trx->bts; - if (ho->new_lchan) - new_bts = ho->new_lchan->ts->trx->bts; /* Only interested in handovers within this BSS or going out into another BSS. Incoming handovers * from another BSS are accounted for in the other BSS. */ @@ -1941,11 +2058,7 @@ static void on_handover_end(struct gsm_subscriber_connection *conn, enum handove LOG_HO(conn, LOGL_NOTICE, "Failed, starting penalty timer (%d s)\n", penalty); conn->hodec2.failures = 0; - - if (new_bts) - bts_penalty_time_add(conn, new_bts, penalty); - else - nik_penalty_time_add(conn, &ho->target_cell, penalty); + penalty_timers_add_list(conn, &conn->hodec2.penalty_timers, &ho->target_cell_ids, penalty); } static struct handover_decision_callbacks hodec2_callbacks = { diff --git a/src/osmo-bsc/handover_fsm.c b/src/osmo-bsc/handover_fsm.c index 573f249a1..24766a5ea 100644 --- a/src/osmo-bsc/handover_fsm.c +++ b/src/osmo-bsc/handover_fsm.c @@ -45,6 +45,8 @@ #include <osmocom/bsc/gsm_08_08.h> #include <osmocom/bsc/bts.h> #include <osmocom/bsc/lcs_loc_req.h> +#include <osmocom/bsc/bsc_stats.h> +#include <osmocom/bsc/lchan.h> #define LOG_FMT_BTS "bts %u lac-ci %u-%u arfcn-bsic %d-%d" #define LOG_ARGS_BTS(bts) \ @@ -59,9 +61,9 @@ lchan ? lchan->ts->trx->bts->nr : 0, \ lchan ? lchan->ts->trx->nr : 0, \ lchan ? lchan->ts->nr : 0, \ - lchan ? gsm_lchant_name(lchan->type) : "?", \ + lchan ? gsm_chan_t_name(lchan->type) : "?", \ lchan ? lchan->nr : 0, \ - lchan ? gsm48_chan_mode_name(lchan->tch_mode) : "?" + lchan ? gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode) : "?" #define LOG_FMT_TO_LCHAN "%u-%u-%u-%s%s%s-%u" #define LOG_ARGS_TO_LCHAN(lchan) \ @@ -88,8 +90,8 @@ LOG_HO(conn, LOGL_DEBUG, "(BSC) incrementing rate counter: %s %s\n", \ bsc_ctr_description[counter].name, \ bsc_ctr_description[counter].description); \ - rate_ctr_inc(&conn->network->bsc_ctrs->ctr[counter]); \ - } while(0) + rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bsc_ctrs, counter)); \ + } while (0) /* Assume presence of local var 'conn' as struct gsm_subscriber_connection. * Handles bts == NULL gracefully @@ -102,15 +104,18 @@ bts_ctr_description[counter].name, \ bts_ctr_description[counter].description); \ if (bts) \ - rate_ctr_inc(&bts->bts_ctrs->ctr[counter]); \ + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, counter)); \ else \ - rate_ctr_inc(&conn->network->bts_unknown_ctrs->ctr[counter]); \ - } while(0) + rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bts_unknown_ctrs, counter)); \ + } while (0) +/* Count handover result on both bts and bsc level. + * Call with 'counter' being the counter name without the "BSC_"/"BTS_" part, + * e.g. ho_count(conn_get_bts(conn), CTR_HANDOVER_ATTEMPTED); */ #define ho_count(bts, counter) do { \ - ho_count_bsc(BSC_##counter); \ - ho_count_bts(bts, BTS_##counter); \ -} while(0) + ho_count_bsc(BSC_##counter); \ + ho_count_bts(bts, BTS_##counter); \ + } while (0) static uint8_t g_next_ho_ref = 1; @@ -134,7 +139,7 @@ const char *handover_status(struct gsm_subscriber_connection *conn) "("LOG_FMT_FROM_LCHAN") --HO-> ("LOG_FMT_BTS",%s) " LOG_FMT_HO_SCOPE, LOG_ARGS_FROM_LCHAN(conn->lchan), LOG_ARGS_BTS(ho->new_bts), - gsm_lchant_name(ho->new_lchan_type), + gsm_chan_t_name(ho->new_lchan_type), LOG_ARGS_HO_SCOPE(conn)); else snprintf(buf, sizeof(buf), @@ -146,7 +151,7 @@ const char *handover_status(struct gsm_subscriber_connection *conn) snprintf(buf, sizeof(buf), "("LOG_FMT_FROM_LCHAN") --HO-> (%s) " LOG_FMT_HO_SCOPE, LOG_ARGS_FROM_LCHAN(conn->lchan), - neighbor_ident_key_name(&ho->target_cell), + cell_ab_to_str_c(OTC_SELECT, &ho->target_cell_ab), LOG_ARGS_HO_SCOPE(conn)); else if (ho->scope & HO_INTER_BSC_IN) { @@ -163,14 +168,14 @@ const char *handover_status(struct gsm_subscriber_connection *conn) ho->inter_bsc_in.cell_id_serving_name, ho->inter_bsc_in.cell_id_target_name, LOG_ARGS_BTS(ho->new_bts), - gsm_lchant_name(ho->new_lchan_type), + gsm_chan_t_name(ho->new_lchan_type), LOG_ARGS_HO_SCOPE(conn)); else snprintf(buf, sizeof(buf), "(remote:%s) --HO-> (local:%s,%s) " LOG_FMT_HO_SCOPE, ho->inter_bsc_in.cell_id_serving_name, ho->inter_bsc_in.cell_id_target_name, - gsm_lchant_name(ho->new_lchan_type), + gsm_chan_t_name(ho->new_lchan_type), LOG_ARGS_HO_SCOPE(conn)); } else snprintf(buf, sizeof(buf), LOG_FMT_HO_SCOPE, LOG_ARGS_HO_SCOPE(conn)); @@ -188,11 +193,11 @@ struct gsm_subscriber_connection *ho_fi_conn(struct osmo_fsm_inst *fi) } static const struct osmo_tdef_state_timeout ho_fsm_timeouts[32] = { - [HO_ST_WAIT_LCHAN_ACTIVE] = { .T = 23042 }, - [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = 23042 }, - [HO_ST_WAIT_RR_HO_DETECT] = { .T = 23042 }, - [HO_ST_WAIT_RR_HO_COMPLETE] = { .T = 23042 }, - [HO_ST_WAIT_LCHAN_ESTABLISHED] = { .T = 23042 }, + [HO_ST_WAIT_LCHAN_ACTIVE] = { /* Guarded by X5 + X6 in lchan_fsm_timeouts */ }, + [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = -9 }, + [HO_ST_WAIT_RR_HO_DETECT] = { .T = 3103 }, + [HO_ST_WAIT_RR_HO_COMPLETE] = { .keep_timer = true }, /* Keep T3103 */ + [HO_ST_WAIT_LCHAN_ESTABLISHED] = { /* Guarded by T3101 in lchan_fsm_timeouts */ }, [HO_OUT_ST_WAIT_HO_COMMAND] = { .T = 7 }, [HO_OUT_ST_WAIT_CLEAR] = { .T = 8 }, }; @@ -211,15 +216,15 @@ static const struct osmo_tdef_state_timeout ho_fsm_timeouts[32] = { LOG_HO(conn, LOGL_ERROR, "Handover failed in state %s, %s: " fmt "\n", \ osmo_fsm_inst_state_name(conn->fi), handover_result_name(result), ## args); \ handover_end(conn, result); \ - } while(0) + } while (0) #define ho_success() do { \ LOG_HO(conn, LOGL_DEBUG, "Handover succeeded\n"); \ handover_end(conn, HO_RESULT_OK); \ - } while(0) + } while (0) /* issue handover to a cell identified by ARFCN and BSIC */ -void handover_request(struct handover_out_req *req) +int handover_request(struct handover_out_req *req) { struct gsm_subscriber_connection *conn; OSMO_ASSERT(req->old_lchan); @@ -227,12 +232,9 @@ void handover_request(struct handover_out_req *req) conn = req->old_lchan->conn; OSMO_ASSERT(conn && conn->fi); - /* Make sure the handover target neighbor_ident_key contains the correct source bts nr */ - req->target_nik.from_bts = req->old_lchan->ts->trx->bts->nr; - /* To make sure we're allowed to start a handover, go through a gscon event dispatch. If that is accepted, the * same req is passed to handover_start(). */ - osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_START, req); + return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_START, req); } /* Check that ho has old_lchan and/or new_lchan and conn pointers match. @@ -267,7 +269,7 @@ static void ho_fsm_update_id(struct osmo_fsm_inst *fi, const char *label) if (conn->fi->id) osmo_fsm_inst_update_id_f(fi, "%s_%s", label, conn->fi->id); else - osmo_fsm_inst_update_id_f(fi, "%s_conn%u", label, conn->sccp.conn_id); + osmo_fsm_inst_update_id_f(fi, "%s_conn%u", label, conn->sccp.conn.conn_id); } static void handover_reset(struct gsm_subscriber_connection *conn) @@ -275,7 +277,8 @@ static void handover_reset(struct gsm_subscriber_connection *conn) struct osmo_mgcpc_ep_ci *ci; if (conn->ho.new_lchan) /* New lchan was activated but never passed to a conn */ - lchan_release(conn->ho.new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + lchan_release(conn->ho.new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, + NULL); ci = conn->ho.created_ci_for_msc; if (ci) { @@ -290,7 +293,7 @@ static void handover_reset(struct gsm_subscriber_connection *conn) }; } -void handover_fsm_init() +static __attribute__((constructor)) void handover_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&ho_fsm) == 0); } @@ -315,10 +318,10 @@ void handover_start(struct handover_out_req *req) OSMO_ASSERT(req && req->old_lchan && req->old_lchan->conn); struct gsm_subscriber_connection *conn = req->old_lchan->conn; - const struct neighbor_ident_key *search_for = &req->target_nik; + const struct cell_ab *search_for = &req->target_cell_ab; struct handover *ho = &conn->ho; struct gsm_bts *local_target_cell = NULL; - const struct gsm0808_cell_id_list2 *remote_target_cell = NULL; + struct gsm0808_cell_id_list2 remote_target_cells = {}; if (conn->ho.fi) { LOG_HO(conn, LOGL_ERROR, "Handover requested while another handover is ongoing; Ignore\n"); @@ -335,9 +338,9 @@ void handover_start(struct handover_out_req *req) ho->from_hodec_id = req->from_hodec_id; ho->new_lchan_type = req->new_lchan_type == GSM_LCHAN_NONE ? req->old_lchan->type : req->new_lchan_type; - ho->target_cell = req->target_nik; + ho->target_cell_ab = req->target_cell_ab; - if (find_handover_target_cell(&local_target_cell, &remote_target_cell, + if (find_handover_target_cell(&local_target_cell, &remote_target_cells, conn, search_for, true)) { handover_end(conn, HO_RESULT_ERROR); return; @@ -349,8 +352,8 @@ void handover_start(struct handover_out_req *req) return; } - if (remote_target_cell) { - handover_start_inter_bsc_out(conn, remote_target_cell); + if (remote_target_cells.id_list_len) { + handover_start_inter_bsc_out(conn, &remote_target_cells); return; } @@ -375,8 +378,12 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn) ho->scope = (ho->new_bts == bts) ? HO_INTRA_CELL : HO_INTRA_BSC; ho->ho_ref = g_next_ho_ref++; ho->async = true; + gsm_bts_cell_id_list(&ho->target_cell_ids, ho->new_bts); - ho->new_lchan = lchan_select_by_type(ho->new_bts, ho->new_lchan_type); + ho->new_lchan = lchan_select_by_type(ho->new_bts, + ho->new_lchan_type, + SELECT_FOR_HANDOVER, + NULL); if (ho->scope & HO_INTRA_CELL) { ho_count(bts, CTR_INTRA_CELL_HO_ATTEMPTED); @@ -384,12 +391,13 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn) } else { ho_count(bts, CTR_INTRA_BSC_HO_ATTEMPTED); ho_fsm_update_id(fi, "intraBSC"); + ho_count_bts(ho->new_bts, BTS_CTR_INCOMING_INTRA_BSC_HO_ATTEMPTED); } if (!ho->new_lchan) { ho_fail(HO_RESULT_FAIL_NO_CHANNEL, "No %s lchan available on BTS %u", - gsm_lchant_name(ho->new_lchan_type), ho->new_bts->nr); + gsm_chan_t_name(ho->new_lchan_type), ho->new_bts->nr); return; } LOG_HO(conn, LOGL_DEBUG, "Selected lchan %s\n", gsm_lchan_name(ho->new_lchan)); @@ -397,20 +405,53 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn) ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE); info = (struct lchan_activate_info){ - .activ_for = FOR_HANDOVER, + .activ_for = ACTIVATE_FOR_HANDOVER, .for_conn = conn, - .chan_mode = conn->lchan->tch_mode, + .ch_mode_rate = conn->lchan->current_ch_mode_rate, .encr = conn->lchan->encr, - .requires_voice_stream = conn->lchan->mgw_endpoint_ci_bts ? true : false, + .ch_indctr = conn->lchan->current_ch_indctr, .msc_assigned_cic = conn->ho.inter_bsc_in.msc_assigned_cic, .re_use_mgw_endpoint_from_lchan = conn->lchan, .wait_before_switching_rtp = true, - .s15_s0 = conn->lchan->activate.info.s15_s0, }; + info.ch_mode_rate.chan_rate = chan_t_to_chan_rate(ho->new_lchan->type); + + /* For intra-cell handover, we know the accurate Timing Advance from the previous lchan. For inter-cell + * handover, no Timing Advance for the new cell is known, so leave it unset. */ + if (ho->new_bts == bts) { + info.ta = conn->lchan->last_ta; + info.ta_known = true; + } lchan_activate(ho->new_lchan, &info); } +/* 3GPP TS 48.008 § 3.2.2.58 Old BSS to New BSS Information */ +static void parse_old2new_bss_info(struct gsm_subscriber_connection *conn, + const uint8_t* data, uint16_t len, + struct handover_in_req *req) +{ + /* § 3.2.2.58: Information contained here shall take precedence over + duplicate information from Information Elements in the HANDOVER + REQUEST as long as the coding is understood by the new BSS */ + /* § 3.2.2.58: <<Reception of an erroneous "Old BSS to New BSS + information" shall not cause a rejection of the HANDOVER REQUEST + message; the "Old BSS to New BSS information" information element + shall be discarded and the handover resource allocation procedure + shall continue>>. See also 3.1.19.7. */ + struct tlv_parsed tp; + if (tlv_parse(&tp, &gsm0808_old_bss_to_new_bss_info_att_tlvdef, data, len, 0, 0) <= 0) { + LOG_HO(conn, LOGL_NOTICE, "Failed to parse IE \"Old BSS to New BSS information\"\n"); + return; + } + + if (TLVP_VAL(&tp, GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID)) { + req->last_eutran_plmn_valid = true; + osmo_plmn_from_bcd(TLVP_VAL(&tp, GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID), + &req->last_eutran_plmn); + } +} + /* 3GPP TS 48.008 § 3.2.1.8 Handover Request */ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struct msgb *msg, struct handover_in_req *req) @@ -422,6 +463,7 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc int payload_length; bool aoip = gscon_is_aoip(conn); bool sccplite = gscon_is_sccplite(conn); + bool has_a54 = false; if ((aoip && sccplite) || !(aoip || sccplite)) { LOG_HO(conn, LOGL_ERROR, "Received BSSMAP Handover Request, but conn is not" @@ -452,6 +494,16 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc LOG_HO(conn, LOGL_ERROR, "Failed to parse Encryption Information IE\n"); return false; } + req->ei_as_bitmask = *e->val; + + if ((e = TLVP_GET(tp, GSM0808_IE_KC_128))) { + if (e->len != 16) { + LOG_HO(conn, LOGL_ERROR, "Invalid length in Kc128 IE: %u bytes (expected 16)\n", e->len); + return false; + } + memcpy(req->kc128, e->val, 16); + req->kc128_present = true; + } if ((e = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_TYPE_1))) { if (e->len != sizeof(req->classmark.classmark1)) { @@ -481,9 +533,10 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc req->chosen_encr_alg); } - LOG_HO(conn, LOGL_DEBUG, "Handover Request encryption info: chosen=A5/%u key=%s\n", - (req->chosen_encr_alg ? : 1) - 1, req->ei.key_len? - osmo_hexdump_nospc(req->ei.key, req->ei.key_len) : "none"); + LOG_HO(conn, LOGL_DEBUG, "Handover Request encryption info: chosen=A5/%u key=%s kc128=%s\n", + (req->chosen_encr_alg ? : 1) - 1, + req->ei.key_len ? osmo_hexdump_nospc(req->ei.key, req->ei.key_len) : "none", + has_a54 ? osmo_hexdump_nospc(req->kc128, 16) : "none"); if (TLVP_PRESENT(tp, GSM0808_IE_AOIP_TRASP_ADDR)) { int rc; @@ -551,21 +604,30 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc return false; } - /* A lot of IEs remain ignored... */ - - return true; -} + if ((e = TLVP_GET(tp, GSM0808_IE_OLD_BSS_TO_NEW_BSS_INFORMATION))) { + parse_old2new_bss_info(conn, e->val, e->len, req); + } -static bool chan_mode_is_tch(enum gsm48_chan_mode mode) -{ - switch (mode) { - case GSM48_CMODE_SPEECH_V1: - case GSM48_CMODE_SPEECH_EFR: - case GSM48_CMODE_SPEECH_AMR: - return true; - default: + /* Decode "Codec List (MSC Preferred)". First set len = 0 to empty the list. (For inter-BSC incoming handover, + * there can't possibly be a list here already, because the conn has just now been created; just do ensure + * sanity.) */ + conn->codec_list = (struct gsm0808_speech_codec_list){}; + if ((e = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST))) { + if (gsm0808_dec_speech_codec_list(&conn->codec_list, e->val, e->len) < 0) { + LOG_HO(conn, LOGL_ERROR, "incoming inter-BSC Handover: HO Request:" + " Unable to decode Codec List (MSC Preferred)\n"); + return false; + } + } + if (aoip && !conn->codec_list.len) { + LOG_HO(conn, LOGL_ERROR, "incoming inter-BSC Handover: HO Request:" + " Invalid or empty Codec List (MSC Preferred)\n"); return false; } + + /* A lot of IEs remain ignored... */ + + return true; } void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, @@ -578,6 +640,7 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, int match_idx; struct osmo_fsm_inst *fi; struct channel_mode_and_rate ch_mode_rate = {}; + int chosen_a5_n; handover_fsm_alloc(conn); @@ -593,7 +656,7 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, ho_fsm_update_id(fi, "interBSCin"); if (!parse_ho_request(conn, ho_request_msg, req)) { - ho_fail(HO_RESULT_ERROR, "Invalid Handover Request message from MSC\n"); + ho_fail(HO_RESULT_ERROR, "Invalid Handover Request message from MSC"); return; } @@ -625,7 +688,10 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, ch_mode_rate.chan_rate == CH_RATE_FULL ? "full-rate" : "half-rate", gsm0808_channel_type_name(&req->ct)); - lchan = lchan_select_by_chan_mode(bts, ch_mode_rate.chan_mode, ch_mode_rate.chan_rate); + lchan = lchan_select_by_chan_mode(bts, + ch_mode_rate.chan_mode, + ch_mode_rate.chan_rate, + SELECT_FOR_HANDOVER, NULL); if (!lchan) { LOG_HO(conn, LOGL_DEBUG, "BTS %u has no matching free channels\n", bts->nr); continue; @@ -658,26 +724,37 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE); info = (struct lchan_activate_info){ - .activ_for = FOR_HANDOVER, + .activ_for = ACTIVATE_FOR_HANDOVER, .for_conn = conn, - .chan_mode = ch_mode_rate.chan_mode, - .s15_s0 = ch_mode_rate.s15_s0, - .requires_voice_stream = chan_mode_is_tch(ch_mode_rate.chan_mode), + .ch_mode_rate = ch_mode_rate, + .ch_indctr = req->ct.ch_indctr, .msc_assigned_cic = req->msc_assigned_cic, }; - if (req->chosen_encr_alg) { - info.encr.alg_id = req->chosen_encr_alg; - if (info.encr.alg_id > 1 && !req->ei.key_len) { - ho_fail(HO_RESULT_ERROR, "Chosen Encryption Algorithm (Serving) reflects A5/%u" - " but there is no key (Encryption Information)", info.encr.alg_id - 1); + /* Figure out the encryption algorithm */ + chosen_a5_n = select_best_cipher(req->ei_as_bitmask, bsc_gsmnet->a5_encryption_mask); + if (chosen_a5_n < 0) { + ho_fail(HO_RESULT_FAIL_RR_HO_FAIL, + "There is no A5 encryption mode that both BSC and MSC permit: MSC 0x%x & BSC 0x%x = 0", + req->ei_as_bitmask, bsc_gsmnet->a5_encryption_mask); + return; + } + if (chosen_a5_n > 0 && !req->ei.key_len) { + /* There is no key. Is A5/0 permitted? */ + if ((req->ei_as_bitmask & bsc_gsmnet->a5_encryption_mask & 0x1) == 0x1) { + chosen_a5_n = 0; + } else { + ho_fail(HO_RESULT_ERROR, + "Encryption is required, but there is no key (Encryption Information)"); return; } } - if (req->ei.key_len) { + /* Put encryption info in the chan activation info */ + info.encr.alg_a5_n = chosen_a5_n; + if (chosen_a5_n > 0) { if (req->ei.key_len > sizeof(info.encr.key)) { - ho_fail(HO_RESULT_ERROR, "Encryption Information IE key length is too large: %u\n", + ho_fail(HO_RESULT_ERROR, "Encryption Information IE key length is too large: %u", req->ei.key_len); return; } @@ -685,9 +762,39 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn, info.encr.key_len = req->ei.key_len; } + if (req->kc128_present) { + memcpy(info.encr.kc128, req->kc128, 16); + info.encr.kc128_present = true; + } + + if (req->last_eutran_plmn_valid) { + conn->fast_return.allowed = ho->new_bts->srvcc_fast_return_allowed; + conn->fast_return.last_eutran_plmn_valid = true; + memcpy(&conn->fast_return.last_eutran_plmn, &req->last_eutran_plmn, + sizeof(conn->fast_return.last_eutran_plmn)); + ho_count(ho->new_bts, CTR_SRVCC_ATTEMPTED); + } + lchan_activate(ho->new_lchan, &info); } +/* Create functions result_counter_{BSC,BTS}_{HANDOVER,...}(), to evaluate the handover result and return + * BSC_CTR_HANDOVER_ATTEMPTED, + * BSC_CTR_HANDOVER_COMPLETED, + * BSC_CTR_HANDOVER_STOPPED, + * BSC_CTR_HANDOVER_NO_CHANNEL, + * BSC_CTR_HANDOVER_TIMEOUT, + * BSC_CTR_HANDOVER_FAILED, + * BSC_CTR_HANDOVER_ERROR, + * or + * BTS_CTR_HANDOVER_ATTEMPTED, + * BTS_CTR_HANDOVER_COMPLETED, + * BTS_CTR_HANDOVER_STOPPED, + * BTS_CTR_HANDOVER_NO_CHANNEL, + * BTS_CTR_HANDOVER_TIMEOUT, + * BTS_CTR_HANDOVER_FAILED, + * BTS_CTR_HANDOVER_ERROR, + */ #define FUNC_RESULT_COUNTER(obj, name) \ static int result_counter_##obj##_##name(enum handover_result result) \ { \ @@ -713,6 +820,7 @@ FUNC_RESULT_COUNTER(BSC, INTRA_CELL_HO) FUNC_RESULT_COUNTER(BSC, INTRA_BSC_HO) FUNC_RESULT_COUNTER(BSC, INTER_BSC_HO_IN) +/* INTRA_BSC_HO_OUT does not have a NO_CHANNEL result, so can't do this with FUNC_RESULT_COUNTER() macro. */ static int result_counter_BSC_INTER_BSC_HO_OUT(enum handover_result result) { switch (result) { case HO_RESULT_OK: @@ -748,8 +856,10 @@ static int result_counter_bsc(enum handover_scope scope, enum handover_result re FUNC_RESULT_COUNTER(BTS, HANDOVER) FUNC_RESULT_COUNTER(BTS, INTRA_CELL_HO) FUNC_RESULT_COUNTER(BTS, INTRA_BSC_HO) +FUNC_RESULT_COUNTER(BTS, INCOMING_INTRA_BSC_HO) FUNC_RESULT_COUNTER(BTS, INTER_BSC_HO_IN) +/* INTRA_BSC_HO_OUT does not have a NO_CHANNEL result, so can't do this with FUNC_RESULT_COUNTER() macro. */ static int result_counter_BTS_INTER_BSC_HO_OUT(enum handover_result result) { switch (result) { case HO_RESULT_OK: @@ -782,6 +892,9 @@ static int result_counter_bts(enum handover_scope scope, enum handover_result re } } +FUNC_RESULT_COUNTER(BSC, SRVCC) +FUNC_RESULT_COUNTER(BTS, SRVCC) + static void send_handover_performed(struct gsm_subscriber_connection *conn) { struct gsm_lchan *lchan = conn->lchan; @@ -807,7 +920,7 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn) }; /* Chosen Channel 3.2.2.33 */ - ho_perf_params.chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode); + ho_perf_params.chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode); if (!ho_perf_params.chosen_channel) { LOG_HO(conn, LOGL_ERROR, "Failed to generate Chosen Channel IE, can't send HANDOVER PERFORMED!\n"); return; @@ -815,19 +928,20 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn) ho_perf_params.chosen_channel_present = true; /* Chosen Encryption Algorithm 3.2.2.44 */ - ho_perf_params.chosen_encr_alg = lchan->encr.alg_id; + ho_perf_params.chosen_encr_alg = ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n); ho_perf_params.chosen_encr_alg_present = true; - if (ho->new_lchan->activate.info.requires_voice_stream) { + if (bsc_chan_ind_requires_rtp_stream(ho->new_lchan->activate.info.ch_indctr)) { /* Speech Version (chosen) 3.2.2.51 */ - ho_perf_params.speech_version_chosen = gsm0808_permitted_speech(lchan->type, lchan->tch_mode); + ho_perf_params.speech_version_chosen = gsm0808_permitted_speech(lchan->type, + lchan->current_ch_mode_rate.chan_mode); ho_perf_params.speech_version_chosen_present = true; /* Speech Codec (chosen) 3.2.2.104 */ if (gscon_is_aoip(conn)) { /* Extrapolate speech codec from speech mode */ gsm0808_speech_codec_from_chan_type(&sc, ho_perf_params.speech_version_chosen); - sc.cfg = conn->lchan->ch_mode_rate.s15_s0; + sc.cfg = conn->lchan->current_ch_mode_rate.s15_s0; memcpy(&ho_perf_params.speech_codec_chosen, &sc, sizeof(sc)); ho_perf_params.speech_codec_chosen_present = true; } @@ -839,7 +953,7 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn) return; } - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED)); rc = gscon_sigtran_send(conn, msg); if (rc < 0) { LOG_HO(conn, LOGL_ERROR, "message sending failed, can't send HANDOVER PERFORMED!\n"); @@ -877,7 +991,7 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r result = bsc_tx_bssmap_ho_complete(conn, ho->new_lchan); } /* Not 'else': above checks may still result in HO_RESULT_ERROR. */ - if (result == HO_RESULT_ERROR) { + if (result != HO_RESULT_OK) { /* Return a BSSMAP Handover Failure, as described in 3GPP TS 48.008 3.1.5.2.2 * "Handover Resource Allocation Failure" */ bsc_tx_bssmap_ho_failure(conn); @@ -903,7 +1017,8 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r /* 3GPP TS 48.008 3.1.5.3.3 "Abnormal Conditions": if neither MS reports * HO Failure nor the MSC sends a Clear Command, we should release the * dedicated radio resources and send a Clear Request to the MSC. */ - lchan_release(conn->lchan, true, true, GSM48_RR_CAUSE_ABNORMAL_TIMER); + lchan_release(conn->lchan, true, true, GSM48_RR_CAUSE_ABNORMAL_TIMER, + gscon_last_eutran_plmn(conn)); /* Once the channel release is through, the BSSMAP Clear will follow. */ break; } @@ -933,13 +1048,30 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r ho_count_bsc(result_counter_bsc(ho->scope, result)); ho_count_bts(bts, result_counter_BTS_HANDOVER(result)); ho_count_bts(bts, result_counter_bts(ho->scope, result)); + /* For inter-cell HO, also increment the "INCOMING" counters on the target BTS. */ + if (ho->scope & HO_INTRA_BSC) + ho_count_bts(ho->new_bts, result_counter_BTS_INCOMING_INTRA_BSC_HO(result)); + if (ho->scope & HO_INTER_BSC_IN && conn->fast_return.last_eutran_plmn_valid) { + /* From outside local BSC and with Last EUTRAN PLMN Id => SRVCC */ + ho_count_bsc(result_counter_BSC_SRVCC(result)); + ho_count_bts(bts, result_counter_BTS_SRVCC(result)); + } LOG_HO(conn, LOGL_INFO, "Result: %s\n", handover_result_name(result)); if (ho->new_lchan && result == HO_RESULT_OK) { + struct gsm_bts *bts; + gscon_change_primary_lchan(conn, conn->ho.new_lchan); ho->new_lchan = NULL; + bts = conn_get_bts(conn); + if (is_siemens_bts(bts) && ts_is_tch(conn->lchan->ts)) { + /* HACK: store the actual Classmark 2 LV from the subscriber and use it here! */ + uint8_t cm2_lv[] = { 0x02, 0x00, 0x00 }; + send_siemens_mrpci(conn->lchan, cm2_lv); + } + /* If a Perform Location Request (LCS) is busy, inform the SMLC that there is a new lchan */ if (conn->lcs.loc_req) osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_HANDOVER_PERFORMED, NULL); @@ -950,7 +1082,7 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r /* Detach the new_lchan last, so we can still see it in above logging */ if (ho->new_lchan) { /* Release new lchan, it didn't work out */ - lchan_release(ho->new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + lchan_release(ho->new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); ho->new_lchan = NULL; } @@ -985,7 +1117,7 @@ static void ho_fsm_wait_lchan_active(struct osmo_fsm_inst *fi, uint32_t event, v * So create an MSC side endpoint CI only if a voice lchan is established for an incoming inter-BSC * handover on AoIP. Otherwise go on to send a Handover Command and wait for the Detect. */ - if (ho->new_lchan->activate.info.requires_voice_stream + if (bsc_chan_ind_requires_rtp_stream(ho->new_lchan->activate.info.ch_indctr) && (ho->scope & HO_INTER_BSC_IN) && gscon_is_aoip(conn)) ho_fsm_state_chg(HO_ST_WAIT_MGW_ENDPOINT_TO_MSC); @@ -1079,6 +1211,7 @@ static void ho_fsm_wait_rr_ho_detect_onenter(struct osmo_fsm_inst *fi, uint32_t struct handover *ho = &conn->ho; struct msgb *rr_ho_cmd = gsm48_make_ho_cmd(ho->new_lchan, + ho->scope, ho->async, ho->new_lchan->ms_power, ho->ho_ref); if (!rr_ho_cmd) { diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c index ade330d46..1e1b6c319 100644 --- a/src/osmo-bsc/handover_logic.c +++ b/src/osmo-bsc/handover_logic.c @@ -112,7 +112,7 @@ int bts_handover_count(struct gsm_bts *bts, int ho_scopes) if (!nm_is_running(&ts->mo.nm_state)) continue; - ts_for_each_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { if (!lchan->conn) continue; if (!lchan->conn->ho.fi) @@ -126,7 +126,7 @@ int bts_handover_count(struct gsm_bts *bts, int ho_scopes) return count; } -/* Find out a handover target cell for the given neighbor_ident_key, +/* Find out a handover target cell for the given arfcn_bsic, * and make sure there are no ambiguous matches. * Given a source BTS and a target ARFCN+BSIC, find which cell is the right handover target. * ARFCN+BSIC may be re-used within and/or across BSS, so make sure that only those cells that are explicitly @@ -138,22 +138,20 @@ int bts_handover_count(struct gsm_bts *bts, int ho_scopes) * to be found. */ int find_handover_target_cell(struct gsm_bts **local_target_cell_p, - const struct gsm0808_cell_id_list2 **remote_target_cell_p, - struct gsm_subscriber_connection *conn, const struct neighbor_ident_key *search_for, + struct gsm0808_cell_id_list2 *remote_target_cells, + struct gsm_subscriber_connection *conn, + const struct cell_ab *search_for, bool log_errors) { struct gsm_network *net = conn->network; - struct gsm_bts *from_bts; struct gsm_bts *local_target_cell = NULL; - const struct gsm0808_cell_id_list2 *remote_target_cell = NULL; - struct gsm_bts_ref *neigh; bool ho_active; bool as_active; + struct gsm_bts *from_bts = conn->lchan->ts->trx->bts; + *remote_target_cells = (struct gsm0808_cell_id_list2){}; if (local_target_cell_p) *local_target_cell_p = NULL; - if (remote_target_cell_p) - *remote_target_cell_p = NULL; if (!search_for) { if (log_errors) @@ -161,7 +159,6 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p, return -EINVAL; } - from_bts = gsm_bts_num(net, search_for->from_bts); if (!from_bts) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Handover without source cell\n"); @@ -174,12 +171,11 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p, if (!ho_active && !as_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot start Handover: Handover and Assignment disabled for this source cell (%s)\n", - neighbor_ident_key_name(search_for)); + cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } - if (llist_empty(&from_bts->local_neighbors) - && !neighbor_ident_bts_entry_exists(from_bts->nr)) { + if (llist_empty(&from_bts->neighbors)) { /* No explicit neighbor entries exist for this BTS. Hence apply the legacy default behavior that all * local cells are neighbors. */ struct gsm_bts *bts; @@ -192,15 +188,16 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p, for (i = 0; i < 2; i++) { bool exact_match = !i; llist_for_each_entry(bts, &net->bts_list, list) { - struct neighbor_ident_key bts_key = *bts_ident_key(bts); - if (neighbor_ident_key_match(&bts_key, search_for, exact_match)) { + struct cell_ab bts_ab; + bts_cell_ab(&bts_ab, bts); + if (cell_ab_match(&bts_ab, search_for, exact_match)) { if (local_target_cell) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "NEIGHBOR CONFIGURATION ERROR: Multiple local cells match %s" " (BTS %d and BTS %d)." " Aborting Handover because of ambiguous network topology.\n", - neighbor_ident_key_name(search_for), + cell_ab_to_str_c(OTC_SELECT, search_for), local_target_cell->nr, bts->nr); return -EINVAL; } @@ -214,7 +211,7 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p, if (!local_target_cell) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot Handover, no cell matches %s\n", - neighbor_ident_key_name(search_for)); + cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } @@ -222,14 +219,14 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p, if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot start re-assignment, Assignment disabled for this cell (%s)\n", - neighbor_ident_key_name(search_for)); + cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } if (local_target_cell != from_bts && !ho_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot start Handover, Handover disabled for this cell (%s)\n", - neighbor_ident_key_name(search_for)); + cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } @@ -243,96 +240,64 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p, LOG_HO(conn, LOGL_DEBUG, "There are explicit neighbors configured for this cell\n"); - /* Iterate explicit local neighbor cells */ - llist_for_each_entry(neigh, &from_bts->local_neighbors, entry) { - struct gsm_bts *neigh_bts = neigh->bts; - struct neighbor_ident_key neigh_bts_key = *bts_ident_key(neigh_bts); - neigh_bts_key.from_bts = from_bts->nr; - - LOG_HO(conn, LOGL_DEBUG, "Local neighbor %s\n", neighbor_ident_key_name(&neigh_bts_key)); - - if (!neighbor_ident_key_match(&neigh_bts_key, search_for, true)) { - LOG_HO(conn, LOGL_DEBUG, "Doesn't match %s\n", neighbor_ident_key_name(search_for)); - continue; - } - - if (local_target_cell) { - if (log_errors) - LOG_HO(conn, LOGL_ERROR, - "NEIGHBOR CONFIGURATION ERROR: Multiple BTS match %s (BTS %d and BTS %d)." - " Aborting Handover because of ambiguous network topology.\n", - neighbor_ident_key_name(search_for), local_target_cell->nr, neigh_bts->nr); - return -EINVAL; - } - - local_target_cell = neigh_bts; + if (resolve_neighbors(&local_target_cell, remote_target_cells, from_bts, search_for, log_errors)) { + LOG_HO(conn, LOGL_ERROR, "Cannot handover BTS %u -> %s: neighbor unknown\n", + from_bts->nr, cell_ab_to_str_c(OTC_SELECT, search_for)); + return -ENOENT; } - /* Any matching remote-BSS neighbor cell? */ - remote_target_cell = neighbor_ident_get(net->neighbor_bss_cells, search_for); + /* We have found possibly a local_target_cell (when != NULL), and / or remote_target_cells (when .id_list_len > + * 0). Figure out what to do with them. */ - if (remote_target_cell) - LOG_HO(conn, LOGL_DEBUG, "Found remote target cell %s\n", - gsm0808_cell_id_list_name(remote_target_cell)); + if (remote_target_cells->id_list_len) + LOG_HO(conn, LOGL_DEBUG, "Found remote target cell(s) %s\n", + gsm0808_cell_id_list_name_c(OTC_SELECT, remote_target_cells)); - if (local_target_cell && remote_target_cell) { + if (local_target_cell && remote_target_cells->id_list_len) { if (log_errors) - LOG_HO(conn, LOGL_ERROR, "NEIGHBOR CONFIGURATION ERROR: Both a local and a remote-BSS cell match %s" - " (BTS %d and remote %s)." + LOG_HO(conn, LOGL_ERROR, "NEIGHBOR CONFIGURATION ERROR: Both a local and a remote-BSS cell" + " match BTS %u -> %s (BTS %d and remote %s)." " Aborting Handover because of ambiguous network topology.\n", - neighbor_ident_key_name(search_for), local_target_cell->nr, - gsm0808_cell_id_list_name(remote_target_cell)); + from_bts->nr, cell_ab_to_str_c(OTC_SELECT, search_for), local_target_cell->bts_nr, + gsm0808_cell_id_list_name_c(OTC_SELECT, remote_target_cells)); return -EINVAL; } if (local_target_cell == from_bts && !as_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, - "Cannot start re-assignment, Assignment disabled for this cell (%s)\n", - neighbor_ident_key_name(search_for)); + "Cannot start re-assignment, Assignment disabled for this cell (BTS %u)\n", + from_bts->nr); return -EINVAL; } if (((local_target_cell && local_target_cell != from_bts) - || remote_target_cell) + || remote_target_cells->id_list_len) && !ho_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, - "Cannot start Handover, Handover disabled for this cell (%s)\n", - neighbor_ident_key_name(search_for)); + "Cannot start Handover, Handover disabled for this cell (BTS %u -> %s)\n", + from_bts->bts_nr, cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } + /* Return the result. After above checks, only one of local or remote cell has been found. */ if (local_target_cell) { if (local_target_cell_p) *local_target_cell_p = local_target_cell; return 0; } - if (remote_target_cell) { - if (remote_target_cell_p) - *remote_target_cell_p = remote_target_cell; + if (remote_target_cells->id_list_len) return 0; - } if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot handover %s: neighbor unknown\n", - neighbor_ident_key_name(search_for)); + cell_ab_to_str_c(OTC_SELECT, search_for)); return -ENODEV; } -struct neighbor_ident_key *bts_ident_key(const struct gsm_bts *bts) -{ - static struct neighbor_ident_key key; - key = (struct neighbor_ident_key){ - .from_bts = NEIGHBOR_IDENT_KEY_ANY_BTS, - .arfcn = bts->c0->arfcn, - .bsic = bts->bsic, - }; - return &key; -} - static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { diff --git a/src/osmo-bsc/handover_vty.c b/src/osmo-bsc/handover_vty.c index e5435f53d..afc12d9d1 100644 --- a/src/osmo-bsc/handover_vty.c +++ b/src/osmo-bsc/handover_vty.c @@ -170,7 +170,7 @@ HODEC1_CFG_ALL_MEMBERS #undef HO_CFG_ONE_MEMBER } -void ho_vty_init() +void ho_vty_init(void) { ho_vty_init_cmds(GSMNET_NODE); install_element(GSMNET_NODE, &cfg_net_ho_congestion_check_interval_cmd); diff --git a/src/osmo-bsc/lb.c b/src/osmo-bsc/lb.c index 654f26144..511a54528 100644 --- a/src/osmo-bsc/lb.c +++ b/src/osmo-bsc/lb.c @@ -31,22 +31,10 @@ #include <osmocom/bsc/osmo_bsc_sigtran.h> #include <osmocom/bsc/lcs_loc_req.h> #include <osmocom/bsc/bssmap_reset.h> - -static struct gsm_subscriber_connection *get_bsc_conn_by_lb_conn_id(int conn_id) -{ - struct gsm_subscriber_connection *conn; - - llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) { - if (conn->lcs.lb.state != SUBSCR_SCCP_ST_NONE - && conn->lcs.lb.conn_id == conn_id) - return conn; - } - - return NULL; -} +#include <osmocom/bsc/gsm_data.h> /* Send reset to SMLC */ -int bssmap_le_tx_reset() +int bssmap_le_tx_reset(void) { struct osmo_ss7_instance *ss7; struct msgb *msg; @@ -58,18 +46,23 @@ int bssmap_le_tx_reset() }, }; + if (!bsc_gsmnet->smlc->sccp_user) { + LOGP(DRESET, LOGL_DEBUG, "Not sending RESET to SMLC, Lb link down\n"); + return -1; + } + ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance); OSMO_ASSERT(ss7); LOGP(DRESET, LOGL_INFO, "Sending RESET to SMLC: %s\n", osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr)); msg = osmo_bssap_le_enc(&reset); - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_UDT_RESET]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_UDT_RESET)); return osmo_sccp_tx_unitdata_msg(bsc_gsmnet->smlc->sccp_user, &bsc_gsmnet->smlc->bsc_addr, &bsc_gsmnet->smlc->smlc_addr, msg); } /* Send reset-ack to SMLC */ -int bssmap_le_tx_reset_ack() +int bssmap_le_tx_reset_ack(void) { struct osmo_ss7_instance *ss7; struct msgb *msg; @@ -85,7 +78,7 @@ int bssmap_le_tx_reset_ack() LOGP(DRESET, LOGL_NOTICE, "Sending RESET ACK to SMLC: %s\n", osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr)); msg = osmo_bssap_le_enc(&reset_ack); - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK)); return osmo_sccp_tx_unitdata_msg(bsc_gsmnet->smlc->sccp_user, &bsc_gsmnet->smlc->bsc_addr, &bsc_gsmnet->smlc->smlc_addr, msg); } @@ -95,8 +88,8 @@ static int handle_unitdata_from_smlc(const struct osmo_sccp_addr *smlc_addr, str { struct osmo_ss7_instance *ss7; struct bssap_le_pdu bssap_le; - struct osmo_bssap_le_err *err; - struct rate_ctr *ctr = bsc_gsmnet->smlc->ctrs->ctr; + struct osmo_bssap_le_err *err = NULL; + struct rate_ctr_group *ctrg = bsc_gsmnet->smlc->ctrs; ss7 = osmo_sccp_get_ss7(osmo_sccp_get_sccp(scu)); OSMO_ASSERT(ss7); @@ -104,13 +97,13 @@ static int handle_unitdata_from_smlc(const struct osmo_sccp_addr *smlc_addr, str if (osmo_sccp_addr_cmp(smlc_addr, &bsc_gsmnet->smlc->smlc_addr, OSMO_SCCP_ADDR_T_MASK)) { LOGP(DLCS, LOGL_ERROR, "Rx BSSMAP-LE UnitData from unknown remote address: %s\n", osmo_sccp_addr_name(ss7, smlc_addr)); - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_UNKNOWN_PEER]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UNKNOWN_PEER)); return -EINVAL; } if (osmo_bssap_le_dec(&bssap_le, &err, msg, msg)) { LOGP(DLCS, LOGL_ERROR, "Rx BSSAP-LE UnitData with error: %s\n", err->logmsg); - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG)); return -EINVAL; } @@ -121,17 +114,17 @@ static int handle_unitdata_from_smlc(const struct osmo_sccp_addr *smlc_addr, str switch (bssap_le.bssmap_le.msg_type) { case BSSMAP_LE_MSGT_RESET: - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_RESET]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_RESET)); LOGP(DLCS, LOGL_NOTICE, "RESET from SMLC: %s\n", osmo_sccp_addr_name(ss7, smlc_addr)); return osmo_fsm_inst_dispatch(bsc_gsmnet->smlc->bssmap_reset->fi, BSSMAP_RESET_EV_RX_RESET, NULL); case BSSMAP_LE_MSGT_RESET_ACK: - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK)); LOGP(DLCS, LOGL_NOTICE, "RESET-ACK from SMLC: %s\n", osmo_sccp_addr_name(ss7, smlc_addr)); return osmo_fsm_inst_dispatch(bsc_gsmnet->smlc->bssmap_reset->fi, BSSMAP_RESET_EV_RX_RESET_ACK, NULL); default: - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG)); LOGP(DLCS, LOGL_ERROR, "Rx unimplemented UDT message type %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le)); return -EINVAL; @@ -142,6 +135,8 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) { struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *)oph; struct osmo_sccp_user *scu = _scu; + struct osmo_sccp_instance *sccp = osmo_sccp_get_sccp(scu); + struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(sccp); struct gsm_subscriber_connection *conn; int rc = 0; @@ -164,7 +159,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM): /* Handle inbound confirmation of outbound connection */ DEBUGP(DLCS, "N-CONNECT.cnf(%u)\n", scu_prim->u.connect.conn_id); - conn = get_bsc_conn_by_lb_conn_id(scu_prim->u.connect.conn_id); + conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.connect.conn_id); if (conn) { conn->lcs.lb.state = SUBSCR_SCCP_ST_CONNECTED; if (msgb_l2len(oph->msg) > 0) { @@ -180,7 +175,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) /* Handle incoming connection oriented data */ DEBUGP(DLCS, "N-DATA.ind(%u)\n", scu_prim->u.data.conn_id); - conn = get_bsc_conn_by_lb_conn_id(scu_prim->u.data.conn_id); + conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.data.conn_id); if (!conn) { LOGP(DLCS, LOGL_ERROR, "N-DATA.ind(%u) for unknown conn_id\n", scu_prim->u.data.conn_id); rc = -EINVAL; @@ -198,12 +193,14 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)), scu_prim->u.disconnect.cause); /* indication of disconnect */ - conn = get_bsc_conn_by_lb_conn_id(scu_prim->u.disconnect.conn_id); + conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.disconnect.conn_id); if (!conn) { LOGP(DLCS, LOGL_ERROR, "N-DISCONNECT.ind for unknown conn_id %u\n", scu_prim->u.disconnect.conn_id); rc = -EINVAL; } else { + bsc_sccp_inst_unregister_gscon(bsc_sccp, &conn->lcs.lb.conn); + conn->lcs.lb.conn.conn_id = SCCP_CONN_ID_UNSET; conn->lcs.lb.state = SUBSCR_SCCP_ST_NONE; if (msgb_l2len(oph->msg) > 0) { rc = lcs_loc_req_rx_bssmap_le(conn, oph->msg); @@ -212,8 +209,9 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) break; default: - LOGP(DLCS, LOGL_ERROR, "Unhandled SIGTRAN operation %s on primitive %u\n", - get_value_string(osmo_prim_op_names, oph->operation), oph->primitive); + LOGP(DLCS, LOGL_ERROR, "Unhandled SIGTRAN primitive %s.%s\n", + osmo_scu_prim_type_name(oph->primitive), + get_value_string(osmo_prim_op_names, oph->operation)); break; } @@ -224,8 +222,9 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) static int lb_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg) { struct osmo_ss7_instance *ss7; - int conn_id; + uint32_t conn_id; int rc; + struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(bsc_gsmnet->smlc->sccp); OSMO_ASSERT(conn); OSMO_ASSERT(msg); @@ -236,35 +235,52 @@ static int lb_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg return -EINVAL; } - conn_id = bsc_sccp_inst_next_conn_id(bsc_gsmnet->smlc->sccp); - if (conn_id < 0) { + conn_id = bsc_sccp_inst_next_conn_id(bsc_sccp); + if (conn_id == SCCP_CONN_ID_UNSET) { LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, "Unable to allocate SCCP Connection ID for BSSMAP-LE to SMLC\n"); return -ENOSPC; } - conn->lcs.lb.conn_id = conn_id; + conn->lcs.lb.conn.conn_id = conn_id; + if (bsc_sccp_inst_register_gscon(bsc_sccp, &conn->lcs.lb.conn) < 0) { + LOGP(DMSC, LOGL_ERROR, "Unable to register Lb SCCP connection (id=%u)\n", conn->lcs.lb.conn.conn_id); + return -1; + } ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance); OSMO_ASSERT(ss7); - LOGPFSMSL(conn->fi, DLCS, LOGL_INFO, "Opening new SCCP connection (id=%i) to SMLC: %s\n", conn_id, + LOGPFSMSL(conn->fi, DLCS, LOGL_INFO, "Opening new SCCP connection (id=%u) to SMLC: %s\n", conn_id, osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr)); rc = osmo_sccp_tx_conn_req_msg(bsc_gsmnet->smlc->sccp_user, conn_id, &bsc_gsmnet->smlc->bsc_addr, &bsc_gsmnet->smlc->smlc_addr, msg); - if (rc >= 0) - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_SUCCESS]); - else - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_ERR_SEND]); - if (rc >= 0) + if (rc >= 0) { + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_SUCCESS)); conn->lcs.lb.state = SUBSCR_SCCP_ST_WAIT_CONN_CONF; + } else { + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_SEND)); + goto failed_unregister_conn_id; + } + + return rc; +failed_unregister_conn_id: + bsc_sccp_inst_unregister_gscon(bsc_sccp, &conn->lcs.lb.conn); + conn->lcs.lb.conn.conn_id = SCCP_CONN_ID_UNSET; return rc; } void lb_close_conn(struct gsm_subscriber_connection *conn) { + struct bsc_sccp_inst *bsc_sccp; if (conn->lcs.lb.state == SUBSCR_SCCP_ST_NONE) return; - osmo_sccp_tx_disconn(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn_id, &bsc_gsmnet->smlc->bsc_addr, 0); + OSMO_ASSERT(conn->lcs.lb.conn.conn_id != SCCP_CONN_ID_UNSET); + + osmo_sccp_tx_disconn(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn.conn_id, &bsc_gsmnet->smlc->bsc_addr, 0); + conn->lcs.lb.state = SUBSCR_SCCP_ST_NONE; + bsc_sccp = osmo_sccp_get_priv(bsc_gsmnet->smlc->sccp); + bsc_sccp_inst_unregister_gscon(bsc_sccp, &conn->lcs.lb.conn); + conn->lcs.lb.conn.conn_id = SCCP_CONN_ID_UNSET; } /* Send data to SMLC, take ownership of *msg */ @@ -297,11 +313,11 @@ int lb_send(struct gsm_subscriber_connection *conn, const struct bssap_le_pdu *b } LOGPFSMSL(conn->fi, DLCS, LOGL_DEBUG, "Tx %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le)); - rc = osmo_sccp_tx_data_msg(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn_id, msg); + rc = osmo_sccp_tx_data_msg(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn.conn_id, msg); if (rc >= 0) - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_SUCCESS]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_SUCCESS)); else - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_ERR_SEND]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_SEND)); count_tx: if (rc < 0) @@ -309,24 +325,24 @@ count_tx: switch (bssap_le->bssmap_le.msg_type) { case BSSMAP_LE_MSGT_PERFORM_LOC_REQ: - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST)); break; case BSSMAP_LE_MSGT_PERFORM_LOC_ABORT: - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_ABORT]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_ABORT)); break; case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO: switch (bssap_le->bssmap_le.conn_oriented_info.apdu.msg_type) { case BSSLAP_MSGT_TA_RESPONSE: - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE)); break; case BSSLAP_MSGT_REJECT: - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT)); break; case BSSLAP_MSGT_RESET: - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET)); break; case BSSLAP_MSGT_ABORT: - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT)); break; default: break; @@ -347,7 +363,7 @@ count_tx: #define DEFAULT_ASP_LOCAL_IP "localhost" #define DEFAULT_ASP_REMOTE_IP "localhost" -void lb_cancel_all() +void lb_cancel_all(void) { struct gsm_subscriber_connection *conn; llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) @@ -379,7 +395,7 @@ void lb_reset_tx_reset_ack(void *data) bssmap_le_tx_reset_ack(); } -static void lb_start_reset_fsm() +static void lb_start_reset_fsm(void) { struct bssmap_reset_cfg cfg = { .conn_cfm_failure_threshold = 3, @@ -399,7 +415,13 @@ static void lb_start_reset_fsm() bsc_gsmnet->smlc->bssmap_reset = bssmap_reset_alloc(bsc_gsmnet, "Lb", &cfg); } -static int lb_start() +static void lb_stop_reset_fsm(void) +{ + bssmap_reset_term_and_free(bsc_gsmnet->smlc->bssmap_reset); + bsc_gsmnet->smlc->bssmap_reset = NULL; +} + +static int lb_start(void) { uint32_t default_pc; struct osmo_ss7_instance *cs7_inst = NULL; @@ -407,6 +429,7 @@ static int lb_start() enum osmo_ss7_asp_protocol used_proto = OSMO_SS7_ASP_PROT_M3UA; char inst_name[32]; const char *smlc_name = "smlc"; + struct bsc_sccp_inst *bsc_sccp; /* Already set up? */ if (bsc_gsmnet->smlc->sccp_user) @@ -440,6 +463,9 @@ static int lb_start() if (!sccp) return -EINVAL; bsc_gsmnet->smlc->sccp = sccp; + bsc_sccp = bsc_sccp_inst_alloc(tall_bsc_ctx); + bsc_sccp->sccp = sccp; + osmo_sccp_set_priv(sccp, bsc_sccp); /* If unset, use default local SCCP address */ if (!bsc_gsmnet->smlc->bsc_addr.presence) @@ -473,7 +499,7 @@ static int lb_start() return 0; } -static int lb_stop() +static int lb_stop(void) { /* Not set up? */ if (!bsc_gsmnet->smlc->sccp_user) @@ -482,12 +508,13 @@ static int lb_stop() LOGP(DLCS, LOGL_INFO, "Shutting down Lb link\n"); lb_cancel_all(); + lb_stop_reset_fsm(); osmo_sccp_user_unbind(bsc_gsmnet->smlc->sccp_user); bsc_gsmnet->smlc->sccp_user = NULL; return 0; } -int lb_start_or_stop() +int lb_start_or_stop(void) { int rc; if (bsc_gsmnet->smlc->enable) { @@ -522,7 +549,7 @@ int lb_start_or_stop() static void smlc_vty_init(void); -int lb_init() +int lb_init(void) { OSMO_ASSERT(!bsc_gsmnet->smlc); bsc_gsmnet->smlc = talloc_zero(bsc_gsmnet, struct smlc_config); @@ -587,6 +614,28 @@ static void enforce_ssn(struct vty *vty, struct osmo_sccp_addr *addr, enum osmo_ addr->ssn = want_ssn; } +/* Prevent mixing addresses from different CS7 instances */ +bool smlc_set_cs7_instance(struct vty *vty, const char *from_vty_cmd, const char *from_addr, + struct osmo_ss7_instance *ss7) +{ + if (bsc_gsmnet->smlc->cs7_instance_valid) { + if (bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) { + LOGP(DLCS, LOGL_ERROR, + "%s: expecting address from cs7 instance %u, but '%s' is from %u\n", + from_vty_cmd, bsc_gsmnet->smlc->cs7_instance, from_addr, ss7->cfg.id); + vty_out(vty, "Error:" + " %s: expecting address from cs7 instance %u, but '%s' is from %u%s", + from_vty_cmd, bsc_gsmnet->smlc->cs7_instance, from_addr, ss7->cfg.id, VTY_NEWLINE); + return false; + } + } else { + bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id; + bsc_gsmnet->smlc->cs7_instance_valid = true; + LOGP(DLCS, LOGL_NOTICE, "Lb interface is using cs7 instance %u\n", bsc_gsmnet->smlc->cs7_instance); + } + return true; +} + DEFUN(cfg_smlc_cs7_bsc_addr, cfg_smlc_cs7_bsc_addr_cmd, "bsc-addr NAME", @@ -601,17 +650,9 @@ DEFUN(cfg_smlc_cs7_bsc_addr, return CMD_ERR_INCOMPLETE; } - /* Prevent mixing addresses from different CS7 instances */ - if (bsc_gsmnet->smlc->cs7_instance_valid - && bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) { - vty_out(vty, - "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s", - bsc_addr_name, VTY_NEWLINE); + if (!smlc_set_cs7_instance(vty, "smlc / bsc-addr", bsc_addr_name, ss7)) return CMD_WARNING; - } - bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id; - bsc_gsmnet->smlc->cs7_instance_valid = true; enforce_ssn(vty, &bsc_gsmnet->smlc->bsc_addr, OSMO_SCCP_SSN_BSC_BSSAP_LE); bsc_gsmnet->smlc->bsc_addr_name = talloc_strdup(bsc_gsmnet, bsc_addr_name); return CMD_SUCCESS; @@ -631,18 +672,9 @@ DEFUN(cfg_smlc_cs7_smlc_addr, return CMD_ERR_INCOMPLETE; } - /* Prevent mixing addresses from different CS7/SS7 instances */ - if (bsc_gsmnet->smlc->cs7_instance_valid) { - if (bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) { - vty_out(vty, - "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s", - smlc_addr_name, VTY_NEWLINE); - return CMD_ERR_INCOMPLETE; - } - } + if (!smlc_set_cs7_instance(vty, "smlc / smlc-addr", smlc_addr_name, ss7)) + return CMD_WARNING; - bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id; - bsc_gsmnet->smlc->cs7_instance_valid = true; enforce_ssn(vty, &bsc_gsmnet->smlc->smlc_addr, OSMO_SCCP_SSN_SMLC_BSSAP_LE); bsc_gsmnet->smlc->smlc_addr_name = talloc_strdup(bsc_gsmnet, smlc_addr_name); return CMD_SUCCESS; diff --git a/src/osmo-bsc/lchan.c b/src/osmo-bsc/lchan.c new file mode 100644 index 000000000..45d8d96f5 --- /dev/null +++ b/src/osmo-bsc/lchan.c @@ -0,0 +1,150 @@ +/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * (C) 2008-2018 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <stdbool.h> +#include <inttypes.h> + +#include <osmocom/gsm/gsm_utils.h> + +#include <osmocom/bsc/lchan.h> +#include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bts_trx.h> +#include <osmocom/bsc/abis_rsl.h> + +void lchan_init(struct gsm_lchan *lchan, struct gsm_bts_trx_ts *ts, unsigned int nr) +{ + lchan->ts = ts; + lchan->nr = nr; + lchan->type = GSM_LCHAN_NONE; + + lchan_update_name(lchan); +} + +void lchan_update_name(struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + if (lchan->name) + talloc_free(lchan->name); + lchan->name = talloc_asprintf(ts->trx, "(bts=%d,trx=%d,ts=%d,ss=%s%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + lchan->vamos.is_secondary ? "shadow" : "", + lchan->nr - (lchan->vamos.is_secondary ? ts->max_primary_lchans : 0)); +} + +/* If the lchan is currently active, return the duration since activation in milliseconds. + * Otherwise return 0. */ +uint64_t gsm_lchan_active_duration_ms(const struct gsm_lchan *lchan) +{ + struct timespec now, elapsed; + + if (lchan->active_start.tv_sec == 0 && lchan->active_start.tv_nsec == 0) + return 0; + + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &lchan->active_start, &elapsed); + + return elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000; +} + +/* For a VAMOS secondary shadow lchan, return its primary lchan. If the lchan is not a secondary lchan, return NULL. */ +struct gsm_lchan *gsm_lchan_vamos_to_primary(const struct gsm_lchan *lchan_vamos) +{ + struct gsm_lchan *lchan_primary; + if (!lchan_vamos || !lchan_vamos->vamos.is_secondary) + return NULL; + /* OsmoBSC currently does not support mixed TCH/F + TCH/H VAMOS multiplexes. Hence the primary <-> secondary + * relation is a simple index shift in the lchan array. If mixed multiplexes were allowed, a TCH/F primary might + * have two TCH/H VAMOS secondary lchans, etc. Fortunately, we don't need to care about that. */ + lchan_primary = (struct gsm_lchan *)lchan_vamos - lchan_vamos->ts->max_primary_lchans; + if (!lchan_primary->fi) + return NULL; + return lchan_primary; +} + +/* For a primary lchan, return its VAMOS secondary shadow lchan. If the lchan is not a primary lchan, return NULL. */ +struct gsm_lchan *gsm_lchan_primary_to_vamos(const struct gsm_lchan *lchan_primary) +{ + struct gsm_lchan *lchan_vamos; + if (!lchan_primary || lchan_primary->vamos.is_secondary) + return NULL; + /* OsmoBSC currently does not support mixed TCH/F + TCH/H VAMOS multiplexes. Hence the primary <-> secondary + * relation is a simple index shift in the lchan array. If mixed multiplexes were allowed, a TCH/F primary might + * have two TCH/H VAMOS secondary lchans, etc. Fortunately, we don't need to care about that. */ + lchan_vamos = (struct gsm_lchan *)lchan_primary + lchan_primary->ts->max_primary_lchans; + if (!lchan_vamos->fi) + return NULL; + return lchan_vamos; +} + +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); +} diff --git a/src/osmo-bsc/lchan_fsm.c b/src/osmo-bsc/lchan_fsm.c index e97c1baeb..55875f0b4 100644 --- a/src/osmo-bsc/lchan_fsm.c +++ b/src/osmo-bsc/lchan_fsm.c @@ -35,11 +35,15 @@ #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/bsc_rll.h> #include <osmocom/bsc/gsm_04_08_rr.h> +#include <osmocom/bsc/gsm_08_08.h> #include <osmocom/bsc/assignment_fsm.h> #include <osmocom/bsc/handover_fsm.h> #include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/codec_pref.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bsc_stats.h> +#include <osmocom/bsc/lchan.h> +#include <osmocom/bsc/vgcs_fsm.h> static struct osmo_fsm lchan_fsm; @@ -66,30 +70,71 @@ bool lchan_may_receive_data(struct gsm_lchan *lchan) } } -void lchan_set_last_error(struct gsm_lchan *lchan, const char *fmt, ...) +bool lchan_is_asci(struct gsm_lchan *lchan) { - va_list ap; - /* This dance allows using an existing error reason in above fmt */ - char *last_error_was = lchan->last_error; - lchan->last_error = NULL; + if (lchan->activate.info.type_for == LCHAN_TYPE_FOR_VGCS || + lchan->activate.info.type_for == LCHAN_TYPE_FOR_VBS) + return true; + return false; +} + +static void lchan_on_mode_modify_success(struct gsm_lchan *lchan) +{ + lchan->modify.concluded = true; + + switch (lchan->modify.info.modify_for) { - if (fmt) { - va_start(ap, fmt); - lchan->last_error = talloc_vasprintf(lchan->ts->trx, fmt, ap); - va_end(ap); + case MODIFY_FOR_ASSIGNMENT: + osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_MODIFIED, lchan); + break; - LOG_LCHAN(lchan, LOGL_ERROR, "%s\n", lchan->last_error); + default: + break; } +} + +#define lchan_on_mode_modify_failure(lchan, modify_for, for_conn) \ + _lchan_on_mode_modify_failure(lchan, modify_for, for_conn, \ + __FILE__, __LINE__) +static void _lchan_on_mode_modify_failure(struct gsm_lchan *lchan, enum lchan_modify_for modify_for, + struct gsm_subscriber_connection *for_conn, + const char *file, int line) +{ + if (lchan->modify.concluded) + return; + lchan->modify.concluded = true; + + switch (modify_for) { + + case MODIFY_FOR_ASSIGNMENT: + LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Assignment FSM of error (%s)\n", + lchan->last_error ? : "unknown error"); + if (!for_conn) { + LOG_LCHAN(lchan, LOGL_ERROR, "lchan Channel Mode Modify failed, " + "but modify request has no conn\n"); + break; + } + _osmo_fsm_inst_dispatch(for_conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, lchan, + file, line); + return; + + case MODIFY_FOR_VTY: + LOG_LCHAN(lchan, LOGL_ERROR, "VTY user invoked lchan Channel Mode Modify failed (%s)\n", + lchan->last_error ? : "unknown error"); + break; - if (last_error_was) - talloc_free(last_error_was); + default: + LOG_LCHAN(lchan, LOGL_ERROR, "lchan Channel Mode Modify failed (%s)\n", + lchan->last_error ? : "unknown error"); + break; + } } /* The idea here is that we must not require to change any lchan state in order to deny a request. */ #define lchan_on_activation_failure(lchan, for_conn, activ_for) \ _lchan_on_activation_failure(lchan, for_conn, activ_for, \ __FILE__, __LINE__) -static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_activate_mode activ_for, +static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_activate_for activ_for, struct gsm_subscriber_connection *for_conn, const char *file, int line) { @@ -99,7 +144,7 @@ static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_act switch (activ_for) { - case FOR_MS_CHANNEL_REQUEST: + case ACTIVATE_FOR_MS_CHANNEL_REQUEST: if (!lchan->activate.immediate_assignment_sent) { /* Failure before Immediate Assignment message, send a reject. */ LOG_LCHAN(lchan, LOGL_NOTICE, "Tx Immediate Assignment Reject (%s)\n", @@ -111,14 +156,26 @@ static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_act * lchan_on_activation_failure(), no additional action or logging needed. */ break; - case FOR_ASSIGNMENT: + case ACTIVATE_FOR_ASSIGNMENT: LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Assignment FSM of error (%s)\n", lchan->last_error ? : "unknown error"); - _osmo_fsm_inst_dispatch(for_conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, lchan, - file, line); - return; + if (!for_conn) { + LOG_LCHAN(lchan, LOGL_ERROR, + "lchan activation for Assignment failed, but activation request has" + " no conn\n"); + break; + } + if (!for_conn->assignment.fi) { + LOG_LCHAN(lchan, LOGL_ERROR, + "lchan activation for Assignment failed, but conn has no ongoing" + " assignment procedure\n"); + break; + } + _osmo_fsm_inst_dispatch(for_conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, + lchan, file, line); + break; - case FOR_HANDOVER: + case ACTIVATE_FOR_HANDOVER: LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Handover FSM of error (%s)\n", lchan->last_error ? : "unknown error"); if (!for_conn) { @@ -136,11 +193,32 @@ static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_act _osmo_fsm_inst_dispatch(for_conn->ho.fi, HO_EV_LCHAN_ERROR, lchan, file, line); break; - case FOR_VTY: + case ACTIVATE_FOR_VGCS_CHANNEL: + LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling VGCS Assignment FSM of error (%s)\n", + lchan->last_error ? : "unknown error"); + if (!for_conn) { + LOG_LCHAN(lchan, LOGL_ERROR, + "lchan activation for VGCS Assignment failed, but activation request has no conn\n"); + break; + } + if (!for_conn->vgcs_chan.fi) { + LOG_LCHAN(lchan, LOGL_ERROR, + "lchan activation for VGCS Assignment failed, but conn has no ongoing" + " assignment procedure\n"); + break; + } + _osmo_fsm_inst_dispatch(for_conn->vgcs_chan.fi, VGCS_EV_LCHAN_ERROR, lchan, file, line); + break; + + case ACTIVATE_FOR_VTY: LOG_LCHAN(lchan, LOGL_ERROR, "VTY user invoked lchan activation failed (%s)\n", lchan->last_error ? : "unknown error"); break; + case ACTIVATE_FOR_MODE_MODIFY_RTP: + lchan_on_mode_modify_failure(lchan, lchan->modify.info.modify_for, for_conn); + break; + default: LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation failed (%s)\n", lchan->last_error ? : "unknown error"); @@ -154,25 +232,42 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan) return; lchan->activate.concluded = true; + /* Set active state timekeeping markers. */ + osmo_clock_gettime(CLOCK_MONOTONIC, &lchan->active_start); + lchan->active_stored = lchan->active_start; + + /* Increment rate counters tracking fully established lchans. */ + switch (lchan->type) { + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_TCH_F: + rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_CHAN_TCH_FULLY_ESTABLISHED)); + break; + case GSM_LCHAN_SDCCH: + rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_CHAN_SDCCH_FULLY_ESTABLISHED)); + break; + default: + break; + } + switch (lchan->activate.info.activ_for) { - case FOR_MS_CHANNEL_REQUEST: + case ACTIVATE_FOR_MS_CHANNEL_REQUEST: /* No signalling to do here, MS is free to use the channel, and should go on to connect * to the MSC and establish a subscriber connection. */ break; - case FOR_ASSIGNMENT: + case ACTIVATE_FOR_ASSIGNMENT: if (!lchan->conn) { LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation for assignment succeeded, but lchan has no conn:" " cannot trigger appropriate actions. Release.\n"); - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); break; } if (!lchan->conn->assignment.fi) { LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation for assignment succeeded, but lchan has no" " assignment ongoing: cannot trigger appropriate actions. Release.\n"); - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); break; } osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ESTABLISHED, @@ -182,18 +277,18 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan) * will try to roll back a modified RTP connection. */ break; - case FOR_HANDOVER: + case ACTIVATE_FOR_HANDOVER: if (!lchan->conn) { LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation for handover succeeded, but lchan has no conn\n"); - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); break; } if (!lchan->conn->ho.fi) { LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation for handover succeeded, but lchan has no" " handover ongoing\n"); - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); break; } osmo_fsm_inst_dispatch(lchan->conn->ho.fi, HO_EV_LCHAN_ESTABLISHED, lchan); @@ -202,6 +297,26 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan) * we will try to roll back a modified RTP connection. */ break; + case ACTIVATE_FOR_VGCS_CHANNEL: + if (!lchan->conn) { + LOG_LCHAN(lchan, LOGL_ERROR, + "lchan activation for VGCS assignment succeeded, but lchan has no conn\n"); + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); + break; + } + if (!lchan->conn->vgcs_chan.fi) { + LOG_LCHAN(lchan, LOGL_ERROR, + "lchan Activation for VGCS assignment requested, but conn has no VGCS resource FSM.\n"); + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); + break; + } + osmo_fsm_inst_dispatch(lchan->conn->vgcs_chan.fi, VGCS_EV_LCHAN_ACTIVE, lchan); + break; + + case ACTIVATE_FOR_MODE_MODIFY_RTP: + lchan_on_mode_modify_success(lchan); + break; + default: LOG_LCHAN(lchan, LOGL_NOTICE, "lchan %s fully established\n", lchan_activate_mode_name(lchan->activate.info.activ_for)); @@ -210,13 +325,18 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan) } struct osmo_tdef_state_timeout lchan_fsm_timeouts[32] = { - [LCHAN_ST_WAIT_TS_READY] = { .T=-5 }, - [LCHAN_ST_WAIT_ACTIV_ACK] = { .T=-6 }, - [LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] = { .T=3101 }, - [LCHAN_ST_WAIT_RLL_RTP_RELEASED] = { .T=3109 }, - [LCHAN_ST_WAIT_BEFORE_RF_RELEASE] = { .T=3111 }, - [LCHAN_ST_WAIT_RF_RELEASE_ACK] = { .T=3111 }, - [LCHAN_ST_WAIT_AFTER_ERROR] = { .T=-3111 }, + [LCHAN_ST_WAIT_TS_READY] = { .T = -5 }, + [LCHAN_ST_WAIT_ACTIV_ACK] = { .T = -6 }, + [LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] = { .T = 3101 }, + [LCHAN_ST_WAIT_RLL_RTP_RELEASED] = { .T = 3109 }, + [LCHAN_ST_WAIT_BEFORE_RF_RELEASE] = { .T = 3111 }, + [LCHAN_ST_WAIT_RF_RELEASE_ACK] = { .T = -6 }, + [LCHAN_ST_WAIT_AFTER_ERROR] = { .T = -3111 }, + [LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK] = { .T = -13 }, + [LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = { .T = -14 }, + [LCHAN_ST_BORKEN] = { .T = -28 }, + [LCHAN_ST_RECOVER_WAIT_ACTIV_ACK] = { .T = -6 }, + [LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK] = { .T = -6 }, }; /* Transition to a state, using the T timer defined in lchan_fsm_timeouts. @@ -238,7 +358,7 @@ struct osmo_tdef_state_timeout lchan_fsm_timeouts[32] = { const uint32_t state_chg = STATE_CHG; \ LOG_LCHAN(_lchan, LOGL_DEBUG, "Handling failure, will then transition to state %s\n", \ osmo_fsm_state_name(fsm, state_chg)); \ - lchan_set_last_error(_lchan, "lchan %s in state %s: " fmt, \ + LCHAN_SET_LAST_ERROR(_lchan, "lchan %s in state %s: " fmt, \ _lchan->activate.concluded ? "failure" : "allocation failed", \ osmo_fsm_state_name(fsm, state_was), ## args); \ lchan_on_activation_failure(_lchan, _lchan->activate.info.activ_for, _lchan->conn); \ @@ -247,10 +367,10 @@ struct osmo_tdef_state_timeout lchan_fsm_timeouts[32] = { else \ LOG_LCHAN(_lchan, LOGL_DEBUG, "After failure handling, already in state %s\n", \ osmo_fsm_state_name(fsm, state_chg)); \ - } while(0) + } while (0) /* Which state to transition to when lchan_fail() is called in a given state. */ -uint32_t lchan_fsm_on_error[34] = { +uint32_t lchan_fsm_on_error[32] = { [LCHAN_ST_UNUSED] = LCHAN_ST_UNUSED, [LCHAN_ST_WAIT_TS_READY] = LCHAN_ST_UNUSED, [LCHAN_ST_WAIT_ACTIV_ACK] = LCHAN_ST_BORKEN, @@ -261,8 +381,10 @@ uint32_t lchan_fsm_on_error[34] = { [LCHAN_ST_WAIT_RF_RELEASE_ACK] = LCHAN_ST_BORKEN, [LCHAN_ST_WAIT_AFTER_ERROR] = LCHAN_ST_UNUSED, [LCHAN_ST_BORKEN] = LCHAN_ST_BORKEN, - [LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK] = LCHAN_ST_BORKEN, - [LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = LCHAN_ST_BORKEN, + [LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK] = LCHAN_ST_WAIT_RF_RELEASE_ACK, + [LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = LCHAN_ST_WAIT_RF_RELEASE_ACK, + [LCHAN_ST_RECOVER_WAIT_ACTIV_ACK] = LCHAN_ST_BORKEN, + [LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK] = LCHAN_ST_BORKEN, }; #define lchan_fail(fmt, args...) lchan_fail_to(lchan_fsm_on_error[fi->state], fmt, ## args) @@ -273,6 +395,16 @@ void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info) OSMO_ASSERT(lchan && info); + if ((info->type_for == LCHAN_TYPE_FOR_VAMOS || lchan->vamos.is_secondary) + && !osmo_bts_has_feature(&lchan->ts->trx->bts->features, BTS_FEAT_VAMOS)) { + lchan->last_error = talloc_strdup(lchan->ts->trx, "VAMOS related channel activation requested," + " but BTS does not support VAMOS"); + LOG_LCHAN(lchan, LOGL_ERROR, + "VAMOS related channel activation requested, but BTS %u does not support VAMOS\n", + lchan->ts->trx->bts->nr); + goto abort; + } + if (!lchan_state_is(lchan, LCHAN_ST_UNUSED)) goto abort; @@ -281,7 +413,7 @@ void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info) switch (info->activ_for) { - case FOR_ASSIGNMENT: + case ACTIVATE_FOR_ASSIGNMENT: if (!info->for_conn || !info->for_conn->fi) { LOG_LCHAN(lchan, LOGL_ERROR, "Activation requested, but no conn\n"); @@ -298,7 +430,7 @@ void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info) } break; - case FOR_HANDOVER: + case ACTIVATE_FOR_HANDOVER: if (!info->for_conn || !info->for_conn->fi) { LOG_LCHAN(lchan, LOGL_ERROR, "Activation requested, but no conn\n"); @@ -320,6 +452,28 @@ void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info) } break; + case ACTIVATE_FOR_VGCS_CHANNEL: + if (!info->for_conn + || !info->for_conn->fi) { + LOG_LCHAN(lchan, LOGL_ERROR, "Activation requested, but no conn\n"); + goto abort; + } + if (!info->for_conn->vgcs_chan.fi) { + LOG_LCHAN(lchan, LOGL_ERROR, + "Activation for VGCS assignment requested, but conn has no VGCS resource FSM.\n"); + goto abort; + } + if (info->for_conn->vgcs_chan.new_lchan != lchan) { + LOG_LCHAN(lchan, LOGL_ERROR, + "Activation for VGCS assignment requested, but conn's VGCS assignment state does" + " not reflect this lchan to be activated (instead: %s)\n", + info->for_conn->vgcs_chan.new_lchan ? + gsm_lchan_name(info->for_conn->vgcs_chan.new_lchan) + : "NULL"); + goto abort; + } + break; + default: break; } @@ -340,23 +494,50 @@ abort: /* Remain in state UNUSED */ } -static void lchan_fsm_update_id(struct gsm_lchan *lchan) +void lchan_mode_modify(struct gsm_lchan *lchan, struct lchan_modify_info *info) +{ + OSMO_ASSERT(lchan && info); + + if ((info->type_for == LCHAN_TYPE_FOR_VAMOS || lchan->vamos.is_secondary) + && !osmo_bts_has_feature(&lchan->ts->trx->bts->features, BTS_FEAT_VAMOS)) { + lchan->last_error = talloc_strdup(lchan->ts->trx, "VAMOS related Channel Mode Modify requested," + " but BTS does not support VAMOS"); + LOG_LCHAN(lchan, LOGL_ERROR, + "VAMOS related Channel Mode Modify requested, but BTS %u does not support VAMOS\n", + lchan->ts->trx->bts->nr); + lchan_on_mode_modify_failure(lchan, info->modify_for, lchan->conn); + return; + } + + /* To make sure that the lchan is actually allowed to initiate Mode Modify, feed through an FSM event. */ + if (osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_REQUEST_MODE_MODIFY, info)) { + LOG_LCHAN(lchan, LOGL_ERROR, + "Channel Mode Modify requested, but cannot dispatch LCHAN_EV_REQUEST_MODE_MODIFY event\n"); + lchan_on_mode_modify_failure(lchan, info->modify_for, lchan->conn); + } +} + +void lchan_fsm_update_id(struct gsm_lchan *lchan) { - osmo_fsm_inst_update_id_f(lchan->fi, "%u-%u-%u-%s-%u", - lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, - gsm_pchan_id(lchan->ts->pchan_on_init), lchan->nr); + lchan_update_name(lchan); + if (!lchan->fi) + return; + osmo_fsm_inst_update_id_f_sanitize(lchan->fi, '_', "%u-%u-%u-%s-%s%u", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + gsm_pchan_name(lchan->ts->pchan_on_init), + lchan->vamos.is_secondary ? "shadow" : "", + lchan->nr - (lchan->vamos.is_secondary ? lchan->ts->max_primary_lchans : 0)); if (lchan->fi_rtp) osmo_fsm_inst_update_id_f(lchan->fi_rtp, lchan->fi->id); } -extern void lchan_rtp_fsm_init(); - -void lchan_fsm_init() +static __attribute__((constructor)) void lchan_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&lchan_fsm) == 0); - lchan_rtp_fsm_init(); } +static void lchan_reset(struct gsm_lchan *lchan); + void lchan_fsm_alloc(struct gsm_lchan *lchan) { OSMO_ASSERT(lchan->ts); @@ -368,6 +549,7 @@ void lchan_fsm_alloc(struct gsm_lchan *lchan) lchan->fi->priv = lchan; lchan_fsm_update_id(lchan); LOGPFSML(lchan->fi, LOGL_DEBUG, "new lchan\n"); + lchan_reset(lchan); } /* Clear volatile state of the lchan. Clear all except @@ -381,8 +563,11 @@ static void lchan_reset(struct gsm_lchan *lchan) { LOG_LCHAN(lchan, LOGL_DEBUG, "Clearing lchan state\n"); - if (lchan->conn) - gscon_forget_lchan(lchan->conn, lchan); + if (lchan->conn) { + struct gsm_subscriber_connection *conn = lchan->conn; + lchan_forget_conn(lchan); + gscon_forget_lchan(conn, lchan); + } if (lchan->rqd_ref) { talloc_free(lchan->rqd_ref); @@ -407,6 +592,10 @@ static void lchan_reset(struct gsm_lchan *lchan) .last_error = lchan->last_error, .release.rr_cause = GSM48_RR_CAUSE_NORMAL, + + .tsc_set = 1, + .interf_dbm = INTERF_DBM_UNKNOWN, + .interf_band = INTERF_BAND_UNKNOWN, }; } @@ -415,6 +604,7 @@ static void lchan_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta struct gsm_lchan *lchan = lchan_fi_lchan(fi); struct gsm_bts *bts = lchan->ts->trx->bts; lchan_reset(lchan); + chan_counts_ts_update(lchan->ts); osmo_fsm_inst_dispatch(lchan->ts->fi, TS_EV_LCHAN_UNUSED, lchan); /* Poll the channel request queue, so that waiting calls can make use of the lchan that just @@ -422,6 +612,12 @@ static void lchan_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta abis_rsl_chan_rqd_queue_poll(bts); } +static void lchan_fsm_cbch_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_lchan *lchan = lchan_fi_lchan(fi); + chan_counts_ts_update(lchan->ts); +} + static void lchan_fsm_wait_after_error_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gsm_lchan *lchan = lchan_fi_lchan(fi); @@ -434,101 +630,87 @@ static void lchan_fsm_wait_after_error_onenter(struct osmo_fsm_inst *fi, uint32_ } /* Configure the multirate setting on this channel. */ -static int lchan_mr_config(struct gsm_lchan *lchan, uint16_t s15_s0) +static int mr_config_filter(struct gsm48_multi_rate_conf *mr_conf_result, + bool full_rate, + const struct amr_multirate_conf *amr_mrc, + const struct gsm48_multi_rate_conf *mr_filter_msc, + uint16_t s15_s0, + const struct gsm_lchan *lchan_for_logging) { - struct gsm48_multi_rate_conf mr_conf; - bool full_rate = (lchan->type == GSM_LCHAN_TCH_F); - struct gsm_bts *bts = lchan->ts->trx->bts; - struct amr_multirate_conf *mr; int rc; - int rc_rate; - struct gsm48_multi_rate_conf mr_conf_filtered; - const struct gsm48_multi_rate_conf *mr_conf_bts; + struct gsm48_multi_rate_conf *mr_filter_bts = (struct gsm48_multi_rate_conf*)amr_mrc->gsm48_ie; /* Generate mr conf struct from S15-S0 bits */ - if (gsm48_mr_cfg_from_gsm0808_sc_cfg(&mr_conf, s15_s0) < 0) { - LOG_LCHAN(lchan, LOGL_ERROR, + if (gsm48_mr_cfg_from_gsm0808_sc_cfg(mr_conf_result, s15_s0) < 0) { + LOG_LCHAN(lchan_for_logging, LOGL_ERROR, "can not determine multirate configuration, S15-S0 (%04x) are ambiguous!\n", s15_s0); return -EINVAL; } /* Do not include 12.2 kbps rate when S1 is set. */ - if (lchan->type == GSM_LCHAN_TCH_H && (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20)) { - /* See also 3GPP TS 28.062, chapter 7.11.3.1.3: "In case this Configuration - * "Config-NB-Code = 1" is signalled in the TFO Negotiation for the HR_AMR - * Codec Type,then it shall be assumed that AMR mode 12.2 kbps is (of course) - * not included. */ - mr_conf.m12_2 = 0; - } - - /* There are two different active sets, depending on the channel rate, - * make sure the appropate one is selected. */ - if (full_rate) - mr = &bts->mr_full; - else - mr = &bts->mr_half; - - if (lchan->activate.info.activ_for == FOR_VTY) - /* If the channel is activated manually from VTY, then there is no - * conn attached to the lchan, also no MSC is involved. Since this - * option is for debugging and the codec choice is an intentional - * decision by the VTY user, we do not filter the mr_conf. */ - memcpy(&mr_conf_filtered, &mr_conf, sizeof(mr_conf_filtered)); - else { - /* The VTY allows to forbid certain codec rates. Unfortunately we can - * not articulate all of the prohibitions on through S0-S15 on the A - * interface. To ensure that the VTY settings are observed we create - * a manipulated copy of the mr_conf that ensures forbidden codec rates - * are not used in the multirate configuration IE. */ - rc_rate = calc_amr_rate_intersection(&mr_conf_filtered, &lchan->conn->sccp.msc->amr_conf, &mr_conf); - if (rc_rate < 0) { - LOG_LCHAN(lchan, LOGL_ERROR, - "can not encode multirate configuration (invalid amr rate setting, MSC)\n"); - return -EINVAL; - } + if ((!full_rate) && (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20)) { + /* See also 3GPP TS 28.062, chapter 7.11.3.1.3: + * + * In case this Configuration "Config-NB-Code = 1" is signalled in the TFO Negotiation for the HR_AMR + * Codec Type, then it shall be assumed that AMR mode 12.2 kbps is (of course) not included. + * + * Further below, we log an error if 12k2 is included for a TCH/H lchan: removing this here ensures that + * we don't log that error for GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20 on a TCH/H lchan. */ + mr_conf_result->m12_2 = 0; } - /* The two last codec rates which are defined for AMR do only work with - * full rate channels. We will pinch off those rates für half-rate - * channels to ensure they are not included accidently. */ - if (!full_rate) { - if (mr_conf_filtered.m10_2 || mr_conf_filtered.m12_2) - LOG_LCHAN(lchan, LOGL_ERROR, "ignoring unsupported amr codec rates\n"); - mr_conf_filtered.m10_2 = 0; - mr_conf_filtered.m12_2 = 0; - } - - /* Ensure that the resulting filtered conf is coherent with the - * configuration that is set for the BTS and the specified rate. - * if the channel activation was triggerd by the VTY, do not - * filter anything (see also comment above) */ - if (lchan->activate.info.activ_for != FOR_VTY) { - mr_conf_bts = (struct gsm48_multi_rate_conf *)mr->gsm48_ie; - rc_rate = calc_amr_rate_intersection(&mr_conf_filtered, mr_conf_bts, &mr_conf_filtered); - if (rc_rate < 0) { - LOG_LCHAN(lchan, LOGL_ERROR, - "can not encode multirate configuration (invalid amr rate setting, BTS)\n"); + if (mr_filter_msc) { + rc = calc_amr_rate_intersection(mr_conf_result, mr_filter_msc, mr_conf_result); + if (rc < 0) { + LOG_LCHAN(lchan_for_logging, LOGL_ERROR, + "can not encode multirate configuration (invalid amr rate setting, MSC)\n"); return -EINVAL; } } - /* Proceed with the generation of the multirate configuration IE - * (MS and BTS) */ - rc = gsm48_multirate_config(lchan->mr_ms_lv, &mr_conf_filtered, mr->ms_mode, mr->num_modes); - if (rc != 0) { - LOG_LCHAN(lchan, LOGL_ERROR, "can not encode multirate configuration (MS)\n"); + rc = calc_amr_rate_intersection(mr_conf_result, mr_filter_bts, mr_conf_result); + if (rc < 0) { + LOG_LCHAN(lchan_for_logging, LOGL_ERROR, + "can not encode multirate configuration (invalid amr rate setting, BTS)\n"); return -EINVAL; } - rc = gsm48_multirate_config(lchan->mr_bts_lv, &mr_conf_filtered, mr->bts_mode, mr->num_modes); - if (rc != 0) { - LOG_LCHAN(lchan, LOGL_ERROR, "can not encode multirate configuration (BTS)\n"); - return -EINVAL; + + /* Set the ICMI according to the BTS. Above gsm48_mr_cfg_from_gsm0808_sc_cfg() always sets ICMI = 1, which + * carried through all of the above rate intersections. */ + mr_conf_result->icmi = mr_filter_bts->icmi; + mr_conf_result->smod = mr_filter_bts->smod; + + /* 10k2 and 12k2 only work for full rate */ + if (!full_rate) { + if (mr_conf_result->m10_2 || mr_conf_result->m12_2) + LOG_LCHAN(lchan_for_logging, LOGL_ERROR, + "half rate lchan: ignoring unsupported AMR codec rates 10k2 and 12k2\n"); + mr_conf_result->m10_2 = 0; + mr_conf_result->m12_2 = 0; } - lchan->s15_s0 = s15_s0; return 0; } +/* Configure the multirate setting on this channel. */ +static int lchan_mr_config(struct gsm48_multi_rate_conf *mr_conf, const struct gsm_lchan *lchan, uint16_t s15_s0) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + bool full_rate = lchan->type == GSM_LCHAN_TCH_F; + struct amr_multirate_conf *amr_mrc = full_rate ? &bts->mr_full : &bts->mr_half; + struct gsm48_multi_rate_conf *mr_filter_msc = NULL; + + /* If activated for VTY, there may not be a conn indicating an MSC AMR configuration. */ + if (lchan->conn && lchan->conn->sccp.msc) + mr_filter_msc = &lchan->conn->sccp.msc->amr_conf; + + return mr_config_filter(mr_conf, + full_rate, + amr_mrc, mr_filter_msc, + s15_s0, + lchan); +} + static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct lchan_activate_info *info = data; @@ -540,10 +722,16 @@ static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *dat OSMO_ASSERT(info); OSMO_ASSERT(!lchan->conn); OSMO_ASSERT(!lchan->mgw_endpoint_ci_bts); - lchan_set_last_error(lchan, NULL); + if (lchan->last_error) + talloc_free(lchan->last_error); + lchan->last_error = NULL; lchan->release.requested = false; lchan->activate.info = *info; + /* To avoid confusion, invalidate info.chreq_reason value if it isn't for a CHREQ */ + if (lchan->activate.info.activ_for != ACTIVATE_FOR_MS_CHANNEL_REQUEST) + lchan->activate.info.chreq_reason = -1; + lchan->activate.concluded = false; lchan_fsm_state_chg(LCHAN_ST_WAIT_TS_READY); break; @@ -553,6 +741,46 @@ static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *dat } } +static int lchan_activate_set_ch_mode_rate_and_mr_config(struct gsm_lchan *lchan) +{ + struct osmo_fsm_inst *fi = lchan->fi; + lchan->activate.ch_mode_rate = lchan->activate.info.ch_mode_rate; + lchan->activate.ch_mode_rate.chan_mode = (lchan->activate.info.type_for == LCHAN_TYPE_FOR_VAMOS) + ? gsm48_chan_mode_to_vamos(lchan->activate.info.ch_mode_rate.chan_mode) + : gsm48_chan_mode_to_non_vamos(lchan->activate.info.ch_mode_rate.chan_mode); + if (lchan->activate.ch_mode_rate.chan_mode < 0) { + lchan_fail("Invalid chan_mode: %s", gsm48_chan_mode_name(lchan->activate.info.ch_mode_rate.chan_mode)); + return -EINVAL; + } + + if (gsm48_chan_mode_to_non_vamos(lchan->activate.ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) { + if (lchan_mr_config(&lchan->activate.mr_conf_filtered, lchan, lchan->activate.ch_mode_rate.s15_s0) < 0) { + lchan_fail("Can not generate multirate configuration IE"); + return -EINVAL; + } + } + + lchan->activate.ch_indctr = lchan->activate.info.ch_indctr; + return 0; +} + +static int lchan_send_imm_ass(struct osmo_fsm_inst *fi) +{ + int rc; + struct gsm_lchan *lchan = lchan_fi_lchan(fi); + rc = rsl_tx_imm_assignment(lchan); + if (rc) { + lchan_fail("Failed to Tx RR Immediate Assignment message (rc=%d %s)", + rc, strerror(-rc)); + return rc; + } + LOG_LCHAN(lchan, LOGL_DEBUG, "Tx RR Immediate Assignment\n"); + lchan->activate.immediate_assignment_sent = true; + return rc; +} + +static void post_activ_ack_accept_preliminary_settings(struct gsm_lchan *lchan); + static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gsm_lchan *lchan = lchan_fi_lchan(fi); @@ -560,68 +788,63 @@ static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t p struct osmo_mgcpc_ep_ci *use_mgwep_ci; struct gsm_lchan *old_lchan = lchan->activate.info.re_use_mgw_endpoint_from_lchan; struct lchan_activate_info *info = &lchan->activate.info; - int ms_power_dbm; + int ms_power_dbm = bts->ms_max_power; + bool requires_rtp_stream; if (lchan->release.requested) { lchan_fail("Release requested while activating"); return; } + chan_counts_ts_update(lchan->ts); + lchan->conn = info->for_conn; /* If there is a previous lchan, and the new lchan is on the same cell as previous one, - * take over power and TA values. Otherwise, use max power and zero TA. */ + * take over MS power values. Otherwise, use configured MS max power. */ if (old_lchan && old_lchan->ts->trx->bts == bts) { - ms_power_dbm = ms_pwr_dbm(bts->band, old_lchan->ms_power); - lchan_update_ms_power_ctrl_level(lchan, ms_power_dbm >= 0 ? ms_power_dbm : bts->ms_max_power); - lchan->bs_power = old_lchan->bs_power; - lchan->rqd_ta = old_lchan->rqd_ta; - } else { - lchan_update_ms_power_ctrl_level(lchan, bts->ms_max_power); - /* Upon last entering the UNUSED state, from lchan_reset(): - * - bs_power is still zero, 0dB reduction, output power = Pn. - * - TA is still zero, to be determined by RACH. */ - } - - if (info->chan_mode == GSM48_CMODE_SPEECH_AMR) { - if (lchan_mr_config(lchan, info->s15_s0) < 0) { - lchan_fail("Can not generate multirate configuration IE\n"); - return; - } + if ((ms_power_dbm = ms_pwr_dbm(bts->band, old_lchan->ms_power)) == 0) + ms_power_dbm = bts->ms_max_power; + } + lchan_update_ms_power_ctrl_level(lchan, ms_power_dbm); + + /* Default BS Power reduction value (in dB) */ + lchan->bs_power_db = (bts->bs_power_ctrl.mode == GSM_PWR_CTRL_MODE_DYN_BTS) ? + bts->bs_power_ctrl.bs_power_max_db : + bts->bs_power_ctrl.bs_power_val_db; + /* BS Power Control is generally not allowed on the BCCH/CCCH carrier. + * However, we allow it in the BCCH carrier power reduction mode of operation. */ + if (lchan->ts->trx == bts->c0) { + lchan->bs_power_db = OSMO_MIN(lchan->ts->c0_max_power_red_db, + lchan->bs_power_db); } - switch (info->chan_mode) { - - case GSM48_CMODE_SIGN: - lchan->rsl_cmode = RSL_CMOD_SPD_SIGN; - lchan->tch_mode = GSM48_CMODE_SIGN; - break; + if (lchan_activate_set_ch_mode_rate_and_mr_config(lchan)) + return; - case GSM48_CMODE_SPEECH_V1: - case GSM48_CMODE_SPEECH_EFR: - case GSM48_CMODE_SPEECH_AMR: - lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; - lchan->tch_mode = info->chan_mode; - break; + /* If enabling VAMOS mode and no specific TSC Set was selected, make sure to select a sane TSC Set by + * default: Set 1 for the primary and Set 2 for the shadow lchan. For non-VAMOS lchans, TSC Set 1. */ + if (lchan->activate.info.tsc_set.present) + lchan->activate.tsc_set = lchan->activate.info.tsc_set.val; + else + lchan->activate.tsc_set = lchan->vamos.is_secondary ? 2 : 1; - default: - lchan_fail("Not implemented: cannot activate for chan mode %s", - gsm48_chan_mode_name(info->chan_mode)); - return; - } + /* Use the TSC provided in the activation request, if any. Otherwise use the timeslot's configured TSC. */ + lchan->activate.tsc = lchan->activate.info.tsc.present ? lchan->activate.info.tsc.val : gsm_ts_tsc(lchan->ts); use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan); + requires_rtp_stream = bsc_chan_ind_requires_rtp_stream(lchan->activate.info.ch_indctr); LOG_LCHAN(lchan, LOGL_INFO, - "Activation requested: %s voice=%s MGW-ci=%s type=%s tch-mode=%s encr-alg=A5/%u ck=%s\n", + "Activation requested: %s rtp=%s MGW-ci=%s type=%s tch-mode=%s encr-alg=A5/%u ck=%s\n", lchan_activate_mode_name(lchan->activate.info.activ_for), - lchan->activate.info.requires_voice_stream ? "yes" : "no", - lchan->activate.info.requires_voice_stream ? + requires_rtp_stream ? "yes" : "no", + requires_rtp_stream ? (use_mgwep_ci ? osmo_mgcpc_ep_ci_name(use_mgwep_ci) : "new") : "none", - gsm_lchant_name(lchan->type), - gsm48_chan_mode_name(lchan->tch_mode), - (lchan->activate.info.encr.alg_id ? : 1)-1, + gsm_chan_t_name(lchan->type), + gsm48_chan_mode_name(lchan->activate.ch_mode_rate.chan_mode), + lchan->activate.info.encr.alg_a5_n, lchan->activate.info.encr.key_len ? osmo_hexdump_nospc(lchan->activate.info.encr.key, lchan->activate.info.encr.key_len) : "none"); @@ -630,8 +853,20 @@ static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t p osmo_fsm_inst_dispatch(lchan->ts->fi, TS_EV_LCHAN_REQUESTED, lchan); /* Prepare an MGW endpoint CI if appropriate. */ - if (lchan->activate.info.requires_voice_stream) + if (requires_rtp_stream) lchan_rtp_fsm_start(lchan); + + if (lchan->activate.info.imm_ass_time == IMM_ASS_TIME_PRE_TS_ACK) { + /* Send the Immediate Assignment even before the timeslot is ready, saving a dyn TS timeslot roundtrip + * on Abis (experimental). + * + * Until the Channel Activation ACK is received, various values still are preliminary and hence live in + * lchan->activate.*. We're doing things early here and need e.g. an accurate lchan->tsc, so already + * copy the preliminary values from lchan->activate.* into the operative places in lchan->* prematurely. + */ + post_activ_ack_accept_preliminary_settings(lchan); + lchan_send_imm_ass(fi); + } } static void lchan_fsm_wait_ts_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -652,7 +887,7 @@ static void lchan_fsm_wait_ts_ready(struct osmo_fsm_inst *fi, uint32_t event, vo return; } - lchan_fail("Failed to setup RTP stream: %s in state %s\n", + lchan_fail("Failed to setup RTP stream: %s in state %s", osmo_fsm_event_name(fi->fsm, event), osmo_fsm_inst_state_name(fi)); return; @@ -675,24 +910,47 @@ static void lchan_fsm_wait_activ_ack_onenter(struct osmo_fsm_inst *fi, uint32_t } switch (lchan->activate.info.activ_for) { - case FOR_MS_CHANNEL_REQUEST: + case ACTIVATE_FOR_MS_CHANNEL_REQUEST: act_type = RSL_ACT_INTRA_IMM_ASS; break; - case FOR_HANDOVER: + case ACTIVATE_FOR_HANDOVER: act_type = lchan->conn->ho.async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC; ho_ref = lchan->conn->ho.ho_ref; break; + case ACTIVATE_FOR_ASSIGNMENT: + case ACTIVATE_FOR_VGCS_CHANNEL: default: - case FOR_ASSIGNMENT: act_type = RSL_ACT_INTRA_NORM_ASS; break; } + /* rsl_tx_chan_activ() and build_encr_info() access lchan->encr, make sure it reflects the values requested for + * activation. + * TODO: rather leave it in lchan->activate.info.encr until the ACK is received, which means that + * rsl_tx_chan_activ() should use lchan->activate.info.encr and build_encr_info() should be passed encr as an + * explicit argument. */ lchan->encr = lchan->activate.info.encr; rc = rsl_tx_chan_activ(lchan, act_type, ho_ref); - if (rc) + if (rc) { lchan_fail_to(LCHAN_ST_UNUSED, "Tx Chan Activ failed: %s (%d)", strerror(-rc), rc); + return; + } + + if (lchan->activate.info.ta_known) + lchan->last_ta = lchan->activate.info.ta; + + if (lchan->activate.info.imm_ass_time == IMM_ASS_TIME_PRE_CHAN_ACK) { + /* Send the Immediate Assignment directly after the Channel Activation request, saving one Abis + * roundtrip between ChanRqd and Imm Ass. + * + * Until the Channel Activation ACK is received, various values still are preliminary and hence live in + * lchan->activate.*. We're doing things early here and need e.g. an accurate lchan->tsc, so already + * copy the preliminary values from lchan->activate.* into the operative places in lchan->* prematurely. + */ + post_activ_ack_accept_preliminary_settings(lchan); + lchan_send_imm_ass(fi); + } } static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi); @@ -751,10 +1009,24 @@ static void lchan_fsm_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, v } } +static void post_activ_ack_accept_preliminary_settings(struct gsm_lchan *lchan) +{ + lchan->current_ch_mode_rate = lchan->activate.ch_mode_rate; + lchan->current_mr_conf = lchan->activate.mr_conf_filtered; + lchan->current_ch_indctr = lchan->activate.ch_indctr; + lchan->vamos.enabled = (lchan->activate.info.type_for == LCHAN_TYPE_FOR_VAMOS); + lchan->tsc_set = lchan->activate.tsc_set; + lchan->tsc = lchan->activate.tsc; +} + static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi) { - int rc; struct gsm_lchan *lchan = lchan_fi_lchan(fi); + + post_activ_ack_accept_preliminary_settings(lchan); + LOG_LCHAN(lchan, LOGL_INFO, "Rx Activ ACK %s\n", + gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode)); + if (lchan->release.requested) { lchan_fail_to(LCHAN_ST_WAIT_RF_RELEASE_ACK, "Release requested while activating"); return; @@ -762,51 +1034,49 @@ static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi) switch (lchan->activate.info.activ_for) { - case FOR_MS_CHANNEL_REQUEST: - rc = rsl_tx_imm_assignment(lchan); - if (rc) { - lchan_fail("Failed to Tx RR Immediate Assignment message (rc=%d %s)\n", - rc, strerror(-rc)); - return; + case ACTIVATE_FOR_MS_CHANNEL_REQUEST: + if (lchan->activate.info.imm_ass_time == IMM_ASS_TIME_POST_CHAN_ACK) { + if (lchan_send_imm_ass(fi)) { + /* lchan_fail() was already called in lchan_send_imm_ass() */ + return; + } } - LOG_LCHAN(lchan, LOGL_DEBUG, "Tx RR Immediate Assignment\n"); - lchan->activate.immediate_assignment_sent = true; break; - case FOR_ASSIGNMENT: + case ACTIVATE_FOR_ASSIGNMENT: if (!lchan->conn) { LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation for assignment succeeded, but lchan has no conn:" " cannot trigger appropriate actions. Release.\n"); - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); - break; + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); + return; } if (!lchan->conn->assignment.fi) { LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation for assignment succeeded, but lchan has no" " assignment ongoing: cannot trigger appropriate actions. Release.\n"); - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); - break; + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); + return; } /* After the Chan Activ Ack, the MS expects to receive an RR Assignment Command. * Let the assignment_fsm handle that. */ osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ACTIVE, lchan); break; - case FOR_HANDOVER: + case ACTIVATE_FOR_HANDOVER: if (!lchan->conn) { LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation for handover succeeded, but lchan has no conn:" " cannot trigger appropriate actions. Release.\n"); - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); - break; + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); + return; } if (!lchan->conn->ho.fi) { LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation for handover succeeded, but lchan has no" " handover ongoing: cannot trigger appropriate actions. Release.\n"); - lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); - break; + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); + return; } /* After the Chan Activ Ack of the new lchan, send the MS an RR Handover Command on the * old channel. The handover_fsm handles that. */ @@ -825,16 +1095,19 @@ static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi) static void lchan_fsm_wait_rll_rtp_establish_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gsm_lchan *lchan = lchan_fi_lchan(fi); + bool requires_rtp_stream = bsc_chan_ind_requires_rtp_stream(lchan->activate.info.ch_indctr); + if (lchan->fi_rtp) osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_LCHAN_READY, 0); /* Prepare an MGW endpoint CI if appropriate (late). */ - else if (lchan->activate.info.requires_voice_stream) + else if (requires_rtp_stream) lchan_rtp_fsm_start(lchan); - /* When activating a channel for VTY, skip waiting for activity from - * lchan_rtp_fsm, but only if no voice stream is required. */ - if (lchan->activate.info.activ_for == FOR_VTY && - !lchan->activate.info.requires_voice_stream) { + /* When activating a channel for VTY or VGCS/VBS, skip waiting for activity from + * lchan_rtp_fsm, but only if no rtp stream is required. */ + if ((lchan->activate.info.activ_for == ACTIVATE_FOR_VTY || + lchan->activate.info.activ_for == ACTIVATE_FOR_VGCS_CHANNEL) && + !requires_rtp_stream) { lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED); } } @@ -842,16 +1115,24 @@ static void lchan_fsm_wait_rll_rtp_establish_onenter(struct osmo_fsm_inst *fi, u static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_lchan *lchan = lchan_fi_lchan(fi); + bool requires_rtp_stream = bsc_chan_ind_requires_rtp_stream(lchan->activate.info.ch_indctr); + switch (event) { case LCHAN_EV_RLL_ESTABLISH_IND: - if (!lchan->activate.info.requires_voice_stream - || lchan_rtp_established(lchan)) + if (!requires_rtp_stream || lchan_rtp_established(lchan)) { + LOG_LCHAN(lchan, LOGL_DEBUG, + "%s\n", + (requires_rtp_stream ? + "RTP already established earlier" : "no voice stream required")); lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED); + } return; case LCHAN_EV_RTP_READY: - if (lchan->sapis[0] != LCHAN_SAPI_UNUSED) + /* If RLL was established or if it does not need to be establised, because of VGCS/VBS channel. */ + if (lchan->sapis[0] != LCHAN_SAPI_UNUSED || + lchan->activate.info.activ_for == ACTIVATE_FOR_VGCS_CHANNEL) lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED); return; @@ -863,7 +1144,7 @@ static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t return; } - lchan_fail("Failed to setup RTP stream: %s in state %s\n", + lchan_fail("Failed to setup RTP stream: %s in state %s", osmo_fsm_event_name(fi->fsm, event), osmo_fsm_inst_state_name(fi)); return; @@ -876,7 +1157,7 @@ static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t static void lchan_fsm_wait_rr_chan_mode_modify_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gsm_lchan *lchan = lchan_fi_lchan(fi); - gsm48_lchan_modify(lchan, lchan->activate.info.chan_mode); + gsm48_lchan_modify(lchan, lchan->modify.ch_mode_rate.chan_mode); } static void lchan_fsm_wait_rr_chan_mode_modify_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -888,7 +1169,7 @@ static void lchan_fsm_wait_rr_chan_mode_modify_ack(struct osmo_fsm_inst *fi, uin return; case LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR: - lchan_fail("Failed to change channel mode on the MS side: %s in state %s\n", + lchan_fail("Failed to change channel mode on the MS side: %s in state %s", osmo_fsm_event_name(fi->fsm, event), osmo_fsm_inst_state_name(fi)); return; @@ -905,7 +1186,7 @@ static void lchan_fsm_wait_rsl_chan_mode_modify_ack_onenter(struct osmo_fsm_inst rc = rsl_chan_mode_modify_req(lchan); if (rc < 0) { - lchan_fail("Failed to send rsl message to change the channel mode on the BTS side: state %s\n", + lchan_fail("Failed to send rsl message to change the channel mode on the BTS side: state %s", osmo_fsm_inst_state_name(fi)); } } @@ -916,14 +1197,37 @@ static void lchan_fsm_wait_rsl_chan_mode_modify_ack(struct osmo_fsm_inst *fi, ui switch (event) { case LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK: - if (lchan->activate.info.requires_voice_stream) + /* The Channel Mode Modify was ACKed, now the requested values become the accepted and used values. */ + lchan->current_ch_mode_rate = lchan->modify.ch_mode_rate; + lchan->current_mr_conf = lchan->modify.mr_conf_filtered; + lchan->current_ch_indctr = lchan->modify.ch_indctr; + lchan->tsc_set = lchan->modify.tsc_set; + lchan->tsc = lchan->modify.tsc; + lchan->vamos.enabled = (lchan->modify.info.type_for == LCHAN_TYPE_FOR_VAMOS); + + if (bsc_chan_ind_requires_rtp_stream(lchan->modify.info.ch_indctr) && !lchan->fi_rtp) { + /* Continue with RTP stream establishing as done in lchan_activate(). Place the requested values in + * lchan->activate.info and continue with voice stream setup. */ + lchan->activate.info = (struct lchan_activate_info){ + .activ_for = ACTIVATE_FOR_MODE_MODIFY_RTP, + .for_conn = lchan->conn, + .ch_mode_rate = lchan->modify.ch_mode_rate, + .ch_indctr = lchan->modify.info.ch_indctr, + .msc_assigned_cic = lchan->modify.info.msc_assigned_cic, + }; + if (lchan_activate_set_ch_mode_rate_and_mr_config(lchan)) + return; + + lchan->activate.concluded = false; lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH); - else + } else { lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED); + lchan_on_mode_modify_success(lchan); + } return; case LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK: - lchan_fail("Failed to change channel mode on the BTS side: %s in state %s\n", + lchan_fail("Failed to change channel mode on the BTS side: %s in state %s", osmo_fsm_event_name(fi->fsm, event), osmo_fsm_inst_state_name(fi)); return; @@ -996,11 +1300,22 @@ static void handle_rll_rel_ind_or_conf(struct osmo_fsm_inst *fi, uint32_t event, lchan->sapis[sapi] = LCHAN_SAPI_UNUSED; rll_indication(lchan, link_id, BSC_RLLR_IND_REL_IND); - /* Releasing SAPI 0 means the conn becomes invalid; but not if the link_id contains a TCH flag. - * (TODO: is this the correct interpretation?) */ + /* Releasing SAPI 0 means the conn becomes invalid; but not if the link_id contains a SACCH flag. */ if (lchan->conn && sapi == 0 && !(link_id & 0xc0)) { - LOG_LCHAN(lchan, LOGL_DEBUG, "lchan is releasing\n"); - gscon_lchan_releasing(lchan->conn, lchan); + /* A VGCS/VBS channel must stay active, even if all SAPIs are released. + * When a talker releases, the channel is available for the listeners and the next talker. The actual + * channel release is performed by the VGCS/VBS call control. */ + if (!lchan_is_asci(lchan)) { + LOG_LCHAN(lchan, LOGL_DEBUG, "lchan is releasing\n"); + gscon_lchan_releasing(lchan->conn, lchan); + } + + /* if SAPI=0 is gone, it makes no sense if other SAPIs are still around, + * this is not a valid configuration and we should forget about them. + * This is particularly relevant in case of Ericsson RBS6000, which doesn't + * seem to send a RLL_REL_IND for SAPI=3 if there was already one for SAPI=0 */ + for_each_active_sapi(sapi, 1, lchan) + lchan->sapis[sapi] = LCHAN_SAPI_UNUSED; } /* The caller shall check whether all SAPIs are released and cause a state chg */ @@ -1009,8 +1324,9 @@ static void handle_rll_rel_ind_or_conf(struct osmo_fsm_inst *fi, uint32_t event, static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_lchan *lchan = lchan_fi_lchan(fi); - struct lchan_activate_info *info; + struct lchan_modify_info *modif_info; struct osmo_mgcpc_ep_ci *use_mgwep_ci; + bool requires_rtp_stream = bsc_chan_ind_requires_rtp_stream(lchan->modify.info.ch_indctr); switch (event) { case LCHAN_EV_RLL_ESTABLISH_IND: @@ -1020,7 +1336,8 @@ static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void case LCHAN_EV_RLL_REL_IND: case LCHAN_EV_RLL_REL_CONF: handle_rll_rel_ind_or_conf(fi, event, data); - if (!lchan_active_sapis(lchan, 0)) + /* Only release channel, if there is no SAPI and this channel is not a VGCS channel. */ + if (!lchan_active_sapis(lchan, 0) && !lchan_is_asci(lchan)) lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_RELEASED); return; @@ -1032,50 +1349,59 @@ static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void return; } - lchan_fail("RTP stream closed unexpectedly: %s in state %s\n", + lchan_fail("RTP stream closed unexpectedly: %s in state %s", osmo_fsm_event_name(fi->fsm, event), osmo_fsm_inst_state_name(fi)); return; case LCHAN_EV_REQUEST_MODE_MODIFY: + modif_info = data; + lchan->modify.info = *modif_info; + lchan->modify.concluded = false; + + use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan); - /* FIXME: Add missing implementation to handle an already existing RTP voice stream on MODE MODIFY. - * there may be transitions from VOICE to SIGNALLING and also from VOICE to VOICE with a different - * codec. */ - if (lchan->fi_rtp) { - lchan_fail("MODE MODIFY not implemented when RTP voice stream is already active (VOICE => SIGNALLING, VOICE/CODEC_A => VOICE/CODEC_B)\n"); + lchan->modify.ch_mode_rate = lchan->modify.info.ch_mode_rate; + lchan->modify.ch_mode_rate.chan_mode = (lchan->modify.info.type_for == LCHAN_TYPE_FOR_VAMOS) + ? gsm48_chan_mode_to_vamos(lchan->modify.info.ch_mode_rate.chan_mode) + : gsm48_chan_mode_to_non_vamos(lchan->modify.info.ch_mode_rate.chan_mode); + if (lchan->modify.ch_mode_rate.chan_mode < 0) { + lchan_fail("Invalid chan_mode: %s", gsm48_chan_mode_name(lchan->modify.info.ch_mode_rate.chan_mode)); return; } - info = data; - lchan->activate.info = *info; - use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan); - - if (info->chan_mode == GSM48_CMODE_SPEECH_AMR) { - if (lchan_mr_config(lchan, info->s15_s0) < 0) { - lchan_fail("Can not generate multirate configuration IE\n"); + if (gsm48_chan_mode_to_non_vamos(modif_info->ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) { + if (lchan_mr_config(&lchan->modify.mr_conf_filtered, lchan, modif_info->ch_mode_rate.s15_s0) + < 0) { + lchan_fail("Can not generate multirate configuration IE"); return; } } + lchan->modify.ch_indctr = lchan->modify.info.ch_indctr; + + /* If enabling VAMOS mode and no specific TSC Set was selected, make sure to select a sane TSC Set by + * default: Set 1 for the primary and Set 2 for the shadow lchan. For non-VAMOS lchans, TSC Set 1. */ + if (lchan->modify.info.tsc_set.present) + lchan->modify.tsc_set = lchan->modify.info.tsc_set.val; + else + lchan->modify.tsc_set = lchan->vamos.is_secondary ? 2 : 1; + + /* Use the TSC provided in the modification request, if any. Otherwise use the timeslot's configured + * TSC. */ + lchan->modify.tsc = lchan->modify.info.tsc.present ? lchan->modify.info.tsc.val : gsm_ts_tsc(lchan->ts); + LOG_LCHAN(lchan, LOGL_INFO, - "Modification requested: %s voice=%s MGW-ci=%s type=%s tch-mode=%s encr-alg=A5/%u ck=%s\n", - lchan_activate_mode_name(lchan->activate.info.activ_for), - lchan->activate.info.requires_voice_stream ? "yes" : "no", - lchan->activate.info.requires_voice_stream ? + "Modification requested: %s rtp=%s MGW-ci=%s type=%s tch-mode=%s tsc=%d/%u\n", + lchan_modify_for_name(lchan->modify.info.modify_for), + requires_rtp_stream ? "yes" : "no", + requires_rtp_stream ? (use_mgwep_ci ? osmo_mgcpc_ep_ci_name(use_mgwep_ci) : "new") : "none", - gsm_lchant_name(lchan->type), - gsm48_chan_mode_name(lchan->tch_mode), - (lchan->activate.info.encr.alg_id ? : 1) - 1, - lchan->activate.info.encr.key_len ? osmo_hexdump_nospc(lchan->activate.info.encr.key, - lchan->activate.info.encr.key_len) : "none"); - - /* While the mode is changed the lchan is virtually "not activated", at least - * from the FSM implementations perspective */ - lchan->activate.concluded = false; + gsm_chan_t_name(lchan->type), + gsm48_chan_mode_name(lchan->modify.ch_mode_rate.chan_mode), + lchan->modify.tsc_set, lchan->modify.tsc); - /* Initiate mode modification, start with the MS side (RR) */ lchan_fsm_state_chg(LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK); return; @@ -1101,8 +1427,14 @@ static bool should_sacch_deact(struct gsm_lchan *lchan) static void lchan_do_release(struct gsm_lchan *lchan) { - if (lchan->release.do_rr_release && lchan->sapis[0] != LCHAN_SAPI_UNUSED) - gsm48_send_rr_release(lchan); + if (lchan->release.do_rr_release) { + /* To main DCCH in dedicated and group transmit mode */ + if (lchan->sapis[0] != LCHAN_SAPI_UNUSED) + gsm48_send_rr_release(lchan, false); + /* As UI to all listeners in group receive mode */ + if (lchan_is_asci(lchan)) + gsm48_send_rr_release(lchan, true); + } if (lchan->fi_rtp) osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_RELEASE, 0); @@ -1186,8 +1518,9 @@ static void lchan_fsm_wait_rf_release_ack_onenter(struct osmo_fsm_inst *fi, uint * lchan_reset(), we make sure it does. But in case of releases from error handling, the * conn might as well notice now already that its lchan is becoming unusable. */ if (lchan->conn) { - gscon_forget_lchan(lchan->conn, lchan); + struct gsm_subscriber_connection *conn = lchan->conn; lchan_forget_conn(lchan); + gscon_forget_lchan(conn, lchan); } rc = rsl_tx_rf_chan_release(lchan); @@ -1212,6 +1545,13 @@ static void lchan_fsm_wait_rf_release_ack(struct osmo_fsm_inst *fi, uint32_t eve /* ignore late lchan_rtp_fsm release events */ return; + case LCHAN_EV_RLL_REL_IND: + /* let's just ignore this. We are already logging the fact + * that this message was received inside abis_rsl.c. There can + * be any number of reasons why the radio link layer failed. + */ + return; + default: OSMO_ASSERT(false); } @@ -1220,6 +1560,7 @@ static void lchan_fsm_wait_rf_release_ack(struct osmo_fsm_inst *fi, uint32_t eve static void lchan_fsm_borken_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gsm_lchan *lchan = lchan_fi_lchan(fi); + struct gsm_bts *bts = lchan->ts->trx->bts; enum bts_counter_id ctr; switch (prev_state) { case LCHAN_ST_UNUSED: @@ -1243,23 +1584,25 @@ static void lchan_fsm_borken_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta default: ctr = BTS_CTR_LCHAN_BORKEN_FROM_UNKNOWN; } - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[ctr]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, ctr)); if (prev_state != LCHAN_ST_BORKEN) - osmo_stat_item_inc(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1); + osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1); /* The actual action besides all the beancounting above */ lchan_reset(lchan); + chan_counts_ts_update(lchan->ts); } static void lchan_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_lchan *lchan = lchan_fi_lchan(fi); + struct gsm_bts *bts = lchan->ts->trx->bts; switch (event) { case LCHAN_EV_RSL_CHAN_ACTIV_ACK: /* A late Chan Activ ACK? Release. */ - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_ACK]); - osmo_stat_item_dec(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_ACK)); + osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1); lchan->release.in_error = true; lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING; lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause); @@ -1268,33 +1611,24 @@ static void lchan_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *dat case LCHAN_EV_RSL_CHAN_ACTIV_NACK: /* A late Chan Activ NACK? Ok then, unused. */ - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_NACK]); - osmo_stat_item_dec(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_NACK)); + osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1); lchan_fsm_state_chg(LCHAN_ST_UNUSED); return; case LCHAN_EV_RSL_RF_CHAN_REL_ACK: /* A late Release ACK? */ - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_RF_CHAN_REL_ACK]); - osmo_stat_item_dec(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_RF_CHAN_REL_ACK)); + osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1); lchan->release.in_error = true; lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING; lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause); lchan_fsm_state_chg(LCHAN_ST_WAIT_AFTER_ERROR); - /* TODO: we used to do this only for sysmobts: - int do_free = is_sysmobts_v2(ts->trx->bts); - LOGP(DRSL, LOGL_NOTICE, - "%s CHAN REL ACK for broken channel. %s.\n", - gsm_lchan_name(lchan), - do_free ? "Releasing it" : "Keeping it broken"); - if (do_free) - do_lchan_free(lchan); - * Clarify the reason. If a BTS sends a RF Chan Rel ACK, we can consider it released, - * independently from the BTS model, right?? */ return; case LCHAN_EV_RTP_RELEASED: case LCHAN_EV_RTP_ERROR: + case LCHAN_EV_RLL_REL_IND: return; default: @@ -1302,6 +1636,71 @@ static void lchan_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *dat } } +static void lchan_fsm_recover_wait_activ_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + int rc; + struct gsm_lchan *lchan = lchan_fi_lchan(fi); + + LOG_LCHAN(lchan, LOGL_INFO, "attempting to recover from BORKEN lchan\n"); + + lchan->type = GSM_LCHAN_SDCCH; + lchan->activate.info.ta_known = true; + + chan_counts_ts_update(lchan->ts); + + rc = rsl_tx_chan_activ(lchan, RSL_ACT_INTRA_NORM_ASS, 0); + if (rc) + lchan_fail("Tx Chan Activ failed: %s (%d)", strerror(-rc), rc); +} + +static void lchan_fsm_recover_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_lchan *lchan = lchan_fi_lchan(fi); + + switch (event) { + + case LCHAN_EV_RSL_CHAN_ACTIV_ACK: + lchan_fsm_state_chg(LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK); + break; + + case LCHAN_EV_RSL_CHAN_ACTIV_NACK: + /* If an earlier lchan activ got through to the BTS, but the + * ACK did not get back to the BSC, it may still be active on + * the BTS side. Proceed to release it. */ + LOG_LCHAN(lchan, LOGL_NOTICE, "received NACK for activation of BORKEN lchan, assuming still active\n"); + lchan_fsm_state_chg(LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK); + break; + + default: + OSMO_ASSERT(false); + } +} + +static void lchan_fsm_recover_wait_rf_release_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + int rc; + struct gsm_lchan *lchan = lchan_fi_lchan(fi); + + rc = rsl_tx_rf_chan_release(lchan); + if (rc) + lchan_fail("Tx RSL RF Channel Release failed: %s (%d)\n", strerror(-rc), rc); +} + +static void lchan_fsm_recover_wait_rf_release_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_lchan *lchan = lchan_fi_lchan(fi); + switch (event) { + + case LCHAN_EV_RSL_RF_CHAN_REL_ACK: + LOG_LCHAN(lchan, LOGL_NOTICE, "successfully recovered BORKEN lchan\n"); + lchan_fsm_state_chg(LCHAN_ST_UNUSED); + break; + + default: + OSMO_ASSERT(false); + } +} + #define S(x) (1 << (x)) static const struct osmo_fsm_state lchan_fsm_states[] = { @@ -1320,6 +1719,7 @@ static const struct osmo_fsm_state lchan_fsm_states[] = { }, [LCHAN_ST_CBCH] = { .name = "CBCH", + .onenter = lchan_fsm_cbch_onenter, .out_state_mask = 0 | S(LCHAN_ST_UNUSED) , @@ -1382,8 +1782,9 @@ static const struct osmo_fsm_state lchan_fsm_states[] = { | S(LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR) , .out_state_mask = 0 - | S(LCHAN_ST_BORKEN) | S(LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK) + | S(LCHAN_ST_WAIT_RF_RELEASE_ACK) + | S(LCHAN_ST_BORKEN) , }, [LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = { @@ -1395,8 +1796,10 @@ static const struct osmo_fsm_state lchan_fsm_states[] = { | S(LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK) , .out_state_mask = 0 - | S(LCHAN_ST_BORKEN) + | S(LCHAN_ST_ESTABLISHED) | S(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH) + | S(LCHAN_ST_WAIT_RF_RELEASE_ACK) + | S(LCHAN_ST_BORKEN) , }, [LCHAN_ST_ESTABLISHED] = { @@ -1452,6 +1855,7 @@ static const struct osmo_fsm_state lchan_fsm_states[] = { .action = lchan_fsm_wait_rf_release_ack, .in_event_mask = 0 | S(LCHAN_EV_RSL_RF_CHAN_REL_ACK) + | S(LCHAN_EV_RLL_REL_IND) /* ignore late REL_IND of SAPI[0] */ | S(LCHAN_EV_RTP_RELEASED) /* ignore late lchan_rtp_fsm release events */ , .out_state_mask = 0 @@ -1480,11 +1884,38 @@ static const struct osmo_fsm_state lchan_fsm_states[] = { | S(LCHAN_EV_RSL_RF_CHAN_REL_ACK) | S(LCHAN_EV_RTP_ERROR) | S(LCHAN_EV_RTP_RELEASED) + | S(LCHAN_EV_RLL_REL_IND) , .out_state_mask = 0 | S(LCHAN_ST_WAIT_RF_RELEASE_ACK) | S(LCHAN_ST_UNUSED) | S(LCHAN_ST_WAIT_AFTER_ERROR) + | S(LCHAN_ST_RECOVER_WAIT_ACTIV_ACK) + , + }, + [LCHAN_ST_RECOVER_WAIT_ACTIV_ACK] = { + .name = "RECOVER_WAIT_ACTIV_ACK", + .onenter = lchan_fsm_recover_wait_activ_ack_onenter, + .action = lchan_fsm_recover_wait_activ_ack, + .in_event_mask = 0 + | S(LCHAN_EV_RSL_CHAN_ACTIV_ACK) + | S(LCHAN_EV_RSL_CHAN_ACTIV_NACK) + , + .out_state_mask = 0 + | S(LCHAN_ST_BORKEN) + | S(LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK) + , + }, + [LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK] = { + .name = "RECOVER_WAIT_RF_RELEASE_ACK", + .onenter = lchan_fsm_recover_wait_rf_release_ack_onenter, + .action = lchan_fsm_recover_wait_rf_release_ack, + .in_event_mask = 0 + | S(LCHAN_EV_RSL_RF_CHAN_REL_ACK) + , + .out_state_mask = 0 + | S(LCHAN_ST_BORKEN) + | S(LCHAN_ST_UNUSED) , }, }; @@ -1507,6 +1938,7 @@ static const struct value_string lchan_fsm_event_names[] = { OSMO_VALUE_STRING(LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR), OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK), OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK), + OSMO_VALUE_STRING(LCHAN_EV_REQUEST_MODE_MODIFY), {} }; @@ -1515,8 +1947,15 @@ static void lchan_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, switch (event) { case LCHAN_EV_TS_ERROR: + { + struct gsm_lchan *lchan = lchan_fi_lchan(fi); + if (fi->state == LCHAN_ST_BORKEN) { + rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_TS_ERROR)); + osmo_stat_item_dec(osmo_stat_item_group_get_item(lchan->ts->trx->bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1); + } lchan_fail_to(LCHAN_ST_UNUSED, "LCHAN_EV_TS_ERROR"); return; + } case LCHAN_EV_RLL_ERR_IND: /* let's just ignore this. We are already logging the @@ -1550,19 +1989,31 @@ static int lchan_fsm_timer_cb(struct osmo_fsm_inst *fi) lchan_fsm_state_chg(LCHAN_ST_UNUSED); return 0; + case LCHAN_ST_BORKEN: + lchan_fsm_state_chg(LCHAN_ST_RECOVER_WAIT_ACTIV_ACK); + return 0; + default: lchan->release.in_error = true; lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING; lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause); - lchan_fail("Timeout"); + if (fi->state == LCHAN_ST_WAIT_RLL_RTP_ESTABLISH) { + lchan_fail("Timeout (rll_ready=%s,rtp_require=%s,voice_ready=%s)", + (lchan->sapis[0] != LCHAN_SAPI_UNUSED) ? "yes" : "no", + bsc_chan_ind_requires_rtp_stream(lchan->activate.info.ch_indctr) ? "yes" : "no", + lchan_rtp_established(lchan) ? "yes" : "no"); + } else { + lchan_fail("Timeout"); + } return 0; } } void lchan_release(struct gsm_lchan *lchan, bool do_rr_release, - bool err, enum gsm48_rr_cause cause_rr) + bool err, enum gsm48_rr_cause cause_rr, + const struct osmo_plmn_id *last_eutran_plmn) { - if (!lchan || !lchan->fi) + if (!lchan || !lchan->fi || lchan->fi->state == LCHAN_ST_UNUSED) return; if (lchan->release.in_release_handler) @@ -1574,6 +2025,10 @@ void lchan_release(struct gsm_lchan *lchan, bool do_rr_release, lchan->release.in_error = err; lchan->release.do_rr_release = do_rr_release; lchan->release.rr_cause = cause_rr; + if (last_eutran_plmn) { + lchan->release.last_eutran_plmn_valid = true; + memcpy(&lchan->release.last_eutran_plmn, last_eutran_plmn, sizeof(*last_eutran_plmn)); + } /* States waiting for events will notice the desire to release when done waiting, so it is enough * to mark for release. */ @@ -1616,8 +2071,8 @@ static void lchan_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause { struct gsm_lchan *lchan = lchan_fi_lchan(fi); if (lchan->fi->state == LCHAN_ST_BORKEN) { - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_TEARDOWN]); - osmo_stat_item_dec(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1); + rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_TEARDOWN)); + osmo_stat_item_dec(osmo_stat_item_group_get_item(lchan->ts->trx->bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1); } lchan_reset(lchan); if (lchan->last_error) { diff --git a/src/osmo-bsc/lchan_rtp_fsm.c b/src/osmo-bsc/lchan_rtp_fsm.c index cd195d021..e8384c604 100644 --- a/src/osmo-bsc/lchan_rtp_fsm.c +++ b/src/osmo-bsc/lchan_rtp_fsm.c @@ -31,6 +31,7 @@ #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/lchan.h> static struct osmo_fsm lchan_rtp_fsm; @@ -43,10 +44,10 @@ struct gsm_lchan *lchan_rtp_fi_lchan(struct osmo_fsm_inst *fi) } struct osmo_tdef_state_timeout lchan_rtp_fsm_timeouts[32] = { - [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = { .T=-9 }, - [LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK] = { .T=-7 }, - [LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = { .T=-8 }, - [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = { .T=-10 }, + [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = { .T = -9 }, + [LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK] = { .T = -7 }, + [LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = { .T = -8 }, + [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = { .T = -10 }, }; /* Transition to a state, using the T timer defined in lchan_rtp_fsm_timeouts. @@ -63,13 +64,13 @@ struct osmo_tdef_state_timeout lchan_rtp_fsm_timeouts[32] = { #define lchan_rtp_fail(fmt, args...) do { \ struct gsm_lchan *_lchan = fi->priv; \ uint32_t state_was = fi->state; \ - lchan_set_last_error(_lchan, "lchan-rtp failure in state %s: " fmt, \ + LCHAN_SET_LAST_ERROR(_lchan, "lchan-rtp failure in state %s: " fmt, \ osmo_fsm_state_name(fi->fsm, state_was), ## args); \ osmo_fsm_inst_dispatch(_lchan->fi, LCHAN_EV_RTP_ERROR, 0); \ - } while(0) + } while (0) /* Called from lchan_fsm_init(), does not need to be visible in lchan_rtp_fsm.h */ -void lchan_rtp_fsm_init() +static __attribute__((constructor)) void lchan_rtp_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&lchan_rtp_fsm) == 0); } @@ -137,11 +138,12 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_in uint32_t prev_state) { struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi); + struct gsm_bts *bts = lchan->ts->trx->bts; struct osmo_mgcpc_ep *mgwep; struct osmo_mgcpc_ep_ci *use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan); - struct mgcp_conn_peer crcx_info = {}; + struct mgcp_conn_peer crcx_info; - if (!is_ipaccess_bts(lchan->ts->trx->bts)) { + if (!is_ipa_abisip_bts(lchan->ts->trx->bts)) { LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Audio link to-BTS via E1, skipping IPACC\n"); lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY); return; @@ -162,14 +164,36 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_in lchan->mgw_endpoint_ci_bts = osmo_mgcpc_ep_ci_add(mgwep, "to-BTS"); + crcx_info = (struct mgcp_conn_peer){ + .ptime = 20, + .x_osmo_osmux_cid = -1, /* -1 is wildcard, .x_osmo_osmux_use set below */ + }; if (lchan->conn) { - crcx_info.call_id = lchan->conn->sccp.conn_id; + crcx_info.call_id = lchan->conn->sccp.conn.conn_id; if (lchan->conn->sccp.msc) crcx_info.x_osmo_ign = lchan->conn->sccp.msc->x_osmo_ign; } - crcx_info.ptime = 20; mgcp_pick_codec(&crcx_info, lchan, true); + /* Set up Osmux use in MGW according to configured policy */ + bool amr_picked = mgcp_codec_is_picked(&crcx_info, CODEC_AMR_8000_1); + switch (bts->use_osmux) { + case OSMUX_USAGE_OFF: + crcx_info.x_osmo_osmux_use = false; + break; + case OSMUX_USAGE_ON: + crcx_info.x_osmo_osmux_use = amr_picked; + break; + case OSMUX_USAGE_ONLY: + if (!amr_picked) { + lchan_rtp_fail("Only AMR codec can be used when configured with policy 'osmux only'." + " Check your configuration."); + return; + } + crcx_info.x_osmo_osmux_use = true; + break; + } + osmo_mgcpc_ep_ci_request(lchan->mgw_endpoint_ci_bts, MGCP_VERB_CRCX, &crcx_info, fi, LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE, LCHAN_RTP_EV_MGW_ENDPOINT_ERROR, 0); @@ -178,11 +202,26 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_in static void lchan_rtp_fsm_wait_mgw_endpoint_available(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi); + struct gsm_bts *bts = lchan->ts->trx->bts; switch (event) { case LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE: LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "MGW endpoint: %s\n", osmo_mgcpc_ep_ci_name(lchan_use_mgw_endpoint_ci_bts(lchan))); + if (osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(lchan->mgw_endpoint_ci_bts, + &lchan->abis_ip.osmux.local_cid)) { + if (bts->use_osmux == OSMUX_USAGE_OFF) { + lchan_rtp_fail("Got Osmux CID from MGW but we didn't ask for it"); + return; + } + lchan->abis_ip.osmux.use = true; + } else { + if (bts->use_osmux == OSMUX_USAGE_ONLY) { + lchan_rtp_fail("Got no Osmux CID from MGW but Osmux is mandatory"); + return; + } + lchan->abis_ip.osmux.use = false; + } lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY); return; @@ -251,7 +290,7 @@ static void lchan_rtp_fsm_post_lchan_ready(struct osmo_fsm_inst *fi) { struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi); - if (is_ipaccess_bts(lchan->ts->trx->bts)) + if (is_ipa_abisip_bts(lchan->ts->trx->bts)) lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK); else lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED); @@ -268,20 +307,41 @@ static void lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter(struct osmo_fsm_inst *fi, return; } - val = ipacc_speech_mode(lchan->tch_mode, lchan->type); - if (val < 0) { - lchan_rtp_fail("Cannot determine Abis/IP speech mode for tch_mode=%s type=%s\n", - get_value_string(gsm48_chan_mode_names, lchan->tch_mode), - gsm_lchant_name(lchan->type)); - return; + if (lchan->current_ch_indctr == GSM0808_CHAN_DATA) { + enum rsl_ipac_rtp_csd_format_d format_d = RSL_IPAC_RTP_CSD_TRAU_BTS; + + if (lchan->activate.ch_mode_rate.data_transparent) { + val = ipacc_rtp_csd_fmt_transp(&lchan->activate.ch_mode_rate, format_d); + if (val < 0) { + lchan_rtp_fail("Cannot determine Abis/IP RTP CSD format for rsl_cmod_csd_t=%d", + lchan->activate.ch_mode_rate.data_rate.t); + return; + } + } else { + val = ipacc_rtp_csd_fmt_non_transp(&lchan->activate.ch_mode_rate, format_d); + if (val < 0) { + lchan_rtp_fail("Cannot determine Abis/IP RTP CSD format for rsl_cmod_csd_nt=%d", + lchan->activate.ch_mode_rate.data_rate.nt); + return; + } + } + lchan->abis_ip.rtp_csd_fmt = val; + } else { + val = ipacc_speech_mode(lchan->activate.ch_mode_rate.chan_mode, lchan->type); + if (val < 0) { + lchan_rtp_fail("Cannot determine Abis/IP speech mode for tch_mode=%s type=%s", + get_value_string(gsm48_chan_mode_names, lchan->activate.ch_mode_rate.chan_mode), + gsm_chan_t_name(lchan->type)); + return; + } + lchan->abis_ip.speech_mode = val; } - lchan->abis_ip.speech_mode = val; - val = ipacc_payload_type(lchan->tch_mode, lchan->type); + val = ipacc_payload_type(lchan->activate.ch_mode_rate.chan_mode, lchan->type); if (val < 0) { - lchan_rtp_fail("Cannot determine Abis/IP payload type for tch_mode=%s type=%s\n", - get_value_string(gsm48_chan_mode_names, lchan->tch_mode), - gsm_lchant_name(lchan->type)); + lchan_rtp_fail("Cannot determine Abis/IP payload type for tch_mode=%s type=%s", + get_value_string(gsm48_chan_mode_names, lchan->activate.ch_mode_rate.chan_mode), + gsm_chan_t_name(lchan->type)); return; } lchan->abis_ip.rtp_payload = val; @@ -411,9 +471,16 @@ static void connect_mgw_endpoint_to_lchan(struct osmo_fsm_inst *fi, struct in_addr addr; const char *addr_str; + if (lchan->abis_ip.osmux.use && !lchan->abis_ip.osmux.remote_cid_present) { + lchan_rtp_fail("BTS didn't provide any remote Osmux CID for the call"); + return; + } + mdcx_info = (struct mgcp_conn_peer){ .port = to_lchan->abis_ip.bound_port, .ptime = 20, + .x_osmo_osmux_use = lchan->abis_ip.osmux.use, + .x_osmo_osmux_cid = lchan->abis_ip.osmux.remote_cid, }; mgcp_pick_codec(&mdcx_info, to_lchan, true); @@ -449,7 +516,7 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(struct osmo_fsm_i return; } - if (!is_ipaccess_bts(lchan->ts->trx->bts)) { + if (!is_ipa_abisip_bts(lchan->ts->trx->bts)) { LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Audio link to-BTS via E1, skipping IPACC\n"); lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY); return; @@ -476,7 +543,7 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_configured(struct osmo_fsm_inst *fi, switch (event) { case LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED: - if (is_ipaccess_bts(lchan->ts->trx->bts)) + if (is_ipa_abisip_bts(lchan->ts->trx->bts)) lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK); else { lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY); @@ -522,6 +589,12 @@ static void lchan_rtp_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void * lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_ROLLBACK); return; + case LCHAN_RTP_EV_READY_TO_SWITCH_RTP: + /* Ignore / silence an "event not permitted" error. In case of an inter-BSC incoming handover, there is + * no previous lchan to be switched over, and we are already in this state when the usual handover code + * path emits this event. */ + return; + default: OSMO_ASSERT(false); } @@ -537,7 +610,7 @@ static void lchan_rtp_fsm_rollback_onenter(struct osmo_fsm_inst *fi, uint32_t pr return; } - if (is_ipaccess_bts(lchan->ts->trx->bts)) + if (is_ipa_abisip_bts(lchan->ts->trx->bts)) connect_mgw_endpoint_to_lchan(fi, lchan->mgw_endpoint_ci_bts, old_lchan); else osmo_fsm_inst_dispatch(fi, LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED, 0); @@ -704,6 +777,7 @@ static const struct osmo_fsm_state lchan_rtp_fsm_states[] = { | S(LCHAN_RTP_EV_ESTABLISHED) | S(LCHAN_RTP_EV_RELEASE) | S(LCHAN_RTP_EV_ROLLBACK) + | S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP) , .out_state_mask = 0 | S(LCHAN_RTP_ST_ESTABLISHED) @@ -796,7 +870,13 @@ static struct osmo_fsm lchan_rtp_fsm = { /* Depending on the channel mode and rate, return the codec type that is signalled towards the MGW. */ static enum mgcp_codecs chan_mode_to_mgcp_codec(enum gsm48_chan_mode chan_mode, bool full_rate) { - switch (chan_mode) { + switch (gsm48_chan_mode_to_non_vamos(chan_mode)) { + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + return CODEC_CLEARMODE; + case GSM48_CMODE_SPEECH_V1: if (full_rate) return CODEC_GSM_8000_1; @@ -834,14 +914,14 @@ static int chan_mode_to_mgcp_bss_pt(enum mgcp_codecs codec) void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *lchan, bool bss_side) { - enum mgcp_codecs codec = chan_mode_to_mgcp_codec(lchan->tch_mode, + enum mgcp_codecs codec = chan_mode_to_mgcp_codec(lchan->activate.ch_mode_rate.chan_mode, lchan->type == GSM_LCHAN_TCH_H? false : true); int custom_pt; if (codec < 0) { LOG_LCHAN(lchan, LOGL_ERROR, "Unable to determine MGCP codec type for %s in chan-mode %s\n", - gsm_lchant_name(lchan->type), gsm48_chan_mode_name(lchan->tch_mode)); + gsm_chan_t_name(lchan->type), gsm48_chan_mode_name(lchan->activate.ch_mode_rate.chan_mode)); verb_info->codecs_len = 0; return; } @@ -853,8 +933,8 @@ void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *l custom_pt = chan_mode_to_mgcp_bss_pt(codec); if (bss_side && custom_pt > 0) { verb_info->ptmap[0].codec = codec; - verb_info->ptmap[0].pt = custom_pt; - verb_info->ptmap_len = 1; + verb_info->ptmap[0].pt = custom_pt; + verb_info->ptmap_len = 1; } /* AMR requires additional parameters to be set up (framing mode) */ @@ -866,7 +946,7 @@ void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *l if (bss_side && verb_info->codecs[0] == CODEC_AMR_8000_1) { /* FIXME: At the moment all BTSs we support are using the * octet-aligned payload format. However, in the future - * we may support BTSs that are using bandwith-efficient + * we may support BTSs that are using bandwidth-efficient * format. In this case we will have to add functionality * that distinguishes by the BTS model which mode to use. */ verb_info->param.amr_octet_aligned = true; @@ -875,3 +955,8 @@ void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *l verb_info->param.amr_octet_aligned = lchan->conn->sccp.msc->amr_octet_aligned; } } + +bool mgcp_codec_is_picked(const struct mgcp_conn_peer *verb_info, enum mgcp_codecs codec) +{ + return verb_info->codecs[0] == codec; +} diff --git a/src/osmo-bsc/lchan_select.c b/src/osmo-bsc/lchan_select.c index 6d3caaced..c830bd1ee 100644 --- a/src/osmo-bsc/lchan_select.c +++ b/src/osmo-bsc/lchan_select.c @@ -2,7 +2,7 @@ * * (C) 2008 by Harald Welte <laforge@gnumonks.org> * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org> - * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * (C) 2018-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * All Rights Reserved * @@ -21,6 +21,8 @@ * */ +#include <stdlib.h> + #include <osmocom/bsc/debug.h> #include <osmocom/bsc/gsm_data.h> @@ -30,148 +32,340 @@ #include <osmocom/bsc/lchan_select.h> #include <osmocom/bsc/bts.h> -static struct gsm_lchan * -_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan, - enum gsm_phys_chan_config as_pchan) +struct lchan_select_ts_list { + struct gsm_bts_trx_ts **list; + unsigned int num; +}; + +const struct value_string lchan_select_reason_names[] = { + OSMO_VALUE_STRING(SELECT_FOR_MS_CHAN_REQ), + OSMO_VALUE_STRING(SELECT_FOR_ASSIGNMENT), + OSMO_VALUE_STRING(SELECT_FOR_HANDOVER), + OSMO_VALUE_STRING(SELECT_FOR_VGCS), + {0, NULL} +}; + +static struct gsm_lchan *pick_better_lchan(struct gsm_lchan *a, struct gsm_lchan *b) +{ + if (!a) + return b; + if (!b) + return a; + /* comparing negative dBm values: smaller value means less interference. */ + if (b->interf_dbm < a->interf_dbm) + return b; + return a; +} + +static struct gsm_lchan *_lc_find(struct lchan_select_ts_list *ts_list, + enum gsm_phys_chan_config pchan, + enum gsm_phys_chan_config as_pchan, + bool allow_pchan_switch, bool log) { struct gsm_lchan *lchan; - struct gsm_bts_trx_ts *ts; - int j, start, stop, dir; + struct gsm_lchan *found_lchan = NULL; -#define LOGPLCHANALLOC(fmt, args...) \ - LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s%s%s: " fmt, \ +#define LOGPLCHANALLOC(fmt, args...) do { \ + if (log) \ + LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s%s%s%s: " fmt, \ gsm_pchan_name(pchan), \ pchan == as_pchan ? "" : " as ", \ - pchan == as_pchan ? "" : gsm_pchan_name(as_pchan), ## args) + pchan == as_pchan ? "" : gsm_pchan_name(as_pchan), \ + ((pchan != as_pchan) && !allow_pchan_switch) ? " without pchan switch" : "", \ + ## args); \ + } while (0) - if (!trx_is_usable(trx)) { - LOGPLCHANALLOC("%s trx not usable\n", gsm_trx_name(trx)); - return NULL; - } + for (unsigned int tn = 0; tn < ts_list->num; tn++) { + struct gsm_bts_trx_ts *ts = ts_list->list[tn]; + int lchans_as_pchan; - if (trx->bts->chan_alloc_reverse) { - /* check TS 7..0 */ - start = 7; - stop = -1; - dir = -1; - } else { - /* check TS 0..7 */ - start = 0; - stop = 8; - dir = 1; - } - - for (j = start; j != stop; j += dir) { - ts = &trx->ts[j]; - if (!ts_is_usable(ts)) - continue; /* The caller first selects what kind of TS to search in, e.g. looking for exact - * GSM_PCHAN_TCH_F, or maybe among dynamic GSM_PCHAN_TCH_F_TCH_H_PDCH... */ + * GSM_PCHAN_TCH_F, or maybe among dynamic GSM_PCHAN_OSMO_DYN... */ if (ts->pchan_on_init != pchan) { LOGPLCHANALLOC("%s is != %s\n", gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan)); continue; } /* Next, is this timeslot in or can it be switched to the pchan we want to use it for? */ - if (!ts_usable_as_pchan(ts, as_pchan)) { - LOGPLCHANALLOC("%s is not usable as %s\n", gsm_ts_and_pchan_name(ts), - gsm_pchan_name(as_pchan)); + if (!ts_usable_as_pchan(ts, as_pchan, allow_pchan_switch)) { + LOGPLCHANALLOC("%s is not usable as %s%s\n", gsm_ts_and_pchan_name(ts), + gsm_pchan_name(as_pchan), + allow_pchan_switch ? "" : " without pchan switch"); continue; } /* TS is (going to be) in desired pchan mode. Go ahead and check for an available lchan. */ - ts_as_pchan_for_each_lchan(lchan, ts, as_pchan) { - if (lchan->fi->state == LCHAN_ST_UNUSED) { - LOGPLCHANALLOC("%s ss=%d is available%s\n", + lchans_as_pchan = pchan_subslots(as_pchan); + ts_for_n_lchans(lchan, ts, lchans_as_pchan) { + struct gsm_lchan *was = found_lchan; + + if (lchan->fi->state != LCHAN_ST_UNUSED) { + LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n", gsm_ts_and_pchan_name(ts), lchan->nr, - ts->pchan_is != as_pchan ? " after dyn PCHAN change" : ""); - return lchan; + gsm_chan_t_name(lchan->type), + osmo_fsm_inst_state_name(lchan->fi)); + continue; } - LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n", - gsm_ts_and_pchan_name(ts), lchan->nr, - gsm_lchant_name(lchan->type), - osmo_fsm_inst_state_name(lchan->fi)); + + found_lchan = pick_better_lchan(found_lchan, lchan); + if (found_lchan != was) + LOGPLCHANALLOC("%s ss=%d interf=%u=%ddBm is %s%s\n", + gsm_ts_and_pchan_name(ts), lchan->nr, + lchan->interf_band, lchan->interf_dbm, + was == NULL ? "available" : "better", + ts->pchan_is != as_pchan ? ", after dyn PCHAN change" : ""); + else + LOGPLCHANALLOC("%s ss=%d interf=%u=%ddBm is also available but not better\n", + gsm_ts_and_pchan_name(ts), lchan->nr, + lchan->interf_band, lchan->interf_dbm); + + /* When picking an lchan with least interference, continue to loop across all lchans. When + * ignoring interference levels, return the first match. */ + if (found_lchan && !ts->trx->bts->chan_alloc_avoid_interf) + return found_lchan; } } - return NULL; + if (found_lchan) + LOGPLCHANALLOC("%s ss=%d interf=%ddBm%s is the best pick\n", + gsm_ts_and_pchan_name(found_lchan->ts), found_lchan->nr, + found_lchan->interf_dbm, + found_lchan->ts->pchan_is != as_pchan ? ", after dyn PCHAN change," : ""); + else + LOGPLCHANALLOC("Nothing found\n"); + return found_lchan; #undef LOGPLCHANALLOC } -static struct gsm_lchan * -_lc_dyn_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan, - enum gsm_phys_chan_config dyn_as_pchan) +static struct gsm_lchan *lc_dyn_find(struct lchan_select_ts_list *ts_list, + enum gsm_phys_chan_config pchan, + enum gsm_phys_chan_config dyn_as_pchan, + bool log) { - struct gsm_bts_trx *trx; - struct gsm_lchan *lc; + struct gsm_lchan *lchan; - if (bts->chan_alloc_reverse) { - llist_for_each_entry_reverse(trx, &bts->trx_list, list) { - lc = _lc_find_trx(trx, pchan, dyn_as_pchan); - if (lc) - return lc; - } - } else { - llist_for_each_entry(trx, &bts->trx_list, list) { - lc = _lc_find_trx(trx, pchan, dyn_as_pchan); - if (lc) - return lc; - } - } + /* First find an lchan that needs no change in its timeslot pchan mode. + * In particular, this ensures that handover to a dynamic timeslot in TCH/H favors timeslots that are currently + * using only one of two TCH/H, so that we don't switch more dynamic timeslots to TCH/H than necessary. + * For non-dynamic timeslots, it is not necessary to do a second pass with allow_pchan_switch == + * true, because they never switch anyway. */ + if ((lchan = _lc_find(ts_list, pchan, dyn_as_pchan, false, log))) + return lchan; + if ((lchan = _lc_find(ts_list, pchan, dyn_as_pchan, true, log))) + return lchan; return NULL; } -static struct gsm_lchan * -_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) +static struct gsm_lchan *lc_find(struct lchan_select_ts_list *ts_list, + enum gsm_phys_chan_config pchan, + bool log) { - return _lc_dyn_find_bts(bts, pchan, pchan); + return _lc_find(ts_list, pchan, pchan, false, log); } -struct gsm_lchan *lchan_select_by_chan_mode(struct gsm_bts *bts, - enum gsm48_chan_mode chan_mode, enum channel_rate chan_rate) +enum gsm_chan_t chan_mode_to_chan_type(enum gsm48_chan_mode chan_mode, enum channel_rate chan_rate) { - enum gsm_chan_t type; - - switch (chan_mode) { + switch (gsm48_chan_mode_to_non_vamos(chan_mode)) { case GSM48_CMODE_SIGN: switch (chan_rate) { - case CH_RATE_SDCCH: type = GSM_LCHAN_SDCCH; break; - case CH_RATE_HALF: type = GSM_LCHAN_TCH_H; break; - case CH_RATE_FULL: type = GSM_LCHAN_TCH_F; break; - default: return NULL; + case CH_RATE_SDCCH: + return GSM_LCHAN_SDCCH; + case CH_RATE_HALF: + return GSM_LCHAN_TCH_H; + case CH_RATE_FULL: + return GSM_LCHAN_TCH_F; + default: + return GSM_LCHAN_NONE; } - break; case GSM48_CMODE_SPEECH_EFR: - /* EFR works over FR channels only */ + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + /* these rates work over full-rate channels only */ if (chan_rate != CH_RATE_FULL) - return NULL; + return GSM_LCHAN_NONE; /* fall through */ case GSM48_CMODE_SPEECH_V1: case GSM48_CMODE_SPEECH_AMR: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: switch (chan_rate) { - case CH_RATE_HALF: type = GSM_LCHAN_TCH_H; break; - case CH_RATE_FULL: type = GSM_LCHAN_TCH_F; break; - default: return NULL; + case CH_RATE_HALF: + return GSM_LCHAN_TCH_H; + case CH_RATE_FULL: + return GSM_LCHAN_TCH_F; + default: + return GSM_LCHAN_NONE; } - break; default: - return NULL; + return GSM_LCHAN_NONE; + } +} + +static int qsort_func(const void *_a, const void *_b) +{ + const struct gsm_bts_trx *trx_a = *(const struct gsm_bts_trx **)_a; + const struct gsm_bts_trx *trx_b = *(const struct gsm_bts_trx **)_b; + + int pwr_a = trx_a->nominal_power - trx_a->max_power_red; + int pwr_b = trx_b->nominal_power - trx_b->max_power_red; + + /* Sort in descending order */ + return pwr_b - pwr_a; +} + +static void populate_ts_list(struct lchan_select_ts_list *ts_list, + struct gsm_bts *bts, + bool chan_alloc_reverse, + bool sort_by_trx_power, + bool log) +{ + struct gsm_bts_trx **trx_list; + struct gsm_bts_trx *trx; + unsigned int num = 0; + + /* Allocate an array with pointers to all TRX instances of a BTS */ + trx_list = talloc_array_ptrtype(bts, trx_list, bts->num_trx); + OSMO_ASSERT(trx_list != NULL); + + llist_for_each_entry(trx, &bts->trx_list, list) + trx_list[trx->nr] = trx; + + /* Sort by TRX power in descending order (if needed) */ + if (sort_by_trx_power) + qsort(&trx_list[0], bts->num_trx, sizeof(trx), &qsort_func); + + for (unsigned int trxn = 0; trxn < bts->num_trx; trxn++) { + trx = trx_list[trxn]; + for (unsigned int tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + if (ts_is_usable(ts)) + ts_list->list[num++] = ts; + else if (log) + LOGP(DRLL, LOGL_DEBUG, "%s is not usable\n", gsm_ts_name(ts)); + } } - return lchan_select_by_type(bts, type); + talloc_free(trx_list); + ts_list->num = num; + + /* Reverse the timeslot list if required */ + if (chan_alloc_reverse) { + for (unsigned int tn = 0; tn < num / 2; tn++) { + struct gsm_bts_trx_ts *temp = ts_list->list[tn]; + ts_list->list[tn] = ts_list->list[num - tn - 1]; + ts_list->list[num - tn - 1] = temp; + } + } +} + +static bool chan_alloc_ass_dynamic_reverse(struct gsm_bts *bts, + void *ctx, bool log) +{ + const struct load_counter *ll = &bts->c0->lchan_load; + const struct gsm_lchan *old_lchan = ctx; + unsigned int lchan_load; + int avg_ul_rxlev; + + OSMO_ASSERT(old_lchan != NULL); + OSMO_ASSERT(old_lchan->ts->trx->bts == bts); + +#define LOG_COND(fmt, args...) do { \ + if (log) \ + LOG_LCHAN(old_lchan, LOGL_DEBUG, fmt, ## args); \ + } while (0) + + /* Condition a) Channel load on the C0 (BCCH carrier) */ + lchan_load = ll->total ? ll->used * 100 / ll->total : 0; + if (lchan_load < bts->chan_alloc_dyn_params.c0_chan_load_thresh) { + LOG_COND("C0 Channel Load %u%% < thresh %u%% => using ascending order\n", + lchan_load, bts->chan_alloc_dyn_params.c0_chan_load_thresh); + return false; + } + + /* Condition b) average Uplink RxLev */ + avg_ul_rxlev = get_meas_rep_avg(old_lchan, TDMA_MEAS_FIELD_RXLEV, + TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_AUTO, + bts->chan_alloc_dyn_params.ul_rxlev_avg_num); + if (avg_ul_rxlev < 0) { + LOG_COND("Unknown AVG UL RxLev => using ascending order\n"); + return false; + } + if (avg_ul_rxlev < bts->chan_alloc_dyn_params.ul_rxlev_thresh) { + LOG_COND("AVG UL RxLev %u < thresh %u => using ascending order\n", + avg_ul_rxlev, bts->chan_alloc_dyn_params.ul_rxlev_thresh); + return false; + } + + LOG_COND("C0 Channel Load %u%% >= thresh %u%% and " + "AVG UL RxLev %u >= thresh %u => using descending order\n", + lchan_load, bts->chan_alloc_dyn_params.c0_chan_load_thresh, + avg_ul_rxlev, bts->chan_alloc_dyn_params.ul_rxlev_thresh); + +#undef LOG_COND + + return true; +} + +struct gsm_lchan *lchan_select_by_chan_mode(struct gsm_bts *bts, + enum gsm48_chan_mode chan_mode, + enum channel_rate chan_rate, + enum lchan_select_reason reason, + void *ctx) +{ + enum gsm_chan_t type = chan_mode_to_chan_type(chan_mode, chan_rate); + if (type == GSM_LCHAN_NONE) + return NULL; + return lchan_select_by_type(bts, type, reason, ctx); } -struct gsm_lchan *lchan_avail_by_type(struct gsm_bts *bts, enum gsm_chan_t type) +struct gsm_lchan *lchan_avail_by_type(struct gsm_bts *bts, + enum gsm_chan_t type, + enum lchan_select_reason reason, + void *ctx, bool log) { struct gsm_lchan *lchan = NULL; enum gsm_phys_chan_config first, first_cbch, second, second_cbch; + struct lchan_select_ts_list ts_list; + bool sort_by_trx_power = false; + bool chan_alloc_reverse = false; - LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_avail_by_type(%s)\n", gsm_lchant_name(type)); + if (log) { + LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_avail_by_type(type=%s, reason=%s)\n", + gsm_chan_t_name(type), lchan_select_reason_name(reason)); + } + + switch (reason) { + case SELECT_FOR_MS_CHAN_REQ: + chan_alloc_reverse = bts->chan_alloc_chan_req_reverse; + break; + case SELECT_FOR_ASSIGNMENT: + if (bts->chan_alloc_assignment_dynamic) { + chan_alloc_reverse = chan_alloc_ass_dynamic_reverse(bts, ctx, log); + sort_by_trx_power = bts->chan_alloc_dyn_params.sort_by_trx_power; + } else { + chan_alloc_reverse = bts->chan_alloc_assignment_reverse; + } + break; + case SELECT_FOR_HANDOVER: + chan_alloc_reverse = bts->chan_alloc_handover_reverse; + break; + case SELECT_FOR_VGCS: + chan_alloc_reverse = bts->chan_alloc_vgcs_reverse; + break; + } + + /* Allocate an array with pointers to all timeslots of a BTS */ + ts_list.list = talloc_array_ptrtype(bts, ts_list.list, bts->num_trx * 8); + if (OSMO_UNLIKELY(ts_list.list == NULL)) + return NULL; + + /* Populate this array with the actual pointers */ + populate_ts_list(&ts_list, bts, chan_alloc_reverse, sort_by_trx_power, log); switch (type) { case GSM_LCHAN_SDCCH: - if (bts->chan_alloc_reverse) { + if (chan_alloc_reverse) { first = GSM_PCHAN_SDCCH8_SACCH8C; first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH; second = GSM_PCHAN_CCCH_SDCCH4; @@ -183,71 +377,79 @@ struct gsm_lchan *lchan_avail_by_type(struct gsm_bts *bts, enum gsm_chan_t type) second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH; } - lchan = _lc_find_bts(bts, first); + lchan = lc_find(&ts_list, first, log); if (lchan == NULL) - lchan = _lc_find_bts(bts, first_cbch); + lchan = lc_find(&ts_list, first_cbch, log); if (lchan == NULL) - lchan = _lc_find_bts(bts, second); + lchan = lc_find(&ts_list, second, log); if (lchan == NULL) - lchan = _lc_find_bts(bts, second_cbch); + lchan = lc_find(&ts_list, second_cbch, log); + /* No dedicated SDCCH available -- try fully dynamic + * TCH/F_TCH/H_SDCCH8_PDCH if BTS supports it: */ + if (lchan == NULL && osmo_bts_has_feature(&bts->features, BTS_FEAT_DYN_TS_SDCCH8)) + lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN, + GSM_PCHAN_SDCCH8_SACCH8C, log); break; case GSM_LCHAN_TCH_F: - lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + lchan = lc_find(&ts_list, GSM_PCHAN_TCH_F, log); /* If we don't have TCH/F available, try dynamic TCH/F_PDCH */ - if (!lchan) { - lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH, - GSM_PCHAN_TCH_F); - /* TCH/F_PDCH used as TCH/F -- here, type is already - * set to GSM_LCHAN_TCH_F, but for clarity's sake... */ - if (lchan) - type = GSM_LCHAN_TCH_F; - } + if (!lchan) + lchan = lc_dyn_find(&ts_list, GSM_PCHAN_TCH_F_PDCH, + GSM_PCHAN_TCH_F, log); /* Try fully dynamic TCH/F_TCH/H_PDCH as TCH/F... */ - if (!lchan && bts->network->dyn_ts_allow_tch_f) { - lchan = _lc_dyn_find_bts(bts, - GSM_PCHAN_TCH_F_TCH_H_PDCH, - GSM_PCHAN_TCH_F); - if (lchan) - type = GSM_LCHAN_TCH_F; - } + if (!lchan && bts->network->dyn_ts_allow_tch_f) + lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN, + GSM_PCHAN_TCH_F, log); break; case GSM_LCHAN_TCH_H: - lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + lchan = lc_find(&ts_list, GSM_PCHAN_TCH_H, log); /* No dedicated TCH/x available -- try fully dynamic * TCH/F_TCH/H_PDCH */ - if (!lchan) { - lchan = _lc_dyn_find_bts(bts, - GSM_PCHAN_TCH_F_TCH_H_PDCH, - GSM_PCHAN_TCH_H); - if (lchan) - type = GSM_LCHAN_TCH_H; - } + if (!lchan) + lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN, + GSM_PCHAN_TCH_H, log); break; default: LOG_BTS(bts, DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type); } + talloc_free(ts_list.list); + return lchan; } /* Return a matching lchan from a specific BTS that is currently available. The next logical step is * lchan_activate() on it, which would possibly cause dynamic timeslot pchan switching, taken care of by * the lchan and timeslot FSMs. */ -struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts, enum gsm_chan_t type) +struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts, + enum gsm_chan_t type, + enum lchan_select_reason reason, + void *ctx) { struct gsm_lchan *lchan = NULL; - lchan = lchan_avail_by_type(bts, type); + LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_select_by_type(type=%s, reason=%s)\n", + gsm_chan_t_name(type), lchan_select_reason_name(reason)); - LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_select_by_type(%s)\n", gsm_lchant_name(type)); + lchan = lchan_avail_by_type(bts, type, reason, ctx, true); - if (lchan) { - lchan->type = type; - LOG_LCHAN(lchan, LOGL_INFO, "Selected\n"); - } else - LOG_BTS(bts, DRLL, LOGL_NOTICE, "Failed to select %s channel\n", - gsm_lchant_name(type)); + if (!lchan) { + LOG_BTS(bts, DRLL, LOGL_NOTICE, "Failed to select %s channel (%s)\n", + gsm_chan_t_name(type), lchan_select_reason_name(reason)); + return NULL; + } + lchan_select_set_type(lchan, type); return lchan; } + +/* Set available lchan to given type. Usually used on lchan obtained with + * lchan_avail_by_type. The next logical step is lchan_activate() on it, which + * would possibly cause dynamic timeslot pchan switching, taken care of by the + * lchan and timeslot FSMs. */ +void lchan_select_set_type(struct gsm_lchan *lchan, enum gsm_chan_t type) +{ + lchan->type = type; + LOG_LCHAN(lchan, LOGL_INFO, "Selected\n"); +} diff --git a/src/osmo-bsc/lcs_loc_req.c b/src/osmo-bsc/lcs_loc_req.c index ca5c7b93f..bb0c5e273 100644 --- a/src/osmo-bsc/lcs_loc_req.c +++ b/src/osmo-bsc/lcs_loc_req.c @@ -81,7 +81,7 @@ static const struct osmo_tdef_state_timeout lcs_loc_req_fsm_timeouts[32] = { .cause_val = cause, \ }; \ lcs_loc_req_fsm_state_chg(lcs_loc_req->fi, LCS_LOC_REQ_ST_FAILED); \ - } while(0) + } while (0) static struct lcs_loc_req *lcs_loc_req_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term) { @@ -104,13 +104,13 @@ static bool parse_bssmap_perf_loc_req(struct lcs_loc_req *lcs_loc_req, struct ms { struct tlv_parsed tp_arr[1]; struct tlv_parsed *tp = &tp_arr[0]; - struct tlv_p_entry *e; + const struct tlv_p_entry *e; int payload_length; #define PARSE_ERR(ERRMSG) do { \ lcs_loc_req_fail(LCS_CAUSE_PROTOCOL_ERROR, "rx BSSMAP Perform Location Request: " ERRMSG); \ return false; \ - } while(0) + } while (0) payload_length = msg->tail - msg->l4h; if (tlv_parse2(tp_arr, 1, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0) <= 0) @@ -127,6 +127,29 @@ static bool parse_bssmap_perf_loc_req(struct lcs_loc_req *lcs_loc_req, struct ms lcs_loc_req->req.cell_id_present = true; } + /* 3GPP TS 49.031, section 10.14 (C) "LCS Client Type" */ + if (TLVP_PRES_LEN(tp, GSM0808_IE_LCS_CLIENT_TYPE, 1)) { + lcs_loc_req->req.client_type = *TLVP_VAL(tp, GSM0808_IE_LCS_CLIENT_TYPE); + lcs_loc_req->req.client_type_present = true; + } else if (lcs_loc_req->req.location_type.location_information == BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC) + PARSE_ERR("Missing LCS Client Type IE"); + + /* 3GPP TS 49.031, section 10.15 (O) "LCS Priority" */ + if (TLVP_PRES_LEN(tp, GSM0808_IE_LCS_PRIORITY, 1)) { + lcs_loc_req->req.priority = *TLVP_VAL(tp, GSM0808_IE_LCS_PRIORITY); + lcs_loc_req->req.priority_present = true; + } + + /* 3GPP TS 49.031, section 10.16 (C) "LCS QoS" */ + if (TLVP_PRES_LEN(tp, GSM0808_IE_LCS_QOS, sizeof(lcs_loc_req->req.qos))) { + size_t qos_len = TLVP_LEN(tp, GSM0808_IE_LCS_QOS); + if (qos_len > sizeof(lcs_loc_req->req.qos)) + qos_len = sizeof(lcs_loc_req->req.qos); + memcpy(&lcs_loc_req->req.qos, TLVP_VAL(tp, GSM0808_IE_LCS_QOS), qos_len); + lcs_loc_req->req.qos_present = true; + } else if (lcs_loc_req->req.location_type.location_information == BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC) + PARSE_ERR("Missing LCS QoS IE"); + if ((e = TLVP_GET(tp, GSM0808_IE_IMSI))) { if (osmo_mobile_identity_decode(&lcs_loc_req->req.imsi, e->val, e->len, false) || lcs_loc_req->req.imsi.type != GSM_MI_TYPE_IMSI) @@ -139,8 +162,6 @@ static bool parse_bssmap_perf_loc_req(struct lcs_loc_req *lcs_loc_req, struct ms PARSE_ERR("Failed to parse IMEI IE"); } - // FIXME LCS QoS IE is mandatory for requesting the location - /* A lot of IEs remain ignored... */ return true; @@ -190,7 +211,7 @@ static int handle_bssmap_le_conn_oriented_info(struct lcs_loc_req *lcs_loc_req, { switch (bssmap_le->conn_oriented_info.apdu.msg_type) { case BSSLAP_MSGT_TA_REQUEST: - rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_REQUEST]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_REQUEST)); LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG, "rx BSSLAP TA Request\n"); /* The TA Request message contains only the message type. */ return lcs_ta_req_start(lcs_loc_req); @@ -205,8 +226,8 @@ int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb { struct lcs_loc_req *lcs_loc_req = conn->lcs.loc_req; struct bssap_le_pdu bssap_le; - struct osmo_bssap_le_err *err; - struct rate_ctr *ctr = bsc_gsmnet->smlc->ctrs->ctr; + struct osmo_bssap_le_err *err = NULL; + struct rate_ctr_group *ctrg = bsc_gsmnet->smlc->ctrs; if (!lcs_loc_req) { LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, @@ -216,13 +237,13 @@ int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb if (osmo_bssap_le_dec(&bssap_le, &err, msg, msg)) { LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSAP-LE message with error: %s\n", err->logmsg); - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG)); return -EINVAL; } if (bssap_le.discr != BSSAP_LE_MSG_DISCR_BSSMAP_LE) { LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSAP-LE: discr %d not implemented\n", bssap_le.discr); - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG)); return -ENOTSUP; } @@ -231,9 +252,9 @@ int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb switch (bssap_le.bssmap_le.msg_type) { case BSSMAP_LE_MSGT_PERFORM_LOC_RESP: if (bssap_le.bssmap_le.perform_loc_resp.location_estimate_present) - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS)); else - rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]); + rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE)); return osmo_fsm_inst_dispatch(lcs_loc_req->fi, LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE, &bssap_le.bssmap_le); @@ -300,6 +321,17 @@ static void lcs_loc_req_wait_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t .cell_id = lcs_loc_req->req.cell_id, .imsi = lcs_loc_req->req.imsi, .imei = lcs_loc_req->req.imei, + + .lcs_client_type_present = lcs_loc_req->req.client_type_present, + .lcs_client_type = lcs_loc_req->req.client_type, + + .more_items = true, + + .lcs_priority_present = lcs_loc_req->req.priority_present, + .lcs_priority = lcs_loc_req->req.priority, + + .lcs_qos_present = lcs_loc_req->req.qos_present, + .lcs_qos = lcs_loc_req->req.qos, }, }, }; @@ -313,7 +345,7 @@ static void lcs_loc_req_wait_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t plr.bssmap_le.perform_loc_req.apdu = (struct bsslap_pdu){ .msg_type = BSSLAP_MSGT_TA_LAYER3, .ta_layer3 = { - .ta = lchan->rqd_ta, + .ta = lchan->last_ta, }, }; } else { @@ -365,11 +397,14 @@ static void lcs_loc_req_handover_performed(struct lcs_loc_req *lcs_loc_req) .msg_type = BSSLAP_MSGT_RESET, .reset = { .cell_id = lchan->ts->trx->bts->cell_identity, - .ta = lchan->rqd_ta, + .ta = lchan->last_ta, .cause = BSSLAP_CAUSE_INTRA_BSS_HO, }, }; - gsm48_lchan2chan_desc(&apdu->reset.chan_desc, lchan); + if (gsm48_lchan2chan_desc(&apdu->reset.chan_desc, lchan, lchan->tsc, false)) { + lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Error encoding Channel Number"); + return; + } } lcs_loc_req_send(lcs_loc_req, &bsslap); @@ -457,9 +492,7 @@ static void lcs_loc_req_got_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Failed to send Perform Location Response (A-interface)\n"); else - rate_ctr_inc(&lcs_loc_req->conn->sccp.msc->msc_ctrs->ctr[ - plr.location_estimate_present ? MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS - : MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]); + rate_ctr_inc(rate_ctr_group_get_ctr(lcs_loc_req->conn->sccp.msc->msc_ctrs, plr.location_estimate_present ? MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS : MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE)); } osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } @@ -481,7 +514,8 @@ static void lcs_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_s }; /* If we're paging this subscriber for LCS, stop paging. */ - paging_request_cancel(lcs_loc_req->conn->bsub, BSC_PAGING_FOR_LCS); + if (lcs_loc_req->conn->bsub) + paging_request_cancel(lcs_loc_req->conn->bsub, BSC_PAGING_FOR_LCS); /* Send Perform Location Abort to SMLC, only if we got started on the Lb */ if (lcs_loc_req->conn->lcs.lb.state == SUBSCR_SCCP_ST_CONNECTED) @@ -498,7 +532,7 @@ static void lcs_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_s LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Failed to send BSSMAP Perform Location Response (A-interface)\n"); else - rate_ctr_inc(&lcs_loc_req->conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]); + rate_ctr_inc(rate_ctr_group_get_ctr(lcs_loc_req->conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE)); } osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } diff --git a/src/osmo-bsc/lcs_ta_req.c b/src/osmo-bsc/lcs_ta_req.c index 97d6eb5c8..a1fa87301 100644 --- a/src/osmo-bsc/lcs_ta_req.c +++ b/src/osmo-bsc/lcs_ta_req.c @@ -56,14 +56,14 @@ static const struct osmo_tdef_state_timeout lcs_ta_req_fsm_timeouts[32] = { osmo_tdef_fsm_inst_state_chg(FI, STATE, \ lcs_ta_req_fsm_timeouts, \ (bsc_gsmnet)->T_defs, \ - 5) + -1) #define lcs_ta_req_fail(cause, fmt, args...) do { \ LOG_LCS_TA_REQ(lcs_ta_req, LOGL_ERROR, "BSSLAP TA Request failed in state %s: " fmt "\n", \ lcs_ta_req ? osmo_fsm_inst_state_name(lcs_ta_req->fi) : "NULL", ## args); \ lcs_ta_req->failure_cause = cause; \ lcs_ta_req_fsm_state_chg(lcs_ta_req->fi, LCS_TA_REQ_ST_FAILED); \ - } while(0) + } while (0) static struct osmo_fsm lcs_ta_req_fsm; @@ -89,7 +89,7 @@ int lcs_ta_req_start(struct lcs_loc_req *lcs_loc_req) struct lcs_ta_req *lcs_ta_req; if (lcs_loc_req->ta_req) { LOG_LCS_TA_REQ(lcs_loc_req->ta_req, LOGL_ERROR, - "Cannot start anoter TA Request FSM, this TA Request is still active\n"); + "Cannot start another TA Request FSM, this TA Request is still active\n"); return -ENOTSUP; } lcs_ta_req = lcs_ta_req_alloc(lcs_loc_req->fi, LCS_LOC_REQ_EV_TA_REQ_END); @@ -122,17 +122,6 @@ void lcs_ta_req_wait_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) return; } - paging = (struct bsc_paging_params){ - .reason = BSC_PAGING_FOR_LCS, - .msc = loc_req->conn->sccp.msc, - .bsub = loc_req->conn->bsub, - .tmsi = GSM_RESERVED_TMSI, - .imsi = loc_req->req.imsi, - .chan_needed = RSL_CHANNEED_ANY, - }; - if (paging.bsub) - bsc_subscr_get(paging.bsub, BSUB_USE_PAGING_START); - /* Do we already have an active lchan with knowledge of TA? */ lchan = loc_req->conn->lchan; if (lchan) { @@ -147,6 +136,17 @@ void lcs_ta_req_wait_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) return; } + paging = (struct bsc_paging_params){ + .reason = BSC_PAGING_FOR_LCS, + .msc = loc_req->conn->sccp.msc, + .bsub = loc_req->conn->bsub, + .tmsi = GSM_RESERVED_TMSI, + .imsi = loc_req->req.imsi, + .chan_needed = RSL_CHANNEED_ANY, + }; + if (paging.bsub) + bsc_subscr_get(paging.bsub, BSUB_USE_PAGING_START); + if (!loc_req->req.cell_id_present) { LOG_LCS_TA_REQ(lcs_ta_req, LOGL_DEBUG, "No Cell Identity in BSSMAP Location Request, paging entire BSS\n"); @@ -210,7 +210,7 @@ void lcs_ta_req_got_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) .msg_type = BSSLAP_MSGT_TA_RESPONSE, .ta_response = { .cell_id = lchan->ts->trx->bts->cell_identity, - .ta = lchan->rqd_ta, + .ta = lchan->last_ta, }, }, }, diff --git a/src/osmo-bsc/meas_feed.c b/src/osmo-bsc/meas_feed.c index 889f6efc3..b18478ff4 100644 --- a/src/osmo-bsc/meas_feed.c +++ b/src/osmo-bsc/meas_feed.c @@ -6,7 +6,7 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/socket.h> -#include <osmocom/core/write_queue.h> +#include <osmocom/core/osmo_io.h> #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> @@ -20,15 +20,17 @@ #include <osmocom/bsc/vty.h> #include <osmocom/bsc/debug.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/lchan.h> struct meas_feed_state { - struct osmo_wqueue wqueue; + struct osmo_io_fd *io_fd; char scenario[31+1]; char *dst_host; uint16_t dst_port; + size_t txqueue_max; }; -static struct meas_feed_state g_mfs = {}; +static struct meas_feed_state g_mfs = { .txqueue_max = MEAS_FEED_TXQUEUE_MAX_LEN_DEFAULT }; static int process_meas_rep(struct gsm_meas_rep *mr) { @@ -36,6 +38,8 @@ static int process_meas_rep(struct gsm_meas_rep *mr) struct meas_feed_meas *mfm; struct bsc_subscr *bsub; + OSMO_ASSERT(g_mfs.io_fd != NULL); + /* ignore measurements as long as we don't know who it is */ if (!mr->lchan) { LOGP(DMEAS, LOGL_DEBUG, "meas_feed: no lchan, not sending report\n"); @@ -48,7 +52,7 @@ static int process_meas_rep(struct gsm_meas_rep *mr) bsub = mr->lchan->conn->bsub; - msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed"); + msg = msgb_alloc(sizeof(struct meas_feed_meas), "meas_feed_msg"); if (!msg) return 0; @@ -83,7 +87,7 @@ static int process_meas_rep(struct gsm_meas_rep *mr) mfm->ss_nr = mr->lchan->nr; /* and send it to the socket */ - if (osmo_wqueue_enqueue(&g_mfs.wqueue, msg) != 0) { + if (osmo_iofd_write_msgb(g_mfs.io_fd, msg)) { LOGP(DMEAS, LOGL_ERROR, "meas_feed %s: sending measurement report failed\n", gsm_lchan_name(mr->lchan)); msgb_free(msg); @@ -108,64 +112,54 @@ static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal, return 0; } -static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg) +static void meas_feed_close(void) { - return write(ofd->fd, msgb_data(msg), msgb_length(msg)); + if (g_mfs.io_fd == NULL) + return; + osmo_signal_unregister_handler(SS_LCHAN, meas_feed_sig_cb, NULL); + osmo_iofd_close(g_mfs.io_fd); + osmo_iofd_free(g_mfs.io_fd); + g_mfs.io_fd = NULL; } -static int feed_read_cb(struct osmo_fd *ofd) +static void meas_feed_noop_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) { - int rc; - char buf[256]; - - rc = read(ofd->fd, buf, sizeof(buf)); - ofd->fd &= ~OSMO_FD_READ; - - return rc; } int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port) { int rc; - int already_initialized = 0; - - if (g_mfs.wqueue.bfd.fd) - already_initialized = 1; - - - if (already_initialized && - !strcmp(dst_host, g_mfs.dst_host) && - dst_port == g_mfs.dst_port) - return 0; - - if (!already_initialized) { - osmo_wqueue_init(&g_mfs.wqueue, 10); - g_mfs.wqueue.write_cb = feed_write_cb; - g_mfs.wqueue.read_cb = feed_read_cb; - osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL); - LOGP(DMEAS, LOGL_DEBUG, "meas_feed: registered signal callback\n"); + /* osmo_io code throws an error if 'write_cb' is NULL, so we set a no-op */ + struct osmo_io_ops meas_feed_oio = { + .read_cb = NULL, + .write_cb = meas_feed_noop_cb, + .segmentation_cb = NULL + }; + /* Already initialized */ + if (g_mfs.io_fd != NULL) { + /* No change needed, do nothing */ + if (!strcmp(dst_host, g_mfs.dst_host) && dst_port == g_mfs.dst_port) + return 0; + meas_feed_close(); } - if (already_initialized) { - osmo_wqueue_clear(&g_mfs.wqueue); - osmo_fd_unregister(&g_mfs.wqueue.bfd); - close(g_mfs.wqueue.bfd.fd); - /* don't set to zero, as that would mean 'not yet initialized' */ - g_mfs.wqueue.bfd.fd = -1; + rc = osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, dst_host, dst_port, OSMO_SOCK_F_CONNECT); + if (rc < 0) { + osmo_signal_unregister_handler(SS_LCHAN, meas_feed_sig_cb, NULL); + return rc; } - rc = osmo_sock_init_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM, - IPPROTO_UDP, dst_host, dst_port, - OSMO_SOCK_F_CONNECT); - if (rc < 0) + g_mfs.io_fd = osmo_iofd_setup(NULL, rc, "meas_iofd", OSMO_IO_FD_MODE_READ_WRITE, &meas_feed_oio, NULL); + if (!g_mfs.io_fd) + return -1; + osmo_iofd_set_txqueue_max_length(g_mfs.io_fd, g_mfs.txqueue_max); + if ((rc = osmo_iofd_register(g_mfs.io_fd, rc))) return rc; - g_mfs.wqueue.bfd.when &= ~OSMO_FD_READ; - - if (g_mfs.dst_host) - talloc_free(g_mfs.dst_host); - g_mfs.dst_host = talloc_strdup(NULL, dst_host); + osmo_talloc_replace_string(NULL, &g_mfs.dst_host, dst_host); g_mfs.dst_port = dst_port; - + osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL); + LOGP(DMEAS, LOGL_DEBUG, "meas_feed: started %s\n", + osmo_sock_get_name2(osmo_iofd_get_fd(g_mfs.io_fd))); return 0; } @@ -175,6 +169,18 @@ void meas_feed_cfg_get(char **host, uint16_t *port) *host = g_mfs.dst_host; } +void meas_feed_txqueue_max_length_set(unsigned int max_length) +{ + g_mfs.txqueue_max = max_length; + if (g_mfs.io_fd) + osmo_iofd_set_txqueue_max_length(g_mfs.io_fd, max_length); +} + +unsigned int meas_feed_txqueue_max_length_get(void) +{ + return g_mfs.txqueue_max; +} + void meas_feed_scenario_set(const char *name) { osmo_strlcpy(g_mfs.scenario, name, sizeof(g_mfs.scenario)); diff --git a/src/osmo-bsc/meas_rep.c b/src/osmo-bsc/meas_rep.c index 73d9a1f21..776c610df 100644 --- a/src/osmo-bsc/meas_rep.c +++ b/src/osmo-bsc/meas_rep.c @@ -31,11 +31,15 @@ static int get_field(const struct gsm_meas_rep *rep, case MEAS_REP_DL_RXLEV_FULL: if (!(rep->flags & MEAS_REP_F_DL_VALID)) return -EINVAL; - return rep->dl.full.rx_lev; + /* Add BS Power value to rxlev: improve the RXLEV value by the amount of power that the BTS is reducing + * transmission. Note that bs_power is coded as dB, a positive value indicating the amount of power reduction + * on the downlink; rxlev is coded in dB, where a higher number means stronger signal. */ + return rep->dl.full.rx_lev + rep->bs_power_db; case MEAS_REP_DL_RXLEV_SUB: if (!(rep->flags & MEAS_REP_F_DL_VALID)) return -EINVAL; - return rep->dl.sub.rx_lev; + /* Apply BS Power as explained above */ + return rep->dl.sub.rx_lev + rep->bs_power_db; case MEAS_REP_DL_RXQUAL_FULL: if (!(rep->flags & MEAS_REP_F_DL_VALID)) return -EINVAL; @@ -76,9 +80,53 @@ unsigned int calc_initial_idx(unsigned int array_size, return idx; } -/* obtain an average over the last 'num' fields in the meas reps */ +static inline enum meas_rep_field choose_meas_rep_field(enum tdma_meas_field field, enum tdma_meas_dir dir, + enum tdma_meas_set set, const struct gsm_meas_rep *meas_rep) +{ + if (set == TDMA_MEAS_SET_AUTO) { + bool dtx_in_use; + dtx_in_use = (meas_rep->flags & ((dir == TDMA_MEAS_DIR_UL) ? MEAS_REP_F_UL_DTX : MEAS_REP_F_DL_DTX)); + set = (dtx_in_use ? TDMA_MEAS_SET_SUB : TDMA_MEAS_SET_FULL); + } + + osmo_static_assert(TDMA_MEAS_FIELD_RXLEV >= 0 && TDMA_MEAS_FIELD_RXLEV <= 1 + && TDMA_MEAS_FIELD_RXQUAL >= 0 && TDMA_MEAS_FIELD_RXQUAL <= 1 + && TDMA_MEAS_DIR_UL >= 0 && TDMA_MEAS_DIR_UL <= 1 + && TDMA_MEAS_DIR_DL >= 0 && TDMA_MEAS_DIR_DL <= 1 + && TDMA_MEAS_SET_FULL >= 0 && TDMA_MEAS_SET_FULL <= 1 + && TDMA_MEAS_SET_SUB >= 0 && TDMA_MEAS_SET_SUB <= 1, + choose_meas_rep_field__mux_macro_input_ranges); +#define MUX(FIELD, DIR, SET) ((FIELD) + ((DIR) << 1) + ((SET) << 2)) + + switch (MUX(field, dir, set)) { + case MUX(TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_FULL): + return MEAS_REP_UL_RXLEV_FULL; + case MUX(TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_SUB): + return MEAS_REP_UL_RXLEV_SUB; + case MUX(TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_FULL): + return MEAS_REP_DL_RXLEV_FULL; + case MUX(TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_SUB): + return MEAS_REP_DL_RXLEV_SUB; + case MUX(TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_FULL): + return MEAS_REP_UL_RXQUAL_FULL; + case MUX(TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_SUB): + return MEAS_REP_UL_RXQUAL_SUB; + case MUX(TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_FULL): + return MEAS_REP_DL_RXQUAL_FULL; + case MUX(TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_SUB): + return MEAS_REP_DL_RXQUAL_SUB; + default: + OSMO_ASSERT(false); + } + +#undef MUX +} + +/* obtain an average over the last 'num' fields in the meas reps. For 'field', pass either DL_RXLEV or DL_RXQUAL, and + * by tdma_meas_set, choose between full, subset or automatic choice of set. */ int get_meas_rep_avg(const struct gsm_lchan *lchan, - enum meas_rep_field field, unsigned int num) + enum tdma_meas_field field, enum tdma_meas_dir dir, enum tdma_meas_set set, + unsigned int num) { unsigned int i, idx; int avg = 0, valid_num = 0; @@ -94,7 +142,11 @@ int get_meas_rep_avg(const struct gsm_lchan *lchan, for (i = 0; i < num; i++) { int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep); - int val = get_field(&lchan->meas_rep[j], field); + enum meas_rep_field use_field; + int val; + + use_field = choose_meas_rep_field(field, dir, set, &lchan->meas_rep[j]); + val = get_field(&lchan->meas_rep[j], use_field); if (val >= 0) { avg += val; @@ -110,8 +162,8 @@ int get_meas_rep_avg(const struct gsm_lchan *lchan, /* Check if N out of M last values for FIELD are >= bd */ int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan, - enum meas_rep_field field, - unsigned int n, unsigned int m, int be) + enum tdma_meas_field field, enum tdma_meas_dir dir, enum tdma_meas_set set, + unsigned int n, unsigned int m, int be) { unsigned int i, idx; int count = 0; @@ -121,7 +173,11 @@ int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan, for (i = 0; i < m; i++) { int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep); - int val = get_field(&lchan->meas_rep[j], field); + enum meas_rep_field use_field; + int val; + + use_field = choose_meas_rep_field(field, dir, set, &lchan->meas_rep[j]); + val = get_field(&lchan->meas_rep[j], use_field); if (val >= be) /* implies that val < 0 will not count */ count++; @@ -132,3 +188,10 @@ int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan, return 0; } + +const struct value_string tdma_meas_set_names[] = { + { TDMA_MEAS_SET_FULL, "full" }, + { TDMA_MEAS_SET_SUB, "subset" }, + { TDMA_MEAS_SET_AUTO, "auto" }, + {} +}; diff --git a/src/osmo-bsc/neighbor_ident.c b/src/osmo-bsc/neighbor_ident.c index 4a0cd47ad..3e42c5f2d 100644 --- a/src/osmo-bsc/neighbor_ident.c +++ b/src/osmo-bsc/neighbor_ident.c @@ -33,88 +33,247 @@ #include <osmocom/bsc/neighbor_ident.h> -struct neighbor_ident_list { - struct llist_head list; -}; +#include <osmocom/ctrl/control_cmd.h> +#include <osmocom/ctrl/control_if.h> -struct neighbor_ident { - struct llist_head entry; +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/debug.h> - struct neighbor_ident_key key; - struct gsm0808_cell_id_list2 val; -}; +void bts_cell_ab(struct cell_ab *arfcn_bsic, const struct gsm_bts *bts) +{ + *arfcn_bsic = (struct cell_ab){ + .arfcn = bts->c0->arfcn, + .bsic = bts->bsic, + }; +} + +/* Find the local gsm_bts pointer that a specific other BTS' neighbor config refers to. Return NULL if there is no such + * local cell in this BSS. + */ +int resolve_local_neighbor(struct gsm_bts **local_neighbor_p, const struct gsm_bts *from_bts, + const struct neighbor *neighbor) +{ + struct gsm_bts *bts; + struct gsm_bts *bts_exact = NULL; + struct gsm_bts *bts_wildcard = NULL; + *local_neighbor_p = NULL; + + switch (neighbor->type) { + case NEIGHBOR_TYPE_BTS_NR: + bts = gsm_bts_num(bsc_gsmnet, neighbor->bts_nr); + goto check_bts; + + case NEIGHBOR_TYPE_CELL_ID: + /* Find cell id below */ + break; + + default: + return -ENOTSUP; + } + + /* NEIGHBOR_TYPE_CELL_ID */ + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + struct gsm0808_cell_id cell_id; + gsm_bts_cell_id(&cell_id, bts); + + if (gsm0808_cell_ids_match(&cell_id, &neighbor->cell_id.id, true)) { + if (bts_exact) { + LOGP(DHO, LOGL_ERROR, + "Neighbor config error: Multiple BTS match %s (BTS %u and BTS %u)\n", + gsm0808_cell_id_name_c(OTC_SELECT, &neighbor->cell_id.id), + bts_exact->nr, bts->nr); + return -EINVAL; + } else { + bts_exact = bts; + } + } + + if (!bts_wildcard && gsm0808_cell_ids_match(&cell_id, &neighbor->cell_id.id, false)) + bts_wildcard = bts; + } -#define APPEND_THING(func, args...) do { \ - int remain = buflen - (pos - buf); \ - int l = func(pos, remain, ##args); \ - if (l < 0 || l > remain) \ - pos = buf + buflen; \ - else \ - pos += l; \ - } while(0) -#define APPEND_STR(fmt, args...) APPEND_THING(snprintf, fmt, ##args) + bts = (bts_exact ? : bts_wildcard); -const char *_neighbor_ident_key_name(char *buf, size_t buflen, const struct neighbor_ident_key *ni_key) +check_bts: + /* A cell cannot be its own neighbor */ + if (bts == from_bts) { + LOGP(DHO, LOGL_ERROR, + "Neighbor config error: BTS %u -> %s: this cell is configured as its own neighbor\n", + from_bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor)); + return -EINVAL; + } + + if (!bts) + return -ENOENT; + + /* Double check whether ARFCN + BSIC config matches, if present. */ + if (neighbor->cell_id.ab_present) { + struct cell_ab cell_ab; + bts_cell_ab(&cell_ab, bts); + if (!cell_ab_match(&cell_ab, &neighbor->cell_id.ab, false)) { + LOGP(DHO, LOGL_ERROR, "Neighbor config error: Local BTS %d matches %s, but not ARFCN+BSIC %s\n", + bts->nr, gsm0808_cell_id_name_c(OTC_SELECT, &neighbor->cell_id.id), + cell_ab_to_str_c(OTC_SELECT, &cell_ab)); + return -EINVAL; + } + } + + *local_neighbor_p = bts; + return 0; +} + +int resolve_neighbors(struct gsm_bts **local_neighbor_p, struct gsm0808_cell_id_list2 *remote_neighbors, + struct gsm_bts *from_bts, const struct cell_ab *target_ab, bool log_errors) { - char *pos = buf; + struct neighbor *n; + struct gsm_bts *local_neighbor = NULL; + struct gsm0808_cell_id_list2 remotes = {}; + + if (local_neighbor_p) + *local_neighbor_p = NULL; + if (remote_neighbors) + *remote_neighbors = (struct gsm0808_cell_id_list2){ 0 }; + + llist_for_each_entry(n, &from_bts->neighbors, entry) { + struct gsm_bts *neigh_bts; + if (resolve_local_neighbor(&neigh_bts, from_bts, n) == 0) { + /* This neighbor entry is a local cell neighbor. Do ARFCN and BSIC match? */ + struct cell_ab ab; + bts_cell_ab(&ab, neigh_bts); + if (!cell_ab_match(&ab, target_ab, false)) + continue; + + /* Found a local cell neighbor that matches the target_ab */ + + /* If we already found one, these are ambiguous local neighbors */ + if (local_neighbor) { + if (log_errors) + LOGP(DHO, LOGL_ERROR, "Neighbor config error:" + " Local BTS %d -> %s resolves to local neighbor BTSes %u *and* %u\n", + from_bts->nr, cell_ab_to_str_c(OTC_SELECT, target_ab), local_neighbor->nr, + neigh_bts->nr); + return -ENOTSUP; + } + local_neighbor = neigh_bts; + + } else if (n->type == NEIGHBOR_TYPE_CELL_ID && n->cell_id.ab_present) { + /* This neighbor entry is a remote-BSS neighbor. There may be multiple remote neighbors, + * collect those in a gsm0808_cell_id_list2 (remote_target_cells). A limitation is that all of + * them need to be of the same cell id type. */ + struct gsm0808_cell_id_list2 add_item; + int rc; + + if (!cell_ab_match(&n->cell_id.ab, target_ab, false)) + continue; + + /* Convert the gsm0808_cell_id to a list, so that we can use gsm0808_cell_id_list_add(). */ + gsm0808_cell_id_to_list(&add_item, &n->cell_id.id); + rc = gsm0808_cell_id_list_add(&remotes, &add_item); + if (rc < 0) { + if (log_errors) + LOGP(DHO, LOGL_ERROR, "Neighbor config error:" + " Local BTS %d -> %s resolves to remote-BSS neighbor %s;" + " Could not store this in neighbors list %s\n", + from_bts->nr, cell_ab_to_str_c(OTC_SELECT, target_ab), + gsm0808_cell_id_name_c(OTC_SELECT, &n->cell_id.id), + gsm0808_cell_id_list_name_c(OTC_SELECT, &remotes)); + return rc; + } + } + /* else: neighbor entry that does not resolve to anything. */ + } - APPEND_STR("BTS "); - if (ni_key->from_bts == NEIGHBOR_IDENT_KEY_ANY_BTS) - APPEND_STR("*"); - else if (ni_key->from_bts >= 0 && ni_key->from_bts <= 255) - APPEND_STR("%d", ni_key->from_bts); - else - APPEND_STR("invalid(%d)", ni_key->from_bts); + if (local_neighbor_p) + *local_neighbor_p = local_neighbor; + if (remote_neighbors) + *remote_neighbors = remotes; - APPEND_STR(" to "); - if (ni_key->bsic == BSIC_ANY) - APPEND_STR("ARFCN %u (any BSIC)", ni_key->arfcn); - else - APPEND_STR("ARFCN %u BSIC %u", ni_key->arfcn, ni_key->bsic & 0x3f); - return buf; + if (!local_neighbor && !remotes.id_list_len) + return -ENOENT; + return 0; } -const char *neighbor_ident_key_name(const struct neighbor_ident_key *ni_key) +int cell_ab_to_str_buf(char *buf, size_t buflen, const struct cell_ab *cell) { - static char buf[64]; - return _neighbor_ident_key_name(buf, sizeof(buf), ni_key); + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + OSMO_STRBUF_PRINTF(sb, "ARFCN-BSIC:%u", cell->arfcn); + if (cell->bsic == BSIC_ANY) + OSMO_STRBUF_PRINTF(sb, "-any"); + else { + OSMO_STRBUF_PRINTF(sb, "-%u", cell->bsic); + if (cell->bsic > 0x3f) + OSMO_STRBUF_PRINTF(sb, "[ERANGE>63]"); + } + return sb.chars_needed; +} + +char *cell_ab_to_str_c(void *ctx, const struct cell_ab *cell) +{ + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", cell_ab_to_str_buf, cell) +} + +int neighbor_to_str_buf(char *buf, size_t buflen, const struct neighbor *n) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + switch (n->type) { + case NEIGHBOR_TYPE_BTS_NR: + OSMO_STRBUF_PRINTF(sb, "BTS %u", n->bts_nr); + break; + case NEIGHBOR_TYPE_CELL_ID: + OSMO_STRBUF_APPEND_NOLEN(sb, gsm0808_cell_id_name_buf, &n->cell_id.id); + if (n->cell_id.ab_present) { + OSMO_STRBUF_PRINTF(sb, " "); + OSMO_STRBUF_APPEND(sb, cell_ab_to_str_buf, &n->cell_id.ab); + } + break; + case NEIGHBOR_TYPE_UNSET: + OSMO_STRBUF_PRINTF(sb, "UNSET"); + break; + default: + OSMO_STRBUF_PRINTF(sb, "INVALID"); + break; + } + return sb.chars_needed; } -struct neighbor_ident_list *neighbor_ident_init(void *talloc_ctx) +char *neighbor_to_str_c(void *ctx, const struct neighbor *n) { - struct neighbor_ident_list *nil = talloc_zero(talloc_ctx, struct neighbor_ident_list); - OSMO_ASSERT(nil); - INIT_LLIST_HEAD(&nil->list); - return nil; + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", neighbor_to_str_buf, n); } -void neighbor_ident_free(struct neighbor_ident_list *nil) +bool neighbor_same(const struct neighbor *a, const struct neighbor *b, bool check_cell_ab) { - if (!nil) - return; - talloc_free(nil); + if (a == b) + return true; + if (a->type != b->type) + return false; + + switch (a->type) { + case NEIGHBOR_TYPE_BTS_NR: + return a->bts_nr == b->bts_nr; + + case NEIGHBOR_TYPE_CELL_ID: + if (check_cell_ab + && (a->cell_id.ab_present != b->cell_id.ab_present + || !cell_ab_match(&a->cell_id.ab, &b->cell_id.ab, true))) + return false; + return gsm0808_cell_ids_match(&a->cell_id.id, &b->cell_id.id, true); + default: + return a->type == b->type; + } } /* Return true when the entry matches the search_for requirements. * If exact_match is false, a BSIC_ANY entry acts as wildcard to match any search_for on that ARFCN, - * and a BSIC_ANY in search_for likewise returns any one entry that matches the ARFCN; - * also a from_bts == NEIGHBOR_IDENT_KEY_ANY_BTS in either entry or search_for will match. - * If exact_match is true, only identical bsic values and identical from_bts values return a match. + * and a BSIC_ANY in search_for likewise returns any one entry that matches the ARFCN. + * If exact_match is true, only identical bsic values return a match. * Note, typically wildcard BSICs are only in entry, e.g. the user configured list, and search_for * contains a specific BSIC, e.g. as received from a Measurement Report. */ -bool neighbor_ident_key_match(const struct neighbor_ident_key *entry, - const struct neighbor_ident_key *search_for, - bool exact_match) +bool cell_ab_match(const struct cell_ab *entry, + const struct cell_ab *search_for, + bool exact_match) { - if (exact_match - && entry->from_bts != search_for->from_bts) - return false; - - if (search_for->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS - && entry->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS - && entry->from_bts != search_for->from_bts) - return false; - if (entry->arfcn != search_for->arfcn) return false; @@ -127,129 +286,210 @@ bool neighbor_ident_key_match(const struct neighbor_ident_key *entry, return entry->bsic == search_for->bsic; } -static struct neighbor_ident *_neighbor_ident_get(const struct neighbor_ident_list *nil, - const struct neighbor_ident_key *key, - bool exact_match) +bool cell_ab_valid(const struct cell_ab *cell) { - struct neighbor_ident *ni; - struct neighbor_ident *wildcard_match = NULL; + if (cell->bsic != BSIC_ANY && cell->bsic > 0x3f) + return false; + return true; +} - /* Do both exact-bsic and wildcard matching in the same iteration: - * Any exact match returns immediately, while for a wildcard match we still go through all - * remaining items in case an exact match exists. */ - llist_for_each_entry(ni, &nil->list, entry) { - if (neighbor_ident_key_match(&ni->key, key, true)) - return ni; - if (!exact_match) { - if (neighbor_ident_key_match(&ni->key, key, false)) - wildcard_match = ni; +int neighbors_check_cfg(void) +{ + /* A local neighbor can be configured by BTS number, or by a cell ID. A local neighbor can omit the ARFCN+BSIC, + * in which case those are taken from that local BTS config. If a local neighbor has ARFCN+BSIC configured, it + * must match the local cell's configuration. + * + * A remote neighbor must always be a cell ID *and* ARFCN+BSIC. + * + * Hence any cell ID with ARFCN+BSIC where the cell ID is not found among the local cells is a remote-BSS + * neighbor. + */ + struct gsm_bts *bts; + bool ok = true; + + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + struct neighbor *neighbor; + struct gsm_bts *local_neighbor; + llist_for_each_entry(neighbor, &bts->neighbors, entry) { + switch (neighbor->type) { + + case NEIGHBOR_TYPE_BTS_NR: + if (!gsm_bts_num(bsc_gsmnet, neighbor->bts_nr)) { + LOGP(DHO, LOGL_ERROR, "Neighbor Configuration Error:" + " BTS %u -> BTS %u: There is no BTS nr %u\n", + bts->nr, neighbor->bts_nr, neighbor->bts_nr); + ok = false; + } + break; + + default: + switch (resolve_local_neighbor(&local_neighbor, bts, neighbor)) { + case 0: + break; + case -ENOENT: + if (!neighbor->cell_id.ab_present) { + LOGP(DHO, LOGL_ERROR, "Neighbor Configuration Error:" + " BTS %u -> %s: There is no such local neighbor\n", + bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor)); + ok = false; + } + break; + default: + /* Error already logged in resolve_local_neighbor() */ + ok = false; + break; + } + break; + } } } - return wildcard_match; -} -static void _neighbor_ident_free(struct neighbor_ident *ni) -{ - llist_del(&ni->entry); - talloc_free(ni); + if (!ok) + return -EINVAL; + return 0; } -bool neighbor_ident_key_valid(const struct neighbor_ident_key *key) +/* Neighbor Resolution CTRL iface */ + +CTRL_CMD_DEFINE_RO(neighbor_resolve_cgi_ps_from_lac_ci, "neighbor_resolve_cgi_ps_from_lac_ci"); + +static int gsm_bts_get_cgi_ps(const struct gsm_bts *bts, struct osmo_cell_global_id_ps *cgi_ps) { - if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS - && (key->from_bts < 0 || key->from_bts > 255)) - return false; + if (bts->gprs.mode == BTS_GPRS_NONE) + return -ENOTSUP; - if (key->bsic != BSIC_ANY && key->bsic > 0x3f) - return false; - return true; + cgi_ps->rai.lac.plmn = bts->network->plmn; + cgi_ps->rai.lac.lac = bts->location_area_code; + cgi_ps->rai.rac = bts->gprs.rac; + cgi_ps->cell_identity = bts->cell_identity; + + return 0; } -/*! Add Cell Identifiers to an ARFCN+BSIC entry. - * Exactly one kind of identifier is allowed per ARFCN+BSIC entry, and any number of entries of that kind - * may be added up to the capacity of gsm0808_cell_id_list2, by one or more calls to this function. To - * replace an existing entry, first call neighbor_ident_del(nil, key). - * \returns number of entries in the resulting identifier list, or negative on error: - * see gsm0808_cell_id_list_add() for the meaning of returned error codes; - * return -ENOMEM when the list is not initialized, -ERANGE when the BSIC value is too large. */ -int neighbor_ident_add(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key, - const struct gsm0808_cell_id_list2 *val) +/* Attempt resolution of cgi_ps from ARFCN+BSIC of neighbor from BTS identified by LAC+CI */ +int neighbor_address_resolution(const struct gsm_network *net, const struct cell_ab *ab, + uint16_t lac, uint16_t cell_id, + struct osmo_cell_global_id_ps *res_cgi_ps) { - struct neighbor_ident *ni; - int rc; + struct gsm_bts *bts_tmp, *bts_found = NULL; + struct osmo_cell_global_id_ps local_cgi_ps; + const struct osmo_cell_global_id_ps *cgi_ps = NULL; + struct gsm_bts *local_neighbor = NULL; + struct gsm0808_cell_id_list2 remote_neighbors = { 0 }; + + llist_for_each_entry(bts_tmp, &net->bts_list, list) { + if (bts_tmp->location_area_code != lac) + continue; + if (bts_tmp->cell_identity != cell_id) + continue; + bts_found = bts_tmp; + break; + } + + if (!bts_found) + goto notfound_err; - if (!nil) - return -ENOMEM; - - if (!neighbor_ident_key_valid(key)) - return -ERANGE; - - ni = _neighbor_ident_get(nil, key, true); - if (!ni) { - ni = talloc_zero(nil, struct neighbor_ident); - OSMO_ASSERT(ni); - *ni = (struct neighbor_ident){ - .key = *key, - .val = *val, - }; - llist_add_tail(&ni->entry, &nil->list); - return ni->val.id_list_len; + LOG_BTS(bts_found, DLINP, LOGL_DEBUG, "Resolving neighbor BTS %u -> %s\n", bts_found->nr, + cell_ab_to_str_c(OTC_SELECT, ab)); + + if (resolve_neighbors(&local_neighbor, &remote_neighbors, bts_found, ab, true)) + goto notfound_err; + + /* resolve_neighbors() returns either a local_neighbor or remote_neighbors. + * Local-BSS neighbor? */ + if (local_neighbor) { + /* Supporting GPRS? */ + if (gsm_bts_get_cgi_ps(local_neighbor, &local_cgi_ps) >= 0) + cgi_ps = &local_cgi_ps; } - rc = gsm0808_cell_id_list_add(&ni->val, val); + /* Remote-BSS neighbor? + * By spec, there can be multiple remote neighbors for a given ARFCN+BSIC, but so far osmo-bsc enforces only a + * single remote neighbor. */ + if (remote_neighbors.id_list_len + && remote_neighbors.id_discr == CELL_IDENT_WHOLE_GLOBAL_PS) { + cgi_ps = &remote_neighbors.id_list[0].global_ps; + } - if (rc < 0) - return rc; + /* No neighbor found */ + if (!cgi_ps) + goto notfound_err; - return ni->val.id_list_len; -} + *res_cgi_ps = *cgi_ps; + return 0; -/*! Find cell identity for given BTS, ARFCN and BSIC, as previously added by neighbor_ident_add(). - */ -const struct gsm0808_cell_id_list2 *neighbor_ident_get(const struct neighbor_ident_list *nil, - const struct neighbor_ident_key *key) -{ - struct neighbor_ident *ni; - if (!nil) - return NULL; - ni = _neighbor_ident_get(nil, key, false); - if (!ni) - return NULL; - return &ni->val; +notfound_err: + return -1; } -bool neighbor_ident_del(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key) +static int get_neighbor_resolve_cgi_ps_from_lac_ci(struct ctrl_cmd *cmd, void *data) { - struct neighbor_ident *ni; - if (!nil) - return false; - ni = _neighbor_ident_get(nil, key, true); - if (!ni) - return false; - _neighbor_ident_free(ni); - return true; + struct gsm_network *net = (struct gsm_network *)data; + char *tmp = NULL, *tok, *saveptr; + struct cell_ab ab; + unsigned int lac, cell_id; + struct osmo_cell_global_id_ps cgi_ps; + + if (!cmd->variable) + goto fmt_err; + + tmp = talloc_strdup(cmd, cmd->variable); + if (!tmp) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + if (!(tok = strtok_r(tmp, ".", &saveptr))) + goto fmt_err; + OSMO_ASSERT(strcmp(tok, "neighbor_resolve_cgi_ps_from_lac_ci") == 0); + + if (!(tok = strtok_r(NULL, ".", &saveptr))) + goto fmt_err; + lac = atoi(tok); + + if (!(tok = strtok_r(NULL, ".", &saveptr))) + goto fmt_err; + cell_id = atoi(tok); + + if (!(tok = strtok_r(NULL, ".", &saveptr))) + goto fmt_err; + ab.arfcn = atoi(tok); + + if (!(tok = strtok_r(NULL, "\0", &saveptr))) + goto fmt_err; + ab.bsic = atoi(tok); + + if (!cell_ab_valid(&ab)) + goto fmt_err; + + if (neighbor_address_resolution(net, &ab, lac, cell_id, &cgi_ps) < 0) + goto notfound_err; + + ctrl_cmd_reply_printf(cmd, "%s", osmo_cgi_ps_name(&cgi_ps)); + talloc_free(tmp); + return CTRL_CMD_REPLY; + +notfound_err: + talloc_free(tmp); + cmd->reply = talloc_strdup(cmd, "No target CGI PS found"); + return CTRL_CMD_ERROR; +fmt_err: + talloc_free(tmp); + cmd->reply = talloc_strdup(cmd, "The format is <src_lac>,<src_cell_id>,<dst_arfcn>,<dst_bsic>"); + return CTRL_CMD_ERROR; } -void neighbor_ident_clear(struct neighbor_ident_list *nil) +int neighbor_ctrl_cmds_install(struct gsm_network *net) { - struct neighbor_ident *ni; - while ((ni = llist_first_entry_or_null(&nil->list, struct neighbor_ident, entry))) - _neighbor_ident_free(ni); + int rc; + + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_neighbor_resolve_cgi_ps_from_lac_ci); + return rc; } -/*! Iterate all neighbor_ident_list entries and call iter_cb for each. - * If iter_cb returns false, the iteration is stopped. */ -void neighbor_ident_iter(const struct neighbor_ident_list *nil, - bool (* iter_cb )(const struct neighbor_ident_key *key, - const struct gsm0808_cell_id_list2 *val, - void *cb_data), - void *cb_data) +struct ctrl_handle *neighbor_controlif_setup(struct gsm_network *net) { - struct neighbor_ident *ni, *ni_next; - if (!nil) - return; - llist_for_each_entry_safe(ni, ni_next, &nil->list, entry) { - if (!iter_cb(&ni->key, &ni->val, cb_data)) - return; - } + /* DEPRECATED: see osmobsc-usermanual.pdf, section 16.1.1 Neighbor Address Resolution Service */ + return ctrl_interface_setup_dynip2(net, net->neigh_ctrl.addr, net->neigh_ctrl.port, + NULL, _LAST_CTRL_NODE_NEIGHBOR); } diff --git a/src/osmo-bsc/neighbor_ident_ctrl.c b/src/osmo-bsc/neighbor_ident_ctrl.c new file mode 100644 index 000000000..a9d7b5dc5 --- /dev/null +++ b/src/osmo-bsc/neighbor_ident_ctrl.c @@ -0,0 +1,753 @@ +/* CTRL interface implementation to manage identity of neighboring BSS cells for inter-BSC handover. */ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * Author: Philipp Maier <pmaier@sysmocom.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 <errno.h> +#include <inttypes.h> +#include <time.h> + +#include <osmocom/ctrl/control_cmd.h> +#include <osmocom/bsc/neighbor_ident.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/vty.h> + +/* Continue to parse ARFCN and BSIC, which are optional parameters at the end of the parameter string in most of the + * commands. The result is ignored when parameter n is set to NULL. */ +static int continue_parse_arfcn_and_bsic(char **saveptr, struct neighbor *n) +{ + int arfcn; + int bsic; + char *tok; + + tok = strtok_r(NULL, "-", saveptr); + + /* No ARFCN and BSIC persent - stop */ + if (!tok) + return 0; + + if (osmo_str_to_int(&arfcn, tok, 10, 0, 1023) < 0) + return -EINVAL; + + tok = strtok_r(NULL, "-", saveptr); + + /* When an ARFCN is given, then the BSIC parameter is + * mandatory */ + if (!tok) + return -EINVAL; + + if (strcmp(tok, "any") == 0) { + bsic = BSIC_ANY; + } else { + if (osmo_str_to_int(&bsic, tok, 10, 0, 63) < 0) + return 1; + } + + /* Make sure there are no excess parameters */ + if (strtok_r(NULL, "-", saveptr)) + return -EINVAL; + + if (n) { + n->cell_id.ab_present = true; + n->cell_id.ab.arfcn = arfcn; + n->cell_id.ab.bsic = bsic; + } + + return 0; +} + +/* This and the following: Add/Remove a BTS as neighbor */ +static int verify_neighbor_bts(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + struct gsm_bts *bts = cmd->node; + const int neigh_bts_nr = atoi(value); + struct gsm_bts *neigh_bts = gsm_bts_num(bts->network, neigh_bts_nr); + + if (!neigh_bts) { + cmd->reply = "Invalid Neighbor BTS number - no such BTS"; + return 1; + } + + return 0; +} + +static int verify_neighbor_bts_add(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + return verify_neighbor_bts(cmd, value, _data); +} + +static int get_neighbor_bts_list(struct ctrl_cmd *cmd, void *data) +{ + /* Max. 256 BTS neighbors (as of now, any bts can be its own neighbor per cfg) comma-separated -> + * max. 255 commas * + trailing '\0': 256 + * 10 of those numbers (0...9) are 1-digit numbers: + 10 = 266 + * 90 of those numbers are 2-digit numbers (10...99): + 90 = 356 + * 255 - 100 + 1 = 156 are 3-digit numbers (100...255): + 156 = 512 bytes + * Double BTS num entries are not possible (check exists and is being tested against in python tests). */ + char log_buf[512]; + struct osmo_strbuf reply = { .buf = log_buf, + .len = sizeof(log_buf), + .pos = log_buf + }; + struct gsm_bts *neighbor_bts, *bts = (struct gsm_bts *)cmd->node; + if (!bts) { + cmd->reply = "BTS not found"; + return CTRL_CMD_ERROR; + } + struct neighbor *n; + llist_for_each_entry(n, &bts->neighbors, entry) + if (resolve_local_neighbor(&neighbor_bts, bts, n) == 0) + OSMO_STRBUF_PRINTF(reply, "%" PRIu8 ",", neighbor_bts->nr); + if (reply.buf == reply.pos) + cmd->reply = ""; + else { /* Get rid of trailing comma */ + reply.pos[-1] = '\0'; + if (!(cmd->reply = talloc_strdup(cmd, reply.buf))) + goto oom; + } + return CTRL_CMD_REPLY; + +oom: + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; +} + +CTRL_CMD_DEFINE_RO(neighbor_bts_list, "neighbor-bts list"); + +static int set_neighbor_bts_add(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + const int bts_nr = atoi(cmd->value); + int rc; + + struct neighbor n = { + .type = NEIGHBOR_TYPE_BTS_NR, + .bts_nr = bts_nr, + }; + rc = neighbor_ident_add_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to add neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: "<num>" + * num: BTS number (0-255) */ +CTRL_CMD_DEFINE_WO(neighbor_bts_add, "neighbor-bts add"); + +static int verify_neighbor_bts_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + return verify_neighbor_bts(cmd, value, _data); +} + +static int set_neighbor_bts_del(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + const int bts_nr = atoi(cmd->value); + int rc; + + struct neighbor n = { + .type = NEIGHBOR_TYPE_BTS_NR, + .bts_nr = bts_nr, + }; + rc = neighbor_ident_del_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to delete neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: (see "add" command above) */ +CTRL_CMD_DEFINE_WO(neighbor_bts_del, "neighbor-bts del"); + +/* This and the following: Add/Remove a LAC as neighbor */ +static int parse_lac(void *ctx, struct neighbor *n, const char *value) +{ + char *tmp = NULL, *tok, *saveptr; + int rc = 0; + int lac; + + if (n) + memset(n, 0, sizeof(*n)); + + tmp = talloc_strdup(ctx, value); + if (!tmp) + return -EINVAL; + + /* Parse LAC */ + tok = strtok_r(tmp, "-", &saveptr); + if (tok) { + if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Optional parameters: ARFCN and BSIC */ + if (continue_parse_arfcn_and_bsic(&saveptr, n)) { + rc = -EINVAL; + goto exit; + } + + if (n) { + n->type = NEIGHBOR_TYPE_CELL_ID; + n->cell_id.id.id_discr = CELL_IDENT_LAC; + n->cell_id.id.id.lac = lac; + } + +exit: + talloc_free(tmp); + return rc; +} + +static int verify_neighbor_lac_add(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (parse_lac(cmd, NULL, value)) + return 1; + return 0; +} + +static int set_neighbor_lac_add(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + struct neighbor n; + + parse_lac(cmd, &n, cmd->value); + rc = neighbor_ident_add_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to add neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: "<lac>[-<arfcn>-<bsic>]" + * lac: Location area of neighbor cell (0-65535) + * arfcn: ARFCN of neighbor cell (0-1023) + * bsic: BSIC of neighbor cell */ +CTRL_CMD_DEFINE_WO(neighbor_lac_add, "neighbor-lac add"); + +static int verify_neighbor_lac_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (parse_lac(cmd, NULL, value)) + return 1; + return 0; +} + +static int set_neighbor_lac_del(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + struct neighbor n; + parse_lac(cmd, &n, cmd->value); + rc = neighbor_ident_del_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to delete neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: (see "add" command above) */ +CTRL_CMD_DEFINE_WO(neighbor_lac_del, "neighbor-lac del"); + +/* This and the following: Add/Remove a LAC-CI as neighbor */ +static int parse_lac_ci(void *ctx, struct neighbor *n, const char *value) +{ + char *tmp = NULL, *tok, *saveptr; + int rc = 0; + int lac; + int ci; + + if (n) + memset(n, 0, sizeof(*n)); + + tmp = talloc_strdup(ctx, value); + if (!tmp) + return -EINVAL; + + /* Parse LAC */ + tok = strtok_r(tmp, "-", &saveptr); + if (tok) { + if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Parse CI */ + tok = strtok_r(NULL, "-", &saveptr); + if (tok) { + if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Optional parameters: ARFCN and BSIC */ + if (continue_parse_arfcn_and_bsic(&saveptr, n)) { + rc = -EINVAL; + goto exit; + } + + if (n) { + n->type = NEIGHBOR_TYPE_CELL_ID; + n->cell_id.id.id_discr = CELL_IDENT_LAC_AND_CI; + n->cell_id.id.id.lac = lac; + n->cell_id.id.id.ci = ci; + } + +exit: + talloc_free(tmp); + return rc; +} + +static int verify_neighbor_lac_ci_add(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (parse_lac_ci(cmd, NULL, value)) + return 1; + return 0; +} + +static int set_neighbor_lac_ci_add(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + struct neighbor n; + + parse_lac_ci(cmd, &n, cmd->value); + rc = neighbor_ident_add_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to add neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: "<lac>-<ci>[-<arfcn>-<bsic>]" + * lac: Location area of neighbor cell (0-65535) + * ci: Cell ID of neighbor cell (0-65535) + * arfcn: ARFCN of neighbor cell (0-1023) + * bsic: BSIC of neighbor cell */ +CTRL_CMD_DEFINE_WO(neighbor_lac_ci_add, "neighbor-lac-ci add"); + +static int verify_neighbor_lac_ci_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (parse_lac_ci(cmd, NULL, value)) + return 1; + return 0; +} + +static int set_neighbor_lac_ci_del(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + struct neighbor n; + parse_lac_ci(cmd, &n, cmd->value); + rc = neighbor_ident_del_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to delete neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: (see "add" command above) */ +CTRL_CMD_DEFINE_WO(neighbor_lac_ci_del, "neighbor-lac-ci del"); + +/* This and the following: Add/Remove a CGI as neighbor */ +static int parse_cgi(void *ctx, struct neighbor *n, const char *value) +{ + char *tmp = NULL, *tok, *saveptr; + int rc = 0; + uint16_t mcc; + uint16_t mnc; + bool mnc_3_digits; + int lac; + int ci; + + if (n) + memset(n, 0, sizeof(*n)); + + tmp = talloc_strdup(ctx, value); + if (!tmp) + return -EINVAL; + + /* Parse MCC */ + tok = strtok_r(tmp, "-", &saveptr); + if (tok) { + if (osmo_mcc_from_str(tok, &mcc)) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Parse MNC */ + tok = strtok_r(NULL, "-", &saveptr); + if (tok) { + if (osmo_mnc_from_str(tok, &mnc, &mnc_3_digits)) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Parse LAC */ + tok = strtok_r(NULL, "-", &saveptr); + if (tok) { + if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Parse CI */ + tok = strtok_r(NULL, "-", &saveptr); + if (tok) { + if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Optional parameters: ARFCN and BSIC */ + if (continue_parse_arfcn_and_bsic(&saveptr, n)) { + rc = -EINVAL; + goto exit; + } + + if (n) { + n->type = NEIGHBOR_TYPE_CELL_ID; + n->cell_id.id.id_discr = CELL_IDENT_WHOLE_GLOBAL; + n->cell_id.id.id.global.lai.lac = lac; + n->cell_id.id.id.global.lai.plmn.mcc = mcc; + n->cell_id.id.id.global.lai.plmn.mnc = mnc; + n->cell_id.id.id.global.lai.plmn.mnc_3_digits = mnc_3_digits; + n->cell_id.id.id.global.cell_identity = ci; + } + +exit: + talloc_free(tmp); + return rc; +} + +static int verify_neighbor_cgi_add(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (parse_cgi(cmd, NULL, value)) + return 1; + return 0; +} + +static int set_neighbor_cgi_add(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + struct neighbor n; + + parse_cgi(cmd, &n, cmd->value); + rc = neighbor_ident_add_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to add neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: "<mcc>-<mnc>-<lac>-<ci>[-<arfcn>-<bsic>]" + * mcc: Mobile country code of neighbor cell (0-999) + * mnc: Mobile network code of neighbor cell (0-999) + * lac: Location area of neighbor cell (0-65535) + * ci: Cell ID of neighbor cell (0-65535) + * arfcn: ARFCN of neighbor cell (0-1023) + * bsic: BSIC of neighbor cell */ +CTRL_CMD_DEFINE_WO(neighbor_cgi_add, "neighbor-cgi add"); + +static int verify_neighbor_cgi_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (parse_cgi(cmd, NULL, value)) + return 1; + return 0; +} + +static int set_neighbor_cgi_del(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + struct neighbor n; + parse_cgi(cmd, &n, cmd->value); + rc = neighbor_ident_del_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to delete neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: (see "add" command above) */ +CTRL_CMD_DEFINE_WO(neighbor_cgi_del, "neighbor-cgi del"); + +/* This and the following: Add/Remove a CGI-PS as neighbor */ +static int parse_cgi_ps(void *ctx, struct neighbor *n, const char *value) +{ + char *tmp = NULL, *tok, *saveptr; + int rc = 0; + uint16_t mcc; + uint16_t mnc; + bool mnc_3_digits; + int lac; + int rac; + int ci; + + if (n) + memset(n, 0, sizeof(*n)); + + tmp = talloc_strdup(ctx, value); + if (!tmp) + return -EINVAL; + + /* Parse MCC */ + tok = strtok_r(tmp, "-", &saveptr); + if (tok) { + if (osmo_mcc_from_str(tok, &mcc)) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Parse MNC */ + tok = strtok_r(NULL, "-", &saveptr); + if (tok) { + if (osmo_mnc_from_str(tok, &mnc, &mnc_3_digits)) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Parse LAC */ + tok = strtok_r(NULL, "-", &saveptr); + if (tok) { + if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Parse RAC */ + tok = strtok_r(NULL, "-", &saveptr); + if (tok) { + if (osmo_str_to_int(&rac, tok, 10, 0, 255) < 0) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Parse CI */ + tok = strtok_r(NULL, "-", &saveptr); + if (tok) { + if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) { + rc = -EINVAL; + goto exit; + } + } else { + rc = -EINVAL; + goto exit; + } + + /* Optional parameters: ARFCN and BSIC */ + if (continue_parse_arfcn_and_bsic(&saveptr, n)) { + rc = -EINVAL; + goto exit; + } + + if (n) { + n->type = NEIGHBOR_TYPE_CELL_ID; + n->cell_id.id.id_discr = CELL_IDENT_WHOLE_GLOBAL_PS; + n->cell_id.id.id.global_ps.rai.lac.lac = lac; + n->cell_id.id.id.global_ps.rai.rac = lac; + n->cell_id.id.id.global_ps.rai.lac.plmn.mcc = mcc; + n->cell_id.id.id.global_ps.rai.lac.plmn.mnc = mnc; + n->cell_id.id.id.global_ps.rai.lac.plmn.mnc_3_digits = mnc_3_digits; + n->cell_id.id.id.global_ps.cell_identity = ci; + } + +exit: + talloc_free(tmp); + return rc; +} + +static int verify_neighbor_cgi_ps_add(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (parse_cgi_ps(cmd, NULL, value)) + return 1; + return 0; +} + +static int set_neighbor_cgi_ps_add(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + struct neighbor n; + + parse_cgi_ps(cmd, &n, cmd->value); + rc = neighbor_ident_add_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to add neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: "<mcc>-<mnc>-<lac>-<rac>-<ci>[-<arfcn>-<bsic>]" + * mcc: Mobile country code of neighbor cell (0-999) + * mnc: Mobile network code of neighbor cell (0-999) + * lac: Location area of neighbor cell (0-65535) + * rac: Routing area of neighbor cell (0-65535) + * ci: Cell ID of neighbor cell (0-65535) + * arfcn: ARFCN of neighbor cell (0-1023) + * bsic: BSIC of neighbor cell */ +CTRL_CMD_DEFINE_WO(neighbor_cgi_ps_add, "neighbor-cgi-ps add"); + +static int verify_neighbor_cgi_ps_del(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (parse_cgi_ps(cmd, NULL, value)) + return 1; + return 0; +} + +static int set_neighbor_cgi_ps_del(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + struct neighbor n; + parse_cgi_ps(cmd, &n, cmd->value); + rc = neighbor_ident_del_neighbor(NULL, bts, &n); + if (rc != CMD_SUCCESS) { + cmd->reply = "Failed to delete neighbor"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +/* Parameter format: (see "add" command above) */ +CTRL_CMD_DEFINE_WO(neighbor_cgi_ps_del, "neighbor-cgi-ps del"); + +/* This and the following: clear all neighbor cell information */ +static int set_neighbor_clear(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + struct neighbor *neighbor; + struct neighbor *neighbor_tmp; + + llist_for_each_entry_safe(neighbor, neighbor_tmp, &bts->neighbors, entry) { + llist_del(&neighbor->entry); + talloc_free(neighbor); + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(neighbor_clear, "neighbor-clear"); + +/* Register control interface commands implemented above */ +int neighbor_ident_ctrl_init(void) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_add); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_del); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_add); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_del); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_ci_add); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_ci_del); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_add); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_del); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_list); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_ps_add); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_ps_del); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_clear); + + return rc; +} diff --git a/src/osmo-bsc/neighbor_ident_vty.c b/src/osmo-bsc/neighbor_ident_vty.c index 7feed2a27..44b9057f7 100644 --- a/src/osmo-bsc/neighbor_ident_vty.c +++ b/src/osmo-bsc/neighbor_ident_vty.c @@ -23,6 +23,9 @@ #include <stdlib.h> #include <string.h> #include <errno.h> +#include <inttypes.h> + +#include <osmocom/ctrl/ports.h> #include <osmocom/vty/command.h> #include <osmocom/gsm/gsm0808.h> @@ -32,43 +35,6 @@ #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/bts.h> -static struct gsm_network *g_net = NULL; -static struct neighbor_ident_list *g_neighbor_cells = NULL; - -/* Parse VTY parameters matching NEIGHBOR_IDENT_VTY_KEY_PARAMS. Pass a pointer so that argv[0] is the - * ARFCN value followed by the BSIC keyword and value. vty *must* reference a BTS_NODE. */ -bool neighbor_ident_vty_parse_key_params(struct vty *vty, const char **argv, - struct neighbor_ident_key *key) -{ - struct gsm_bts *bts = vty->index; - - OSMO_ASSERT(vty->node == BTS_NODE); - OSMO_ASSERT(bts); - - return neighbor_ident_bts_parse_key_params(vty, bts, argv, key); -} - -/* same as neighbor_ident_vty_parse_key_params() but pass an explicit bts, so it works on any node. */ -bool neighbor_ident_bts_parse_key_params(struct vty *vty, struct gsm_bts *bts, const char **argv, - struct neighbor_ident_key *key) -{ - const char *arfcn_str = argv[0]; - const char *bsic_str = argv[1]; - - OSMO_ASSERT(bts); - - *key = (struct neighbor_ident_key){ - .from_bts = bts->nr, - .arfcn = atoi(arfcn_str), - }; - - if (!strcmp(bsic_str, "any")) - key->bsic = BSIC_ANY; - else - key->bsic = atoi(bsic_str); - return true; -} - #define NEIGHBOR_ADD_CMD "neighbor " #define NEIGHBOR_DEL_CMD "no neighbor " #define NEIGHBOR_DOC "Manage local and remote-BSS neighbor cells\n" @@ -76,64 +42,51 @@ bool neighbor_ident_bts_parse_key_params(struct vty *vty, struct gsm_bts *bts, c #define NEIGHBOR_DEL_DOC NO_STR "Remove local or remote-BSS neighbor cell\n" #define LAC_PARAMS "lac <0-65535>" +#define LAC_ARGC 1 #define LAC_DOC "Neighbor cell by LAC\n" "LAC\n" #define LAC_CI_PARAMS "lac-ci <0-65535> <0-65535>" +#define LAC_CI_ARGC 2 #define LAC_CI_DOC "Neighbor cell by LAC and CI\n" "LAC\n" "CI\n" #define CGI_PARAMS "cgi <0-999> <0-999> <0-65535> <0-65535>" +#define CGI_ARGC 4 #define CGI_DOC "Neighbor cell by cgi\n" "MCC\n" "MNC\n" "LAC\n" "CI\n" +#define CGI_PS_PARAMS "cgi-ps <0-999> <0-999> <0-65535> <0-255> <0-65535>" +#define CGI_PS_ARGC 5 +#define CGI_PS_DOC "Neighbor cell by cgi (Packet Switched, with RAC)\n" "MCC\n" "MNC\n" "LAC\n" "RAC\n" "CI\n" + #define LOCAL_BTS_PARAMS "bts <0-255>" #define LOCAL_BTS_DOC "Neighbor cell by local BTS number\n" "BTS number\n" -static struct gsm_bts *neighbor_ident_vty_parse_bts_nr(struct vty *vty, const char **argv) +static int neighbor_ident_vty_parse_lac(struct vty *vty, struct gsm0808_cell_id *cell_id, const char **argv) { - const char *bts_nr_str = argv[0]; - struct gsm_bts *bts = gsm_bts_num(g_net, atoi(bts_nr_str)); - if (!bts) - vty_out(vty, "%% No such BTS: nr = %s%s\n", bts_nr_str, VTY_NEWLINE); - return bts; -} - -static struct gsm_bts *bts_by_cell_id(struct vty *vty, struct gsm0808_cell_id *cell_id) -{ - struct gsm_bts *bts = gsm_bts_by_cell_id(g_net, cell_id, 0); - if (!bts) - vty_out(vty, "%% No such BTS: %s%s\n", gsm0808_cell_id_name(cell_id), VTY_NEWLINE); - return bts; -} - -static struct gsm0808_cell_id *neighbor_ident_vty_parse_lac(struct vty *vty, const char **argv) -{ - static struct gsm0808_cell_id cell_id; - cell_id = (struct gsm0808_cell_id){ + *cell_id = (struct gsm0808_cell_id){ .id_discr = CELL_IDENT_LAC, .id.lac = atoi(argv[0]), }; - return &cell_id; + return 0; } -static struct gsm0808_cell_id *neighbor_ident_vty_parse_lac_ci(struct vty *vty, const char **argv) +static int neighbor_ident_vty_parse_lac_ci(struct vty *vty, struct gsm0808_cell_id *cell_id, const char **argv) { - static struct gsm0808_cell_id cell_id; - cell_id = (struct gsm0808_cell_id){ + *cell_id = (struct gsm0808_cell_id){ .id_discr = CELL_IDENT_LAC_AND_CI, .id.lac_and_ci = { .lac = atoi(argv[0]), .ci = atoi(argv[1]), }, }; - return &cell_id; + return 0; } -static struct gsm0808_cell_id *neighbor_ident_vty_parse_cgi(struct vty *vty, const char **argv) +static int neighbor_ident_vty_parse_cgi(struct vty *vty, struct gsm0808_cell_id *cell_id, const char **argv) { - static struct gsm0808_cell_id cell_id; - cell_id = (struct gsm0808_cell_id){ + *cell_id = (struct gsm0808_cell_id){ .id_discr = CELL_IDENT_WHOLE_GLOBAL, }; - struct osmo_cell_global_id *cgi = &cell_id.id.global; + struct osmo_cell_global_id *cgi = &cell_id->id.global; const char *mcc = argv[0]; const char *mnc = argv[1]; const char *lac = argv[2]; @@ -141,379 +94,388 @@ static struct gsm0808_cell_id *neighbor_ident_vty_parse_cgi(struct vty *vty, con if (osmo_mcc_from_str(mcc, &cgi->lai.plmn.mcc)) { vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE); - return NULL; + return -1; } if (osmo_mnc_from_str(mnc, &cgi->lai.plmn.mnc, &cgi->lai.plmn.mnc_3_digits)) { vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE); - return NULL; + return -1; } cgi->lai.lac = atoi(lac); cgi->cell_identity = atoi(ci); - return &cell_id; + return 0; } -static int add_local_bts(struct vty *vty, struct gsm_bts *neigh) +static int neighbor_ident_vty_parse_cgi_ps(struct vty *vty, struct gsm0808_cell_id *cell_id, const char **argv) { - int rc; - struct gsm_bts *bts = vty->index; - if (vty->node != BTS_NODE) { - vty_out(vty, "%% Error: cannot add local BTS neighbor, not on BTS node%s", - VTY_NEWLINE); - return CMD_WARNING; - } - if (!bts) { - vty_out(vty, "%% Error: cannot add local BTS neighbor, no BTS on this node%s", - VTY_NEWLINE); - return CMD_WARNING; - } - if (!neigh) { - vty_out(vty, "%% Error: cannot add local BTS neighbor to BTS %u, no such neighbor BTS%s" - "%% (To add remote-BSS neighbors, pass full ARFCN and BSIC as well)%s", - bts->nr, VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; - } - rc = gsm_bts_local_neighbor_add(bts, neigh); - if (rc < 0) { - vty_out(vty, "%% Error: cannot add local BTS %u as neighbor to BTS %u: %s%s", - neigh->nr, bts->nr, strerror(-rc), VTY_NEWLINE); - return CMD_WARNING; - } else - vty_out(vty, "%% BTS %u %s local neighbor BTS %u with LAC %u CI %u and ARFCN %u BSIC %u%s", - bts->nr, rc? "now has" : "already had", - neigh->nr, neigh->location_area_code, neigh->cell_identity, - neigh->c0->arfcn, neigh->bsic, VTY_NEWLINE); - return CMD_SUCCESS; -} + *cell_id = (struct gsm0808_cell_id){ + .id_discr = CELL_IDENT_WHOLE_GLOBAL_PS, + }; + struct osmo_cell_global_id_ps *cgi_ps = &cell_id->id.global_ps; + const char *mcc = argv[0]; + const char *mnc = argv[1]; + const char *lac = argv[2]; + const char *rac = argv[3]; + const char *ci = argv[4]; -static int del_local_bts(struct vty *vty, struct gsm_bts *neigh) -{ - int rc; - struct gsm_bts *bts = vty->index; - if (vty->node != BTS_NODE) { - vty_out(vty, "%% Error: cannot remove local BTS neighbor, not on BTS node%s", - VTY_NEWLINE); - return CMD_WARNING; - } - if (!bts) { - vty_out(vty, "%% Error: cannot remove local BTS neighbor, no BTS on this node%s", - VTY_NEWLINE); - return CMD_WARNING; - } - if (!neigh) { - vty_out(vty, "%% Error: cannot remove local BTS neighbor from BTS %u, no such neighbor BTS%s", - bts->nr, VTY_NEWLINE); - return CMD_WARNING; - } - rc = gsm_bts_local_neighbor_del(bts, neigh); - if (rc < 0) { - vty_out(vty, "%% Error: cannot remove local BTS %u neighbor from BTS %u: %s%s", - neigh->nr, bts->nr, strerror(-rc), VTY_NEWLINE); - return CMD_WARNING; + if (osmo_mcc_from_str(mcc, &cgi_ps->rai.lac.plmn.mcc)) { + vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE); + return -1; } - if (rc == 0) - vty_out(vty, "%% BTS %u is no neighbor of BTS %u%s", - neigh->nr, bts->nr, VTY_NEWLINE); - return CMD_SUCCESS; -} -DEFUN(cfg_neighbor_add_bts_nr, cfg_neighbor_add_bts_nr_cmd, - NEIGHBOR_ADD_CMD LOCAL_BTS_PARAMS, - NEIGHBOR_ADD_DOC LOCAL_BTS_DOC) -{ - return add_local_bts(vty, neighbor_ident_vty_parse_bts_nr(vty, argv)); -} + if (osmo_mnc_from_str(mnc, &cgi_ps->rai.lac.plmn.mnc, &cgi_ps->rai.lac.plmn.mnc_3_digits)) { + vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE); + return -1; + } -DEFUN(cfg_neighbor_add_lac, cfg_neighbor_add_lac_cmd, - NEIGHBOR_ADD_CMD LAC_PARAMS, - NEIGHBOR_ADD_DOC LAC_DOC) -{ - return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac(vty, argv))); + cgi_ps->rai.lac.lac = atoi(lac); + cgi_ps->rai.rac = atoi(rac); + cgi_ps->cell_identity = atoi(ci); + return 0; } -DEFUN(cfg_neighbor_add_lac_ci, cfg_neighbor_add_lac_ci_cmd, - NEIGHBOR_ADD_CMD LAC_CI_PARAMS, - NEIGHBOR_ADD_DOC LAC_CI_DOC) +void neighbor_ident_vty_parse_arfcn_bsic(struct cell_ab *ab, const char **argv) { - return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac_ci(vty, argv))); -} - -DEFUN(cfg_neighbor_add_cgi, cfg_neighbor_add_cgi_cmd, - NEIGHBOR_ADD_CMD CGI_PARAMS, - NEIGHBOR_ADD_DOC CGI_DOC) -{ - return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_cgi(vty, argv))); -} + const char *arfcn_str = argv[0]; + const char *bsic_str = argv[1]; -bool neighbor_ident_key_matches_bts(const struct neighbor_ident_key *key, struct gsm_bts *bts) -{ - if (!bts || !key) - return false; - return key->arfcn == bts->c0->arfcn - && (key->bsic == BSIC_ANY || key->bsic == bts->bsic); + *ab = (struct cell_ab){ + .arfcn = atoi(arfcn_str), + .bsic = (!strcmp(bsic_str, "any")) ? BSIC_ANY : atoi(bsic_str), + }; } -static int add_remote_or_local_bts(struct vty *vty, const struct gsm0808_cell_id *cell_id, - const struct neighbor_ident_key *key) +#define LOGPORVTY(vty, fmt, args...) \ +{ \ + if (vty) \ + vty_out(vty, "%% " fmt "%s", ## args, VTY_NEWLINE); \ + else \ + LOGP(DLINP, LOGL_NOTICE, fmt "\n", ## args); \ +} while (0) \ + +/* Add a neighbor from neighborlist. When the parameter *vty is set to NULL all error messages are redirected to the + * logtext. */ +int neighbor_ident_add_neighbor(struct vty *vty, struct gsm_bts *bts, struct neighbor *n) { - int rc; - struct gsm_bts *local_neigh; - const struct gsm0808_cell_id_list2 *exists; - struct gsm0808_cell_id_list2 cil; - struct gsm_bts *bts = vty->index; + struct neighbor *neighbor; - if (vty->node != BTS_NODE) { - vty_out(vty, "%% Error: cannot add BTS neighbor, not on BTS node%s", - VTY_NEWLINE); - return CMD_WARNING; - } - if (!bts) { - vty_out(vty, "%% Error: cannot add BTS neighbor, no BTS on this node%s", - VTY_NEWLINE); - return CMD_WARNING; - } + OSMO_ASSERT(bts); + OSMO_ASSERT(!vty || (vty->node == BTS_NODE)); + + llist_for_each_entry(neighbor, &bts->neighbors, entry) { + /* Check against duplicates */ + if (neighbor_same(neighbor, n, false)) { + /* Found a match on Cell ID or BTS number, without ARFCN+BSIC. If they are fully identical, ignore the + * duplicate. If the ARFCN+BSIC part differs, it's an error. */ + LOGPORVTY(vty, "BTS %u already had neighbor %s", bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor)); + if (!neighbor_same(neighbor, n, true)) { + LOGPORVTY(vty, "ERROR: duplicate Cell ID in neighbor config, with differing ARFCN+BSIC: %s", + neighbor_to_str_c(OTC_SELECT, n)); + return CMD_WARNING; + } + /* Exact same neighbor again, just ignore. */ + return CMD_SUCCESS; + } - /* Is there a local BTS that matches the cell_id? */ - local_neigh = gsm_bts_by_cell_id(g_net, cell_id, 0); - if (local_neigh) { - /* But do the advertised ARFCN and BSIC match as intended? - * The user may omit ARFCN and BSIC for local cells, but if they are provided, - * they need to match. */ - if (!neighbor_ident_key_matches_bts(key, local_neigh)) { - vty_out(vty, "%% Error: bts %u: neighbor cell id %s indicates local BTS %u," - " but it does not match ARFCN+BSIC %s%s", - bts->nr, gsm0808_cell_id_name(cell_id), local_neigh->nr, - neighbor_ident_key_name(key), VTY_NEWLINE); - /* TODO: error out fatally for non-interactive VTY? */ + /* Allow only one cell ID per remote-BSS neighbor, see OS#3656 */ + if (n->type == NEIGHBOR_TYPE_CELL_ID + && n->cell_id.ab_present && neighbor->cell_id.ab_present + && cell_ab_match(&n->cell_id.ab, &neighbor->cell_id.ab, true)) { + LOGPORVTY(vty, "Error: only one Cell Identifier entry is allowed per remote neighbor." + " Already have: BTS %u -> %s", bts->nr, + neighbor_to_str_c(OTC_SELECT, neighbor)); return CMD_WARNING; } - return add_local_bts(vty, local_neigh); } - /* Allow only one cell ID per remote-BSS neighbor, see OS#3656 */ - exists = neighbor_ident_get(g_neighbor_cells, key); - if (exists) { - vty_out(vty, "%% Error: only one Cell Identifier entry is allowed per remote neighbor." - " Already have: %s -> %s%s", neighbor_ident_key_name(key), - gsm0808_cell_id_list_name(exists), VTY_NEWLINE); - return CMD_WARNING; - } + neighbor = talloc_zero(bts, struct neighbor); + *neighbor = *n; + llist_add_tail(&neighbor->entry, &bts->neighbors); + return CMD_SUCCESS; +} - /* The cell_id is not known in this BSS, so it must be a remote cell. */ - gsm0808_cell_id_to_list(&cil, cell_id); - rc = neighbor_ident_add(g_neighbor_cells, key, &cil); +/* Delete a neighbor from neighborlist. When the parameter *vty is set to NULL all error messages are redirected to the + * logtext. */ +int neighbor_ident_del_neighbor(struct vty *vty, struct gsm_bts *bts, struct neighbor *n) +{ + struct neighbor *neighbor; - if (rc < 0) { - const char *reason; - switch (rc) { - case -EINVAL: - reason = ": mismatching type between current and newly added cell identifier"; - break; - case -ENOSPC: - reason = ": list is full"; - break; + OSMO_ASSERT(bts); + OSMO_ASSERT(!vty || (vty->node == BTS_NODE)); + + llist_for_each_entry(neighbor, &bts->neighbors, entry) { + if (neighbor->type != n->type) + continue; + + switch (n->type) { + case NEIGHBOR_TYPE_BTS_NR: + if (neighbor->bts_nr == n->bts_nr) + break; + continue; + + case NEIGHBOR_TYPE_CELL_ID: + if (gsm0808_cell_ids_match(&neighbor->cell_id.id, &n->cell_id.id, true)) + break; + continue; default: - reason = ""; - break; + continue; } - vty_out(vty, "%% Error adding neighbor-BSS Cell Identifier %s%s%s", - gsm0808_cell_id_name(cell_id), reason, VTY_NEWLINE); - return CMD_WARNING; + llist_del(&neighbor->entry); + talloc_free(neighbor); + return CMD_SUCCESS; } - vty_out(vty, "%% %s now has %d remote BSS Cell Identifier List %s%s", - neighbor_ident_key_name(key), rc, rc == 1? "entry" : "entries", VTY_NEWLINE); - return CMD_SUCCESS; + LOGPORVTY(vty, "Error: no such neighbor on BTS %d: %s", + bts->nr, neighbor_to_str_c(OTC_SELECT, n)); + return CMD_WARNING; } -static int del_by_key(struct vty *vty, const struct neighbor_ident_key *key) +static int del_neighbor_by_cell_ab(struct vty *vty, const struct cell_ab *cell_ab) { - int removed = 0; - int rc; struct gsm_bts *bts = vty->index; - struct gsm_bts_ref *neigh, *safe; + struct neighbor *neighbor, *safe; + struct gsm_bts *neighbor_bts; + struct cell_ab neighbor_ab; + int count = 0; - if (vty->node != BTS_NODE) { - vty_out(vty, "%% Error: cannot remove BTS neighbor, not on BTS node%s", - VTY_NEWLINE); - return CMD_WARNING; - } - if (!bts) { - vty_out(vty, "%% Error: cannot remove BTS neighbor, no BTS on this node%s", - VTY_NEWLINE); - return CMD_WARNING; - } + OSMO_ASSERT((vty->node == BTS_NODE) && bts); + + llist_for_each_entry_safe(neighbor, safe, &bts->neighbors, entry) { + switch (neighbor->type) { + case NEIGHBOR_TYPE_BTS_NR: + if (resolve_local_neighbor(&neighbor_bts, bts, neighbor)) + continue; + bts_cell_ab(&neighbor_ab, neighbor_bts); + if (!cell_ab_match(&neighbor_ab, cell_ab, false)) + continue; + break; - /* Is there a local BTS that matches the key? */ - llist_for_each_entry_safe(neigh, safe, &bts->local_neighbors, entry) { - struct gsm_bts *neigh_bts = neigh->bts; - if (!neighbor_ident_key_matches_bts(key, neigh->bts)) + case NEIGHBOR_TYPE_CELL_ID: + if (!neighbor->cell_id.ab_present) + continue; + if (!cell_ab_match(&neighbor->cell_id.ab, cell_ab, false)) + continue; + break; + default: continue; - rc = gsm_bts_local_neighbor_del(bts, neigh->bts); - if (rc > 0) { - vty_out(vty, "%% Removed local neighbor bts %u to bts %u%s", - bts->nr, neigh_bts->nr, VTY_NEWLINE); - removed += rc; } - } - if (neighbor_ident_del(g_neighbor_cells, key)) { - vty_out(vty, "%% Removed remote BSS neighbor %s%s", - neighbor_ident_key_name(key), VTY_NEWLINE); - removed ++; + llist_del(&neighbor->entry); + talloc_free(neighbor); + count++; } + if (count) + return CMD_SUCCESS; + + vty_out(vty, "%% Cannot remove: no such neighbor on BTS %u: %s%s", + bts->nr, cell_ab_to_str_c(OTC_SELECT, cell_ab), VTY_NEWLINE); + return CMD_WARNING; +} + +DEFUN(cfg_neighbor_add_bts_nr, cfg_neighbor_add_bts_nr_cmd, + NEIGHBOR_ADD_CMD LOCAL_BTS_PARAMS, + NEIGHBOR_ADD_DOC LOCAL_BTS_DOC) +{ + struct neighbor n = { + .type = NEIGHBOR_TYPE_BTS_NR, + .bts_nr = atoi(argv[0]), + }; + return neighbor_ident_add_neighbor(vty, vty->index, &n); +} - if (!removed) { - vty_out(vty, "%% Cannot remove, no such neighbor: %s%s", - neighbor_ident_key_name(key), VTY_NEWLINE); +DEFUN(cfg_neighbor_add_lac, cfg_neighbor_add_lac_cmd, + NEIGHBOR_ADD_CMD LAC_PARAMS, + NEIGHBOR_ADD_DOC LAC_DOC) +{ + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + }; + if (neighbor_ident_vty_parse_lac(vty, &n.cell_id.id, argv)) return CMD_WARNING; - } - return CMD_SUCCESS; + return neighbor_ident_add_neighbor(vty, vty->index, &n); } -struct nil_match_bts_data { - int bts_nr; - const struct neighbor_ident_key *found; -}; +DEFUN(cfg_neighbor_add_lac_ci, cfg_neighbor_add_lac_ci_cmd, + NEIGHBOR_ADD_CMD LAC_CI_PARAMS, + NEIGHBOR_ADD_DOC LAC_CI_DOC) +{ + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + }; + if (neighbor_ident_vty_parse_lac_ci(vty, &n.cell_id.id, argv)) + return CMD_WARNING; + return neighbor_ident_add_neighbor(vty, vty->index, &n); +} -static bool nil_match_bts(const struct neighbor_ident_key *key, - const struct gsm0808_cell_id_list2 *val, - void *cb_data) +DEFUN(cfg_neighbor_add_cgi, cfg_neighbor_add_cgi_cmd, + NEIGHBOR_ADD_CMD CGI_PARAMS, + NEIGHBOR_ADD_DOC CGI_DOC) { - struct nil_match_bts_data *d = cb_data; - if (key->from_bts == d->bts_nr) { - d->found = key; - return false; - } - return true; + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + }; + if (neighbor_ident_vty_parse_cgi(vty, &n.cell_id.id, argv)) + return CMD_WARNING; + return neighbor_ident_add_neighbor(vty, vty->index, &n); } -bool neighbor_ident_bts_entry_exists(uint8_t from_bts) +DEFUN(cfg_neighbor_add_cgi_ps, cfg_neighbor_add_cgi_ps_cmd, + NEIGHBOR_ADD_CMD CGI_PS_PARAMS, + NEIGHBOR_ADD_DOC CGI_PS_DOC) { - struct nil_match_bts_data d = { - .bts_nr = from_bts, + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, }; - neighbor_ident_iter(g_neighbor_cells, nil_match_bts, &d); - return (bool)d.found; + if (neighbor_ident_vty_parse_cgi_ps(vty, &n.cell_id.id, argv)) + return CMD_WARNING; + return neighbor_ident_add_neighbor(vty, vty->index, &n); } static int neighbor_del_all(struct vty *vty) { - int rc; - int removed = 0; struct gsm_bts *bts = vty->index; - + struct neighbor *n; OSMO_ASSERT((vty->node == BTS_NODE) && bts); - /* Remove all local neighbors and print to VTY for the user to know what changed */ - while (1) { - struct gsm_bts_ref *neigh = llist_first_entry_or_null(&bts->local_neighbors, struct gsm_bts_ref, entry); - struct gsm_bts *neigh_bts; - if (!neigh) - break; - - neigh_bts = neigh->bts; - OSMO_ASSERT(neigh_bts); - - /* It would be more efficient to just llist_del() the gsm_bts_ref directly, but for the sake of - * safe/sane API use and against code dup, rather invoke the central gsm_bts_local_neighbor_del() - * function intended for this task. */ - rc = gsm_bts_local_neighbor_del(bts, neigh_bts); - if (rc > 0) { - vty_out(vty, "%% Removed local neighbor bts %u to bts %u%s", - bts->nr, neigh_bts->nr, VTY_NEWLINE); - removed += rc; - } else { - vty_out(vty, "%% Error while removing local neighbor bts %u to bts %u, aborted%s", - bts->nr, neigh_bts->nr, VTY_NEWLINE); - return CMD_WARNING; - } + if (llist_empty(&bts->neighbors)) { + vty_out(vty, "%% No neighbors configured%s", VTY_NEWLINE); + return CMD_SUCCESS; } - /* Remove all remote-BSS neighbors */ - while (1) { - struct neighbor_ident_key k; - struct nil_match_bts_data d = { - .bts_nr = bts->nr, - }; - neighbor_ident_iter(g_neighbor_cells, nil_match_bts, &d); - if (!d.found) - break; - k = *d.found; - if (neighbor_ident_del(g_neighbor_cells, &k)) { - vty_out(vty, "%% Removed remote BSS neighbor %s%s", - neighbor_ident_key_name(&k), VTY_NEWLINE); - removed++; - } else { - vty_out(vty, "%% Error while removing remote BSS neighbor %s, aborted%s", - neighbor_ident_key_name(&k), VTY_NEWLINE); - return CMD_WARNING; - } + /* Remove all local neighbors and print to VTY for the user to know what changed */ + while ((n = llist_first_entry_or_null(&bts->neighbors, struct neighbor, entry))) { + vty_out(vty, "%% Removed neighbor: BTS %u to %s%s", + bts->nr, neighbor_to_str_c(OTC_SELECT, n), VTY_NEWLINE); + llist_del(&n->entry); + talloc_free(n); } - - if (!removed) - vty_out(vty, "%% No neighbors configured%s", VTY_NEWLINE); return CMD_SUCCESS; } DEFUN(cfg_neighbor_add_lac_arfcn_bsic, cfg_neighbor_add_lac_arfcn_bsic_cmd, - NEIGHBOR_ADD_CMD LAC_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS, - NEIGHBOR_ADD_DOC LAC_DOC NEIGHBOR_IDENT_VTY_KEY_DOC) + NEIGHBOR_ADD_CMD LAC_PARAMS " " CELL_AB_VTY_PARAMS, + NEIGHBOR_ADD_DOC LAC_DOC CELL_AB_VTY_DOC) { - struct neighbor_ident_key nik; - struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_lac(vty, argv); - if (!cell_id) - return CMD_WARNING; - if (!neighbor_ident_vty_parse_key_params(vty, argv + 1, &nik)) + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + .cell_id.ab_present = true, + }; + if (neighbor_ident_vty_parse_lac(vty, &n.cell_id.id, argv)) return CMD_WARNING; - return add_remote_or_local_bts(vty, cell_id, &nik); + neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + LAC_ARGC); + return neighbor_ident_add_neighbor(vty, vty->index, &n); } DEFUN(cfg_neighbor_add_lac_ci_arfcn_bsic, cfg_neighbor_add_lac_ci_arfcn_bsic_cmd, - NEIGHBOR_ADD_CMD LAC_CI_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS, - NEIGHBOR_ADD_DOC LAC_CI_DOC NEIGHBOR_IDENT_VTY_KEY_DOC) + NEIGHBOR_ADD_CMD LAC_CI_PARAMS " " CELL_AB_VTY_PARAMS, + NEIGHBOR_ADD_DOC LAC_CI_DOC CELL_AB_VTY_DOC) { - struct neighbor_ident_key nik; - struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_lac_ci(vty, argv); - if (!cell_id) - return CMD_WARNING; - if (!neighbor_ident_vty_parse_key_params(vty, argv + 2, &nik)) + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + .cell_id.ab_present = true, + }; + if (neighbor_ident_vty_parse_lac_ci(vty, &n.cell_id.id, argv)) return CMD_WARNING; - return add_remote_or_local_bts(vty, cell_id, &nik); + neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + LAC_CI_ARGC); + return neighbor_ident_add_neighbor(vty, vty->index, &n); } DEFUN(cfg_neighbor_add_cgi_arfcn_bsic, cfg_neighbor_add_cgi_arfcn_bsic_cmd, - NEIGHBOR_ADD_CMD CGI_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS, - NEIGHBOR_ADD_DOC CGI_DOC NEIGHBOR_IDENT_VTY_KEY_DOC) + NEIGHBOR_ADD_CMD CGI_PARAMS " " CELL_AB_VTY_PARAMS, + NEIGHBOR_ADD_DOC CGI_DOC CELL_AB_VTY_DOC) { - struct neighbor_ident_key nik; - struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_cgi(vty, argv); - if (!cell_id) + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + .cell_id.ab_present = true, + }; + if (neighbor_ident_vty_parse_cgi(vty, &n.cell_id.id, argv)) return CMD_WARNING; - if (!neighbor_ident_vty_parse_key_params(vty, argv + 4, &nik)) + neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + CGI_ARGC); + return neighbor_ident_add_neighbor(vty, vty->index, &n); +} + +DEFUN(cfg_neighbor_add_cgi_ps_arfcn_bsic, cfg_neighbor_add_cgi_ps_arfcn_bsic_cmd, + NEIGHBOR_ADD_CMD CGI_PS_PARAMS " " CELL_AB_VTY_PARAMS, + NEIGHBOR_ADD_DOC CGI_PS_DOC CELL_AB_VTY_DOC) +{ + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + .cell_id.ab_present = true, + }; + if (neighbor_ident_vty_parse_cgi_ps(vty, &n.cell_id.id, argv)) return CMD_WARNING; - return add_remote_or_local_bts(vty, cell_id, &nik); + neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + CGI_PS_ARGC); + return neighbor_ident_add_neighbor(vty, vty->index, &n); } DEFUN(cfg_neighbor_del_bts_nr, cfg_neighbor_del_bts_nr_cmd, NEIGHBOR_DEL_CMD LOCAL_BTS_PARAMS, NEIGHBOR_DEL_DOC LOCAL_BTS_DOC) { - return del_local_bts(vty, neighbor_ident_vty_parse_bts_nr(vty, argv)); + struct neighbor n = { + .type = NEIGHBOR_TYPE_BTS_NR, + .bts_nr = atoi(argv[0]), + }; + return neighbor_ident_del_neighbor(vty, vty->index, &n); } -DEFUN(cfg_neighbor_del_arfcn_bsic, cfg_neighbor_del_arfcn_bsic_cmd, - NEIGHBOR_DEL_CMD NEIGHBOR_IDENT_VTY_KEY_PARAMS, - NEIGHBOR_DEL_DOC NEIGHBOR_IDENT_VTY_KEY_DOC) +DEFUN(cfg_neighbor_del_lac, cfg_neighbor_del_lac_cmd, + NEIGHBOR_DEL_CMD LAC_PARAMS, + NEIGHBOR_DEL_DOC LAC_DOC) { - struct neighbor_ident_key key; + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + }; + if (neighbor_ident_vty_parse_lac(vty, &n.cell_id.id, argv)) + return CMD_WARNING; + return neighbor_ident_del_neighbor(vty, vty->index, &n); +} - if (!neighbor_ident_vty_parse_key_params(vty, argv, &key)) +DEFUN(cfg_neighbor_del_lac_ci, cfg_neighbor_del_lac_ci_cmd, + NEIGHBOR_DEL_CMD LAC_CI_PARAMS, + NEIGHBOR_DEL_DOC LAC_CI_DOC) +{ + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + }; + if (neighbor_ident_vty_parse_lac_ci(vty, &n.cell_id.id, argv)) return CMD_WARNING; + return neighbor_ident_del_neighbor(vty, vty->index, &n); +} - return del_by_key(vty, &key); +DEFUN(cfg_neighbor_del_cgi, cfg_neighbor_del_cgi_cmd, + NEIGHBOR_DEL_CMD CGI_PARAMS, + NEIGHBOR_DEL_DOC CGI_DOC) +{ + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + }; + if (neighbor_ident_vty_parse_cgi(vty, &n.cell_id.id, argv)) + return CMD_WARNING; + return neighbor_ident_del_neighbor(vty, vty->index, &n); +} + +DEFUN(cfg_neighbor_del_cgi_ps, cfg_neighbor_del_cgi_ps_cmd, + NEIGHBOR_DEL_CMD CGI_PS_PARAMS, + NEIGHBOR_DEL_DOC CGI_PS_DOC) +{ + struct neighbor n = { + .type = NEIGHBOR_TYPE_CELL_ID, + }; + if (neighbor_ident_vty_parse_cgi_ps(vty, &n.cell_id.id, argv)) + return CMD_WARNING; + return neighbor_ident_del_neighbor(vty, vty->index, &n); +} + +DEFUN(cfg_neighbor_del_arfcn_bsic, cfg_neighbor_del_arfcn_bsic_cmd, + NEIGHBOR_DEL_CMD CELL_AB_VTY_PARAMS, + NEIGHBOR_DEL_DOC CELL_AB_VTY_DOC) +{ + struct cell_ab ab; + neighbor_ident_vty_parse_arfcn_bsic(&ab, argv); + return del_neighbor_by_cell_ab(vty, &ab); } DEFUN(cfg_neighbor_del_all, cfg_neighbor_del_all_cmd, @@ -525,107 +487,107 @@ DEFUN(cfg_neighbor_del_all, cfg_neighbor_del_all_cmd, return neighbor_del_all(vty); } -struct write_neighbor_ident_entry_data { - struct vty *vty; - const char *indent; - struct gsm_bts *bts; -}; +DEFUN(cfg_neighbor_bind, cfg_neighbor_bind_cmd, + "neighbor-resolution bind " VTY_IPV46_CMD " [<0-65535>]", + NEIGHBOR_DOC "Bind Neighbor Resolution Service (CTRL interface) to given ip and port\n" + IP_STR IPV6_STR "Port to bind the service to [defaults to 4248 if not provided]\n") +{ + vty_out(vty, "%% Warning: The CTRL interface for Neighbor Address Resolution is now deprecated." + "Upgrade osmo-pcu and drop the 'neighbor-resolution bind " VTY_IPV46_CMD " [<0-65535>]' VTY " + "option in order to let osmo-pcu use the new resolution method using the PCUIF over IPA " + "multiplex, which will work out of the box without required configuration.%s", VTY_NEWLINE); + osmo_talloc_replace_string(bsc_gsmnet, &bsc_gsmnet->neigh_ctrl.addr, argv[0]); + if (argc > 1) + bsc_gsmnet->neigh_ctrl.port = atoi(argv[1]); + else + bsc_gsmnet->neigh_ctrl.port = OSMO_CTRL_PORT_BSC_NEIGH; + return CMD_SUCCESS; +} + +void neighbor_ident_vty_write_network(struct vty *vty, const char *indent) +{ + if (bsc_gsmnet->neigh_ctrl.addr) + vty_out(vty, "%sneighbor-resolution bind %s %" PRIu16 "%s", indent, bsc_gsmnet->neigh_ctrl.addr, + bsc_gsmnet->neigh_ctrl.port, VTY_NEWLINE); +} -static bool write_neighbor_ident_list(const struct neighbor_ident_key *key, - const struct gsm0808_cell_id_list2 *val, - void *cb_data) +static int vty_write_cell_id_u(struct vty *vty, enum CELL_IDENT id_discr, const union gsm0808_cell_id_u *cell_id_u) { - struct write_neighbor_ident_entry_data *d = cb_data; - struct vty *vty = d->vty; - int i; - - if (d->bts) { - if (d->bts->nr != key->from_bts) - return true; - } else if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS) - return true; - -#define NEIGH_BSS_WRITE(fmt, args...) do { \ - vty_out(vty, "%sneighbor " fmt " arfcn %u ", d->indent, ## args, key->arfcn); \ - if (key->bsic == BSIC_ANY) \ - vty_out(vty, "bsic any"); \ - else \ - vty_out(vty, "bsic %u", key->bsic & 0x3f); \ - vty_out(vty, "%s", VTY_NEWLINE); \ - } while(0) - - switch (val->id_discr) { + const struct osmo_cell_global_id *cgi; + const struct osmo_cell_global_id_ps *cgi_ps; + + switch (id_discr) { case CELL_IDENT_LAC: - for (i = 0; i < val->id_list_len; i++) { - NEIGH_BSS_WRITE("lac %u", val->id_list[i].lac); - } + vty_out(vty, "lac %u", cell_id_u->lac); break; case CELL_IDENT_LAC_AND_CI: - for (i = 0; i < val->id_list_len; i++) { - NEIGH_BSS_WRITE("lac-ci %u %u", - val->id_list[i].lac_and_ci.lac, - val->id_list[i].lac_and_ci.ci); - } + vty_out(vty, "lac-ci %u %u", cell_id_u->lac_and_ci.lac, cell_id_u->lac_and_ci.ci); break; case CELL_IDENT_WHOLE_GLOBAL: - for (i = 0; i < val->id_list_len; i++) { - const struct osmo_cell_global_id *cgi = &val->id_list[i].global; - NEIGH_BSS_WRITE("cgi %s %s %u %u", - osmo_mcc_name(cgi->lai.plmn.mcc), - osmo_mnc_name(cgi->lai.plmn.mnc, cgi->lai.plmn.mnc_3_digits), - cgi->lai.lac, cgi->cell_identity); - } + cgi = &cell_id_u->global; + vty_out(vty, "cgi %s %s %u %u", + osmo_mcc_name(cgi->lai.plmn.mcc), + osmo_mnc_name(cgi->lai.plmn.mnc, cgi->lai.plmn.mnc_3_digits), + cgi->lai.lac, cgi->cell_identity); + break; + case CELL_IDENT_WHOLE_GLOBAL_PS: + cgi_ps = &cell_id_u->global_ps; + vty_out(vty, "cgi-ps %s %s %u %u %u", + osmo_mcc_name(cgi_ps->rai.lac.plmn.mcc), + osmo_mnc_name(cgi_ps->rai.lac.plmn.mnc, cgi_ps->rai.lac.plmn.mnc_3_digits), + cgi_ps->rai.lac.lac, cgi_ps->rai.rac, + cgi_ps->cell_identity); break; default: - vty_out(vty, "%% Unsupported Cell Identity%s", VTY_NEWLINE); + return -1; } -#undef NEIGH_BSS_WRITE - - return true; + return 0; } -void neighbor_ident_vty_write_remote_bss(struct vty *vty, const char *indent, struct gsm_bts *bts) +void neighbor_ident_vty_write_bts(struct vty *vty, const char *indent, struct gsm_bts *bts) { - struct write_neighbor_ident_entry_data d = { - .vty = vty, - .indent = indent, - .bts = bts, - }; + struct neighbor *n; - neighbor_ident_iter(g_neighbor_cells, write_neighbor_ident_list, &d); -} + llist_for_each_entry(n, &bts->neighbors, entry) { + switch (n->type) { + case NEIGHBOR_TYPE_BTS_NR: + vty_out(vty, "%sneighbor bts %u%s", indent, n->bts_nr, VTY_NEWLINE); + break; -void neighbor_ident_vty_write_local_neighbors(struct vty *vty, const char *indent, struct gsm_bts *bts) -{ - struct gsm_bts_ref *neigh; + case NEIGHBOR_TYPE_CELL_ID: + vty_out(vty, "%sneighbor ", indent); + if (vty_write_cell_id_u(vty, n->cell_id.id.id_discr, &n->cell_id.id.id)) { + vty_out(vty, "[Unsupported Cell Identity]%s", VTY_NEWLINE); + continue; + } + + if (n->cell_id.ab_present) { + vty_out(vty, " arfcn %u ", n->cell_id.ab.arfcn); + if (n->cell_id.ab.bsic == BSIC_ANY) + vty_out(vty, "bsic any"); + else + vty_out(vty, "bsic %u", n->cell_id.ab.bsic & 0x3f); + } + vty_out(vty, "%s", VTY_NEWLINE); + break; - llist_for_each_entry(neigh, &bts->local_neighbors, entry) { - vty_out(vty, "%sneighbor bts %u%s", indent, neigh->bts->nr, VTY_NEWLINE); + default: + /* Ignore anything invalid */ + break; + } } } -void neighbor_ident_vty_write(struct vty *vty, const char *indent, struct gsm_bts *bts) -{ - neighbor_ident_vty_write_local_neighbors(vty, indent, bts); - neighbor_ident_vty_write_remote_bss(vty, indent, bts); -} - DEFUN(show_bts_neighbor, show_bts_neighbor_cmd, - "show bts <0-255> neighbor " NEIGHBOR_IDENT_VTY_KEY_PARAMS, + "show bts <0-255> neighbor " CELL_AB_VTY_PARAMS, SHOW_STR "Display information about a BTS\n" "BTS number\n" "Query which cell would be the target for this neighbor ARFCN+BSIC\n" - NEIGHBOR_IDENT_VTY_KEY_DOC) + CELL_AB_VTY_DOC) { - int found = 0; - struct neighbor_ident_key key; - struct gsm_bts_ref *neigh; - const struct gsm0808_cell_id_list2 *res; - struct gsm_bts *bts = gsm_bts_num(g_net, atoi(argv[0])); - struct write_neighbor_ident_entry_data d = { - .vty = vty, - .indent = "% ", - .bts = bts, - }; + struct cell_ab ab; + struct gsm_bts *local_neighbor = NULL; + struct gsm0808_cell_id_list2 remote_neighbors = { 0 }; + struct gsm_bts *bts = gsm_bts_num(bsc_gsmnet, atoi(argv[0])); if (!bts) { vty_out(vty, "%% Error: cannot find BTS '%s'%s", argv[0], @@ -633,43 +595,57 @@ DEFUN(show_bts_neighbor, show_bts_neighbor_cmd, return CMD_WARNING; } - if (!neighbor_ident_bts_parse_key_params(vty, bts, &argv[1], &key)) - return CMD_WARNING; + neighbor_ident_vty_parse_arfcn_bsic(&ab, &argv[1]); - /* Is there a local BTS that matches the key? */ - llist_for_each_entry(neigh, &bts->local_neighbors, entry) { - if (!neighbor_ident_key_matches_bts(&key, neigh->bts)) - continue; - vty_out(vty, "%% %s resolves to local BTS %u lac-ci %u %u%s", - neighbor_ident_key_name(&key), neigh->bts->nr, neigh->bts->location_area_code, - neigh->bts->cell_identity, VTY_NEWLINE); - found++; + switch (resolve_neighbors(&local_neighbor, &remote_neighbors, bts, &ab, true)) { + case 0: + break; + case -ENOENT: + vty_out(vty, "%% No entry for BTS %u -> %s%s", bts->nr, cell_ab_to_str_c(OTC_SELECT, &ab), VTY_NEWLINE); + return CMD_WARNING; + default: + vty_out(vty, "%% Error while resolving neighbors BTS %u -> %s%s", bts->nr, + cell_ab_to_str_c(OTC_SELECT, &ab), VTY_NEWLINE); + return CMD_WARNING; } - res = neighbor_ident_get(g_neighbor_cells, &key); - if (res) { - write_neighbor_ident_list(&key, res, &d); - found++; + /* From successful rc == 0, there is exactly either a local_neighbor or a nonempty remote_neighbors list. */ + + vty_out(vty, "%% BTS %u -> %s resolves to", bts->nr, cell_ab_to_str_c(OTC_SELECT, &ab)); + if (local_neighbor) { + vty_out(vty, " local BTS %u lac-ci %u %u%s", + local_neighbor->nr, + local_neighbor->location_area_code, + local_neighbor->cell_identity, VTY_NEWLINE); } - if (!found) - vty_out(vty, "%% No entry for %s%s", neighbor_ident_key_name(&key), VTY_NEWLINE); + if (remote_neighbors.id_list_len) { + vty_out(vty, " remote-BSS neighbors: %s%s", + gsm0808_cell_id_list_name_c(OTC_SELECT, &remote_neighbors), + VTY_NEWLINE); + } return CMD_SUCCESS; } -void neighbor_ident_vty_init(struct gsm_network *net, struct neighbor_ident_list *nil) +void neighbor_ident_vty_init(void) { - g_net = net; - g_neighbor_cells = nil; + install_element(GSMNET_NODE, &cfg_neighbor_bind_cmd); + install_element(BTS_NODE, &cfg_neighbor_add_bts_nr_cmd); install_element(BTS_NODE, &cfg_neighbor_add_lac_cmd); install_element(BTS_NODE, &cfg_neighbor_add_lac_ci_cmd); install_element(BTS_NODE, &cfg_neighbor_add_cgi_cmd); + install_element(BTS_NODE, &cfg_neighbor_add_cgi_ps_cmd); install_element(BTS_NODE, &cfg_neighbor_add_lac_arfcn_bsic_cmd); install_element(BTS_NODE, &cfg_neighbor_add_lac_ci_arfcn_bsic_cmd); install_element(BTS_NODE, &cfg_neighbor_add_cgi_arfcn_bsic_cmd); + install_element(BTS_NODE, &cfg_neighbor_add_cgi_ps_arfcn_bsic_cmd); install_element(BTS_NODE, &cfg_neighbor_del_bts_nr_cmd); + install_element(BTS_NODE, &cfg_neighbor_del_lac_cmd); + install_element(BTS_NODE, &cfg_neighbor_del_lac_ci_cmd); + install_element(BTS_NODE, &cfg_neighbor_del_cgi_cmd); + install_element(BTS_NODE, &cfg_neighbor_del_cgi_ps_cmd); install_element(BTS_NODE, &cfg_neighbor_del_arfcn_bsic_cmd); install_element(BTS_NODE, &cfg_neighbor_del_all_cmd); install_element_ve(&show_bts_neighbor_cmd); diff --git a/src/osmo-bsc/net_init.c b/src/osmo-bsc/net_init.c index 6cbb40cdb..e5d3fad28 100644 --- a/src/osmo-bsc/net_init.c +++ b/src/osmo-bsc/net_init.c @@ -25,41 +25,71 @@ #include <osmocom/bsc/handover_cfg.h> #include <osmocom/bsc/chan_alloc.h> #include <osmocom/bsc/neighbor_ident.h> +#include <osmocom/bsc/bts_setup_ramp.h> +#include <osmocom/bsc/paging.h> static struct osmo_tdef gsm_network_T_defs[] = { - { .T=7, .default_val=10, .desc="inter-BSC/MSC Handover outgoing, BSSMAP HO Required to HO Command timeout" }, - { .T=8, .default_val=10, .desc="inter-BSC/MSC Handover outgoing, BSSMAP HO Command to final Clear timeout" }, - { .T=10, .default_val=6, .desc="RR Assignment" }, - { .T=101, .default_val=10, .desc="inter-BSC/MSC Handover incoming, BSSMAP HO Request to HO Accept" }, - { .T=3101, .default_val=3, .desc="RR Immediate Assignment" }, - { .T=3103, .default_val=5, .desc="Handover" }, - { .T=3105, .default_val=100, .unit=OSMO_TDEF_MS, .desc="Physical Information" }, - { .T=3107, .default_val=5, .desc="(unused)" }, - { .T=3109, .default_val=5, .desc="RSL SACCH deactivation" }, - { .T=3111, .default_val=2, .desc="Wait time before RSL RF Channel Release" }, - { .T=3113, .default_val=7, .desc="Paging"}, - { .T=3115, .default_val=10, .desc="(unused)" }, - { .T=3117, .default_val=10, .desc="(unused)" }, - { .T=3119, .default_val=10, .desc="(unused)" }, - { .T=3122, .default_val=GSM_T3122_DEFAULT, .desc="Wait time after RR Immediate Assignment Reject" }, - { .T=3141, .default_val=10, .desc="(unused)" }, - { .T=3212, .default_val=5, .unit=OSMO_TDEF_CUSTOM, - .desc="Periodic Location Update timer, sent to MS (1 = 6 minutes)" }, - { .T=-4, .default_val=60, .desc="After Clear Request, wait for MSC to Clear Command (sanity)" }, - { .T=-5, .default_val=5, .desc="Timeout to switch dynamic timeslot PCHAN modes"}, - { .T=-6, .default_val=5, .desc="Timeout for RSL Channel Activate ACK after sending RSL Channel Activate" }, - { .T=-7, .default_val=5, .desc="Timeout for RSL IPA CRCX ACK after sending RSL IPA CRCX" }, - { .T=-8, .default_val=5, .desc="Timeout for RSL IPA MDCX ACK after sending RSL IPA MDCX" }, - { .T=-9, .default_val=5, .desc="Timeout for availability of MGW endpoint" }, - { .T=-10, .default_val=5, .desc="Timeout for fully configured MGW endpoint" }, - { .T=-11, .default_val=5, .desc="Timeout for Perform Location Response from SMLC" }, - { .T=-3111, .default_val=4, .desc="Wait time after lchan was released in error (should be T3111 + 2s)" }, - { .T=-3210, .default_val=20, .desc="After L3 Complete, wait for MSC to confirm" }, + { .T = 4, .default_val = 5, .desc = "Timeout to receive BSSMAP RESET ACKNOWLEDGE from the MSC" }, + { .T = 7, .default_val = 10, .desc = "inter-BSC/MSC Handover outgoing, BSSMAP HO Required to HO Command timeout" }, + { .T = 8, .default_val = 10, .desc = "inter-BSC/MSC Handover outgoing, BSSMAP HO Command to final Clear timeout" }, + { .T = 10, .default_val = 6, .desc = "RR Assignment" }, + { .T = 101, .default_val = 10, .desc = "inter-BSC/MSC Handover incoming, BSSMAP HO Request to HO Accept" }, + { .T = 3101, .default_val = 3, .desc = "RR Immediate Assignment" }, + { .T = 3103, .default_val = 5, .desc = "Handover" }, + { .T = 3105, .default_val = GSM_T3105_DEFAULT, .min_val = 1, .unit = OSMO_TDEF_MS, .desc = "Physical Information" }, + { .T = 3107, .default_val = 5, .desc = "(unused)" }, + { .T = 3109, .default_val = 5, .desc = "RSL SACCH deactivation" }, + { .T = 3111, .default_val = 2, .desc = "Wait time before RSL RF Channel Release" }, + { .T = 3113, .default_val = 7, .desc = "Paging"}, + { .T = 3115, .default_val = 10, .desc = "(unused)" }, + { .T = 3117, .default_val = 10, .desc = "(unused)" }, + { .T = 3119, .default_val = 10, .desc = "(unused)" }, + { .T = 3122, .default_val = GSM_T3122_DEFAULT, .desc = "Wait time after RR Immediate Assignment Reject" }, + { .T = 3141, .default_val = 10, .desc = "(unused)" }, + { .T = 3212, .default_val = 5, .unit = OSMO_TDEF_CUSTOM, + .desc = "Periodic Location Update timer, sent to MS (1 = 6 minutes)" }, + { .T = -4, .default_val = 60, .desc = "After Clear Request, wait for MSC to Clear Command (sanity)" }, + { .T = -5, .default_val = 5, .desc = "Timeout to switch dynamic timeslot PCHAN modes"}, + { .T = -6, .default_val = 5, .desc = "Timeout for RSL Channel Activate ACK after sending RSL Channel Activate" }, + { .T = -7, .default_val = 5, .desc = "Timeout for RSL IPA CRCX ACK after sending RSL IPA CRCX" }, + { .T = -8, .default_val = 5, .desc = "Timeout for RSL IPA MDCX ACK after sending RSL IPA MDCX" }, + { .T = -9, .default_val = 5, .desc = "Timeout for availability of MGW endpoint" }, + { .T = -10, .default_val = 5, .desc = "Timeout for fully configured MGW endpoint" }, + { .T = -11, .default_val = 5, .desc = "Timeout for Perform Location Response from SMLC" }, + { .T = -12, .default_val = 5, .desc = "Timeout for obtaining TA after BSSLAP TA Request" }, + { .T = -13, .default_val = 5, .desc = "Timeout for RR Channel Mode Modify ACK (BSC <-> MS)" }, + { .T = -14, .default_val = 5, .desc = "Timeout for RSL Channel Mode Modify ACK (BSC <-> BTS)" }, + { .T = -16, .default_val = 1000, .unit = OSMO_TDEF_MS, + .desc = "Granularity for all_allocated:* rate counters: amount of milliseconds that one counter increment" + " represents. See also X17, X18" }, + { .T = -17, .default_val = 0, .unit = OSMO_TDEF_MS, + .desc = "Rounding threshold for all_allocated:* rate counters: round up to the next counter increment" + " after this many milliseconds. If set to half of X16 (or 0), employ the usual round() behavior:" + " round up after half of a granularity period. If set to 1, behave like ceil(): already" + " increment the counter immediately when all channels are allocated. If set >= X16, behave like" + " floor(): only increment after a full X16 period of all channels being occupied." + " See also X16, X18" }, + { .T = -18, .default_val = 60000, .unit = OSMO_TDEF_MS, + .desc = "Forget-sum period for all_allocated:* rate counters:" + " after this amount of idle time, forget internally cumulated time remainders. Zero to always" + " keep remainders. See also X16, X17." }, + { .T = -25, .default_val = 5, .desc = "Timeout for initial user data after an MSC initiated an SCCP connection to the BSS" }, + { .T = -28, .default_val = 30, .desc = "Interval at which to try to recover a BORKEN lchan" }, + { .T = -3105, .default_val = GSM_NY1_DEFAULT, .unit = OSMO_TDEF_CUSTOM, + .desc = "Ny1: Maximum number of Physical Information (re)transmissions" }, + { .T = -3111, .default_val = 4, .desc = "Wait time after lchan was released in error (should be T3111 + 2s)" }, + { .T = -3113, .default_val = PAGING_THRESHOLD_X3113_DEFAULT_SEC, + .desc = "Maximum Paging Request Transmit Delay Threshold: " \ + "If the estimated transmit delay of the messages in the paging queue surpasses this threshold, then new incoming " + "paging requests will if possible replace a request in retransmission state from the queue or otherwise be discarded, " + "hence limiting the size of the queue and maximum delay of its scheduled requests. " + "X3113 also serves as the upper boundary for dynamic T3113 when estimating the expected maximum delay to get a response" }, + { .T = -3210, .default_val = 20, .desc = "After L3 Complete, wait for MSC to confirm" }, {} }; struct osmo_tdef g_mgw_tdefs[] = { - { .T=-2427, .default_val=5, .desc="timeout for MGCP response from MGW" }, + { .T = -2427, .default_val = 5, .desc = "timeout for MGCP response from MGW" }, {} }; @@ -90,8 +120,7 @@ struct gsm_network *gsm_network_init(void *ctx) INIT_LLIST_HEAD(&net->subscr_conns); - net->bsc_subscribers = talloc_zero(net, struct llist_head); - INIT_LLIST_HEAD(net->bsc_subscribers); + net->bsc_subscribers = bsc_subscr_store_alloc(net); INIT_LLIST_HEAD(&net->bts_list); net->num_bts = 0; @@ -105,5 +134,7 @@ struct gsm_network *gsm_network_init(void *ctx) net->null_nri_ranges = osmo_nri_ranges_alloc(net); net->nri_bitlen = OSMO_NRI_BITLEN_DEFAULT; + bts_setup_ramp_init_network(net); + return net; } diff --git a/src/osmo-bsc/nm_bb_transc_fsm.c b/src/osmo-bsc/nm_bb_transc_fsm.c index c29f53d3c..a781cb1dc 100644 --- a/src/osmo-bsc/nm_bb_transc_fsm.c +++ b/src/osmo-bsc/nm_bb_transc_fsm.c @@ -1,6 +1,6 @@ /* NM BaseBand Transceiver FSM */ -/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * Author: Pau Espin Pedrol <pespin@sysmocom.de> * * All Rights Reserved @@ -13,7 +13,7 @@ * 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 General Public License for more details. + * 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/>. @@ -32,6 +32,7 @@ #include <osmocom/bsc/signal.h> #include <osmocom/bsc/abis_nm.h> #include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h> +#include <osmocom/bsc/ipaccess.h> #include <osmocom/bsc/nm_common_fsm.h> #include <osmocom/bsc/debug.h> @@ -40,6 +41,18 @@ #define nm_bb_transc_fsm_state_chg(fi, NEXT_STATE) \ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) +static inline void nm_bb_transc_fsm_becomes_enabled(struct gsm_bts_bb_trx *bb_transc) +{ + struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); + nm_obj_fsm_becomes_enabled_disabled(trx->bts, bb_transc, NM_OC_BASEB_TRANSC, true); +} + +static inline void nm_bb_transc_fsm_becomes_disabled(struct gsm_bts_bb_trx *bb_transc) +{ + struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); + nm_obj_fsm_becomes_enabled_disabled(trx->bts, bb_transc, NM_OC_BASEB_TRANSC, false); +} + ////////////////////////// // FSM STATE ACTIONS ////////////////////////// @@ -48,21 +61,30 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3 { struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; + bb_transc->mo.sw_act_rep_received = false; + bb_transc->mo.get_attr_sent = false; + bb_transc->mo.get_attr_rep_received = false; bb_transc->mo.adm_unlock_sent = false; + bb_transc->mo.rsl_connect_sent = false; + bb_transc->mo.rsl_connect_ack_received = false; bb_transc->mo.opstart_sent = false; } static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_SW_ACT_REP: + bb_transc->mo.sw_act_rep_received = true; + break; + case NM_EV_SETUP_RAMP_READY: break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /*should not happen... */ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_ENABLED); @@ -84,10 +106,36 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event } } -static void configure_loop(struct gsm_bts_bb_trx *bb_transc, struct gsm_nm_state *state, bool allow_opstart) +static void configure_loop(struct gsm_bts_bb_trx *bb_transc, const struct gsm_nm_state *state, bool allow_opstart) { struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); - if (state->administrative != NM_STATE_UNLOCKED && !bb_transc->mo.adm_unlock_sent) { + + if (bts_setup_ramp_wait(trx->bts)) + return; + + /* nanoBTS only: delay until SW Activated Report is received, which + * tells us the IPA Object version (may be used to set attr conditionally). */ + if (is_nanobts(trx->bts) && !bb_transc->mo.sw_act_rep_received) + return; + + /* Request TRX-level attributes */ + if (!bb_transc->mo.get_attr_sent && !bb_transc->mo.get_attr_rep_received) { + uint8_t attr_buf[3]; /* enlarge if needed */ + uint8_t *ptr = &attr_buf[0]; + + *(ptr++) = NM_ATT_MANUF_STATE; + *(ptr++) = NM_ATT_SW_CONFIG; + if (is_ipa_abisip_bts(trx->bts)) + *(ptr++) = NM_ATT_IPACC_SUPP_FEATURES; + + OSMO_ASSERT((ptr - attr_buf) <= sizeof(attr_buf)); + abis_nm_get_attr(trx->bts, NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, + &attr_buf[0], (ptr - attr_buf)); + bb_transc->mo.get_attr_sent = true; + } + + if (bb_transc->mo.get_attr_rep_received && + state->administrative != NM_STATE_UNLOCKED && !bb_transc->mo.adm_unlock_sent) { bb_transc->mo.adm_unlock_sent = true; /* Note: nanoBTS sometimes fails NACKing the BaseBand Transceiver Unlock command while in Dependency, specially @@ -98,14 +146,22 @@ static void configure_loop(struct gsm_bts_bb_trx *bb_transc, struct gsm_nm_state NM_STATE_UNLOCKED); } + /* Provision BTS with RSL IP addr & port to connect to: */ + if (allow_opstart && state->administrative == NM_STATE_UNLOCKED && + !bb_transc->mo.rsl_connect_sent && !bb_transc->mo.rsl_connect_ack_received) { + bb_transc->mo.rsl_connect_sent = true; + abis_nm_ipaccess_rsl_connect(trx, trx->bts->ip_access.rsl_ip, + 3003, trx->rsl_tei_primary); + } + + /* OPSTART after receiving RSL CONNECT ACK. We cannot delay until the + * RSL/IPA socket is connected to us because nanoBTS only attempts + * connection after receiving an OPSTART: */ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED && - !bb_transc->mo.opstart_sent) { + bb_transc->mo.rsl_connect_ack_received && !bb_transc->mo.opstart_sent) { bb_transc->mo.opstart_sent = true; abis_nm_opstart(trx->bts, NM_OC_BASEB_TRANSC, trx->bts->bts_nr, trx->nr, 0xff); - /* TRX software is active, tell it to initiate RSL Link */ - abis_nm_ipaccess_rsl_connect(trx, trx->bts->ip_access.rsl_ip, - 3003, trx->rsl_tei); - } + } } static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) @@ -113,7 +169,7 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); - if (trx->bts->site_mgr.peer_has_no_avstate_offline) { + if (trx->bts->site_mgr->peer_has_no_avstate_offline) { nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE); return; } @@ -123,13 +179,31 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; + struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + bb_transc->mo.sw_act_rep_received = true; + configure_loop(bb_transc, &bb_transc->mo.nm_state, false); + break; + case NM_EV_GET_ATTR_REP: + bb_transc->mo.get_attr_rep_received = true; + bb_transc->mo.get_attr_sent = false; + configure_loop(bb_transc, &bb_transc->mo.nm_state, false); + return; + case NM_EV_RSL_CONNECT_ACK: + bb_transc->mo.rsl_connect_ack_received = true; + bb_transc->mo.rsl_connect_sent = false; + configure_loop(bb_transc, &bb_transc->mo.nm_state, false); + break; + case NM_EV_RSL_CONNECT_NACK: + ipaccess_drop_oml_deferred(trx->bts); + break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /* should not happen... */ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_ENABLED); @@ -150,6 +224,9 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(bb_transc, &bb_transc->mo.nm_state, false); + break; default: OSMO_ASSERT(0); } @@ -159,8 +236,6 @@ static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t p { struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; - /* Warning: In here we may be acessing an state older than new_state - from prev (syncrhonous) FSM state */ configure_loop(bb_transc, &bb_transc->mo.nm_state, true); } @@ -169,12 +244,29 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + bb_transc->mo.sw_act_rep_received = true; + configure_loop(bb_transc, &bb_transc->mo.nm_state, true); + break; + case NM_EV_GET_ATTR_REP: + bb_transc->mo.get_attr_rep_received = true; + bb_transc->mo.get_attr_sent = false; + configure_loop(bb_transc, &bb_transc->mo.nm_state, true); + return; + case NM_EV_RSL_CONNECT_ACK: + bb_transc->mo.rsl_connect_ack_received = true; + bb_transc->mo.rsl_connect_sent = false; + configure_loop(bb_transc, &bb_transc->mo.nm_state, true); + break; + case NM_EV_RSL_CONNECT_NACK: + ipaccess_drop_oml_deferred(trx->bts); + break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_ENABLED); return; @@ -187,7 +279,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi case NM_AVSTATE_DEPENDENCY: /* There's no point in moving back to Dependency, since it's broken and it acts actually as if it was in Offline state */ - if (!trx->bts->site_mgr.peer_has_no_avstate_offline) { + if (!trx->bts->site_mgr->peer_has_no_avstate_offline) { nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY); } else { /* Moreover, in nanoBTS we need to check here for tx @@ -203,6 +295,9 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(bb_transc, &bb_transc->mo.nm_state, true); + break; default: OSMO_ASSERT(0); } @@ -214,31 +309,41 @@ static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state /* Reset state, we don't need it in this state and it will need to be reused as soon as we move back to Disabled */ - bb_transc->mo.opstart_sent = false; + bb_transc->mo.get_attr_sent = false; + bb_transc->mo.get_attr_rep_received = false; bb_transc->mo.adm_unlock_sent = false; + bb_transc->mo.rsl_connect_ack_received = false; + bb_transc->mo.rsl_connect_sent = false; + bb_transc->mo.opstart_sent = false; + + nm_bb_transc_fsm_becomes_enabled(bb_transc); } static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) return; switch (new_state->availability) { /* operational = DISABLED */ case NM_AVSTATE_NOT_INSTALLED: case NM_AVSTATE_POWER_OFF: + nm_bb_transc_fsm_becomes_disabled(bb_transc); nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED); return; case NM_AVSTATE_DEPENDENCY: + nm_bb_transc_fsm_becomes_disabled(bb_transc); nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY); return; case NM_AVSTATE_OFF_LINE: case NM_AVSTATE_OK: + nm_bb_transc_fsm_becomes_disabled(bb_transc); nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE); return; default: @@ -260,7 +365,11 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) bb_transc->mo.opstart_sent = false; break; case NM_EV_OML_DOWN: - nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED); + if (fi->state != NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED) { + if (fi->state == NM_BB_TRANSC_ST_OP_ENABLED) + nm_bb_transc_fsm_becomes_disabled(bb_transc); + nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED); + } break; default: OSMO_ASSERT(0); @@ -271,7 +380,8 @@ static struct osmo_fsm_state nm_bb_transc_fsm_states[] = { [NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED] = { .in_event_mask = X(NM_EV_SW_ACT_REP) | - X(NM_EV_STATE_CHG_REP), + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY) | X(NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE) | @@ -282,7 +392,12 @@ static struct osmo_fsm_state nm_bb_transc_fsm_states[] = { }, [NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY] = { .in_event_mask = - X(NM_EV_STATE_CHG_REP), + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_GET_ATTR_REP) | + X(NM_EV_RSL_CONNECT_ACK) | + X(NM_EV_RSL_CONNECT_NACK) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED) | X(NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE) | @@ -293,7 +408,12 @@ static struct osmo_fsm_state nm_bb_transc_fsm_states[] = { }, [NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE] = { .in_event_mask = - X(NM_EV_STATE_CHG_REP), + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_GET_ATTR_REP) | + X(NM_EV_RSL_CONNECT_ACK) | + X(NM_EV_RSL_CONNECT_NACK) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED) | X(NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY) | diff --git a/src/osmo-bsc/nm_bts_fsm.c b/src/osmo-bsc/nm_bts_fsm.c index 5f47fdcc1..aaccea0bf 100644 --- a/src/osmo-bsc/nm_bts_fsm.c +++ b/src/osmo-bsc/nm_bts_fsm.c @@ -1,6 +1,6 @@ /* NM BTS FSM */ -/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * Author: Pau Espin Pedrol <pespin@sysmocom.de> * * All Rights Reserved @@ -13,7 +13,7 @@ * 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 General Public License for more details. + * 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/>. @@ -48,6 +48,9 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3 { struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + bts->mo.sw_act_rep_received = false; + bts->mo.get_attr_sent = false; + bts->mo.get_attr_rep_received = false; bts->mo.set_attr_sent = false; bts->mo.set_attr_ack_received = false; bts->mo.adm_unlock_sent = false; @@ -56,15 +59,19 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3 static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_SW_ACT_REP: + bts->mo.sw_act_rep_received = true; + break; + case NM_EV_SETUP_RAMP_READY: break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /*should not happen... */ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED); @@ -86,30 +93,78 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event } } -static void configure_loop(struct gsm_bts *bts, struct gsm_nm_state *state, bool allow_opstart) { +static void configure_loop(struct gsm_bts *bts, const struct gsm_nm_state *state, bool allow_opstart) +{ struct msgb *msgb; - if (!bts->mo.set_attr_sent && !bts->mo.set_attr_ack_received) { + if (bts_setup_ramp_wait(bts)) + return; + + /* nanoBTS only: delay until SW Activated Report is received, which + * tells us the IPA Object version (may be used to set attr conditionally). */ + if (is_nanobts(bts) && !bts->mo.sw_act_rep_received) + return; + + /* Request generic BTS-level attributes */ + if (!bts->mo.get_attr_sent && !bts->mo.get_attr_rep_received) { + uint8_t attr_buf[3]; /* enlarge if needed */ + uint8_t *ptr = &attr_buf[0]; + + *(ptr++) = NM_ATT_MANUF_ID; + *(ptr++) = NM_ATT_SW_CONFIG; + if (is_ipa_abisip_bts(bts)) + *(ptr++) = NM_ATT_IPACC_SUPP_FEATURES; + + OSMO_ASSERT((ptr - attr_buf) <= sizeof(attr_buf)); + abis_nm_get_attr(bts, NM_OC_BTS, 0, 0xff, 0xff, + &attr_buf[0], (ptr - attr_buf)); + bts->mo.get_attr_sent = true; + } + + if (bts->mo.get_attr_rep_received && + !bts->mo.set_attr_sent && !bts->mo.set_attr_ack_received) { bts->mo.set_attr_sent = true; - msgb = nanobts_attr_bts_get(bts); + msgb = nanobts_gen_set_bts_attr(bts); abis_nm_set_bts_attr(bts, msgb->data, msgb->len); msgb_free(msgb); } - if (state->administrative != NM_STATE_UNLOCKED && !bts->mo.adm_unlock_sent) { + if (bts->mo.set_attr_ack_received && + state->administrative != NM_STATE_UNLOCKED && !bts->mo.adm_unlock_sent) { bts->mo.adm_unlock_sent = true; abis_nm_chg_adm_state(bts, NM_OC_BTS, bts->bts_nr, 0xff, 0xff, NM_STATE_UNLOCKED); + /* Message containing BTS attributes, including the interference band bounds, was ACKed by the BTS. + * Store the sent bounds as the ones being used for logging and comparing interference levels. */ + bts->interf_meas_params_used = bts->interf_meas_params_cfg; } if (allow_opstart && state->administrative == NM_STATE_UNLOCKED && bts->mo.set_attr_ack_received) { - if (!bts->mo.opstart_sent) { - bts->mo.opstart_sent = true; - abis_nm_opstart(bts, NM_OC_BTS, bts->bts_nr, 0xff, 0xff); - } - } + if (!bts->mo.opstart_sent) { + bts->mo.opstart_sent = true; + abis_nm_opstart(bts, NM_OC_BTS, bts->bts_nr, 0xff, 0xff); + } + } +} + +static void rx_get_attr_rep(struct gsm_bts *bts, bool allow_opstart) +{ + struct gsm_gprs_nsvc *nsvc; + + bts->mo.get_attr_rep_received = true; + bts->mo.get_attr_sent = false; + + /* Announce bts_features are available to related NSVC MOs */ + for (int i = 0; i < ARRAY_SIZE(bts->site_mgr->gprs.nsvc); i++) { + nsvc = gsm_bts_sm_nsvc_num(bts->site_mgr, i); + if (nsvc) + osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_FEATURE_NEGOTIATED, NULL); + } + + /* Move FSM forward */ + configure_loop(bts, &bts->mo.nm_state, allow_opstart); } static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) @@ -119,7 +174,7 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_ /* nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST be sent during Dependency, so we simply move to OFFLINE state here to avoid duplicating code */ - if (bts->site_mgr.peer_has_no_avstate_offline) { + if (bts->site_mgr->peer_has_no_avstate_offline) { nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_OFFLINE); return; } @@ -130,9 +185,16 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, { struct gsm_bts *bts = (struct gsm_bts *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + bts->mo.sw_act_rep_received = true; + configure_loop(bts, &bts->mo.nm_state, false); + break; + case NM_EV_GET_ATTR_REP: + rx_get_attr_rep(bts, false); + return; case NM_EV_SET_ATTR_ACK: bts->mo.set_attr_ack_received = true; bts->mo.set_attr_sent = false; @@ -140,7 +202,7 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, return; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /* should not happen... */ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED); @@ -161,6 +223,9 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(bts, &bts->mo.nm_state, false); + break; default: OSMO_ASSERT(0); } @@ -170,8 +235,6 @@ static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t p { struct gsm_bts *bts = (struct gsm_bts *)fi->priv; - /* Warning: In here we may be acessing an state older than new_state - from prev (syncrhonous) FSM state */ configure_loop(bts, &bts->mo.nm_state, true); } @@ -179,9 +242,16 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi { struct gsm_bts *bts = (struct gsm_bts *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + bts->mo.sw_act_rep_received = true; + configure_loop(bts, &bts->mo.nm_state, true); + break; + case NM_EV_GET_ATTR_REP: + rx_get_attr_rep(bts, true); + return; case NM_EV_SET_ATTR_ACK: bts->mo.set_attr_ack_received = true; bts->mo.set_attr_sent = false; @@ -189,7 +259,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi return; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED); return; @@ -202,7 +272,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi case NM_AVSTATE_DEPENDENCY: /* There's no point in moving back to Dependency, since it's broken and it acts actually as if it was in Offline state */ - if (!bts->site_mgr.peer_has_no_avstate_offline) { + if (!bts->site_mgr->peer_has_no_avstate_offline) { nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_DEPENDENCY); } else { /* Moreover, in nanoBTS we need to check here for tx @@ -218,6 +288,9 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(bts, &bts->mo.nm_state, true); + break; default: OSMO_ASSERT(0); } @@ -231,19 +304,28 @@ static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state reused as soon as we move back to Disabled */ bts->mo.opstart_sent = false; bts->mo.adm_unlock_sent = false; + bts->mo.get_attr_sent = false; + bts->mo.get_attr_rep_received = false; bts->mo.set_attr_sent = false; bts->mo.set_attr_ack_received = false; + + /* Resume power saving on the BCCH carrier, if was enabled */ + if (bts->c0_max_power_red_db > 0) { + LOG_BTS(bts, DRSL, LOGL_NOTICE, "Resuming BCCH carrier power reduction " + "operation mode (maximum %u dB)\n", bts->c0_max_power_red_db); + gsm_bts_send_c0_power_red(bts, bts->c0_max_power_red_db); + } } static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) return; switch (new_state->availability) { /* operational = DISABLED */ @@ -277,7 +359,8 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) bts->mo.opstart_sent = false; break; case NM_EV_OML_DOWN: - nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED); + if (fi->state != NM_BTS_ST_OP_DISABLED_NOTINSTALLED) + nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED); break; default: OSMO_ASSERT(0); @@ -288,7 +371,8 @@ static struct osmo_fsm_state nm_bts_fsm_states[] = { [NM_BTS_ST_OP_DISABLED_NOTINSTALLED] = { .in_event_mask = X(NM_EV_SW_ACT_REP) | - X(NM_EV_STATE_CHG_REP), + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BTS_ST_OP_DISABLED_DEPENDENCY) | X(NM_BTS_ST_OP_DISABLED_OFFLINE) | @@ -299,8 +383,11 @@ static struct osmo_fsm_state nm_bts_fsm_states[] = { }, [NM_BTS_ST_OP_DISABLED_DEPENDENCY] = { .in_event_mask = + X(NM_EV_SW_ACT_REP) | X(NM_EV_STATE_CHG_REP) | - X(NM_EV_SET_ATTR_ACK), + X(NM_EV_GET_ATTR_REP) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) | X(NM_BTS_ST_OP_DISABLED_OFFLINE) | @@ -311,8 +398,11 @@ static struct osmo_fsm_state nm_bts_fsm_states[] = { }, [NM_BTS_ST_OP_DISABLED_OFFLINE] = { .in_event_mask = + X(NM_EV_SW_ACT_REP) | X(NM_EV_STATE_CHG_REP) | - X(NM_EV_SET_ATTR_ACK), + X(NM_EV_GET_ATTR_REP) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) | X(NM_BTS_ST_OP_DISABLED_DEPENDENCY) | @@ -322,15 +412,15 @@ static struct osmo_fsm_state nm_bts_fsm_states[] = { .action = st_op_disabled_offline, }, [NM_BTS_ST_OP_ENABLED] = { - .in_event_mask = - X(NM_EV_STATE_CHG_REP), - .out_state_mask = - X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) | - X(NM_BTS_ST_OP_DISABLED_DEPENDENCY) | - X(NM_BTS_ST_OP_DISABLED_OFFLINE), - .name = "ENABLED", - .onenter = st_op_enabled_on_enter, - .action = st_op_enabled, + .in_event_mask = + X(NM_EV_STATE_CHG_REP), + .out_state_mask = + X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_BTS_ST_OP_DISABLED_DEPENDENCY) | + X(NM_BTS_ST_OP_DISABLED_OFFLINE), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, }, }; diff --git a/src/osmo-bsc/nm_bts_sm_fsm.c b/src/osmo-bsc/nm_bts_sm_fsm.c index ce9e15b0e..24a1ec3c6 100644 --- a/src/osmo-bsc/nm_bts_sm_fsm.c +++ b/src/osmo-bsc/nm_bts_sm_fsm.c @@ -1,6 +1,6 @@ /* NM BTS Site Manager FSM */ -/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * Author: Pau Espin Pedrol <pespin@sysmocom.de> * * All Rights Reserved @@ -13,7 +13,7 @@ * 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 General Public License for more details. + * 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/>. @@ -49,6 +49,7 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3 struct gsm_bts *bts = gsm_bts_sm_get_bts(site_mgr); site_mgr->peer_has_no_avstate_offline = (bts->type == GSM_BTS_TYPE_NANOBTS); + site_mgr->mo.sw_act_rep_received = false; site_mgr->mo.opstart_sent = false; } @@ -57,14 +58,17 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; struct gsm_bts *bts = gsm_bts_sm_get_bts(site_mgr); struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_SW_ACT_REP: + site_mgr->mo.sw_act_rep_received = true; + break; + case NM_EV_SETUP_RAMP_READY: break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /* nanobts always go directly into Reported ENABLED state during startup, but we still need to OPSTART it, otherwise it won't @@ -78,8 +82,8 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event "have your .cfg with 'type nanobts'. Otherwise, you probably " "are using an old osmo-bts; automatically adjusting OML " "behavior to be backward-compatible.\n"); - bts->site_mgr.peer_has_no_avstate_offline = true; } + site_mgr->peer_has_no_avstate_offline = true; nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED); return; } @@ -99,15 +103,41 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event } } +static void configure_loop(struct gsm_bts_sm *site_mgr, const struct gsm_nm_state *_state, bool allow_opstart) +{ + struct gsm_bts *bts = gsm_bts_sm_get_bts(site_mgr); + + if (bts_setup_ramp_wait(bts)) + return; + + /* nanoBTS only: delay until SW Activated Report is received, which + * tells us the IPA Object version (may be used to set attr conditionally). */ + if (is_nanobts(bts) && !site_mgr->mo.sw_act_rep_received) + return; + + if (allow_opstart && !site_mgr->mo.opstart_sent) { + site_mgr->mo.opstart_sent = true; + abis_nm_opstart(bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff); + } +} + + static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + site_mgr->mo.sw_act_rep_received = true; + configure_loop(site_mgr, &site_mgr->mo.nm_state, false); + break; + case NM_EV_SETUP_RAMP_READY: + break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED); return; @@ -132,22 +162,24 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; - struct gsm_bts *bts = gsm_bts_sm_get_bts(site_mgr); - if (!site_mgr->mo.opstart_sent) { - site_mgr->mo.opstart_sent = true; - abis_nm_opstart(bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff); - } + + configure_loop(site_mgr, &site_mgr->mo.nm_state, true); } static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; + struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; switch (event) { + case NM_EV_SW_ACT_REP: + site_mgr->mo.sw_act_rep_received = true; + configure_loop(site_mgr, &site_mgr->mo.nm_state, true); + break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED); return; @@ -166,19 +198,23 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(site_mgr, &site_mgr->mo.nm_state, true); + break; default: OSMO_ASSERT(0); } } + static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) return; switch (new_state->availability) { /* operational = DISABLED */ @@ -212,7 +248,8 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) site_mgr->mo.opstart_sent = false; break; case NM_EV_OML_DOWN: - nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED); + if (fi->state != NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) + nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED); break; default: OSMO_ASSERT(0); @@ -223,7 +260,8 @@ static struct osmo_fsm_state nm_bts_sm_fsm_states[] = { [NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED] = { .in_event_mask = X(NM_EV_SW_ACT_REP) | - X(NM_EV_STATE_CHG_REP), + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY) | X(NM_BTS_SM_ST_OP_DISABLED_OFFLINE) | @@ -234,7 +272,9 @@ static struct osmo_fsm_state nm_bts_sm_fsm_states[] = { }, [NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY] = { .in_event_mask = - X(NM_EV_STATE_CHG_REP), + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) | X(NM_BTS_SM_ST_OP_DISABLED_OFFLINE) | @@ -244,7 +284,9 @@ static struct osmo_fsm_state nm_bts_sm_fsm_states[] = { }, [NM_BTS_SM_ST_OP_DISABLED_OFFLINE] = { .in_event_mask = - X(NM_EV_STATE_CHG_REP), + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) | X(NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY) | diff --git a/src/osmo-bsc/nm_channel_fsm.c b/src/osmo-bsc/nm_channel_fsm.c index 676c471e6..2f389d7ac 100644 --- a/src/osmo-bsc/nm_channel_fsm.c +++ b/src/osmo-bsc/nm_channel_fsm.c @@ -1,6 +1,6 @@ /* NM Radio Channel FSM */ -/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * Author: Pau Espin Pedrol <pespin@sysmocom.de> * * All Rights Reserved @@ -13,7 +13,7 @@ * 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 General Public License for more details. + * 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/>. @@ -35,6 +35,7 @@ #include <osmocom/bsc/ipaccess.h> #include <osmocom/bsc/nm_common_fsm.h> #include <osmocom/bsc/debug.h> +#include <osmocom/bsc/timeslot_fsm.h> #define X(s) (1 << (s)) @@ -58,14 +59,15 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3 static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_SW_ACT_REP: + case NM_EV_SETUP_RAMP_READY: break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /*should not happen... */ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED); @@ -87,24 +89,19 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event } } -static void configure_loop(struct gsm_bts_trx_ts *ts, struct gsm_nm_state *state, bool allow_opstart) +static void configure_loop(struct gsm_bts_trx_ts *ts, const struct gsm_nm_state *state, bool allow_opstart) { enum abis_nm_chan_comb ccomb; struct gsm_bts_trx *trx = ts->trx; + if (bts_setup_ramp_wait(ts->trx->bts)) + return; + if (!ts->mo.set_attr_sent && !ts->mo.set_attr_ack_received) { ts->mo.set_attr_sent = true; ccomb = abis_nm_chcomb4pchan(ts->pchan_from_config); - if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) { - /* FIXME: using this here makes crazy lots of .o - dependencies be fulled in, ending up in - osmo_bsc_main.o which conficts due to containing its - own main() */ - LOGPFSML(ts->mo.fi, LOGL_ERROR, - "FIXME: Here OML link should br dropped, " - "something is wrong in your setup!\n"); - //ipaccess_drop_oml_deferred(trx->bts); - } + if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) + ipaccess_drop_oml_deferred(trx->bts); } if (state->administrative != NM_STATE_UNLOCKED && !ts->mo.adm_unlock_sent) { @@ -125,7 +122,7 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_ { struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; - if (ts->trx->bts->site_mgr.peer_has_no_avstate_offline) { + if (ts->trx->bts->site_mgr->peer_has_no_avstate_offline) { nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE); return; } @@ -136,9 +133,12 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, { struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + configure_loop(ts, &ts->mo.nm_state, false); + break; case NM_EV_SET_ATTR_ACK: ts->mo.set_attr_ack_received = true; ts->mo.set_attr_sent = false; @@ -146,7 +146,7 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, return; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /* should not happen... */ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED); @@ -167,6 +167,9 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(ts, &ts->mo.nm_state, false); + break; default: OSMO_ASSERT(0); } @@ -176,8 +179,6 @@ static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t p { struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; - /* Warning: In here we may be acessing an state older than new_state - from prev (syncrhonous) FSM state */ configure_loop(ts, &ts->mo.nm_state, true); } @@ -185,9 +186,12 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi { struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + configure_loop(ts, &ts->mo.nm_state, true); + break; case NM_EV_SET_ATTR_ACK: ts->mo.set_attr_ack_received = true; ts->mo.set_attr_sent = false; @@ -195,7 +199,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi return; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED); return; @@ -208,7 +212,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi case NM_AVSTATE_DEPENDENCY: /* There's no point in moving back to Dependency, since it's broken and it acts actually as if it was in Offline state */ - if (!ts->trx->bts->site_mgr.peer_has_no_avstate_offline) { + if (!ts->trx->bts->site_mgr->peer_has_no_avstate_offline) { nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY); } else { /* Moreover, in nanoBTS we need to check here for tx @@ -224,6 +228,9 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(ts, &ts->mo.nm_state, true); + break; default: OSMO_ASSERT(0); } @@ -239,17 +246,19 @@ static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state ts->mo.adm_unlock_sent = false; ts->mo.set_attr_ack_received = false; ts->mo.set_attr_sent = false; + + osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL); } static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) return; switch (new_state->availability) { /* operational = DISABLED */ @@ -283,7 +292,10 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) ts->mo.opstart_sent = false; break; case NM_EV_OML_DOWN: - nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED); + if (fi->state != NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) { + osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_DOWN, NULL); + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED); + } break; default: OSMO_ASSERT(0); @@ -294,9 +306,9 @@ static struct osmo_fsm_state nm_chan_fsm_states[] = { [NM_CHAN_ST_OP_DISABLED_NOTINSTALLED] = { .in_event_mask = X(NM_EV_SW_ACT_REP) | - X(NM_EV_STATE_CHG_REP), + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = - X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY) | X(NM_CHAN_ST_OP_DISABLED_OFFLINE) | X(NM_CHAN_ST_OP_ENABLED), @@ -306,8 +318,10 @@ static struct osmo_fsm_state nm_chan_fsm_states[] = { }, [NM_CHAN_ST_OP_DISABLED_DEPENDENCY] = { .in_event_mask = + X(NM_EV_SW_ACT_REP) | X(NM_EV_STATE_CHG_REP) | - X(NM_EV_SET_ATTR_ACK), + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | X(NM_CHAN_ST_OP_DISABLED_OFFLINE) | @@ -318,8 +332,10 @@ static struct osmo_fsm_state nm_chan_fsm_states[] = { }, [NM_CHAN_ST_OP_DISABLED_OFFLINE] = { .in_event_mask = + X(NM_EV_SW_ACT_REP) | X(NM_EV_STATE_CHG_REP) | - X(NM_EV_SET_ATTR_ACK), + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY) | diff --git a/src/osmo-bsc/nm_common_fsm.c b/src/osmo-bsc/nm_common_fsm.c index 42c01e0b1..7719f0672 100644 --- a/src/osmo-bsc/nm_common_fsm.c +++ b/src/osmo-bsc/nm_common_fsm.c @@ -1,6 +1,6 @@ /* NM FSM, common bits */ -/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * Author: Pau Espin Pedrol <pespin@sysmocom.de> * * All Rights Reserved @@ -13,22 +13,78 @@ * 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 General Public License for more details. + * 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/linuxlist.h> +#include <osmocom/bsc/bts.h> #include <osmocom/bsc/nm_common_fsm.h> +#include <osmocom/bsc/signal.h> const struct value_string nm_fsm_event_names[] = { { NM_EV_SW_ACT_REP, "SW_ACT_REP" }, { NM_EV_STATE_CHG_REP, "STATE_CHG_REP" }, + { NM_EV_GET_ATTR_REP, "GET_ATTR_REP" }, { NM_EV_SET_ATTR_ACK, "SET_ATTR_ACK" }, { NM_EV_OPSTART_ACK, "OPSTART_ACK" }, { NM_EV_OPSTART_NACK, "OPSTART_NACK" }, { NM_EV_OML_DOWN, "OML_DOWN" }, { NM_EV_FORCE_LOCK, "FORCE_LOCK_CHG" }, + { NM_EV_FEATURE_NEGOTIATED, "FEATURE_NEGOTIATED" }, + { NM_EV_RSL_CONNECT_ACK, "RSL_CONNECT_ACK" }, + { NM_EV_RSL_CONNECT_NACK, "RSL_CONNECT_NACK" }, { 0, NULL } }; + +void nm_obj_fsm_becomes_enabled_disabled(struct gsm_bts *bts, void *obj, + enum abis_nm_obj_class obj_class, bool running) +{ + struct nm_running_chg_signal_data nsd; + + memset(&nsd, 0, sizeof(nsd)); + nsd.bts = bts; + nsd.obj_class = obj_class; + nsd.obj = obj; + nsd.running = running; + + osmo_signal_dispatch(SS_NM, S_NM_RUNNING_CHG, &nsd); +} + +/* nm_configuring_fsm_inst_dispatch(struct gsm_abis_mo *mo, uint32_t event, void *data) */ +#define nm_configuring_fsm_inst_dispatch(mo, event, data) do { \ + if ((mo)->nm_state.operational != NM_OPSTATE_ENABLED) \ + _osmo_fsm_inst_dispatch((mo)->fi, event, data, __FILE__, __LINE__); \ + } while (0) + +/*! + * Dispatch an event to all configuring/non-enabled BTS NM fsms + * + * \param[in] bts a pointer to the BTS instance + * \param[in] event the FSM event. See \fn osmo_fsm_inst_dispatch + * \param[in] data the private data of the event. + */ +void nm_fsm_dispatch_all_configuring(struct gsm_bts *bts, uint32_t event, void *data) +{ + struct gsm_bts_trx *trx; + + nm_configuring_fsm_inst_dispatch(&bts->site_mgr->mo, event, data); + nm_configuring_fsm_inst_dispatch(&bts->mo, event, data); + llist_for_each_entry(trx, &bts->trx_list, list) { + nm_configuring_fsm_inst_dispatch(&trx->mo, event, data); + nm_configuring_fsm_inst_dispatch(&trx->bb_transc.mo, event, data); + for (unsigned long i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + nm_configuring_fsm_inst_dispatch(&ts->mo, event, data); + } + } + + /* GPRS MOs */ + nm_configuring_fsm_inst_dispatch(&bts->site_mgr->gprs.nse.mo, event, data); + for (unsigned long i = 0; i < ARRAY_SIZE(bts->site_mgr->gprs.nsvc); i++) + nm_configuring_fsm_inst_dispatch(&bts->site_mgr->gprs.nsvc[i].mo, event, data); + nm_configuring_fsm_inst_dispatch(&bts->gprs.cell.mo, event, data); +} diff --git a/src/osmo-bsc/nm_gprs_cell_fsm.c b/src/osmo-bsc/nm_gprs_cell_fsm.c new file mode 100644 index 000000000..10f8d07de --- /dev/null +++ b/src/osmo-bsc/nm_gprs_cell_fsm.c @@ -0,0 +1,432 @@ +/* NM GPRS Cell FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <unistd.h> +#include <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h> +#include <osmocom/bsc/nm_common_fsm.h> +#include <osmocom/bsc/debug.h> + +#define X(s) (1 << (s)) + +#define nm_gprs_cell_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + + cell->mo.sw_act_rep_received = false; + cell->mo.get_attr_sent = false; + cell->mo.get_attr_rep_received = false; + cell->mo.set_attr_sent = false; + cell->mo.set_attr_ack_received = false; + cell->mo.adm_unlock_sent = false; + cell->mo.opstart_sent = false; +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + cell->mo.sw_act_rep_received = true; + break; + case NM_EV_SETUP_RAMP_READY: + break; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + /* should not happen... */ + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_DEPENDENCY: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE); + return; + default: + return; + } + default: + OSMO_ASSERT(0); + } +} + +static void configure_loop(struct gsm_gprs_cell *cell, const struct gsm_nm_state *state, bool allow_opstart) +{ + struct msgb *msgb; + struct gsm_bts *bts = container_of(cell, struct gsm_bts, gprs.cell); + + if (bts->gprs.mode == BTS_GPRS_NONE) + return; + + if (bts_setup_ramp_wait(bts)) + return; + + /* nanoBTS only: delay until SW Activated Report is received, which + * tells us the IPA Object version (may be used to set attr conditionally). */ + if (is_nanobts(bts) && !cell->mo.sw_act_rep_received) + return; + + if (!cell->mo.get_attr_sent && !cell->mo.get_attr_rep_received) { + uint8_t attr_buf[2]; /* enlarge if needed */ + uint8_t *ptr = &attr_buf[0]; + + *(ptr++) = NM_ATT_SW_CONFIG; + if (is_ipa_abisip_bts(bts)) + *(ptr++) = NM_ATT_IPACC_SUPP_FEATURES; + + OSMO_ASSERT((ptr - attr_buf) <= sizeof(attr_buf)); + abis_nm_get_attr(bts, NM_OC_GPRS_CELL, + bts->bts_nr, 0x00, 0xff, + &attr_buf[0], (ptr - attr_buf)); + cell->mo.get_attr_sent = true; + } + + /* OS#6172: old osmo-bts versions do NACK Get Attributes for GPRS Cell, + * so we do not check if cell->mo.get_attr_rep_received is set here. */ + if (!cell->mo.set_attr_sent && !cell->mo.set_attr_ack_received) { + cell->mo.set_attr_sent = true; + msgb = nanobts_gen_set_cell_attr(bts); + OSMO_ASSERT(msgb); + abis_nm_ipaccess_set_attr(bts, NM_OC_GPRS_CELL, bts->bts_nr, + 0, 0xff, msgb->data, msgb->len); + msgb_free(msgb); + } + + if (state->administrative != NM_STATE_UNLOCKED && !cell->mo.adm_unlock_sent) { + cell->mo.adm_unlock_sent = true; + abis_nm_chg_adm_state(bts, NM_OC_GPRS_CELL, + bts->bts_nr, 0, 0xff, + NM_STATE_UNLOCKED); + } + + if (allow_opstart && state->administrative == NM_STATE_UNLOCKED && + cell->mo.set_attr_ack_received) { + if (!cell->mo.opstart_sent) { + cell->mo.opstart_sent = true; + abis_nm_opstart(bts, NM_OC_GPRS_CELL, bts->bts_nr, 0, 0xff); + } + } +} + +static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + struct gsm_bts *bts = container_of(cell, struct gsm_bts, gprs.cell); + + /* nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST be sent + during Dependency, so we simply move to OFFLINE state here to avoid + duplicating code */ + if (bts->site_mgr->peer_has_no_avstate_offline) { + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE); + return; + } + configure_loop(cell, &cell->mo.nm_state, false); +} + +static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + cell->mo.sw_act_rep_received = true; + configure_loop(cell, &cell->mo.nm_state, false); + break; + case NM_EV_GET_ATTR_REP: + cell->mo.get_attr_rep_received = true; + cell->mo.get_attr_sent = false; + configure_loop(cell, &cell->mo.nm_state, false); + return; + case NM_EV_SET_ATTR_ACK: + cell->mo.set_attr_ack_received = true; + cell->mo.set_attr_sent = false; + configure_loop(cell, &cell->mo.nm_state, false); + return; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + /* should not happen... */ + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE); + return; + case NM_AVSTATE_DEPENDENCY: + configure_loop(cell, new_state, false); + return; + default: + return; + } + case NM_EV_SETUP_RAMP_READY: + configure_loop(cell, &cell->mo.nm_state, false); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + + configure_loop(cell, &cell->mo.nm_state, true); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + struct gsm_bts *bts = container_of(cell, struct gsm_bts, gprs.cell); + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + cell->mo.sw_act_rep_received = true; + configure_loop(cell, &cell->mo.nm_state, true); + break; + case NM_EV_GET_ATTR_REP: + cell->mo.get_attr_rep_received = true; + cell->mo.get_attr_sent = false; + configure_loop(cell, &cell->mo.nm_state, true); + return; + case NM_EV_SET_ATTR_ACK: + cell->mo.set_attr_ack_received = true; + cell->mo.set_attr_sent = false; + configure_loop(cell, &cell->mo.nm_state, true); + return; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_DEPENDENCY: + /* There's no point in moving back to Dependency, since it's broken + and it acts actually as if it was in Offline state */ + if (!bts->site_mgr->peer_has_no_avstate_offline) { + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY); + } else { + /* Moreover, in nanoBTS we need to check here for tx + Opstart since we may have gone Unlocked state + in this event, which means Opstart may be txed here. */ + configure_loop(cell, new_state, true); + } + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + configure_loop(cell, new_state, true); + return; + default: + return; + } + case NM_EV_SETUP_RAMP_READY: + configure_loop(cell, &cell->mo.nm_state, true); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + + /* Reset state, we don't need it in this state and it will need to be + reused as soon as we move back to Disabled */ + cell->mo.opstart_sent = false; + cell->mo.adm_unlock_sent = false; + cell->mo.set_attr_ack_received = false; + cell->mo.set_attr_sent = false; +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) + return; + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_DEPENDENCY: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE); + return; + default: + return; + } + default: + OSMO_ASSERT(0); + } +} + +static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + struct gsm_bts *bts = container_of(cell, struct gsm_bts, gprs.cell); + + switch (event) { + case NM_EV_OPSTART_ACK: + case NM_EV_OPSTART_NACK: + /* TODO: if on state OFFLINE and rx NACK, try again? */ + cell->mo.opstart_sent = false; + break; + case NM_EV_FORCE_LOCK: + cell->mo.force_rf_lock = (bool)(intptr_t)data; + abis_nm_chg_adm_state(bts, NM_OC_GPRS_CELL, + bts->bts_nr, 0, 0xff, + cell->mo.force_rf_lock ? NM_STATE_LOCKED : NM_STATE_UNLOCKED); + break; + case NM_EV_OML_DOWN: + if (fi->state != NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(0); + } +} + +static struct osmo_fsm_state nm_gprs_cell_fsm_states[] = { + [NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE) | + X(NM_GPRS_CELL_ST_OP_ENABLED), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_GET_ATTR_REP) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE) | + X(NM_GPRS_CELL_ST_OP_ENABLED), + .name = "DISABLED_DEPENDENCY", + .onenter = st_op_disabled_dependency_on_enter, + .action = st_op_disabled_dependency, + }, + [NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_GET_ATTR_REP) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_CELL_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_GPRS_CELL_ST_OP_ENABLED] = { + .in_event_mask = + X(NM_EV_STATE_CHG_REP), + .out_state_mask = + X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_gprs_cell_fsm = { + .name = "NM_GPRS_CELL_OP", + .states = nm_gprs_cell_fsm_states, + .num_states = ARRAY_SIZE(nm_gprs_cell_fsm_states), + .allstate_event_mask = + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK) | + X(NM_EV_FORCE_LOCK) | + X(NM_EV_OML_DOWN), + .allstate_action = st_op_allstate, + .event_names = nm_fsm_event_names, + .log_subsys = DNM, +}; + +static __attribute__((constructor)) void nm_gprs_cell_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_gprs_cell_fsm) == 0); +} diff --git a/src/osmo-bsc/nm_gprs_nse_fsm.c b/src/osmo-bsc/nm_gprs_nse_fsm.c new file mode 100644 index 000000000..295f5fbce --- /dev/null +++ b/src/osmo-bsc/nm_gprs_nse_fsm.c @@ -0,0 +1,403 @@ +/* NM GPRS NSE FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <unistd.h> +#include <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmocom/bsc/bts_sm.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h> +#include <osmocom/bsc/nm_common_fsm.h> +#include <osmocom/bsc/debug.h> + +#define X(s) (1 << (s)) + +#define nm_gprs_nse_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + + nse->mo.sw_act_rep_received = false; + nse->mo.set_attr_sent = false; + nse->mo.set_attr_ack_received = false; + nse->mo.adm_unlock_sent = false; + nse->mo.opstart_sent = false; +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + nse->mo.sw_act_rep_received = true; + break; + case NM_EV_SETUP_RAMP_READY: + break; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + /* should not happen... */ + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_DEPENDENCY: + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE); + return; + default: + return; + } + default: + OSMO_ASSERT(0); + } +} + +static void configure_loop(struct gsm_gprs_nse *nse, const struct gsm_nm_state *state, bool allow_opstart) +{ + struct msgb *msgb; + struct gsm_bts_sm *bts_sm = container_of(nse, struct gsm_bts_sm, gprs.nse); + struct gsm_bts *bts = gsm_bts_sm_get_bts(bts_sm); + + if (bts_setup_ramp_wait(bts)) + return; + + /* nanoBTS only: delay until SW Activated Report is received, which + * tells us the IPA Object version (may be used to set attr conditionally). */ + if (is_nanobts(bts) && !nse->mo.sw_act_rep_received) + return; + + if (!nse->mo.set_attr_sent && !nse->mo.set_attr_ack_received) { + nse->mo.set_attr_sent = true; + msgb = nanobts_gen_set_nse_attr(bts_sm); + abis_nm_ipaccess_set_attr(bts, NM_OC_GPRS_NSE, bts->bts_nr, + 0xff, 0xff, msgb->data, + msgb->len); + msgb_free(msgb); + } + + /* Attributes must be set before unlocking */ + if (state->administrative != NM_STATE_UNLOCKED && nse->mo.set_attr_ack_received && + !nse->mo.adm_unlock_sent) { + nse->mo.adm_unlock_sent = true; + abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, + bts->bts_nr, 0xff, 0xff, + NM_STATE_UNLOCKED); + } + + if (allow_opstart && state->administrative == NM_STATE_UNLOCKED && + nse->mo.set_attr_ack_received) { + if (!nse->mo.opstart_sent) { + nse->mo.opstart_sent = true; + abis_nm_opstart(bts, NM_OC_GPRS_NSE, bts->bts_nr, 0xff, 0xff); + } + } +} + +static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + struct gsm_bts_sm *bts_sm = container_of(nse, struct gsm_bts_sm, gprs.nse); + + /* nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST be sent + during Dependency, so we simply move to OFFLINE state here to avoid + duplicating code */ + if (bts_sm->peer_has_no_avstate_offline) { + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE); + return; + } + configure_loop(nse, &nse->mo.nm_state, false); +} + +static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + nse->mo.sw_act_rep_received = true; + configure_loop(nse, &nse->mo.nm_state, false); + break; + case NM_EV_SET_ATTR_ACK: + nse->mo.set_attr_ack_received = true; + nse->mo.set_attr_sent = false; + configure_loop(nse, &nse->mo.nm_state, false); + return; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + /* should not happen... */ + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE); + return; + case NM_AVSTATE_DEPENDENCY: + configure_loop(nse, new_state, false); + return; + default: + return; + } + case NM_EV_SETUP_RAMP_READY: + configure_loop(nse, &nse->mo.nm_state, false); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + + configure_loop(nse, &nse->mo.nm_state, true); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + struct gsm_bts_sm *bts_sm = container_of(nse, struct gsm_bts_sm, gprs.nse); + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + nse->mo.sw_act_rep_received = true; + configure_loop(nse, &nse->mo.nm_state, true); + break; + case NM_EV_SET_ATTR_ACK: + nse->mo.set_attr_ack_received = true; + nse->mo.set_attr_sent = false; + configure_loop(nse, &nse->mo.nm_state, true); + return; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_DEPENDENCY: + /* There's no point in moving back to Dependency, since it's broken + and it acts actually as if it was in Offline state */ + if (!bts_sm->peer_has_no_avstate_offline) { + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY); + } else { + /* Moreover, in nanoBTS we need to check here for tx + Opstart since we may have gone Unlocked state + in this event, which means Opstart may be txed here. */ + configure_loop(nse, new_state, true); + } + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + configure_loop(nse, new_state, true); + return; + default: + return; + } + case NM_EV_SETUP_RAMP_READY: + configure_loop(nse, &nse->mo.nm_state, true); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + + /* Reset state, we don't need it in this state and it will need to be + reused as soon as we move back to Disabled */ + nse->mo.opstart_sent = false; + nse->mo.adm_unlock_sent = false; + nse->mo.set_attr_ack_received = false; + nse->mo.set_attr_sent = false; +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) + return; + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_DEPENDENCY: + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE); + return; + default: + return; + } + default: + OSMO_ASSERT(0); + } +} + +static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + struct gsm_bts_sm *bts_sm = container_of(nse, struct gsm_bts_sm, gprs.nse); + struct gsm_bts *bts = gsm_bts_sm_get_bts(bts_sm); + + switch (event) { + case NM_EV_OPSTART_ACK: + case NM_EV_OPSTART_NACK: + /* TODO: if on state OFFLINE and rx NACK, try again? */ + nse->mo.opstart_sent = false; + break; + case NM_EV_FORCE_LOCK: + nse->mo.force_rf_lock = (bool)(intptr_t)data; + abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, + bts->bts_nr, 0xff, 0xff, + nse->mo.force_rf_lock ? NM_STATE_LOCKED : NM_STATE_UNLOCKED); + break; + case NM_EV_OML_DOWN: + if (fi->state != NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(0); + } +} + +static struct osmo_fsm_state nm_gprs_nse_fsm_states[] = { + [NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE) | + X(NM_GPRS_NSE_ST_OP_ENABLED), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE) | + X(NM_GPRS_NSE_ST_OP_ENABLED), + .name = "DISABLED_DEPENDENCY", + .onenter = st_op_disabled_dependency_on_enter, + .action = st_op_disabled_dependency, + }, + [NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_NSE_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_GPRS_NSE_ST_OP_ENABLED] = { + .in_event_mask = + X(NM_EV_STATE_CHG_REP), + .out_state_mask = + X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_gprs_nse_fsm = { + .name = "NM_GPRS_NSE_OP", + .states = nm_gprs_nse_fsm_states, + .num_states = ARRAY_SIZE(nm_gprs_nse_fsm_states), + .allstate_event_mask = + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK) | + X(NM_EV_FORCE_LOCK) | + X(NM_EV_OML_DOWN), + .allstate_action = st_op_allstate, + .event_names = nm_fsm_event_names, + .log_subsys = DNM, +}; + +static __attribute__((constructor)) void nm_gprs_nse_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_gprs_nse_fsm) == 0); +} diff --git a/src/osmo-bsc/nm_gprs_nsvc_fsm.c b/src/osmo-bsc/nm_gprs_nsvc_fsm.c new file mode 100644 index 000000000..c37d46ad0 --- /dev/null +++ b/src/osmo-bsc/nm_gprs_nsvc_fsm.c @@ -0,0 +1,434 @@ +/* NM GPRS NSVC FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Alexander Couzens <lynxis@fe80.eu> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <unistd.h> +#include <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmocom/bsc/bts_sm.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h> +#include <osmocom/bsc/nm_common_fsm.h> +#include <osmocom/bsc/debug.h> + +#define X(s) (1 << (s)) + +#define nm_gprs_nsvc_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + + nsvc->mo.sw_act_rep_received = false; + nsvc->mo.set_attr_sent = false; + nsvc->mo.set_attr_sent = false; + nsvc->mo.set_attr_ack_received = false; + nsvc->mo.adm_unlock_sent = false; + nsvc->mo.opstart_sent = false; +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + nsvc->mo.sw_act_rep_received = true; + break; + case NM_EV_FEATURE_NEGOTIATED: + case NM_EV_SETUP_RAMP_READY: + break; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + /* should not happen... */ + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_DEPENDENCY: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE); + return; + default: + return; + } + default: + OSMO_ASSERT(0); + } +} + +static bool has_valid_nsvc(const struct gsm_gprs_nsvc *nsvc) +{ + /* If not configured (enabled) at all. */ + if (!nsvc->enabled) + return false; + + /* remote address must be valid */ + if (osmo_sockaddr_is_any(&nsvc->remote)) + return false; + /* remote port must be valid */ + switch (nsvc->remote.u.sa.sa_family) { + case AF_INET: + return nsvc->remote.u.sin.sin_port != 0; + case AF_INET6: + return nsvc->remote.u.sin6.sin6_port != 0; + default: + return false; + } +} + +static void configure_loop(struct gsm_gprs_nsvc *nsvc, const struct gsm_nm_state *state, bool allow_opstart) +{ + struct msgb *msgb; + + if (nsvc->bts->gprs.mode == BTS_GPRS_NONE) + return; + + if (bts_setup_ramp_wait(nsvc->bts)) + return; + + /* nanoBTS only: delay until SW Activated Report is received, which + * tells us the IPA Object version (may be used to set attr conditionally). */ + if (is_nanobts(nsvc->bts) && !nsvc->mo.sw_act_rep_received) + return; + + /* We need to know BTS features in order to know if we can set IPv6 addresses */ + if (gsm_bts_features_negotiated(nsvc->bts) && !nsvc->mo.set_attr_sent && + !nsvc->mo.set_attr_ack_received) { + if (!osmo_bts_has_feature(&nsvc->bts->features, BTS_FEAT_IPV6_NSVC) && + nsvc->remote.u.sa.sa_family == AF_INET6) { + LOGPFSML(nsvc->mo.fi, LOGL_ERROR, + "BTS%d does not support IPv6 NSVC but an IPv6 address was configured!\n", + nsvc->bts->nr); + return; + } + if (!has_valid_nsvc(nsvc)) + return; + + nsvc->mo.set_attr_sent = true; + msgb = nanobts_gen_set_nsvc_attr(nsvc); + OSMO_ASSERT(msgb); + abis_nm_ipaccess_set_attr(nsvc->bts, NM_OC_GPRS_NSVC, nsvc->bts->bts_nr, + nsvc->id, 0xff, msgb->data, msgb->len); + msgb_free(msgb); + } + + if (nsvc->mo.set_attr_ack_received && state->administrative != NM_STATE_UNLOCKED && + !nsvc->mo.adm_unlock_sent) { + nsvc->mo.adm_unlock_sent = true; + abis_nm_chg_adm_state(nsvc->bts, NM_OC_GPRS_NSVC, + nsvc->bts->bts_nr, nsvc->id, 0xff, + NM_STATE_UNLOCKED); + } + + if (allow_opstart && state->administrative == NM_STATE_UNLOCKED && + nsvc->mo.set_attr_ack_received) { + if (!nsvc->mo.opstart_sent) { + nsvc->mo.opstart_sent = true; + abis_nm_opstart(nsvc->bts, NM_OC_GPRS_NSVC, + nsvc->bts->bts_nr, nsvc->id, 0xff); + } + } +} + +static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + + /* nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST be sent + during Dependency, so we simply move to OFFLINE state here to avoid + duplicating code */ + if (nsvc->bts->site_mgr->peer_has_no_avstate_offline) { + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE); + return; + } + configure_loop(nsvc, &nsvc->mo.nm_state, false); +} + +static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + nsvc->mo.sw_act_rep_received = true; + /* fall-through */ + case NM_EV_FEATURE_NEGOTIATED: + configure_loop(nsvc, &nsvc->mo.nm_state, false); + return; + case NM_EV_SET_ATTR_ACK: + nsvc->mo.set_attr_ack_received = true; + nsvc->mo.set_attr_sent = false; + configure_loop(nsvc, &nsvc->mo.nm_state, false); + return; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + /* should not happen... */ + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE); + return; + case NM_AVSTATE_DEPENDENCY: + configure_loop(nsvc, new_state, false); + return; + default: + return; + } + case NM_EV_SETUP_RAMP_READY: + configure_loop(nsvc, &nsvc->mo.nm_state, false); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + + configure_loop(nsvc, &nsvc->mo.nm_state, true); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_SW_ACT_REP: + nsvc->mo.sw_act_rep_received = true; + /* fall-through */ + case NM_EV_FEATURE_NEGOTIATED: + configure_loop(nsvc, &nsvc->mo.nm_state, true); + return; + case NM_EV_SET_ATTR_ACK: + nsvc->mo.set_attr_ack_received = true; + nsvc->mo.set_attr_sent = false; + configure_loop(nsvc, &nsvc->mo.nm_state, true); + return; + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) { + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_ENABLED); + return; + } + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_DEPENDENCY: + /* There's no point in moving back to Dependency, since it's broken + and it acts actually as if it was in Offline state */ + if (!nsvc->bts->site_mgr->peer_has_no_avstate_offline) { + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY); + } else { + /* Moreover, in nanoBTS we need to check here for tx + Opstart since we may have gone Unlocked state + in this event, which means Opstart may be txed here. */ + configure_loop(nsvc, new_state, true); + } + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + configure_loop(nsvc, new_state, true); + return; + default: + return; + } + case NM_EV_SETUP_RAMP_READY: + configure_loop(nsvc, &nsvc->mo.nm_state, true); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + + /* Reset state, we don't need it in this state and it will need to be + reused as soon as we move back to Disabled */ + nsvc->mo.opstart_sent = false; + nsvc->mo.adm_unlock_sent = false; + nsvc->mo.set_attr_sent = false; + nsvc->mo.set_attr_ack_received = false; +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct nm_statechg_signal_data *nsd; + const struct gsm_nm_state *new_state; + + switch (event) { + case NM_EV_STATE_CHG_REP: + nsd = (struct nm_statechg_signal_data *)data; + new_state = &nsd->new_state; + if (new_state->operational == NM_OPSTATE_ENABLED) + return; + switch (new_state->availability) { /* operational = DISABLED */ + case NM_AVSTATE_NOT_INSTALLED: + case NM_AVSTATE_POWER_OFF: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED); + return; + case NM_AVSTATE_DEPENDENCY: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY); + return; + case NM_AVSTATE_OFF_LINE: + case NM_AVSTATE_OK: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE); + return; + default: + return; + } + default: + OSMO_ASSERT(0); + } +} + +static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + + switch (event) { + case NM_EV_OPSTART_ACK: + case NM_EV_OPSTART_NACK: + /* TODO: if on state OFFLINE and rx NACK, try again? */ + nsvc->mo.opstart_sent = false; + break; + case NM_EV_OML_DOWN: + if (fi->state != NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(0); + } +} + +static struct osmo_fsm_state nm_gprs_nsvc_fsm_states[] = { + [NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_FEATURE_NEGOTIATED) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE) | + X(NM_GPRS_NSVC_ST_OP_ENABLED), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_FEATURE_NEGOTIATED) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE) | + X(NM_GPRS_NSVC_ST_OP_ENABLED), + .name = "DISABLED_DEPENDENCY", + .onenter = st_op_disabled_dependency_on_enter, + .action = st_op_disabled_dependency, + }, + [NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_SW_ACT_REP) | + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_FEATURE_NEGOTIATED) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), + .out_state_mask = + X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_NSVC_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_GPRS_NSVC_ST_OP_ENABLED] = { + .in_event_mask = + X(NM_EV_STATE_CHG_REP), + .out_state_mask = + X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_gprs_nsvc_fsm = { + .name = "NM_GPRS_NSVC_OP", + .states = nm_gprs_nsvc_fsm_states, + .num_states = ARRAY_SIZE(nm_gprs_nsvc_fsm_states), + .allstate_event_mask = + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK) | + X(NM_EV_OML_DOWN), + .allstate_action = st_op_allstate, + .event_names = nm_fsm_event_names, + .log_subsys = DNM, +}; + +static __attribute__((constructor)) void nm_gprs_nsvc_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_gprs_nsvc_fsm) == 0); +} diff --git a/src/osmo-bsc/nm_rcarrier_fsm.c b/src/osmo-bsc/nm_rcarrier_fsm.c index 8702ebeec..f5d7c270f 100644 --- a/src/osmo-bsc/nm_rcarrier_fsm.c +++ b/src/osmo-bsc/nm_rcarrier_fsm.c @@ -1,6 +1,6 @@ /* NM Radio Carrier FSM */ -/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * Author: Pau Espin Pedrol <pespin@sysmocom.de> * * All Rights Reserved @@ -13,7 +13,7 @@ * 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 General Public License for more details. + * 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/>. @@ -40,6 +40,16 @@ #define nm_rcarrier_fsm_state_chg(fi, NEXT_STATE) \ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) +static inline void nm_rcarrier_fsm_becomes_enabled(struct gsm_bts_trx *trx) +{ + nm_obj_fsm_becomes_enabled_disabled(trx->bts, trx, NM_OC_RADIO_CARRIER, true); +} + +static inline void nm_rcarrier_fsm_becomes_disabled(struct gsm_bts_trx *trx) +{ + nm_obj_fsm_becomes_enabled_disabled(trx->bts, trx, NM_OC_RADIO_CARRIER, false); +} + ////////////////////////// // FSM STATE ACTIONS ////////////////////////// @@ -48,6 +58,9 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3 { struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; + trx->mo.sw_act_rep_received = false; + trx->mo.get_attr_sent = false; + trx->mo.get_attr_rep_received = false; trx->mo.set_attr_sent = false; trx->mo.set_attr_ack_received = false; trx->mo.adm_unlock_sent = false; @@ -56,15 +69,19 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3 static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_SW_ACT_REP: + trx->mo.sw_act_rep_received = true; + break; + case NM_EV_SETUP_RAMP_READY: break; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /*should not happen... */ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED); @@ -86,13 +103,38 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event } } -static void configure_loop(struct gsm_bts_trx *trx, struct gsm_nm_state *state, bool allow_opstart) +static void configure_loop(struct gsm_bts_trx *trx, const struct gsm_nm_state *state, bool allow_opstart) { struct msgb *msgb; + if (bts_setup_ramp_wait(trx->bts)) + return; + + /* nanoBTS only: delay until SW Activated Report is received, which + * tells us the IPA Object version (may be used to set attr conditionally). */ + if (is_nanobts(trx->bts) && !trx->mo.sw_act_rep_received) + return; + + if (!trx->mo.get_attr_sent && !trx->mo.get_attr_rep_received) { + uint8_t attr_buf[2]; /* enlarge if needed */ + uint8_t *ptr = &attr_buf[0]; + + *(ptr++) = NM_ATT_SW_CONFIG; + if (is_ipa_abisip_bts(trx->bts)) + *(ptr++) = NM_ATT_IPACC_SUPP_FEATURES; + + OSMO_ASSERT((ptr - attr_buf) <= sizeof(attr_buf)); + abis_nm_get_attr(trx->bts, NM_OC_RADIO_CARRIER, + trx->bts->bts_nr, trx->nr, 0xff, + &attr_buf[0], (ptr - attr_buf)); + trx->mo.get_attr_sent = true; + } + + /* OS#6172: old osmo-bts versions do NACK Get Attributes for Radio Carrier, + * so we do not check if trx->mo.get_attr_rep_received is set here. */ if (!trx->mo.set_attr_sent && !trx->mo.set_attr_ack_received) { trx->mo.set_attr_sent = true; - msgb = nanobts_attr_radio_get(trx->bts, trx); + msgb = nanobts_gen_set_radio_attr(trx->bts, trx); abis_nm_set_radio_attr(trx, msgb->data, msgb->len); msgb_free(msgb); } @@ -120,7 +162,7 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_ * be sent during Dependency, so we simply move to OFFLINE state here to * avoid duplicating code. However, RadioCarrier seems to be implemented * correctly and goes to Offline state during startup. If some HW - * version is found with the aboev estated bug, this code needs to be + * version is found with the above estated bug, this code needs to be * enabled, similar to what we do in nm_bb_transc_fsm: */ /*if (trx->bts->site_mgr.peer_has_no_avstate_offline) { @@ -134,9 +176,18 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, { struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + trx->mo.sw_act_rep_received = true; + configure_loop(trx, &trx->mo.nm_state, false); + break; + case NM_EV_GET_ATTR_REP: + trx->mo.get_attr_rep_received = true; + trx->mo.get_attr_sent = false; + configure_loop(trx, &trx->mo.nm_state, false); + return; case NM_EV_SET_ATTR_ACK: trx->mo.set_attr_ack_received = true; trx->mo.set_attr_sent = false; @@ -144,7 +195,7 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, return; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { /* should not happen... */ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED); @@ -165,6 +216,9 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(trx, &trx->mo.nm_state, false); + break; default: OSMO_ASSERT(0); } @@ -174,8 +228,6 @@ static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t p { struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; - /* Warning: In here we may be acessing an state older than new_state - from prev (syncrhonous) FSM state */ configure_loop(trx, &trx->mo.nm_state, true); } @@ -183,9 +235,18 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi { struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { + case NM_EV_SW_ACT_REP: + trx->mo.sw_act_rep_received = true; + configure_loop(trx, &trx->mo.nm_state, true); + break; + case NM_EV_GET_ATTR_REP: + trx->mo.get_attr_rep_received = true; + trx->mo.get_attr_sent = false; + configure_loop(trx, &trx->mo.nm_state, true); + return; case NM_EV_SET_ATTR_ACK: trx->mo.set_attr_ack_received = true; trx->mo.set_attr_sent = false; @@ -193,7 +254,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi return; case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; + new_state = &nsd->new_state; if (new_state->operational == NM_OPSTATE_ENABLED) { nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED); return; @@ -213,6 +274,9 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi default: return; } + case NM_EV_SETUP_RAMP_READY: + configure_loop(trx, &trx->mo.nm_state, true); + break; default: OSMO_ASSERT(0); } @@ -228,29 +292,54 @@ static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state trx->mo.adm_unlock_sent = false; trx->mo.set_attr_ack_received = false; trx->mo.set_attr_sent = false; + + nm_rcarrier_fsm_becomes_enabled(trx); } static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; struct nm_statechg_signal_data *nsd; - struct gsm_nm_state *new_state; + const struct gsm_nm_state *new_state; switch (event) { case NM_EV_STATE_CHG_REP: nsd = (struct nm_statechg_signal_data *)data; - new_state = nsd->new_state; - if (new_state->operational == NM_OPSTATE_ENABLED) + new_state = &nsd->new_state; + /* Op state stays in Enabled, hence either Avail or Admin changed: */ + if (new_state->operational == NM_OPSTATE_ENABLED) { + /* Some sort of availability change we don't care about: */ + if (nsd->old_state.administrative == new_state->administrative) + return; + /* HACK: Admin state change without Op state change: + * According to TS 52.021 sec 5.3.1, Locking the NM obj should make + * it go into Disabled Dependency state, but current and older + * versions of osmo-bts (and potentially nanobts?) don't move from + * Operative=Enabled state and only change the Adminsitrative one. + * Let's account for this behavior here: */ + switch (new_state->administrative) { + case NM_STATE_LOCKED: + nm_rcarrier_fsm_becomes_disabled(trx); + break; + case NM_STATE_UNLOCKED: + nm_rcarrier_fsm_becomes_enabled(trx); + break; + } return; + } switch (new_state->availability) { /* operational = DISABLED */ case NM_AVSTATE_NOT_INSTALLED: case NM_AVSTATE_POWER_OFF: + nm_rcarrier_fsm_becomes_disabled(trx); nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED); return; case NM_AVSTATE_DEPENDENCY: + nm_rcarrier_fsm_becomes_disabled(trx); nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY); return; case NM_AVSTATE_OFF_LINE: case NM_AVSTATE_OK: + nm_rcarrier_fsm_becomes_disabled(trx); nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE); return; default: @@ -278,7 +367,11 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) trx->mo.force_rf_lock ? NM_STATE_LOCKED : NM_STATE_UNLOCKED); break; case NM_EV_OML_DOWN: - nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED); + if (fi->state != NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) { + if (fi->state == NM_RCARRIER_ST_OP_ENABLED) + nm_rcarrier_fsm_becomes_disabled(trx); + nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED); + } break; default: OSMO_ASSERT(0); @@ -289,7 +382,8 @@ static struct osmo_fsm_state nm_rcarrier_fsm_states[] = { [NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED] = { .in_event_mask = X(NM_EV_SW_ACT_REP) | - X(NM_EV_STATE_CHG_REP), + X(NM_EV_STATE_CHG_REP) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY) | X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE) | @@ -300,8 +394,11 @@ static struct osmo_fsm_state nm_rcarrier_fsm_states[] = { }, [NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY] = { .in_event_mask = + X(NM_EV_SW_ACT_REP) | X(NM_EV_STATE_CHG_REP) | - X(NM_EV_SET_ATTR_ACK), + X(NM_EV_GET_ATTR_REP) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) | X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE) | @@ -312,8 +409,11 @@ static struct osmo_fsm_state nm_rcarrier_fsm_states[] = { }, [NM_RCARRIER_ST_OP_DISABLED_OFFLINE] = { .in_event_mask = + X(NM_EV_SW_ACT_REP) | X(NM_EV_STATE_CHG_REP) | - X(NM_EV_SET_ATTR_ACK), + X(NM_EV_GET_ATTR_REP) | + X(NM_EV_SET_ATTR_ACK) | + X(NM_EV_SETUP_RAMP_READY), .out_state_mask = X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) | X(NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY) | @@ -323,15 +423,15 @@ static struct osmo_fsm_state nm_rcarrier_fsm_states[] = { .action = st_op_disabled_offline, }, [NM_RCARRIER_ST_OP_ENABLED] = { - .in_event_mask = - X(NM_EV_STATE_CHG_REP), - .out_state_mask = - X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) | - X(NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY) | - X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE), - .name = "ENABLED", - .onenter = st_op_enabled_on_enter, - .action = st_op_enabled, + .in_event_mask = + X(NM_EV_STATE_CHG_REP), + .out_state_mask = + X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY) | + X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, }, }; diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c index f168b6501..2a9805444 100644 --- a/src/osmo-bsc/osmo_bsc_bssap.c +++ b/src/osmo-bsc/osmo_bsc_bssap.c @@ -29,11 +29,14 @@ #include <osmocom/bsc/bsc_subscriber.h> #include <osmocom/bsc/paging.h> #include <osmocom/bsc/gsm_04_08_rr.h> +#include <osmocom/bsc/gsm_08_08.h> #include <osmocom/bsc/bsc_subscr_conn_fsm.h> #include <osmocom/bsc/codec_pref.h> +#include <osmocom/bsc/data_rate_pref.h> #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/handover_fsm.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bsc_stats.h> #include <osmocom/gsm/protocol/gsm_08_08.h> #include <osmocom/gsm/gsm0808.h> @@ -46,6 +49,8 @@ #include <osmocom/core/sockaddr_str.h> #include <osmocom/bsc/lcs_loc_req.h> #include <osmocom/bsc/bssmap_reset.h> +#include <osmocom/bsc/assignment_fsm.h> +#include <osmocom/bsc/vgcs_fsm.h> #define IP_V4_ADDR_LEN 4 @@ -62,7 +67,7 @@ static void update_msc_osmux_support(struct bsc_msc_data *msc, int rc; bool old_value = msc->remote_supports_osmux; - rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0); + rc = osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1); if (rc < 0) LOGP(DMSC, LOGL_NOTICE, "Failed parsing TLV looking for Osmux support\n"); @@ -113,8 +118,7 @@ static int bssmap_handle_reset(struct bsc_msc_data *msc, /* Page a subscriber based on TMSI and LAC via the specified BTS. * The msc parameter is the MSC which issued the corresponding paging request. * Log an error if paging failed. */ -static void -page_subscriber(const struct bsc_paging_params *params, struct gsm_bts *bts, uint32_t lac) +static void page_subscriber(const struct bsc_paging_params *params, struct gsm_bts *bts, uint32_t lac) { int ret; @@ -131,16 +135,14 @@ page_subscriber(const struct bsc_paging_params *params, struct gsm_bts *bts, uin "Paging request failed, or repeated paging on LAC %u\n", lac); } -static void -page_all_bts(const struct bsc_paging_params *params) +static void page_all_bts(const struct bsc_paging_params *params) { struct gsm_bts *bts; llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) page_subscriber(params, bts, GSM_LAC_RESERVED_ALL_BTS); } -static void -page_cgi(const struct bsc_paging_params *params) +static void page_cgi(const struct bsc_paging_params *params) { int i; for (i = 0; i < params->cil.id_list_len; i++) { @@ -169,8 +171,7 @@ page_cgi(const struct bsc_paging_params *params) } } -static void -page_lac_and_ci(const struct bsc_paging_params *params) +static void page_lac_and_ci(const struct bsc_paging_params *params) { int i; @@ -192,8 +193,7 @@ page_lac_and_ci(const struct bsc_paging_params *params) } } -static void -page_ci(const struct bsc_paging_params *params) +static void page_ci(const struct bsc_paging_params *params) { int i; @@ -213,8 +213,7 @@ page_ci(const struct bsc_paging_params *params) } } -static void -page_lai_and_lac(const struct bsc_paging_params *params) +static void page_lai_and_lac(const struct bsc_paging_params *params) { int i; @@ -241,8 +240,7 @@ page_lai_and_lac(const struct bsc_paging_params *params) } } -static void -page_lac(const struct bsc_paging_params *params) +static void page_lac(const struct bsc_paging_params *params) { int i; @@ -276,7 +274,10 @@ static int bssmap_handle_paging(struct bsc_msc_data *msc, .tmsi = GSM_RESERVED_TMSI, }; - tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0); + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) { + LOGP(DMSC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } remain = payload_length - 1; if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) { @@ -343,7 +344,7 @@ static int bssmap_handle_paging(struct bsc_msc_data *msc, int bsc_paging_start(struct bsc_paging_params *params) { - rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_PAGING_ATTEMPTED]); + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_PAGING_ATTEMPTED)); if (!params->bsub) { params->bsub = bsc_subscr_find_or_create_by_imsi(bsc_gsmnet->bsc_subscribers, params->imsi.imsi, @@ -353,8 +354,12 @@ int bsc_paging_start(struct bsc_paging_params *params) return -EINVAL; } } - if (params->tmsi != GSM_RESERVED_TMSI) - params->bsub->tmsi = params->tmsi; + if (params->tmsi != GSM_RESERVED_TMSI) { + if (bsc_subscr_set_tmsi(params->bsub, params->tmsi) < 0) { + LOG_PAGING(params, DMSC, LOGL_ERROR, "Paging request failed: Could not set TMSI on subscriber\n"); + return -EINVAL; + } + } log_set_context(LOG_CTX_BSC_SUBSCR, params->bsub); switch (params->cil.id_discr) { @@ -399,68 +404,52 @@ int bsc_paging_start(struct bsc_paging_params *params) return 0; } -/* select the best cipher permitted by the intersection of both masks */ -static int select_best_cipher(uint8_t msc_mask, uint8_t bsc_mask) +/* Select the best cipher permitted by the intersection of both masks. Return as the n in A5/n, or -1 if the + * intersection is empty. */ +int select_best_cipher(uint8_t msc_mask, uint8_t bsc_mask) { + /* A5/7 ... A5/3: We assume higher is better, + * but: A5/1 is better than A5/2, which is better than A5/0 */ + const uint8_t codec_by_strength[8] = { 7, 6, 5, 4, 3, 1, 2, 0 }; uint8_t intersection = msc_mask & bsc_mask; int i; - for (i = 7; i >= 0; i--) { - if (intersection & (1 << i)) - return i; + for (i = 0; i < ARRAY_SIZE(codec_by_strength); i++) { + uint8_t codec = codec_by_strength[i]; + if (intersection & (1 << codec)) + return codec; } return -1; } -/*! We received a GSM 08.08 CIPHER MODE from the MSC */ -static int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher, - const uint8_t *key, int len, int include_imeisv) -{ - if (cipher > 0 && key == NULL) { - LOGP(DRSL, LOGL_ERROR, "%s: Need to have an encryption key.\n", - bsc_subscr_name(conn->bsub)); - return -1; - } - - if (len > MAX_A5_KEY_LEN) { - LOGP(DRSL, LOGL_ERROR, "%s: The key is too long: %d\n", - bsc_subscr_name(conn->bsub), len); - return -1; - } - - LOGP(DRSL, LOGL_DEBUG, "(subscr %s) Cipher Mode: cipher=%d key=%s include_imeisv=%d\n", - bsc_subscr_name(conn->bsub), cipher, osmo_hexdump_nospc(key, len), include_imeisv); - - conn->lchan->encr.alg_id = RSL_ENC_ALG_A5(cipher); - if (key) { - conn->lchan->encr.key_len = len; - memcpy(conn->lchan->encr.key, key, len); - } - - return gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv); -} - static int bssmap_handle_clear_cmd(struct gsm_subscriber_connection *conn, struct msgb *msg, unsigned int length) { struct tlv_parsed tp; - struct gscon_clear_cmd_data ccd = { - .is_csfb = false, - }; + enum gsm0808_cause cause_0808; - tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0); + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } - ccd.cause_0808 = gsm0808_get_cause(&tp); - if (ccd.cause_0808 < 0) { + cause_0808 = gsm0808_get_cause(&tp); + if (cause_0808 < 0) { LOGPFSML(conn->fi, LOGL_ERROR, "Clear Command: Mandatory Cause IE not present.\n"); /* Clear anyway, but without a proper cause. */ - ccd.cause_0808 = GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE; + cause_0808 = GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE; } - if (TLVP_PRESENT(&tp, GSM0808_IE_CSFB_INDICATION)) - ccd.is_csfb = true; + if (TLVP_PRESENT(&tp, GSM0808_IE_CSFB_INDICATION) && + !conn->fast_return.last_eutran_plmn_valid) { + LOGPFSML(conn->fi, LOGL_NOTICE, + "Clear Command: CSFB Indication present, " + "but subscriber has no Last Used E-UTRAN PLMN Id! " + "This probably means MSC doesn't support proper return " + "to the last used PLMN after CS fallback.\n"); + } - osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, &ccd); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, &cause_0808); return 0; } @@ -486,8 +475,9 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn, uint16_t enc_key_len; uint8_t enc_bits_msc; int chosen_cipher; + const struct tlv_p_entry *ie_kc128; - if (!conn) { + if (!conn || !conn->lchan) { LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n"); return -1; } @@ -500,7 +490,11 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn, conn->ciphering_handled = 1; - tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0); + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } + if (!TLVP_PRESENT(&tp, GSM0808_IE_ENCRYPTION_INFORMATION)) { LOGP(DMSC, LOGL_ERROR, "IE Encryption Information missing.\n"); reject_cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; @@ -534,9 +528,6 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn, * a5_encryption == 2 --> 0x04 ... */ enc_bits_msc = data[0]; - /* The bit-mask of permitted ciphers from the MSC (sent in ASSIGNMENT COMMAND) is intersected - * with the vty-configured mask a the BSC. Finally, the best (highest) possible cipher is - * chosen. */ chosen_cipher = select_best_cipher(enc_bits_msc, bsc_gsmnet->a5_encryption_mask); if (chosen_cipher < 0) { LOGP(DMSC, LOGL_ERROR, "Reject: no overlapping A5 ciphers between BSC (0x%02x) " @@ -545,13 +536,51 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn, goto reject; } - /* To complete the confusion, gsm0808_cipher_mode again expects the encryption as a number - * from 0 to 7. */ - if (gsm0808_cipher_mode(conn, chosen_cipher, enc_key, enc_key_len, - include_imeisv)) { + if (chosen_cipher > 0 && !enc_key_len) { + LOGP(DRSL, LOGL_ERROR, "%s: Need to have an encryption key.\n", + bsc_subscr_name(conn->bsub)); reject_cause = GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC; goto reject; } + + if (enc_key_len > MAX_A5_KEY_LEN) { + LOGP(DRSL, LOGL_ERROR, "%s: The key is too long: %d\n", + bsc_subscr_name(conn->bsub), len); + reject_cause = GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC; + goto reject; + } + + conn->lchan->encr.alg_a5_n = chosen_cipher; + if (enc_key_len) { + conn->lchan->encr.key_len = enc_key_len; + memcpy(conn->lchan->encr.key, enc_key, enc_key_len); + } + if ((ie_kc128 = TLVP_GET(&tp, GSM0808_IE_KC_128))) { + if (ie_kc128->len != sizeof(conn->lchan->encr.kc128)) { + LOGPFSML(conn->fi, LOGL_ERROR, "Kc128 IE has wrong length: %u (expect %zu)\n", + ie_kc128->len, sizeof(conn->lchan->encr.kc128)); + reject_cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + goto reject; + } + memcpy(conn->lchan->encr.kc128, ie_kc128->val, sizeof(conn->lchan->encr.kc128)); + conn->lchan->encr.kc128_present = true; + } + + if (chosen_cipher == 4 && !conn->lchan->encr.kc128_present) { + LOGPFSML(conn->fi, LOGL_ERROR, "A5/4 encryption selected, but no Kc128\n"); + reject_cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + goto reject; + } + + LOGP(DRSL, LOGL_DEBUG, "(subscr %s) Cipher Mode: cipher=%d key=%s kc128=%s include_imeisv=%d\n", + bsc_subscr_name(conn->bsub), chosen_cipher, osmo_hexdump_nospc(enc_key, enc_key_len), + ie_kc128? osmo_hexdump_nospc_c(OTC_SELECT, ie_kc128->val, ie_kc128->len) : "-", + include_imeisv); + + if (gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv) < 0) { + reject_cause = GSM0808_CAUSE_RADIO_INTERFACE_FAILURE; + goto reject; + } return 0; reject: @@ -561,7 +590,7 @@ reject: return -1; } - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CIPHER_REJECT]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CIPHER_REJECT)); osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); return -1; } @@ -613,16 +642,14 @@ static int bssmap_handle_lcls_connect_ctrl(struct gsm_subscriber_connection *con struct msgb *resp; struct tlv_parsed tp; const uint8_t *config, *control; - int rc; OSMO_ASSERT(conn); - rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0); - if (rc < 0) { - LOGPFSML(conn->fi, LOGL_ERROR, "Error parsing TLVs of LCLS CONNT CTRL: %s\n", - msgb_hexdump(msg)); - return rc; + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; } + config = TLVP_VAL_MINLEN(&tp, GSM0808_IE_LCLS_CONFIG, 1); control = TLVP_VAL_MINLEN(&tp, GSM0808_IE_LCLS_CONN_STATUS_CTRL, 1); @@ -642,25 +669,85 @@ static int bssmap_handle_lcls_connect_ctrl(struct gsm_subscriber_connection *con LOGPFSM(conn->fi, "Tx LCLS CONNECT CTRL ACK (%s)\n", gsm0808_lcls_status_name(lcls_get_status(conn))); resp = gsm0808_create_lcls_conn_ctrl_ack(lcls_get_status(conn)); - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_LCLS_CONNECT_CTRL_ACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_LCLS_CONNECT_CTRL_ACK)); osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); return 0; } -/* Select a preferred and an alternative codec rate depending on the available +/* Select a preferred and an alternative data rate depending on the available * capabilities. This decision does not include the actual channel load yet, * this is also the reason why the result is a preferred and an alternate * setting. The final decision is made in assignment_fsm.c when the actual * lchan is requested. The preferred lchan will be requested first. If we * find an alternate setting here, this one will be tried secondly if our * primary choice fails. */ -static int select_codecs(struct assignment_request *req, struct gsm0808_channel_type *ct, - struct gsm_subscriber_connection *conn) +static int select_data_rates(struct assignment_request *req, struct gsm0808_channel_type *ct, + struct gsm_subscriber_connection *conn) +{ + int rc, i, nc = 0; + + switch (ct->ch_rate_type) { + case GSM0808_DATA_FULL_BM: + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true); + nc += (rc == 0) ? 1 : 0; + break; + case GSM0808_DATA_HALF_LM: + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false); + nc += (rc == 0) ? 1 : 0; + break; + case GSM0808_DATA_FULL_PREF_NO_CHANGE: + case GSM0808_DATA_FULL_PREF: + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true); + nc += (rc == 0) ? 1 : 0; + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false); + nc += (rc == 0) ? 1 : 0; + break; + case GSM0808_DATA_HALF_PREF_NO_CHANGE: + case GSM0808_DATA_HALF_PREF: + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false); + nc += (rc == 0) ? 1 : 0; + rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true); + nc += (rc == 0) ? 1 : 0; + break; + default: + rc = -EINVAL; + break; + } + + if (!nc) { + LOGP(DMSC, LOGL_ERROR, "No supported data rate found for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[%s] }\n", + ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len)); + return -EINVAL; + } + + for (i = 0; i < nc; i++) { + DEBUGP(DMSC, "Found matching data rate (pref=%d): %s %s for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", + i, + req->ch_mode_rate_list[i].chan_rate == CH_RATE_FULL ? "full rate" : "half rate", + get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_list[i].chan_mode), + ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len)); + } + + req->n_ch_mode_rate = nc; + + return 0; +} + +/* Select a preferred and an alternative codec rate depending on the available + * capabilities. This decision does not include the actual lchan availability yet, + * this is also the reason why the result is a preferred and an alternate + * setting. The final decision is made in assignment_fsm.c when the actual + * lchan is requested. The preferred lchan will be requested first. If we + * find an alternate setting here, this one will be tried secondly if our + * primary choice fails. */ +static int select_codecs(struct assignment_request *req, const struct gsm0808_channel_type *ct, + struct gsm_subscriber_connection *conn, struct gsm_bts *bts) { int rc, i, nc = 0; struct bsc_msc_data *msc; - struct gsm_bts *bts = conn_get_bts(conn); if (!bts) { LOGP(DMSC, LOGL_ERROR, "No lchan, cannot select codecs\n"); @@ -671,34 +758,34 @@ static int select_codecs(struct assignment_request *req, struct gsm0808_channel_ switch (ct->ch_rate_type) { case GSM0808_SPEECH_FULL_BM: - rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts, + rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts, RATE_PREF_FR); - nc += (rc == 0); + nc += (rc == 0) ? 1 : 0; break; case GSM0808_SPEECH_HALF_LM: - rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts, + rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts, RATE_PREF_HR); - nc += (rc == 0); + nc += (rc == 0) ? 1 : 0; break; case GSM0808_SPEECH_PERM: case GSM0808_SPEECH_PERM_NO_CHANGE: case GSM0808_SPEECH_FULL_PREF_NO_CHANGE: case GSM0808_SPEECH_FULL_PREF: - rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts, + rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts, RATE_PREF_FR); - nc += (rc == 0); - rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts, + nc += (rc == 0) ? 1 : 0; + rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts, RATE_PREF_HR); - nc += (rc == 0); + nc += (rc == 0) ? 1 : 0; break; case GSM0808_SPEECH_HALF_PREF_NO_CHANGE: case GSM0808_SPEECH_HALF_PREF: - rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts, + rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts, RATE_PREF_HR); - nc += (rc == 0); - rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts, + nc += (rc == 0) ? 1 : 0; + rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts, RATE_PREF_FR); - nc += (rc == 0); + nc += (rc == 0) ? 1 : 0; break; default: rc = -EINVAL; @@ -718,8 +805,8 @@ static int select_codecs(struct assignment_request *req, struct gsm0808_channel_ DEBUGP(DMSC, "Found matching audio type (pref=%d): %s %s for channel_type =" " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", i, - req->ch_mode_rate[i].chan_rate == CH_RATE_FULL ? "full rate" : "half rate", - get_value_string(gsm48_chan_mode_names, req->ch_mode_rate[i].chan_mode), + req->ch_mode_rate_list[i].chan_rate == CH_RATE_FULL ? "full rate" : "half rate", + get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_list[i].chan_mode), ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len)); } @@ -734,49 +821,263 @@ static int select_sign_chan(struct assignment_request *req, struct gsm0808_chann switch (ct->ch_rate_type) { case GSM0808_SIGN_ANY: - req->ch_mode_rate[nc++].chan_rate = CH_RATE_SDCCH; - req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF; - req->ch_mode_rate[nc++].chan_rate = CH_RATE_FULL; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL; break; case GSM0808_SIGN_SDCCH: - req->ch_mode_rate[nc++].chan_rate = CH_RATE_SDCCH; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH; break; case GSM0808_SIGN_SDCCH_FULL_BM: - req->ch_mode_rate[nc++].chan_rate = CH_RATE_SDCCH; - req->ch_mode_rate[nc++].chan_rate = CH_RATE_FULL; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL; break; case GSM0808_SIGN_SDCCH_HALF_LM: - req->ch_mode_rate[nc++].chan_rate = CH_RATE_SDCCH; - req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF; break; case GSM0808_SIGN_FULL_BM: - req->ch_mode_rate[nc++].chan_rate = CH_RATE_FULL; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL; break; case GSM0808_SIGN_HALF_LM: - req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF; break; case GSM0808_SIGN_FULL_PREF: case GSM0808_SIGN_FULL_PREF_NO_CHANGE: - req->ch_mode_rate[nc++].chan_rate = CH_RATE_FULL; - req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF; break; case GSM0808_SIGN_HALF_PREF: case GSM0808_SIGN_HALF_PREF_NO_CHANGE: - req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF; - req->ch_mode_rate[nc++].chan_rate = CH_RATE_FULL; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF; + req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL; break; default: break; } for (i = 0; i < nc; i++) - req->ch_mode_rate[i].chan_mode = GSM48_CMODE_SIGN; + req->ch_mode_rate_list[i].chan_mode = GSM48_CMODE_SIGN; req->n_ch_mode_rate = nc; return nc > 0 ? 0 : -EINVAL; } +static int bssmap_handle_ass_req_tp_cic(struct tlv_parsed *tp, bool aoip, uint16_t *cic, uint8_t *cause) +{ + if (TLVP_PRESENT(tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) { + /* CIC is permitted in both AoIP and SCCPlite */ + *cic = osmo_load16be(TLVP_VAL(tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)); + return 0; + } + + if (!aoip) { + /* no CIC but SCCPlite: illegal */ + LOGP(DMSC, LOGL_ERROR, "SCCPlite MSC, but no CIC in ASSIGN REQ?\n"); + *cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + return -1; + } + + return 0; +} + +static int bssmap_handle_ass_req_tp_rtp_addr(struct tlv_parsed *tp, bool aoip, char *msc_rtp_addr, + size_t msc_rtp_addr_len, uint16_t *msc_rtp_port, uint8_t *cause) +{ + struct sockaddr_storage rtp_addr; + int rc; + unsigned int rc2; + + if (TLVP_PRESENT(tp, GSM0808_IE_AOIP_TRASP_ADDR)) { + if (!aoip) { + /* SCCPlite and AoIP transport address: illegal */ + LOGP(DMSC, LOGL_ERROR, "AoIP Transport address over IPA ?!?\n"); + *cause = GSM0808_CAUSE_INCORRECT_VALUE; + return -1; + } + /* Decode AoIP transport address element */ + rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr, + TLVP_VAL(tp, GSM0808_IE_AOIP_TRASP_ADDR), + TLVP_LEN(tp, GSM0808_IE_AOIP_TRASP_ADDR)); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n"); + *cause = GSM0808_CAUSE_INCORRECT_VALUE; + return -1; + } + + rc2 = osmo_sockaddr_to_str_and_uint(msc_rtp_addr, msc_rtp_addr_len, msc_rtp_port, + (const struct sockaddr *)&rtp_addr); + if (!rc2 || rc >= msc_rtp_addr_len) { + LOGP(DMSC, LOGL_ERROR, "Assignment request: RTP address is too long\n"); + *cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; + return -1; + } + return 0; + } + + if (aoip) { + /* no AoIP transport level address but AoIP transport: illegal */ + LOGP(DMSC, LOGL_ERROR, "AoIP transport address missing in ASSIGN REQ, " + "audio would not work; rejecting\n"); + *cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + return -1; + } + + return 0; +} + +static int bssmap_handle_ass_req_tp_osmux(struct gsm_subscriber_connection *conn, struct tlv_parsed *tp, + bool *use_osmux, uint8_t *osmux_cid, uint8_t *cause) +{ + int rc; + + if (TLVP_PRESENT(tp, GSM0808_IE_OSMO_OSMUX_CID)) { + if (conn->sccp.msc->use_osmux == OSMUX_USAGE_OFF) { + LOGP(DMSC, LOGL_ERROR, "MSC using Osmux but we have it disabled.\n"); + *cause = GSM0808_CAUSE_INCORRECT_VALUE; + return -1; + } + *use_osmux = true; + rc = gsm0808_dec_osmux_cid(osmux_cid, + TLVP_VAL(tp, GSM0808_IE_OSMO_OSMUX_CID), + TLVP_LEN(tp, GSM0808_IE_OSMO_OSMUX_CID)); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Unable to decode Osmux CID.\n"); + *cause = GSM0808_CAUSE_INCORRECT_VALUE; + return -1; + } + return 0; + } + + if (conn->sccp.msc->use_osmux == OSMUX_USAGE_ONLY) { + LOGP(DMSC, LOGL_ERROR, "MSC not using Osmux but we are forced to use it.\n"); + *cause = GSM0808_CAUSE_INCORRECT_VALUE; + return -1; + } + + if (conn->sccp.msc->use_osmux == OSMUX_USAGE_ON) + LOGP(DMSC, LOGL_NOTICE, "MSC not using Osmux but we have Osmux enabled.\n"); + + return 0; +} + +static int bssmap_handle_ass_req_tp_codec_list(struct gsm_subscriber_connection *conn, struct tlv_parsed *tp, bool aoip, + uint8_t *cause) +{ + int rc; + + /* Decode speech codec list. First set len = 0. */ + conn->codec_list = (struct gsm0808_speech_codec_list){}; + /* Check for speech codec list element */ + if (TLVP_PRESENT(tp, GSM0808_IE_SPEECH_CODEC_LIST)) { + /* Decode Speech Codec list */ + rc = gsm0808_dec_speech_codec_list(&conn->codec_list, + TLVP_VAL(tp, GSM0808_IE_SPEECH_CODEC_LIST), + TLVP_LEN(tp, GSM0808_IE_SPEECH_CODEC_LIST)); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n"); + *cause = GSM0808_CAUSE_INCORRECT_VALUE; + return -1; + } + } + + if (aoip && !conn->codec_list.len) { + LOGP(DMSC, LOGL_ERROR, "%s: AoIP Assignment Request:" + " Missing or empty Speech Codec List IE\n", bsc_subscr_name(conn->bsub)); + *cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + return -1; + } + + return 0; +} + +static int bssmap_handle_ass_req_ct_data(struct gsm_subscriber_connection *conn, struct tlv_parsed *tp, + struct gsm0808_channel_type *ct, struct assignment_request *req, + uint8_t *cause) +{ + bool aoip = gscon_is_aoip(conn); + int rc; + + *req = (struct assignment_request){ + .assign_for = ASSIGN_FOR_BSSMAP_REQ, + .aoip = aoip, + }; + + if (bssmap_handle_ass_req_tp_cic(tp, aoip, &req->msc_assigned_cic, cause) < 0) + return -1; + + if (bssmap_handle_ass_req_tp_rtp_addr(tp, aoip, req->msc_rtp_addr, sizeof(req->msc_rtp_addr), &req->msc_rtp_port, cause) < 0) + return -1; + + /* According to 3GPP TS 48.008 § 3.2.1.1 note 13, the codec list IE + * shall be included for aoip unless channel type is signalling. */ + if (bssmap_handle_ass_req_tp_codec_list(conn, tp, aoip, cause) < 0) + return -1; + + rc = select_data_rates(req, ct, conn); + if (rc < 0) { + *cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; + return -1; + } + + return 0; +} + +int bssmap_handle_ass_req_ct_speech(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, + struct tlv_parsed *tp, struct gsm0808_channel_type *ct, + struct assignment_request *req, uint8_t *cause) +{ + bool aoip = gscon_is_aoip(conn); + int rc; + + *req = (struct assignment_request){ + .assign_for = ASSIGN_FOR_BSSMAP_REQ, + .aoip = aoip, + }; + + if (bssmap_handle_ass_req_tp_cic(tp, aoip, &req->msc_assigned_cic, cause) < 0) + return -1; + + if (bssmap_handle_ass_req_tp_rtp_addr(tp, aoip, req->msc_rtp_addr, sizeof(req->msc_rtp_addr), &req->msc_rtp_port, cause) < 0) + return -1; + + if (bssmap_handle_ass_req_tp_osmux(conn, tp, &req->use_osmux, &req->osmux_cid, cause) < 0) + return -1; + + if (bssmap_handle_ass_req_tp_codec_list(conn, tp, aoip, cause) < 0) + return -1; + + /* Match codec information from the assignment command against the + * local preferences of the BSC and BTS */ + rc = select_codecs(req, ct, conn, bts); + if (rc < 0) { + *cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; + return -1; + } + + return 0; +} + +static int bssmap_handle_ass_req_ct_sign(struct gsm_subscriber_connection *conn, struct gsm0808_channel_type *ct, + struct assignment_request *req, uint8_t *cause) +{ + int rc; + + *req = (struct assignment_request){ + .assign_for = ASSIGN_FOR_BSSMAP_REQ, + .aoip = gscon_is_aoip(conn), + }; + + rc = select_sign_chan(req, ct); + if (rc < 0) { + *cause = GSM0808_CAUSE_INCORRECT_VALUE; + return rc; + } + + return 0; +} + /* * Handle the assignment request message. * @@ -787,12 +1088,8 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, { struct msgb *resp; struct tlv_parsed tp; - uint16_t cic = 0; - bool aoip = false; - bool use_osmux = false; - uint8_t osmux_cid = 0; - struct sockaddr_storage rtp_addr; struct gsm0808_channel_type ct; + struct gsm0808_group_callref gc; uint8_t cause; int rc; struct assignment_request req = {}; @@ -803,9 +1100,10 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, return -1; } - aoip = gscon_is_aoip(conn); - - tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0); + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } /* Check for channel type element, if its missing, immediately reject */ if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)) { @@ -823,151 +1121,93 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, goto reject; } - bssmap_handle_ass_req_lcls(conn, &tp); + /* Check for assignment to VGCS channel. */ + if (TLVP_PRESENT(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)) { + struct gsm_bts *bts = conn_get_bts(conn); - /* Currently we only support a limited subset of all - * possible channel types, such as multi-slot or CSD */ - switch (ct.ch_indctr) { - case GSM0808_CHAN_DATA: - LOGP(DMSC, LOGL_ERROR, "Unsupported channel type, currently only speech is supported!\n"); - cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP; - goto reject; - case GSM0808_CHAN_SPEECH: - if (TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) { - /* CIC is permitted in both AoIP and SCCPlite */ - cic = osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)); - } else { - if (!aoip) { - /* no CIC but SCCPlite: illegal */ - LOGP(DMSC, LOGL_ERROR, "SCCPlite MSC, but no CIC in ASSIGN REQ?\n"); - cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; - goto reject; - } + OSMO_ASSERT(bts); + /* Decode Group Call Reference. */ + rc = gsm0808_dec_group_callref(&gc, TLVP_VAL(&tp, GSM0808_IE_GROUP_CALL_REFERENCE), + TLVP_LEN(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Unable to decode Group Call Reference.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; } - if (TLVP_PRESENT(&tp, GSM0808_IE_AOIP_TRASP_ADDR)) { - if (!aoip) { - /* SCCPlite and AoIP transport address: illegal */ - LOGP(DMSC, LOGL_ERROR, "AoIP Transport address over IPA ?!?\n"); - cause = GSM0808_CAUSE_INCORRECT_VALUE; - goto reject; - } - /* Decode AoIP transport address element */ - rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr, - TLVP_VAL(&tp, GSM0808_IE_AOIP_TRASP_ADDR), - TLVP_LEN(&tp, GSM0808_IE_AOIP_TRASP_ADDR)); - if (rc < 0) { - LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n"); - cause = GSM0808_CAUSE_INCORRECT_VALUE; - goto reject; - } - } else if (aoip) { - /* no AoIP transport level address but AoIP transport: illegal */ - LOGP(DMSC, LOGL_ERROR, "AoIP transport address missing in ASSIGN REQ, " - "audio would not work; rejecting\n"); - cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + req.target_lchan = vgcs_vbs_find_lchan(bts, &gc); + if (!req.target_lchan) { + cause = GSM0808_CAUSE_INCORRECT_VALUE; goto reject; } + req.assign_for = ASSIGN_FOR_BSSMAP_REQ; + req.vgcs = true; - if (TLVP_PRESENT(&tp, GSM0808_IE_OSMO_OSMUX_CID)) { - if (conn->sccp.msc->use_osmux == OSMUX_USAGE_OFF) { - LOGP(DMSC, LOGL_ERROR, "MSC using Osmux but we have it disabled.\n"); - cause = GSM0808_CAUSE_INCORRECT_VALUE; - goto reject; - } - use_osmux = true; - rc = gsm0808_dec_osmux_cid(&osmux_cid, - TLVP_VAL(&tp, GSM0808_IE_OSMO_OSMUX_CID), - TLVP_LEN(&tp, GSM0808_IE_OSMO_OSMUX_CID)); - if (rc < 0) { - LOGP(DMSC, LOGL_ERROR, "Unable to decode Osmux CID.\n"); - cause = GSM0808_CAUSE_INCORRECT_VALUE; - goto reject; - } - } else { - if (conn->sccp.msc->use_osmux == OSMUX_USAGE_ONLY) { - LOGP(DMSC, LOGL_ERROR, "MSC not using Osmux but we are forced to use it.\n"); - cause = GSM0808_CAUSE_INCORRECT_VALUE; - goto reject; - } else if (conn->sccp.msc->use_osmux == OSMUX_USAGE_ON) - LOGP(DMSC, LOGL_NOTICE, "MSC not using Osmux but we have Osmux enabled.\n"); + /* Copy timing advance. */ + if (conn->lchan) { + req.target_lchan->activate.info.ta_known = conn->lchan->activate.info.ta_known; + req.target_lchan->activate.info.ta = conn->lchan->activate.info.ta; } - /* Decode speech codec list. First set len = 0. */ - conn->codec_list = (struct gsm0808_speech_codec_list){}; - /* Check for speech codec list element */ - if (TLVP_PRESENT(&tp, GSM0808_IE_SPEECH_CODEC_LIST)) { - /* Decode Speech Codec list */ - rc = gsm0808_dec_speech_codec_list(&conn->codec_list, - TLVP_VAL(&tp, GSM0808_IE_SPEECH_CODEC_LIST), - TLVP_LEN(&tp, GSM0808_IE_SPEECH_CODEC_LIST)); - if (rc < 0) { - LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n"); - cause = GSM0808_CAUSE_INCORRECT_VALUE; - goto reject; - } - } + /* Send reactivation on target lchan to prepare VGCS channel for assignment. + * See patent EP 1 858 275 A1. */ + rsl_tx_chan_activ(req.target_lchan, RSL_ACT_TYPE_REACT | RSL_ACT_INTRA_NORM_ASS, 0); - if (aoip && !conn->codec_list.len) { - LOGP(DMSC, LOGL_ERROR, "%s: AoIP speech mode Assignment Request:" - " Missing or empty Speech Codec List IE\n", bsc_subscr_name(conn->bsub)); - cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; - goto reject; - } + return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req); + } - req = (struct assignment_request){ - .aoip = aoip, - .msc_assigned_cic = cic, - .use_osmux = use_osmux, - .osmux_cid = osmux_cid, - }; + bssmap_handle_ass_req_lcls(conn, &tp); - /* Match codec information from the assignment command against the - * local preferences of the BSC and BTS */ - rc = select_codecs(&req, &ct, conn); - if (rc < 0) { - cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; + /* Currently we only support a limited subset of all + * possible channel types, such as multi-slot */ + switch (ct.ch_indctr) { + case GSM0808_CHAN_DATA: + if (bssmap_handle_ass_req_ct_data(conn, &tp, &ct, &req, &cause) < 0) + goto reject; + break; + case GSM0808_CHAN_SPEECH: + if (bssmap_handle_ass_req_ct_speech(conn, conn_get_bts(conn), &tp, &ct, &req, &cause) < 0) goto reject; - } - - if (aoip) { - unsigned int rc = osmo_sockaddr_to_str_and_uint(req.msc_rtp_addr, - sizeof(req.msc_rtp_addr), - &req.msc_rtp_port, - (const struct sockaddr*)&rtp_addr); - if (!rc || rc >= sizeof(req.msc_rtp_addr)) { - LOGP(DMSC, LOGL_ERROR, "Assignment request: RTP address is too long\n"); - cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; - goto reject; - } - } break; case GSM0808_CHAN_SIGN: - req = (struct assignment_request){ - .aoip = aoip, - }; - - rc = select_sign_chan(&req, &ct); - if (rc < 0) { - cause = GSM0808_CAUSE_INCORRECT_VALUE; + if (bssmap_handle_ass_req_ct_sign(conn, &ct, &req, &cause) < 0) goto reject; - } break; default: cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS; goto reject; } + req.ch_indctr = ct.ch_indctr; + return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req); reject: resp = gsm0808_create_assignment_failure(cause, NULL); OSMO_ASSERT(resp); - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_FAILURE]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_FAILURE)); osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); return -1; } +/* Handle Handover Request message, part of inter-BSC handover: + * The MSC opened a new SCCP connection and is asking this BSS to accept an inter-BSC incoming handover. + * If we accept, we'll send a Handover Request Acknowledge. + * This function is only called when the Handover Request is *not* included in the initial SCCP N-Connect message, but + * follows an "empty" N-Connect in a separate DT1 message. + */ +static int bssmap_handle_handover_request(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + if (osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_INITIAL_USER_DATA, msg)) { + /* A Handover Request message should come in on a newly opened SCCP conn. Apparently the MSC has sent a + * Handover Request on an already busy SCCP conn, and naturally we cannot accept another subscriber + * here. This is unlikely to ever happen in practice. Respond in the only possible way: */ + bsc_tx_bssmap_ho_failure(conn); + return -EINVAL; + } + return 0; +} + /* Handle Handover Command message, part of inter-BSC handover: * This BSS sent a Handover Required message. * The MSC contacts the remote BSS and receives from it an RR Handover Command; this BSSMAP Handover @@ -988,7 +1228,10 @@ static int bssmap_handle_handover_cmd(struct gsm_subscriber_connection *conn, return -EINVAL; } - tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0); + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } /* Check for channel type element, if its missing, immediately reject */ if (!TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_INFORMATION)) { @@ -1026,7 +1269,10 @@ static int bssmap_handle_confusion(struct gsm_subscriber_connection *conn, enum gsm0808_cause_class cause_class; struct gsm0808_diagnostics *diag; - osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1); + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } /* Check for the Cause and Diagnostic mandatory elements */ if (!TLVP_PRESENT(&tp, GSM0808_IE_CAUSE) || !TLVP_PRESENT(&tp, GSM0808_IE_DIAGNOSTIC)) { @@ -1068,9 +1314,11 @@ static int bssmap_handle_common_id(struct gsm_subscriber_connection *conn, struct msgb *msg, unsigned int length) { struct tlv_parsed tp; - struct osmo_mobile_identity mi_imsi; - osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1); + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } /* Check for the mandatory elements */ if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) { @@ -1080,13 +1328,102 @@ static int bssmap_handle_common_id(struct gsm_subscriber_connection *conn, return -EINVAL; } - if (osmo_mobile_identity_decode(&mi_imsi, TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI), false) - || mi_imsi.type != GSM_MI_TYPE_IMSI) { - LOGPFSML(conn->fi, LOGL_ERROR, "CommonID: could not parse IMSI\n"); - return -EINVAL; + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_COMMON_ID_IND, &tp); + + return 0; +} + +/* Handle (VGCS) UPLINK REQUEST ACKNOWLEDGE: + * + * See 3GPP TS 48.008 §3.2.1.58 + */ +static int bssmap_handle_uplink_rqst_acknowledge(struct gsm_subscriber_connection *conn, + struct msgb *msg, unsigned int length) +{ + struct tlv_parsed tp; + + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } + + if (conn->vgcs_call.fi) + osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_ACK, NULL); + return 0; +} + +/* Handle (VGCS) UPLINK REJECT COMMAND message. + * + * See 3GPP TS 48.008 §3.2.1.61 + */ +static int bssmap_handle_uplink_reject_cmd(struct gsm_subscriber_connection *conn, + struct msgb *msg, unsigned int length) +{ + struct tlv_parsed tp; + + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } + + if (conn->vgcs_call.fi) + osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_REJECT, NULL); + return 0; +} + +/* Handle (VGCS) UPLINK RELEASE COMMAND message, MSC indicating an error to us: + * + * See 3GPP TS 48.008 §3.2.1.62 + */ +static int bssmap_handle_uplink_release_cmd(struct gsm_subscriber_connection *conn, + struct msgb *msg, unsigned int length) +{ + struct tlv_parsed tp; + + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } + + if (conn->vgcs_call.fi) + osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_RELEASE, NULL); + return 0; +} + +/* Handle (VGCS) UPLINK SEIZED COMMAND message: + * + * See 3GPP TS 48.008 §3.2.1.63 + */ +static int bssmap_handle_uplink_seized_cmd(struct gsm_subscriber_connection *conn, + struct msgb *msg, unsigned int length) +{ + struct tlv_parsed tp; + + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; + } + + if (conn->vgcs_call.fi) + osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_SEIZE, NULL); + return 0; +} + +/* Handle VGCS/VBS ADDITIONAL INFO message: + * + * See 3GPP TS 48.008 §3.2.1.78 + */ +static int bssmap_handle_vgcs_addl_info(struct gsm_subscriber_connection *conn, + struct msgb *msg, unsigned int length) +{ + struct tlv_parsed tp; + + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + return -1; } - osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_COMMON_ID_IND, &mi_imsi); + LOGPFSML(conn->fi, LOGL_ERROR, "VGCS ADDITIONAL INFO is not supported.\n"); return 0; } @@ -1151,14 +1488,18 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn, rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_CIPHER_MODE_CMD]); ret = bssmap_handle_cipher_mode(conn, msg, length); break; - case BSS_MAP_MSG_ASSIGMENT_RQST: - rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_ASSIGMENT_RQST]); + case BSS_MAP_MSG_ASSIGNMENT_RQST: + rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_ASSIGNMENT_RQST]); ret = bssmap_handle_assignm_req(conn, msg, length); break; case BSS_MAP_MSG_LCLS_CONNECT_CTRL: rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_LCLS_CONNECT_CTRL]); ret = bssmap_handle_lcls_connect_ctrl(conn, msg, length); break; + case BSS_MAP_MSG_HANDOVER_RQST: + rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST]); + ret = bssmap_handle_handover_request(conn, msg); + break; case BSS_MAP_MSG_HANDOVER_CMD: rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_CMD]); ret = bssmap_handle_handover_cmd(conn, msg, length); @@ -1190,6 +1531,26 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn, ret = 0; } break; + case BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE: + rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UPLINK_RQST_ACKNOWLEDGE]); + ret = bssmap_handle_uplink_rqst_acknowledge(conn, msg, length); + break; + case BSS_MAP_MSG_UPLINK_REJECT_CMD: + rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UPLINK_REJECT_CMD]); + ret = bssmap_handle_uplink_reject_cmd(conn, msg, length); + break; + case BSS_MAP_MSG_UPLINK_RELEASE_CMD: + rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UPLINK_RELEASE_CMD]); + ret = bssmap_handle_uplink_release_cmd(conn, msg, length); + break; + case BSS_MAP_MSG_UPLINK_SEIZED_CMD: + rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UPLINK_SEIZED_CMD]); + ret = bssmap_handle_uplink_seized_cmd(conn, msg, length); + break; + case BSS_MAP_MSG_VGCS_ADDL_INFO: + rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_VGCS_ADDL_INFO]); + ret = bssmap_handle_vgcs_addl_info(conn, msg, length); + break; default: rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UNKNOWN]); LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n", @@ -1200,16 +1561,6 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn, return ret; } -/* RSL Link Identifier is defined in 3GPP TS 3GPP TS 48.058, section 9.3.2. - * .... .SSS - SAPI value used on the radio link; - * ...P P... - priority for SAPI0 messages; - * CC.. .... - control channel identification: - * 00.. .... - main signalling channel (FACCH or SDCCH), - * 01.. .... - SACCH, - * other values are reserved. */ -#define DLCI2RSL_LINK_ID(dlci) \ - ((dlci & 0xc0) == 0xc0 ? 0x40 : 0x00) | (dlci & 0x07) - static int dtap_rcvmsg(struct gsm_subscriber_connection *conn, struct msgb *msg, unsigned int length) { @@ -1263,7 +1614,10 @@ static int dtap_rcvmsg(struct gsm_subscriber_connection *conn, /* convert DLCI to RSL link ID, store in msg->cb */ OBSC_LINKID_CB(gsm48) = DLCI2RSL_LINK_ID(header->link_id); - dtap_rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, gsm48); + if (conn->vgcs_call.fi) + dtap_rc = osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_DTAP, gsm48); + else + dtap_rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, gsm48); return dtap_rc; } @@ -1357,6 +1711,7 @@ int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell { int rc; struct msgb *msg; + struct gsm_subscriber_connection *conn = lchan->conn; struct gsm0808_handover_required params = { .cause = GSM0808_CAUSE_BETTER_CELL, .cil = *target_cells, @@ -1364,12 +1719,21 @@ int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell .current_channel_type_1 = gsm0808_current_channel_type_1(lchan->type), }; + /* Even if fast_return is now allowed locally, we may still want to + * signal the Last EUTRAN PLMN Id to the new cell, since destination + * config may differ and allow fast return */ + if (conn->fast_return.last_eutran_plmn_valid) { + params.old_bss_to_new_bss_info_present = true; + params.old_bss_to_new_bss_info.last_eutran_plmn_id_present = true; + params.old_bss_to_new_bss_info.last_eutran_plmn_id = conn->fast_return.last_eutran_plmn; + } + switch (lchan->type) { case GSM_LCHAN_TCH_F: case GSM_LCHAN_TCH_H: params.speech_version_used_present = true; params.speech_version_used = gsm0808_permitted_speech(lchan->type, - lchan->tch_mode); + lchan->current_ch_mode_rate.chan_mode); if (!params.speech_version_used) { LOG_HO(lchan->conn, LOGL_ERROR, "Cannot encode Speech Version (Used)" " for BSSMAP Handover Required message\n"); @@ -1382,14 +1746,14 @@ int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell msg = gsm0808_create_handover_required(¶ms); if (!msg) { - LOG_HO(lchan->conn, LOGL_ERROR, "Cannot compose BSSMAP Handover Required message\n"); + LOG_HO(conn, LOGL_ERROR, "Cannot compose BSSMAP Handover Required message\n"); return -EINVAL; } - rate_ctr_inc(&lchan->conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_REQUIRED]); - rc = gscon_sigtran_send(lchan->conn, msg); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_REQUIRED)); + rc = gscon_sigtran_send(conn, msg); if (rc) { - LOG_HO(lchan->conn, LOGL_ERROR, "Cannot send BSSMAP Handover Required message\n"); + LOG_HO(conn, LOGL_ERROR, "Cannot send BSSMAP Handover Required message\n"); return rc; } @@ -1407,14 +1771,17 @@ int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn, struct .l3_info = rr_ho_command->data, .l3_info_len = rr_ho_command->len, .chosen_channel_present = true, - .chosen_channel = gsm0808_chosen_channel(new_lchan->type, new_lchan->tch_mode), - .chosen_encr_alg = new_lchan->encr.alg_id, - .chosen_speech_version = gsm0808_permitted_speech(new_lchan->type, new_lchan->tch_mode), + .chosen_channel = gsm0808_chosen_channel(new_lchan->type, new_lchan->current_ch_mode_rate.chan_mode), + .chosen_encr_alg = ALG_A5_NR_TO_BSSAP(new_lchan->encr.alg_a5_n), + .chosen_speech_version = gsm0808_permitted_speech(new_lchan->type, + new_lchan->current_ch_mode_rate.chan_mode), }; - if (gscon_is_aoip(conn)) { + if (gscon_is_aoip(conn) && bsc_chan_ind_requires_rtp_stream(new_lchan->activate.info.ch_indctr)) { struct osmo_sockaddr_str to_msc_rtp; const struct mgcp_conn_peer *rtp_info = osmo_mgcpc_ep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc); + int rc; + int perm_spch; if (!rtp_info) { LOG_HO(conn, LOGL_ERROR, "Handover Request Acknowledge: no RTP address known to send as" @@ -1430,9 +1797,22 @@ int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn, struct return -EINVAL; } params.aoip_transport_layer = &ss; + + /* speech_codec_chosen */ + perm_spch = gsm0808_permitted_speech(new_lchan->type, new_lchan->current_ch_mode_rate.chan_mode); + params.speech_codec_chosen_present = true; + rc = gsm0808_speech_codec_from_chan_type(¶ms.speech_codec_chosen, perm_spch); + if (rc) { + LOG_HO(conn, LOGL_ERROR, "Unable to compose Speech Codec (Chosen)\n"); + return -EINVAL; + } + + /* Codec list (BSS Supported) */ + params.more_items = true; + gen_bss_supported_codec_list(¶ms.codec_list_bss_supported, conn->sccp.msc, new_lchan->ts->trx->bts); } - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_RQST_ACKNOWLEDGE]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_RQST_ACKNOWLEDGE)); LOG_HO(conn, LOGL_DEBUG, "Sending BSSMAP Handover Request Acknowledge\n"); msg = gsm0808_create_handover_request_ack2(¶ms); msgb_free(rr_ho_command); @@ -1448,7 +1828,7 @@ int bsc_tx_bssmap_ho_detect(struct gsm_subscriber_connection *conn) if (!msg) return -ENOMEM; - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_DETECT]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_DETECT)); return osmo_bsc_sigtran_send(conn, msg); } @@ -1462,18 +1842,18 @@ enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection struct gsm0808_handover_complete params = { .chosen_encr_alg_present = true, - .chosen_encr_alg = lchan->encr.alg_id, + .chosen_encr_alg = ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n), .chosen_channel_present = true, - .chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode), + .chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode), .lcls_bss_status_present = (lcls_status != 0xff), .lcls_bss_status = lcls_status, }; /* speech_codec_chosen */ - if (ho->new_lchan->activate.info.requires_voice_stream && gscon_is_aoip(conn)) { - int perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode); + if (bsc_chan_ind_requires_rtp_stream(ho->new_lchan->activate.info.ch_indctr) && gscon_is_aoip(conn)) { + int perm_spch = gsm0808_permitted_speech(lchan->type, lchan->current_ch_mode_rate.chan_mode); params.speech_codec_chosen_present = true; rc = gsm0808_speech_codec_from_chan_type(¶ms.speech_codec_chosen, perm_spch); if (rc) { @@ -1488,7 +1868,7 @@ enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection return HO_RESULT_ERROR; } - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE)); rc = osmo_bsc_sigtran_send(conn, msg); if (rc) { LOG_HO(conn, LOGL_ERROR, "Cannot send BSSMAP Handover Complete message\n"); @@ -1510,9 +1890,186 @@ void bsc_tx_bssmap_ho_failure(struct gsm_subscriber_connection *conn) return; } - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE)); rc = osmo_bsc_sigtran_send(conn, msg); if (rc) LOG_HO(conn, LOGL_ERROR, "Cannot send BSSMAP Handover Failure message (rc=%d %s)\n", rc, strerror(-rc)); } + +/* Send SETUP ACKNOWLEDGE to MSC. */ +void bsc_tx_setup_ack(struct gsm_subscriber_connection *conn, struct gsm0808_vgcs_feature_flags *ff) +{ + struct msgb *resp; + struct gsm0808_vgcs_vbs_setup_ack sa = {}; + + if (ff) { + sa.vgcs_feature_flags_present = true; + sa.flags = *ff; + } + resp = gsm0808_create_vgcs_vbs_setup_ack(&sa); + OSMO_ASSERT(resp); + + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_SETUP_ACK)); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); +} + +/* Send SETUP REFUSE to MSC. */ +void bsc_tx_setup_refuse(struct gsm_subscriber_connection *conn, uint8_t cause) +{ + struct msgb *resp; + resp = gsm0808_create_vgcs_vbs_setup_refuse(cause); + + OSMO_ASSERT(resp); + + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_SETUP_REFUSE)); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); +} + +/* Send ASSIGNMENT FAILURE to MSC. */ +void bsc_tx_vgcs_vbs_assignment_fail(struct gsm_subscriber_connection *conn, uint8_t cause) +{ + struct msgb *resp; + struct gsm0808_vgcs_vbs_assign_fail af = { + .cause = cause, + }; + + resp = gsm0808_create_vgcs_vbs_assign_fail(&af); + OSMO_ASSERT(resp); + + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGN_FAIL)); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); +} + +/* Send ASSIGNMENT RESULT to MSC. */ +void bsc_tx_vgcs_vbs_assignment_result(struct gsm_subscriber_connection *conn, struct gsm0808_channel_type *ct, + struct gsm0808_cell_id *ci, uint32_t call_id) +{ + struct gsm_lchan *lchan = conn->lchan; + struct msgb *resp; + struct gsm0808_vgcs_vbs_assign_res ar = { + .channel_type = *ct, + .cell_identifier = *ci, + }; + int perm_spch; + uint8_t osmux_cid; + + /* Chosen Channel */ + ar.chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode); + if (!ar.chosen_channel) { + LOGP(DMSC, LOGL_ERROR, "Unable to compose Chosen Channel for mode=%s type=%s", + get_value_string(gsm48_chan_mode_names, lchan->current_ch_mode_rate.chan_mode), + gsm_chan_t_name(lchan->type)); + bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + return; + } + ar.chosen_channel_present = true; + + /* Generate RTP related fields. */ + if (gscon_is_aoip(conn)) { + /* AoIP Transport Layer Address (BSS) */ + if (!osmo_mgcpc_ep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc, + &ar.aoip_transport_layer)) { + LOGP(DMSC, LOGL_ERROR, "Unable to compose RTP address of MGW -> MSC"); + bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + return; + } + ar.aoip_transport_layer_present = true; + + /* Call Identifier */ + ar.call_id = call_id; + ar.call_id_present = true; + + /* Osmux */ + if (conn->assignment.req.use_osmux) { + if (!osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(conn->user_plane.mgw_endpoint_ci_msc, + &osmux_cid)) { + LOGP(DMSC, LOGL_ERROR, "Unable to compose Osmux CID of MGW -> MSC"); + bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + return; + } + } + + /* Extrapolate speech codec from speech mode */ + perm_spch = gsm0808_permitted_speech(lchan->type, lchan->current_ch_mode_rate.chan_mode); + gsm0808_speech_codec_from_chan_type(&ar.codec_msc_chosen, perm_spch); + ar.codec_msc_chosen.cfg = conn->lchan->current_ch_mode_rate.s15_s0; + ar.codec_present = true; + } + + resp = gsm0808_create_vgcs_vbs_assign_res(&ar); + OSMO_ASSERT(resp); + if (conn->assignment.req.use_osmux) + bssap_extend_osmux(resp, osmux_cid); + + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGN_RESULT)); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); +} + +/* Send UPLINK REQUEST to MSC. */ +void bsc_tx_uplink_req(struct gsm_subscriber_connection *conn) +{ + struct msgb *resp; + struct gsm0808_uplink_request ur = {}; + + resp = gsm0808_create_uplink_request(&ur); + OSMO_ASSERT(resp); + + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_UPLINK_RQST)); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); +} + +/* Send UPLINK REQUEST CONFIRMATION to MSC. */ +void bsc_tx_uplink_req_conf(struct gsm_subscriber_connection *conn, struct gsm0808_cell_id *ci, uint8_t *l3_info, + uint8_t length) +{ + struct msgb *resp; + struct gsm0808_uplink_request_cnf ur = { + .cell_identifier = *ci, + }; + + OSMO_ASSERT(length <= LAYER_3_INFORMATION_MAXLEN); + if (length) { + memcpy(ur.l3.l3, l3_info, length); + ur.l3.l3_len = length; + } + resp = gsm0808_create_uplink_request_cnf(&ur); + OSMO_ASSERT(resp); + + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_UPLINK_RQST_CONFIRMATION)); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); +} + +/* Send UPLINK APPLICATION DATA to MSC. */ +void bsc_tx_uplink_app_data(struct gsm_subscriber_connection *conn, struct gsm0808_cell_id *ci, uint8_t *l3_info, + uint8_t length) +{ + struct msgb *resp; + struct gsm0808_uplink_app_data ad = { + .cell_identifier = *ci, + }; + + OSMO_ASSERT(length <= LAYER_3_INFORMATION_MAXLEN); + memcpy(ad.l3.l3, l3_info, length); + ad.l3.l3_len = length; + resp = gsm0808_create_uplink_app_data(&ad); + OSMO_ASSERT(resp); + + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_UPLINK_APP_DATA)); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); +} + +/* Send UPLINK RELEASE INDICATION to MSC. */ +void bsc_tx_uplink_release_ind(struct gsm_subscriber_connection *conn, uint8_t cause) +{ + struct msgb *resp; + struct gsm0808_uplink_release_ind ri = { + .cause = cause, + }; + + resp = gsm0808_create_uplink_release_ind(&ri); + OSMO_ASSERT(resp); + + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_UPLINK_RELEASE_INDICATION)); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); +} diff --git a/src/osmo-bsc/osmo_bsc_filter.c b/src/osmo-bsc/osmo_bsc_filter.c index 497b3e4de..2b58ccf54 100644 --- a/src/osmo-bsc/osmo_bsc_filter.c +++ b/src/osmo-bsc/osmo_bsc_filter.c @@ -119,5 +119,23 @@ int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg) bsc_patch_mm_info(conn, &gh->data[0], length); } + if (conn && conn->lchan) { + struct rate_ctr_group *bts_ctrs = conn->lchan->ts->trx->bts->bts_ctrs; + switch (mtype) { + case GSM48_MT_MM_LOC_UPD_ACCEPT: + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_LOCATION_UPDATE_ACCEPT)); + break; + case GSM48_MT_MM_LOC_UPD_REJECT: + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_LOCATION_UPDATE_REJECT)); + break; + case GSM48_MT_MM_IMSI_DETACH_IND: + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_LOCATION_UPDATE_DETACH)); + break; + default: + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_LOCATION_UPDATE_UNKNOWN)); + break; + } + } + return 0; } diff --git a/src/osmo-bsc/osmo_bsc_lcls.c b/src/osmo-bsc/osmo_bsc_lcls.c index 4ab04419d..eab0be4d1 100644 --- a/src/osmo-bsc/osmo_bsc_lcls.c +++ b/src/osmo-bsc/osmo_bsc_lcls.c @@ -30,6 +30,7 @@ #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/osmo_bsc_lcls.h> #include <osmocom/bsc/lchan_rtp_fsm.h> +#include <osmocom/bsc/lchan.h> #include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> struct value_string lcls_event_names[] = { @@ -243,13 +244,19 @@ static inline void lcls_rsl(const struct gsm_subscriber_connection *conn, bool e uint32_t ip = enable ? conn->lcls.other->lchan->abis_ip.bound_ip : lchan->abis_ip.connect_ip; /* RSL_IE_IPAC_REMOTE_PORT */ uint16_t port = enable ? conn->lcls.other->lchan->abis_ip.bound_port : lchan->abis_ip.connect_port; + struct msgb *msg; if (!conn->lcls.other) { LOGPFSM(conn->lcls.fi, "%s LCLS: other conn is not available!\n", enable ? "enable" : "disable"); return; } - abis_rsl_sendmsg(rsl_make_ipacc_mdcx(lchan, ip, port)); + msg = rsl_make_ipacc_mdcx(lchan, ip, port); + if (!msg) { + LOGPFSML(conn->lcls.fi, LOGL_ERROR, "Error encoding IPACC MDCX\n"); + return; + } + abis_rsl_sendmsg(msg); } static inline bool lcls_check_toggle_allowed(const struct gsm_subscriber_connection *conn, bool enable) @@ -345,15 +352,14 @@ static bool lcls_enable_possible(const struct gsm_subscriber_connection *conn) return false; } - if (conn->lchan->tch_mode != conn->lcls.other->lchan->tch_mode + if (conn->lchan->current_ch_mode_rate.chan_mode != conn->lcls.other->lchan->current_ch_mode_rate.chan_mode && conn->sccp.msc->lcls_codec_mismatch_allow == false) { LOGPFSM(conn->lcls.fi, "Not enabling LS due to TCH-mode mismatch: %s:%s != %s:%s\n", gsm_lchan_name(conn->lchan), - gsm48_chan_mode_name(conn->lchan->tch_mode), + gsm48_chan_mode_name(conn->lchan->current_ch_mode_rate.chan_mode), gsm_lchan_name(conn->lcls.other->lchan), - gsm48_chan_mode_name(conn->lcls.other->lchan-> - tch_mode)); + gsm48_chan_mode_name(conn->lcls.other->lchan->current_ch_mode_rate.chan_mode)); return false; } @@ -556,7 +562,7 @@ static void lcls_req_lcls_not_supp_fn(struct osmo_fsm_inst *fi, uint32_t event, case LCLS_EV_UPDATE_CFG_CSC: if (lcls_handle_cfg_update(conn, data) != 0) return; - //FIXME osmo_fsm_inst_state_chg(fi, + //FIXME osmo_fsm_inst_state_chg(fi, return; case LCLS_EV_APPLY_CFG_CSC: if (lcls_perform_correlation(conn) != 0) { diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c index aa45bf07f..8da319993 100644 --- a/src/osmo-bsc/osmo_bsc_main.c +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -39,6 +39,7 @@ #include <osmocom/bsc/handover_fsm.h> #include <osmocom/bsc/smscb.h> #include <osmocom/bsc/lb.h> +#include <osmocom/bsc/meas_feed.h> #include <osmocom/ctrl/control_cmd.h> #include <osmocom/ctrl/control_if.h> @@ -57,6 +58,7 @@ #include <osmocom/vty/cpu_sched_vty.h> #include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> +#include <osmocom/mgcp_client/mgcp_client_pool.h> #include <osmocom/abis/abis.h> #include <osmocom/bsc/abis_om2000.h> @@ -67,8 +69,10 @@ #include <osmocom/bsc/codec_pref.h> #include <osmocom/bsc/system_information.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bsc_stats.h> #include <osmocom/mgcp_client/mgcp_client.h> +#include <osmocom/mgcp_client/mgcp_client_pool.h> #include <osmocom/sigtran/xua_msg.h> @@ -84,17 +88,16 @@ #include "../../bscconfig.h" -struct gsm_network *bsc_gsmnet = 0; static const char *config_file = "osmo-bsc.cfg"; static const char *rf_ctrl = NULL; static int daemonize = 0; -static void print_usage() +static void print_usage(void) { printf("Usage: osmo-bsc\n"); } -static void print_help() +static void print_help(void) { printf("Some useful options:\n"); printf(" -h --help This text.\n"); @@ -104,10 +107,8 @@ static void print_help() printf(" -T --timestamp Print a timestamp in the debug output.\n"); printf(" -V --version Print the version of OsmoBSC.\n"); printf(" -c --config-file filename The config file to use.\n"); - printf(" -l --local IP The local address of the MGCP.\n"); printf(" -e --log-level number Set a global loglevel.\n"); printf(" -r --rf-ctl NAME A unix domain socket to listen for cmds.\n"); - printf(" -t --testmode A special mode to provoke failures at the MSC.\n"); printf("\nVTY reference generation:\n"); printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"); @@ -152,16 +153,14 @@ static void handle_options(int argc, char **argv) {"disable-color", 0, 0, 's'}, {"timestamp", 0, 0, 'T'}, {"version", 0, 0, 'V' }, - {"local", 1, 0, 'l'}, {"log-level", 1, 0, 'e'}, {"rf-ctl", 1, 0, 'r'}, - {"testmode", 0, 0, 't'}, {"vty-ref-mode", 1, &long_option, 1}, {"vty-ref-xml", 0, &long_option, 2}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, "hd:DsTVc:e:r:t", + c = getopt_long(argc, argv, "hd:DsTVc:e:r:", long_options, &option_index); if (c == -1) break; @@ -233,7 +232,7 @@ static int oml_msg_nack(struct nm_nack_signal_data *nack) return 0; } - if (is_ipaccess_bts(nack->bts)) + if (is_ipa_abisip_bts(nack->bts)) ipaccess_drop_oml_deferred(nack->bts); return 0; @@ -256,14 +255,14 @@ static int nm_sig_cb(unsigned int subsys, unsigned int signal, } /* Produce a MA as specified in 10.5.2.21 */ -static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts) +static void generate_ma_for_ts(struct gsm_bts_trx_ts *ts) { /* we have three bitvecs: the per-timeslot ARFCNs, the cell chan ARFCNs * and the MA */ + const size_t num_cell_arfcns = ts->trx->bts->si_common.cell_chan_num; const struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc; const struct bitvec *ts_arfcn = &ts->hopping.arfcns; struct bitvec *ma = &ts->hopping.ma; - unsigned int num_cell_arfcns; int i; /* re-set the MA to all-zero */ @@ -271,14 +270,7 @@ static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts) memset(ma->data, 0, ma->data_len); if (!ts->hopping.enabled) - return 0; - - /* count the number of ARFCNs in the cell channel allocation */ - num_cell_arfcns = 0; - for (i = 0; i < 1024; i++) { - if (bitvec_get_bit_pos(cell_chan, i)) - num_cell_arfcns++; - } + return; /* pad it to octet-aligned number of bits */ ts->hopping.ma_len = OSMO_BYTES_FOR_BITS(num_cell_arfcns); @@ -303,21 +295,34 @@ static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts) else bitvec_set_bit_pos(ma, ma->cur_bit, 0); } +} - return 0; +static void generate_ma_for_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + unsigned int tn; + + OSMO_ASSERT(bts->si_common.cell_chan_num > 0); + OSMO_ASSERT(bts->si_common.cell_chan_num <= 64); + + llist_for_each_entry(trx, &bts->trx_list, list) { + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) + generate_ma_for_ts(&trx->ts[tn]); + } } static void bootstrap_rsl(struct gsm_bts_trx *trx) { + struct gsm_bts *bts = trx->bts; unsigned int i; + int rc; LOG_TRX(trx, DRSL, LOGL_NOTICE, "bootstrapping RSL " "on ARFCN %u using MCC-MNC %s LAC=%u CID=%u BSIC=%u\n", trx->arfcn, osmo_plmn_name(&bsc_gsmnet->plmn), - trx->bts->location_area_code, - trx->bts->cell_identity, trx->bts->bsic); + bts->location_area_code, bts->cell_identity, bts->bsic); - if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) { + if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) { rsl_nokia_si_begin(trx); } @@ -334,170 +339,71 @@ static void bootstrap_rsl(struct gsm_bts_trx *trx) return; } - if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) { + if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) { /* channel unspecific, power reduction in 2 dB steps */ rsl_bs_power_control(trx, 0xFF, trx->max_power_red / 2); rsl_nokia_si_end(trx); } + if (bts->model->power_ctrl_send_def_params != NULL) { + rc = bts->model->power_ctrl_send_def_params(trx); + if (rc) { + LOG_TRX(trx, DRSL, LOGL_ERROR, "Failed to send default " + "MS/BS Power control parameters (rc=%d)\n", rc); + /* TODO: should we drop RSL connection here? */ + } + } + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { struct gsm_bts_trx_ts *ts = &trx->ts[i]; - generate_ma_for_ts(ts); OSMO_ASSERT(ts->fi); osmo_fsm_inst_dispatch(ts->fi, TS_EV_RSL_READY, NULL); } - /* Start CBCH transmit timer if CBCH is present */ - if (trx->nr == 0 && gsm_bts_get_cbch(trx->bts)) - bts_cbch_timer_schedule(trx->bts); - /* Drop all expired channel requests in the list */ - abis_rsl_chan_rqd_queue_flush(trx->bts); + abis_rsl_chan_rqd_queue_flush(bts); } -static void all_ts_dispatch_event(struct gsm_bts_trx *trx, uint32_t event) +struct osmo_timer_list update_connection_stats_timer; + +/* Periodically call bsc_update_connection_stats() to keep stat items updated. + * It would be nicer to trigger this only when OML or RSL state is seen to flip. I tried hard to find all code paths + * that should call this and failed to get accurate results; this trivial timer covers all of them. */ +static void update_connection_stats_cb(void *data) { - int ts_i; - for (ts_i = 0; ts_i < ARRAY_SIZE(trx->ts); ts_i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[ts_i]; - if (ts->fi) - osmo_fsm_inst_dispatch(ts->fi, event, 0); - } + bsc_update_connection_stats(bsc_gsmnet); + osmo_timer_setup(&update_connection_stats_timer, update_connection_stats_cb, NULL); + osmo_timer_schedule(&update_connection_stats_timer, 1, 0); } -/* Callback function to be called every time we receive a signal from INPUT */ -static int inp_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) +static bool nch_position_compatible_with_combined_ccch(const struct gsm_bts *bts) { - struct input_signal_data *isd = signal_data; - struct gsm_bts_trx *trx = isd->trx; - /* N. B: we rely on attribute order when parsing response in abis_nm_rx_get_attr_resp() */ - const uint8_t bts_attr[] = { NM_ATT_MANUF_ID, NM_ATT_SW_CONFIG, }; - const uint8_t trx_attr[] = { NM_ATT_MANUF_STATE, NM_ATT_SW_CONFIG, }; - - /* we should not request more attributes than we're ready to handle */ - OSMO_ASSERT(sizeof(bts_attr) < MAX_BTS_ATTR); - OSMO_ASSERT(sizeof(trx_attr) < MAX_BTS_ATTR); - - if (subsys != SS_L_INPUT) - return -EINVAL; - - LOGP(DLMI, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__, - get_value_string(e1inp_signal_names, signal)); - switch (signal) { - case S_L_INP_TEI_UP: - if (isd->link_type == E1INP_SIGN_OML) { - /* TODO: this is required for the Nokia BTS, hopping is configured - during OML, other MA is not set. */ - struct gsm_bts_trx *cur_trx; - uint8_t ca[20]; - /* has to be called before generate_ma_for_ts to - set bts->si_common.cell_alloc */ - generate_cell_chan_list(ca, trx->bts); - - /* Request generic BTS-level attributes */ - abis_nm_get_attr(trx->bts, NM_OC_BTS, 0xFF, 0xFF, 0xFF, bts_attr, sizeof(bts_attr)); - - llist_for_each_entry(cur_trx, &trx->bts->trx_list, list) { - int i; - /* Request TRX-level attributes */ - abis_nm_get_attr(cur_trx->bts, NM_OC_BASEB_TRANSC, 0, cur_trx->nr, 0xFF, - trx_attr, sizeof(trx_attr)); - for (i = 0; i < ARRAY_SIZE(cur_trx->ts); i++) - generate_ma_for_ts(&cur_trx->ts[i]); - } - } - if (isd->link_type == E1INP_SIGN_RSL) - bootstrap_rsl(trx); + switch (bts->nch.num_blocks) { + case 0: + /* no NCH enabled, so we are fine */ + return true; + case 1: + if (bts->nch.first_block == 0 || bts->nch.first_block == 1) + return true; break; - case S_L_INP_TEI_DN: - LOG_TRX(trx, DLMI, LOGL_ERROR, "Lost E1 %s link\n", e1inp_signtype_name(isd->link_type)); - - if (isd->link_type == E1INP_SIGN_OML) { - rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_OML_FAIL]); - all_ts_dispatch_event(trx, TS_EV_OML_DOWN); - } else if (isd->link_type == E1INP_SIGN_RSL) { - rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]); - acc_ramp_abort(&trx->bts->acc_ramp); - all_ts_dispatch_event(trx, TS_EV_RSL_DOWN); - if (trx->nr == 0) - osmo_timer_del(&trx->bts->cbch_timer); - } - - gsm_bts_mo_reset(trx->bts); - - abis_nm_clear_queue(trx->bts); + case 2: + if (bts->nch.first_block == 0) + return true; break; default: break; } - return 0; + /* anything else is not permitted */ + return false; } -static int bootstrap_bts(struct gsm_bts *bts) +static void bootstrap_bts(struct gsm_bts *bts) { - struct gsm_bts_trx *trx; unsigned int n = 0; - if (!bts->model) - return -EFAULT; - - if (bts->model->start && !bts->model->started) { - int ret = bts->model->start(bts->network); - if (ret < 0) - return ret; - - bts->model->started = true; - } - - /* FIXME: What about secondary TRX of a BTS? What about a BTS that has TRX - * in different bands? Why is 'band' a parameter of the BTS and not of the TRX? */ - switch (bts->band) { - case GSM_BAND_1800: - if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) { - LOGP(DNM, LOGL_ERROR, "GSM1800 channel must be between 512-885.\n"); - return -EINVAL; - } - break; - case GSM_BAND_1900: - if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) { - LOGP(DNM, LOGL_ERROR, "GSM1900 channel must be between 512-810.\n"); - return -EINVAL; - } - break; - case GSM_BAND_900: - if ((bts->c0->arfcn > 124 && bts->c0->arfcn < 955) || - bts->c0->arfcn > 1023) { - LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 0-124, 955-1023.\n"); - return -EINVAL; - } - break; - case GSM_BAND_850: - if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) { - LOGP(DNM, LOGL_ERROR, "GSM850 channel must be between 128-251.\n"); - return -EINVAL; - } - break; - default: - LOGP(DNM, LOGL_ERROR, "Unsupported frequency band.\n"); - return -EINVAL; - } - - /* Verify the physical channel mapping */ - llist_for_each_entry(trx, &bts->trx_list, list) { - if (!trx_has_valid_pchan_config(trx)) { - LOGP(DNM, LOGL_ERROR, "TRX %u has invalid timeslot " - "configuration\n", trx->nr); - return -EINVAL; - } - } - /* Control Channel Description is set from vty/config */ - /* Indicate R99 MSC in SI3 */ - bts->si_common.chan_desc.mscr = 1; - /* Determine the value of CCCH_CONF. Is TS0/C0 combined? */ if (bts->c0->ts[0].pchan_from_config != GSM_PCHAN_CCCH) { bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C; @@ -510,6 +416,11 @@ static int bootstrap_bts(struct gsm_bts *bts) bts->si_common.chan_desc.bs_ag_blks_res); bts->si_common.chan_desc.bs_ag_blks_res = 2; } + + if (!nch_position_compatible_with_combined_ccch(bts)) { + LOG_BTS(bts, DNM, LOGL_ERROR, "CCCH is combined with SDCCHs, but NCH position/size is " + "incompatible with that. Please fix your config!\n"); + } } else { /* Non-combined TS0/C0 configuration */ /* There can be additional CCCHs on even timeslot numbers */ n += (bts->c0->ts[2].pchan_from_config == GSM_PCHAN_CCCH); @@ -518,18 +429,84 @@ static int bootstrap_bts(struct gsm_bts *bts) bts->si_common.chan_desc.ccch_conf = (n << 1); } - bts->si_common.cell_options.pwrc = 0; /* PWRC not set */ - - bts->si_common.cell_sel_par.acs = 0; - - bts->si_common.ncc_permitted = 0xff; + if (bts->nch.first_block + bts->nch.num_blocks > bts->si_common.chan_desc.bs_ag_blks_res) { + LOG_BTS(bts, DNM, LOGL_ERROR, "Position/Number of NCH blocks (%u..%u) exceeds AGCH (%u)." + "Please fix your config!\n", bts->nch.first_block, + bts->nch.first_block + bts->nch.num_blocks - 1, + bts->si_common.chan_desc.bs_ag_blks_res); + } - bts->chan_load_samples_idx = 0; + bts_setup_ramp_init_bts(bts); /* ACC ramping is initialized from vty/config */ /* Initialize the BTS state */ - gsm_bts_mo_reset(bts); + gsm_bts_sm_mo_reset(bts->site_mgr); + + /* Generate Mobile Allocation bit-masks for all timeslots. + * This needs to be done here, because it's used for TS configuration. */ + generate_ma_for_bts(bts); +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + struct gsm_bts_trx *trx = isd->trx; + int rc; + + if (subsys != SS_L_INPUT) + return -EINVAL; + + LOGP(DLMI, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__, + get_value_string(e1inp_signal_names, signal)); + switch (signal) { + case S_L_INP_TEI_UP: + if (isd->link_type == E1INP_SIGN_OML) { + /* Check parameters and apply vty config dependent parameters */ + rc = gsm_bts_check_cfg(trx->bts); + if (rc < 0) { + LOGP(DNM, LOGL_ERROR, "(bts=%u) Error in BTS configuration -- cannot bootstrap BTS\n", + trx->bts->nr); + return rc; + } + bootstrap_bts(trx->bts); + } + if (isd->link_type == E1INP_SIGN_RSL) { + rc = gsm_bts_check_cfg(trx->bts); + if (rc < 0) { + LOGP(DNM, LOGL_ERROR, "(bts=%u) Error in BTS configuration -- cannot bootstrap RSL\n", + trx->bts->nr); + return rc; + } + bootstrap_rsl(trx); + } + break; + case S_L_INP_TEI_DN: + LOG_TRX(trx, DLMI, LOGL_ERROR, "Lost E1 %s link\n", e1inp_signtype_name(isd->link_type)); + + if (isd->link_type == E1INP_SIGN_OML) { + rate_ctr_inc(rate_ctr_group_get_ctr(trx->bts->bts_ctrs, BTS_CTR_BTS_OML_FAIL)); + /* ip.access BTS models have a single global A-bis/OML link for all + * transceivers, so once it's lost we need to notify them all. */ + if (is_ipa_abisip_bts(trx->bts)) + gsm_bts_all_ts_dispatch(trx->bts, TS_EV_OML_DOWN, NULL); + else /* Other BTS models (e.g. Ericsson) have per-TRX OML links */ + gsm_trx_all_ts_dispatch(trx, TS_EV_OML_DOWN, NULL); + } else if (isd->link_type == E1INP_SIGN_RSL) { + rate_ctr_inc(rate_ctr_group_get_ctr(trx->bts->bts_ctrs, BTS_CTR_BTS_RSL_FAIL)); + acc_ramp_abort(&trx->bts->acc_ramp); + gsm_trx_all_ts_dispatch(trx, TS_EV_RSL_DOWN, NULL); + } + + gsm_bts_sm_mo_reset(trx->bts->site_mgr); + + abis_nm_clear_queue(trx->bts); + break; + default: + break; + } return 0; } @@ -541,13 +518,12 @@ static int bsc_network_configure(const char *config_file) rc = vty_read_config_file(config_file, NULL); if (rc < 0) { - LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", config_file); + LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s' (%s)\n", config_file, strerror(-rc)); return rc; } /* start telnet after reading config for vty_get_bind_addr() */ - rc = telnet_init_dynif(tall_bsc_ctx, bsc_gsmnet, vty_get_bind_addr(), - OSMO_VTY_PORT_NITB_BSC); + rc = telnet_init_default(tall_bsc_ctx, bsc_gsmnet, OSMO_VTY_PORT_NITB_BSC); if (rc < 0) return rc; @@ -555,11 +531,12 @@ static int bsc_network_configure(const char *config_file) osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { - rc = bootstrap_bts(bts); + rc = gsm_bts_check_cfg(bts); if (rc < 0) { - LOGP(DNM, LOGL_FATAL, "Error bootstrapping BTS\n"); + LOGP(DNM, LOGL_FATAL, "(bts=%u) cannot bootstrap BTS, invalid BTS configuration\n", bts->nr); return rc; } + bootstrap_bts(bts); rc = e1_reconfig_bts(bts); if (rc < 0) { LOGP(DNM, LOGL_FATAL, "Error enabling E1 input driver\n"); @@ -577,6 +554,11 @@ static int bsc_vty_go_parent(struct vty *vty) vty->node = CONFIG_NODE; vty->index = NULL; break; + case MGW_NODE: + vty->node = GSMNET_NODE; + vty->index = bsc_gsmnet; + vty->index_sub = NULL; + break; case BTS_NODE: vty->node = GSMNET_NODE; { @@ -586,6 +568,21 @@ static int bsc_vty_go_parent(struct vty *vty) vty->index_sub = NULL; } break; + case POWER_CTRL_NODE: + vty->node = BTS_NODE; + { + const struct gsm_power_ctrl_params *cp = vty->index; + struct gsm_bts *bts; + + if (cp->dir == GSM_PWR_CTRL_DIR_UL) + bts = container_of(cp, struct gsm_bts, ms_power_ctrl); + else + bts = container_of(cp, struct gsm_bts, bs_power_ctrl); + + vty->index_sub = &bts->description; + vty->index = bts; + } + break; case TRX_NODE: vty->node = BTS_NODE; { @@ -601,7 +598,6 @@ static int bsc_vty_go_parent(struct vty *vty) /* set vty->index correctly ! */ struct gsm_bts_trx_ts *ts = vty->index; vty->index = ts->trx; - vty->index_sub = &ts->trx->description; } break; case OML_NODE: @@ -659,7 +655,9 @@ static struct vty_app_info vty_info = { .copyright = "Copyright (C) 2008-2018 Harald Welte, Holger Freyther\r\n" "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n" - "Dieter Spaar, Andreas Eversberg, Sylvain Munaut, Neels Hofmeyr\r\n\r\n" + "Dieter Spaar, Andreas Eversberg, Sylvain Munaut, Neels Hofmeyr\r\n" + "Copyright (C) 2013-2022 sysmocom - s.f.m.c. GmbH\r\n" + "\r\n" "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" "This is free software: you are free to change and redistribute it.\r\n" "There is NO WARRANTY, to the extent permitted by law.\r\n", @@ -673,30 +671,44 @@ static struct vty_app_info vty_info = { "This command applies on A-bis RSL link (re)establishment", [BSC_VTY_ATTR_NEW_LCHAN] = \ "This command applies for newly created lchans", + [BSC_VTY_ATTR_VENDOR_SPECIFIC] = \ + "This command/parameter is BTS vendor specific", }, .usr_attr_letters = { [BSC_VTY_ATTR_RESTART_ABIS_OML_LINK] = 'o', [BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK] = 'r', [BSC_VTY_ATTR_NEW_LCHAN] = 'l', + [BSC_VTY_ATTR_VENDOR_SPECIFIC] = 'v', }, }; extern int bsc_shutdown_net(struct gsm_network *net); -static void signal_handler(int signal) +static void signal_handler(int signum) { - fprintf(stdout, "signal %u received\n", signal); + fprintf(stdout, "signal %u received\n", signum); - switch (signal) { + switch (signum) { case SIGINT: case SIGTERM: + /* If SIGTERM was already sent before, just terminate immediately. */ + if (osmo_select_shutdown_requested()) + exit(-1); bsc_shutdown_net(bsc_gsmnet); osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); - sleep(3); - exit(0); + osmo_select_shutdown_request(); break; case SIGABRT: - /* in case of abort, we want to obtain a talloc report - * and then return to the caller, who will abort the process */ + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_bsc_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; case SIGUSR1: talloc_report(tall_vty_ctx, stderr); talloc_report_full(tall_bsc_ctx, stderr); @@ -818,11 +830,23 @@ static const struct log_info_cat osmo_bsc_categories[] = { .description = "Location Services", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DASCI] = { + .name = "DASCI", + .description = "Advanced Speech Call Items (VGCS/VBS)", + .color = "\033[1;38m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, [DRESET] = { .name = "DRESET", .description = "RESET/ACK on A and Lb interfaces", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DLOOP] = { + .name = "DLOOP", + .description = "Control loops", + .color = "\033[0;34m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; static int filter_fn(const struct log_context *ctx, struct log_target *tar) @@ -847,7 +871,45 @@ const struct log_info log_info = { extern void *tall_paging_ctx; extern void *tall_fle_ctx; extern void *tall_tqe_ctx; -extern void *tall_ctr_ctx; + +static int bsc_mgw_setup(void) +{ + struct mgcp_client *mgcp_client_single; + unsigned int pool_members_initalized; + + /* Initialize MGW pool. This initializes and connects all MGCP clients that are currently configured in + * the pool. Adding additional MGCP clients to the pool is possible but the user has to configure and + * (re)connect them manually from the VTY. */ + if (!mgcp_client_pool_empty(bsc_gsmnet->mgw.mgw_pool)) { + pool_members_initalized = mgcp_client_pool_connect(bsc_gsmnet->mgw.mgw_pool); + if (!pool_members_initalized) { + LOGP(DNM, LOGL_ERROR, "MGW pool failed to initialize any pool members\n"); + return -EINVAL; + } + LOGP(DNM, LOGL_NOTICE, + "MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'msc').\n", + pool_members_initalized); + return 0; + } + + /* Initialize and connect a single MGCP client. This MGCP client will appear as the one and only pool + * member if there is no MGW pool configured. */ + LOGP(DNM, LOGL_NOTICE, "No MGW pool configured, using MGW configuration in VTY node 'msc'\n"); + mgcp_client_single = mgcp_client_init(bsc_gsmnet, bsc_gsmnet->mgw.conf); + if (!mgcp_client_single) { + LOGP(DNM, LOGL_ERROR, "MGW (single) client initialization failed\n"); + return -EINVAL; + } + if (mgcp_client_connect(mgcp_client_single)) { + LOGP(DNM, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n", + bsc_gsmnet->mgw.conf->remote_addr, + bsc_gsmnet->mgw.conf->remote_port); + return -EINVAL; + } + mgcp_client_pool_register_single(bsc_gsmnet->mgw.mgw_pool, mgcp_client_single); + + return 0; +} int main(int argc, char **argv) { @@ -863,13 +925,13 @@ int main(int argc, char **argv) tall_paging_ctx = talloc_named_const(tall_bsc_ctx, 0, "paging_request"); tall_fle_ctx = talloc_named_const(tall_bsc_ctx, 0, "bs11_file_list_entry"); tall_tqe_ctx = talloc_named_const(tall_bsc_ctx, 0, "subch_txq_entry"); - tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter"); osmo_init_logging2(tall_bsc_ctx, &log_info); osmo_stats_init(tall_bsc_ctx); rate_ctr_init(tall_bsc_ctx); osmo_fsm_set_dealloc_ctx(OTC_SELECT); + osmo_fsm_log_timeouts(true); /* Allocate global gsm_network struct */ rc = bsc_network_alloc(); @@ -878,8 +940,8 @@ int main(int argc, char **argv) exit(1); } - bsc_gsmnet->mgw.conf = talloc_zero(bsc_gsmnet, struct mgcp_client_conf); - mgcp_client_conf_init(bsc_gsmnet->mgw.conf); + bsc_gsmnet->mgw.conf = mgcp_client_conf_alloc(bsc_gsmnet); + bsc_gsmnet->mgw.mgw_pool = mgcp_client_pool_alloc(bsc_gsmnet); bts_init(); libosmo_abis_init(tall_bsc_ctx); @@ -906,12 +968,11 @@ int main(int argc, char **argv) /* seed the PRNG */ srand(time(NULL)); - ts_fsm_init(); - lchan_fsm_init(); - bsc_subscr_conn_fsm_init(); - assignment_fsm_init(); - handover_fsm_init(); lb_init(); + acc_ramp_global_init(); + paging_global_init(); + smscb_global_init(); + meas_feed_txqueue_max_length_set(MEAS_FEED_TXQUEUE_MAX_LEN_DEFAULT); /* Read the config */ rc = bsc_network_configure(config_file); @@ -920,11 +981,14 @@ int main(int argc, char **argv) exit(1); } + if (neighbors_check_cfg()) { + fprintf(stderr, "Errors in neighbor configuration, check the DHO log. exiting.\n"); + exit(1); + } + /* start control interface after reading config for * ctrl_vty_get_bind_addr() */ - bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet, - ctrl_vty_get_bind_addr(), - OSMO_CTRL_PORT_NITB_BSC); + bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet, OSMO_CTRL_PORT_NITB_BSC); if (!bsc_gsmnet->ctrl) { fprintf(stderr, "Failed to init the control interface. Exiting.\n"); exit(1); @@ -936,6 +1000,19 @@ int main(int argc, char **argv) exit(1); } + if (bsc_gsmnet->neigh_ctrl.addr) { + bsc_gsmnet->neigh_ctrl.handle = neighbor_controlif_setup(bsc_gsmnet); + if (!bsc_gsmnet->neigh_ctrl.handle) { + fprintf(stderr, "Failed to bind Neighbor Resolution Service. Exiting.\n"); + exit(1); + } + rc = neighbor_ctrl_cmds_install(bsc_gsmnet); + if (rc < 0) { + fprintf(stderr, "Failed to install Neighbor Resolution Service commands. Exiting.\n"); + exit(1); + } + } + if (rf_ctrl) osmo_talloc_replace_string(bsc_gsmnet, &bsc_gsmnet->rf_ctrl_name, rf_ctrl); @@ -957,6 +1034,9 @@ int main(int argc, char **argv) } } + if (bsc_mgw_setup() != 0) + exit(1); + llist_for_each_entry(msc, &bsc_gsmnet->mscs, entry) { if (osmo_bsc_msc_init(msc) != 0) { LOGP(DMSC, LOGL_ERROR, "Failed to start up. Exiting.\n"); @@ -964,15 +1044,6 @@ int main(int argc, char **argv) } } - bsc_gsmnet->mgw.client = mgcp_client_init(bsc_gsmnet, bsc_gsmnet->mgw.conf); - - if (mgcp_client_connect(bsc_gsmnet->mgw.client)) { - LOGP(DNM, LOGL_ERROR, "MGW connect failed at (%s:%u)\n", - bsc_gsmnet->mgw.conf->remote_addr, - bsc_gsmnet->mgw.conf->remote_port); - exit(1); - } - if (osmo_bsc_sigtran_init(&bsc_gsmnet->mscs) != 0) { LOGP(DNM, LOGL_ERROR, "Failed to initialize sigtran backhaul.\n"); exit(1); @@ -990,6 +1061,9 @@ int main(int argc, char **argv) signal(SIGUSR2, &signal_handler); osmo_init_ignore_signals(); + update_connection_stats_cb(NULL); + chan_counts_sig_init(); + if (daemonize) { rc = osmo_daemonize(); if (rc < 0) { @@ -998,7 +1072,7 @@ int main(int argc, char **argv) } } - while (1) { + while (!osmo_select_shutdown_done()) { osmo_select_main_ctx(0); } diff --git a/src/osmo-bsc/osmo_bsc_mgcp.c b/src/osmo-bsc/osmo_bsc_mgcp.c index 90a73e509..d7de4d264 100644 --- a/src/osmo-bsc/osmo_bsc_mgcp.c +++ b/src/osmo-bsc/osmo_bsc_mgcp.c @@ -2,6 +2,7 @@ * SCCPlite MGCP handling * * (C) 2018 by Harald Welte <laforge@gnumonks.org> + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * This program is free software; you can redistribute it and/or modify @@ -19,6 +20,15 @@ * */ +#include <string.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/sockaddr_str.h> +#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> +#include <osmocom/mgcp_client/mgcp_client.h> + #include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/osmo_bsc.h> #include <osmocom/bsc/gsm_data.h> @@ -33,33 +43,136 @@ static struct bsc_msc_data *msc_from_asp(struct osmo_ss7_asp *asp) { int msc_nr; + const char *asp_name = osmo_ss7_asp_get_name(asp); /* this is rather ugly, as we of course have MTP-level routing between * the local SCCP user (BSC) and the AS/ASPs. However, for the most simple * SCCPlite case, there is a 1:1 mapping between ASP and AS, and using * the libosmo-sigtran "simple client", the names are "as[p]-clnt-msc-%u", * as set in osmo_bsc_sigtran_init() */ - if (sscanf(asp->cfg.name, "asp-clnt-msc-%u", &msc_nr) != 1) { - LOGP(DMSC, LOGL_ERROR, "Cannot find to which MSC the ASP %s belongs\n", asp->cfg.name); + if (!asp_name || sscanf(asp_name, "asp-clnt-msc-%u", &msc_nr) != 1) { + LOGP(DMSC, LOGL_ERROR, "Cannot find to which MSC the ASP '%s' belongs\n", asp_name); return NULL; } return osmo_msc_data_find(bsc_gsmnet, msc_nr); } +/* negative on error, zero upon success */ +static int parse_local_endpoint_name(char *buf, size_t buf_len, const char *data) +{ + char line[1024]; + char *epstart, *sep; + const char *start = data; + char *eol = strpbrk(start, "\r\n"); + + if (!eol) + return -1; + + if (eol - start > sizeof(line)) + return -1; + memcpy(line, start, eol - start); + line[eol - start] = '\0'; + + if (!(epstart = strchr(line, ' '))) + return -1; + epstart++; + /* epstart now points to trans */ + + if (!(epstart = strchr(epstart, ' '))) + return -1; + epstart++; + /* epstart now points to endpoint */ + if (!(sep = strchr(epstart, '@'))) + return -1; + if (sep - epstart >= buf_len) + return -1; + + *sep = '\0'; + osmo_strlcpy(buf, epstart, buf_len); + return 0; +} + /* We received an IPA-encapsulated MGCP message from a MSC. Transfers msg ownership. */ int bsc_sccplite_rx_mgcp(struct osmo_ss7_asp *asp, struct msgb *msg) { struct bsc_msc_data *msc; + struct gsm_subscriber_connection *conn; + char rcv_ep_local_name[1024]; + struct osmo_sockaddr_str osa_str = {}; + struct osmo_sockaddr osa = {}; + socklen_t dest_len; + struct mgcp_client *mgcp_cli = NULL; int rc; - LOGP(DMSC, LOGL_NOTICE, "%s: Received IPA-encapsulated MGCP: %s\n", asp->cfg.name, msg->l2h); + LOGP(DMSC, LOGL_INFO, "%s: Received IPA-encapsulated MGCP: %s\n", + osmo_ss7_asp_get_name(asp), msg->l2h); + msc = msc_from_asp(asp); - if (msc) { - /* we don't have a write queue here as we simply expect the socket buffers - * to be large enough to deal with whatever small/infrequent MGCP messages */ - rc = send(msc->mgcp_ipa.ofd.fd, msgb_l2(msg), msgb_l2len(msg), 0); - } else + if (!msc) { rc = 0; + goto free_msg_ret; + } + + rc = parse_local_endpoint_name(rcv_ep_local_name, sizeof(rcv_ep_local_name), (const char *)msg->l2h); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "(%s:) Received IPA-encapsulated MGCP: Failed to parse CIC\n", + osmo_ss7_asp_get_name(asp)); + goto free_msg_ret; + } + + /* Lookup which conn attached to the MSC holds an MGW endpoint with the + * name Endpoint Number as the one provided in the MGCP msg we received + * from MSC. Since CIC are unique per MSC, that's the same MGW in the + * pool where we have to forward the MGCP message. */ + llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) { + const char *ep_local_name; + if (conn->sccp.msc != msc) + continue; /* Only conns belonging to this MSC */ + if (!conn->user_plane.mgw_endpoint) + continue; + ep_local_name = osmo_mgcpc_ep_local_name(conn->user_plane.mgw_endpoint); + LOGPFSMSL(conn->fi, DMSC, LOGL_DEBUG, "ep_local_name='%s' vs rcv_ep_local_name='%s'\n", + ep_local_name ? : "(null)", rcv_ep_local_name); + if (!ep_local_name) + continue; + if (strcmp(ep_local_name, rcv_ep_local_name) != 0) + continue; + mgcp_cli = osmo_mgcpc_ep_client(conn->user_plane.mgw_endpoint); + if (!mgcp_cli) + continue; + break; + } + + if (!mgcp_cli) { + LOGP(DMSC, LOGL_ERROR, "(%s:) Received IPA-encapsulated MGCP: Failed to find associated MGW\n", + osmo_ss7_asp_get_name(asp)); + rc = 0; + goto free_msg_ret; + } + + rc = osmo_sockaddr_str_from_str(&osa_str, mgcp_client_remote_addr_str(mgcp_cli), + mgcp_client_remote_port(mgcp_cli)); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "(%s:) Received IPA-encapsulated MGCP: Failed to parse MGCP address %s:%u\n", + osmo_ss7_asp_get_name(asp), mgcp_client_remote_addr_str(mgcp_cli), mgcp_client_remote_port(mgcp_cli)); + goto free_msg_ret; + } + + LOGP(DMSC, LOGL_NOTICE, "%s: Forwarding IPA-encapsulated MGCP to MGW at " OSMO_SOCKADDR_STR_FMT "\n", + osmo_ss7_asp_get_name(asp), OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL(&osa_str)); + + rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "(%s:) Received IPA-encapsulated MGCP: Failed to parse MGCP address " OSMO_SOCKADDR_STR_FMT "\n", + osmo_ss7_asp_get_name(asp), OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL(&osa_str)); + goto free_msg_ret; + } + dest_len = osmo_sockaddr_size(&osa); + + /* we don't have a write queue here as we simply expect the socket buffers + * to be large enough to deal with whatever small/infrequent MGCP messages */ + rc = sendto(msc->mgcp_ipa.ofd.fd, msgb_l2(msg), msgb_l2len(msg), 0, &osa.u.sa, dest_len); +free_msg_ret: msgb_free(msg); return rc; } diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c index 772bea588..24255924f 100644 --- a/src/osmo-bsc/osmo_bsc_msc.c +++ b/src/osmo-bsc/osmo_bsc_msc.c @@ -40,6 +40,7 @@ #include <osmocom/abis/ipa.h> #include <osmocom/mgcp_client/mgcp_client.h> +#include <osmocom/mgcp_client/mgcp_client_pool.h> #include <sys/socket.h> #include <netinet/tcp.h> @@ -47,67 +48,281 @@ static const struct rate_ctr_desc msc_ctr_description[] = { /* Rx message counters (per specific message) */ - [MSC_CTR_BSSMAP_RX_UDT_RESET_ACKNOWLEDGE] = {"bssmap:rx:udt:reset:ack", "Number of received BSSMAP UDT RESET ACKNOWLEDGE messages"}, - [MSC_CTR_BSSMAP_RX_UDT_RESET] = {"bssmap:rx:udt:reset:request", "Number of received BSSMAP UDT RESET messages"}, - [MSC_CTR_BSSMAP_RX_UDT_PAGING] = {"bssmap:rx:udt:paging", "Number of received BSSMAP UDT PAGING messages"}, - [MSC_CTR_BSSMAP_RX_UDT_UNKNOWN] = {"bssmap:rx:udt:err_unknown", "Number of received BSSMAP unknown UDT messages"}, - [MSC_CTR_BSSMAP_RX_DT1_CLEAR_CMD] = {"bssmap:rx:dt1:clear:cmd", "Number of received BSSMAP DT1 CLEAR CMD messages"}, - [MSC_CTR_BSSMAP_RX_DT1_CIPHER_MODE_CMD] = {"bssmap:rx:dt1:cipher_mode:cmd", "Number of received BSSMAP DT1 CIPHER MODE CMD messages"}, - [MSC_CTR_BSSMAP_RX_DT1_ASSIGMENT_RQST] = {"bssmap:rx:dt1:assignment:rqst", "Number of received BSSMAP DT1 ASSIGMENT RQST messages"}, - [MSC_CTR_BSSMAP_RX_DT1_LCLS_CONNECT_CTRL] = {"bssmap:rx:dt1:lcls_connect_ctrl:cmd", "Number of received BSSMAP DT1 LCLS CONNECT CTRL messages"}, - [MSC_CTR_BSSMAP_RX_DT1_HANDOVER_CMD] = {"bssmap:rx:dt1:handover:cmd", "Number of received BSSMAP DT1 HANDOVER CMD messages"}, - [MSC_CTR_BSSMAP_RX_DT1_CLASSMARK_RQST] = {"bssmap:rx:dt1:classmark:rqst", "Number of received BSSMAP DT1 CLASSMARK RQST messages"}, - [MSC_CTR_BSSMAP_RX_DT1_CONFUSION] = {"bssmap:rx:dt1:confusion", "Number of received BSSMAP DT1 CONFUSION messages"}, - [MSC_CTR_BSSMAP_RX_DT1_COMMON_ID] = {"bssmap:rx:dt1:common_id", "Number of received BSSMAP DT1 COMMON ID messages"}, - [MSC_CTR_BSSMAP_RX_DT1_UNKNOWN] = {"bssmap:rx:dt1:err_unknown", "Number of received BSSMAP unknown DT1 messages"}, - [MSC_CTR_BSSMAP_RX_DT1_DTAP] = {"bssmap:rx:dt1:dtap:good", "Number of received BSSMAP DTAP messages"}, - [MSC_CTR_BSSMAP_RX_DT1_DTAP_ERROR] = {"bssmap:rx:dt1:dtap:error", "Number of received BSSMAP DTAP messages with errors"}, - [MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST] = {"bssmap:rx:dt1:location:request", "Number of received BSSMAP Perform Location Request messages"}, - [MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_ABORT] = {"bssmap:tx:dt1:location:abort", "Number of received BSSMAP Perform Location Abort messages"}, + [MSC_CTR_BSSMAP_RX_UDT_RESET_ACKNOWLEDGE] = { + "bssmap:rx:udt:reset:ack", + "Number of received BSSMAP UDT RESET ACKNOWLEDGE messages" + }, + [MSC_CTR_BSSMAP_RX_UDT_RESET] = { + "bssmap:rx:udt:reset:request", + "Number of received BSSMAP UDT RESET messages" + }, + [MSC_CTR_BSSMAP_RX_UDT_PAGING] = { + "bssmap:rx:udt:paging", + "Number of received BSSMAP UDT PAGING messages" + }, + [MSC_CTR_BSSMAP_RX_UDT_UNKNOWN] = { + "bssmap:rx:udt:err_unknown", + "Number of received BSSMAP unknown UDT messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_CLEAR_CMD] = { + "bssmap:rx:dt1:clear:cmd", + "Number of received BSSMAP DT1 CLEAR CMD messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_CIPHER_MODE_CMD] = { + "bssmap:rx:dt1:cipher_mode:cmd", + "Number of received BSSMAP DT1 CIPHER MODE CMD messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_ASSIGNMENT_RQST] = { + "bssmap:rx:dt1:assignment:rqst", + "Number of received BSSMAP DT1 ASSIGNMENT RQST messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_LCLS_CONNECT_CTRL] = { + "bssmap:rx:dt1:lcls_connect_ctrl:cmd", + "Number of received BSSMAP DT1 LCLS CONNECT CTRL messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST] = { + "bssmap:rx:dt1:handover:rqst", + "Number of received BSSMAP DT1 HANDOVER RQST messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_HANDOVER_CMD] = { + "bssmap:rx:dt1:handover:cmd", + "Number of received BSSMAP DT1 HANDOVER CMD messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_CLASSMARK_RQST] = { + "bssmap:rx:dt1:classmark:rqst", + "Number of received BSSMAP DT1 CLASSMARK RQST messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_CONFUSION] = { + "bssmap:rx:dt1:confusion", + "Number of received BSSMAP DT1 CONFUSION messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_COMMON_ID] = { + "bssmap:rx:dt1:common_id", + "Number of received BSSMAP DT1 COMMON ID messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_UNKNOWN] = { + "bssmap:rx:dt1:err_unknown", + "Number of received BSSMAP unknown DT1 messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_DTAP] = { + "bssmap:rx:dt1:dtap:good", + "Number of received BSSMAP DTAP messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_DTAP_ERROR] = { + "bssmap:rx:dt1:dtap:error", + "Number of received BSSMAP DTAP messages with errors" + }, + [MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST] = { + "bssmap:rx:dt1:location:request", + "Number of received BSSMAP Perform Location Request messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_ABORT] = { + "bssmap:tx:dt1:location:abort", + "Number of received BSSMAP Perform Location Abort messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_VGCS_VBS_SETUP] = { + "bssmap:rx:dt1:vgcs_vbs_setup", + "Number of received BSSMAP DT1 VGCS/VBS SETUP messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_VGCS_VBS_ASSIGN_RQST] = { + "bssmap:rx:dt1:vgcs_vbs_assignment:req", + "Number of received BSSMAP DT1 VGCS/VBS ASSIGNMENT messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_UPLINK_RQST_ACKNOWLEDGE] = { + "bssmap:rx:dt1:uplink_rqst:ack", + "Number of received BSSMAP DT1 UPLINK REQUEST ACKNOWLEDGE messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_UPLINK_REJECT_CMD] = { + "bssmap:rx:dt1:uplink_reject:cmd", + "Number of received BSSMAP DT1 UPLINK REJECT COMMAND messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_UPLINK_RELEASE_CMD] = { + "bssmap:rx:dt1:uplink_release:cmd", + "Number of received BSSMAP DT1 UPLINK RELEASE COMMAND messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_UPLINK_SEIZED_CMD] = { + "bssmap:rx:dt1:uplink_seized:cmd", + "Number of received BSSMAP DT1 UPLINK SEIZED COMMAND messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_VGCS_ADDL_INFO] = { + "bssmap:rx:dt1:vgcs_addl_info", + "Number of received BSSMAP DT1 VGCS/VBS ASSITIONAL INFORMATION messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_VGCS_SMS] = { + "bssmap:rx:dt1:vgcs_sms", + "Number of received BSSMAP DT1 VGCS SMS messages" + }, + [MSC_CTR_BSSMAP_RX_DT1_NOTIFICATION_DATA] = { + "bssmap:rx:dt1:notification_data", + "Number of received BSSMAP DT1 NOTIFICATION DATA messages" + }, /* Tx message counters (per message type) * * The counters here follow the logic of the osmo_bsc_sigtran_send() function * which receives DT1 messages from the upper layers and actually sends them to the MSC. * These counters cover all messages passed to the function by the upper layers: */ - [MSC_CTR_BSSMAP_TX_BSS_MANAGEMENT] = {"bssmap:tx:type:bss_management", "Number of transmitted BSS MANAGEMENT messages"}, - [MSC_CTR_BSSMAP_TX_DTAP] = {"bssmap:tx:type:dtap", "Number of transmitted DTAP messages"}, - [MSC_CTR_BSSMAP_TX_UNKNOWN] = {"bssmap:tx:type:err_unknown", "Number of transmitted messages with unknown type (an error in our code?)"}, - [MSC_CTR_BSSMAP_TX_SHORT] = {"bssmap:tx:type:err_short", "Number of transmitted messages which are too short (an error in our code?)"}, + [MSC_CTR_BSSMAP_TX_BSS_MANAGEMENT] = { + "bssmap:tx:type:bss_management", + "Number of transmitted BSS MANAGEMENT messages" + }, + [MSC_CTR_BSSMAP_TX_DTAP] = { + "bssmap:tx:type:dtap", + "Number of transmitted DTAP messages" + }, + [MSC_CTR_BSSMAP_TX_UNKNOWN] = { + "bssmap:tx:type:err_unknown", + "Number of transmitted messages with unknown type (an error in our code?)" + }, + [MSC_CTR_BSSMAP_TX_SHORT] = { + "bssmap:tx:type:err_short", + "Number of transmitted messages which are too short (an error in our code?)" + }, /* The next counters are also counted in the osmo_bsc_sigtran_send() function and * sum up to the exactly same number as the counters above but instead of message * classes they split by the result of the sending attempt: */ - [MSC_CTR_BSSMAP_TX_ERR_CONN_NOT_READY] = {"bssmap:tx:result:err_conn_not_ready", "Number of BSSMAP messages we tried to send when the connection was not ready yet"}, - [MSC_CTR_BSSMAP_TX_ERR_SEND] = {"bssmap:tx:result:err_send", "Number of socket errors while sending BSSMAP messages"}, - [MSC_CTR_BSSMAP_TX_SUCCESS] = {"bssmap:tx:result:success", "Number of successfully sent BSSMAP messages"}, + [MSC_CTR_BSSMAP_TX_ERR_CONN_NOT_READY] = { + "bssmap:tx:result:err_conn_not_ready", + "Number of BSSMAP messages we tried to send when the connection was not ready yet" + }, + [MSC_CTR_BSSMAP_TX_ERR_SEND] = { + "bssmap:tx:result:err_send", + "Number of socket errors while sending BSSMAP messages" + }, + [MSC_CTR_BSSMAP_TX_SUCCESS] = { + "bssmap:tx:result:success", + "Number of successfully sent BSSMAP messages" + }, /* Tx message counters (per specific message) * * Theoretically, the DT1 counters should sum up to the same number as the Tx counters * above but since these counters are coming from the upper layers, there might be * some difference if we forget some code path. */ - [MSC_CTR_BSSMAP_TX_UDT_RESET] = {"bssmap:tx:udt:reset:request", "Number of transmitted BSSMAP UDT RESET messages"}, - [MSC_CTR_BSSMAP_TX_UDT_RESET_ACK] = {"bssmap:tx:udt:reset:ack", "Number of transmitted BSSMAP UDT RESET ACK messages"}, - [MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST] = {"bssmap:tx:dt1:clear:rqst", "Number of transmitted BSSMAP DT1 CLEAR RQSTtx messages"}, - [MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE] = {"bssmap:tx:dt1:clear:complete", "Number of transmitted BSSMAP DT1 CLEAR COMPLETE messages"}, - [MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_FAILURE] = {"bssmap:tx:dt1:assigment:failure", "Number of transmitted BSSMAP DT1 ASSIGMENT FAILURE messages"}, - [MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_COMPLETE] = {"bssmap:tx:dt1:assigment:complete", "Number of transmitted BSSMAP DT1 ASSIGMENT COMPLETE messages"}, - [MSC_CTR_BSSMAP_TX_DT1_SAPI_N_REJECT] = {"bssmap:tx:dt1:sapi_n:reject", "Number of transmitted BSSMAP DT1 SAPI N REJECT messages"}, - [MSC_CTR_BSSMAP_TX_DT1_CIPHER_COMPLETE] = {"bssmap:tx:dt1:cipher_mode:complete", "Number of transmitted BSSMAP DT1 CIPHER COMPLETE messages"}, - [MSC_CTR_BSSMAP_TX_DT1_CIPHER_REJECT] = {"bssmap:tx:dt1:cipher_mode:reject", "Number of transmitted BSSMAP DT1 CIPHER REJECT messages"}, - [MSC_CTR_BSSMAP_TX_DT1_CLASSMARK_UPDATE] = {"bssmap:tx:dt1:classmark:update", "Number of transmitted BSSMAP DT1 CLASSMARK UPDATE messages"}, - [MSC_CTR_BSSMAP_TX_DT1_LCLS_CONNECT_CTRL_ACK] = {"bssmap:tx:dt1:lcls_connect_ctrl:ack", "Number of transmitted BSSMAP DT1 LCLS CONNECT CTRL ACK messages"}, - [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_REQUIRED] = {"bssmap:tx:dt1:handover:required", "Number of transmitted BSSMAP DT1 HANDOVER REQUIRED messages"}, - [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED] = {"bssmap:tx:dt1:handover:performed", "Number of transmitted BSSMAP DT1 HANDOVER PERFORMED messages"}, - [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_RQST_ACKNOWLEDGE] = {"bssmap:tx:dt1:handover:rqst_acknowledge", "Number of transmitted BSSMAP DT1 HANDOVER RQST ACKNOWLEDGE messages"}, - [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_DETECT] = {"bssmap:tx:dt1:handover:detect", "Number of transmitted BSSMAP DT1 HANDOVER DETECT messages"}, - [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE] = {"bssmap:tx:dt1:handover:complete", "Number of transmitted BSSMAP DT1 HANDOVER COMPLETE messages"}, - [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE] = {"bssmap:tx:dt1:handover:failure", "Number of transmitted BSSMAP DT1 HANDOVER FAILURE messages"}, - [MSC_CTR_BSSMAP_TX_DT1_DTAP] = {"bssmap:tx:dt1:dtap", "Number of transmitted BSSMAP DT1 DTAP messages"}, - [MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS] = {"bssmap:tx:dt1:location:response_success", - "Number of transmitted BSSMAP Perform Location Response messages containing a location estimate"}, - [MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE] = {"bssmap:tx:dt1:location:response_failure", - "Number of transmitted BSSMAP Perform Location Response messages containing a failure cause"}, + [MSC_CTR_BSSMAP_TX_UDT_RESET] = { + "bssmap:tx:udt:reset:request", + "Number of transmitted BSSMAP UDT RESET messages" + }, + [MSC_CTR_BSSMAP_TX_UDT_RESET_ACK] = { + "bssmap:tx:udt:reset:ack", + "Number of transmitted BSSMAP UDT RESET ACK messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST] = { + "bssmap:tx:dt1:clear:rqst", + "Number of transmitted BSSMAP DT1 CLEAR RQSTtx messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE] = { + "bssmap:tx:dt1:clear:complete", + "Number of transmitted BSSMAP DT1 CLEAR COMPLETE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_FAILURE] = { + "bssmap:tx:dt1:assignment:failure", + "Number of transmitted BSSMAP DT1 ASSIGNMENT FAILURE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_COMPLETE] = { + "bssmap:tx:dt1:assignment:complete", + "Number of transmitted BSSMAP DT1 ASSIGNMENT COMPLETE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_SAPI_N_REJECT] = { + "bssmap:tx:dt1:sapi_n:reject", + "Number of transmitted BSSMAP DT1 SAPI N REJECT messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_CIPHER_COMPLETE] = { + "bssmap:tx:dt1:cipher_mode:complete", + "Number of transmitted BSSMAP DT1 CIPHER COMPLETE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_CIPHER_REJECT] = { + "bssmap:tx:dt1:cipher_mode:reject", + "Number of transmitted BSSMAP DT1 CIPHER REJECT messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_CLASSMARK_UPDATE] = { + "bssmap:tx:dt1:classmark:update", + "Number of transmitted BSSMAP DT1 CLASSMARK UPDATE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_LCLS_CONNECT_CTRL_ACK] = { + "bssmap:tx:dt1:lcls_connect_ctrl:ack", + "Number of transmitted BSSMAP DT1 LCLS CONNECT CTRL ACK messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_REQUIRED] = { + "bssmap:tx:dt1:handover:required", + "Number of transmitted BSSMAP DT1 HANDOVER REQUIRED messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED] = { + "bssmap:tx:dt1:handover:performed", + "Number of transmitted BSSMAP DT1 HANDOVER PERFORMED messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_RQST_ACKNOWLEDGE] = { + "bssmap:tx:dt1:handover:rqst_acknowledge", + "Number of transmitted BSSMAP DT1 HANDOVER RQST ACKNOWLEDGE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_DETECT] = { + "bssmap:tx:dt1:handover:detect", + "Number of transmitted BSSMAP DT1 HANDOVER DETECT messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE] = { + "bssmap:tx:dt1:handover:complete", + "Number of transmitted BSSMAP DT1 HANDOVER COMPLETE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE] = { + "bssmap:tx:dt1:handover:failure", + "Number of transmitted BSSMAP DT1 HANDOVER FAILURE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_DTAP] = { + "bssmap:tx:dt1:dtap", + "Number of transmitted BSSMAP DT1 DTAP messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS] = { + "bssmap:tx:dt1:location:response_success", + "Number of transmitted BSSMAP Perform Location Response messages containing a location estimate" + }, + [MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE] = { + "bssmap:tx:dt1:location:response_failure", + "Number of transmitted BSSMAP Perform Location Response messages containing a failure cause" + }, + [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_SETUP_ACK] = { + "bssmap:tx:dt1:vgcs_vbs_setup:ack", + "Number of transmitted BSSMAP DT1 VGCS/VBS SETUP ACK messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_SETUP_REFUSE] = { + "bssmap:tx:dt1:vgcs_vbs_setup:refuse", + "Number of transmitted BSSMAP DT1 VGCS/VBS SETUP REFUSE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGN_RESULT] = { + "bssmap:tx:dt1:vgcs_vbs_assignment:res", + "Number of transmitted BSSMAP DT1 VGCS/VBS ASSIGNMENT RESULT messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGN_FAIL] = { + "bssmap:tx:dt1:vgcs_vbs_assignment:fail", + "Number of transmitted BSSMAP DT1 VGCS/VBS ASSIGNMENT FAILURE messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_QUEUING_INDICATION] = { + "bssmap:tx:dt1:vgcs_vbs_queuing:ind", + "Number of transmitted BSSMAP DT1 VGCS/VBS QUEUING INDICATION messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_UPLINK_RQST] = { + "bssmap:tx:dt1:uplink_rqst", + "Number of transmitted BSSMAP DT1 UPLINK REQUEST messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGNMENT_STATUS] = { + "bssmap:tx:dt1:vgcs_vbs_assignment:status", + "Number of transmitted BSSMAP DT1 VGCS/VBS ASSIGNMENT STATUS messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_AREA_CELL_INFO] = { + "bssmap:tx:dt1:vgcs_vbs_area_cell:info", + "Number of transmitted BSSMAP DT1 VGCS/VBS AREA CELL INFO messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_UPLINK_RQST_CONFIRMATION] = { + "bssmap:tx:dt1:uplink_rqst:cnf", + "Number of transmitted BSSMAP DT1 UPLINK REQUEST CONFIRMATION messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_UPLINK_RELEASE_INDICATION] = { + "bssmap:tx:dt1:uplink_release:ind", + "Number of transmitted BSSMAP DT1 UPLINK RELEASE INDICATION messages" + }, + [MSC_CTR_BSSMAP_TX_DT1_UPLINK_APP_DATA] = { + "bssmap:tx:dt1:uplink_app_data", + "Number of transmitted BSSMAP DT1 UPLINK APPLICATION DATA messages" + }, /* Indicators for MSC pool usage */ [MSC_CTR_MSCPOOL_SUBSCR_NEW] = { @@ -147,8 +362,8 @@ static const struct rate_ctr_group_desc msc_ctrg_desc = { }; static const struct osmo_stat_item_desc msc_stat_desc[] = { - { "msc_links:active", "Number of active MSC links", "", 16, 0 }, - { "msc_links:total", "Number of configured MSC links", "", 16, 0 }, + [MSC_STAT_MSC_LINKS_ACTIVE] = { "msc_links:active", "Number of active MSC links", "", 16, 0 }, + [MSC_STAT_MSC_LINKS_TOTAL] = { "msc_links:total", "Number of configured MSC links", "", 16, 0 }, }; static const struct osmo_stat_item_group_desc msc_statg_desc = { @@ -161,24 +376,22 @@ static const struct osmo_stat_item_group_desc msc_statg_desc = { int osmo_bsc_msc_init(struct bsc_msc_data *msc) { - struct gsm_network *net = msc->network; - uint16_t mgw_port; int rc; - if (net->mgw.conf->remote_port >= 0) - mgw_port = net->mgw.conf->remote_port; - else - mgw_port = MGCP_CLIENT_REMOTE_PORT_DEFAULT; + /* Everything below refers to SCCP-Lite MSC connections only. */ + if (msc_is_aoip(msc)) + return 0; rc = osmo_sock_init2_ofd(&msc->mgcp_ipa.ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, msc->mgcp_ipa.local_addr, msc->mgcp_ipa.local_port, - net->mgw.conf->remote_addr, mgw_port, - OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); + NULL, 0, OSMO_SOCK_F_BIND); if (rc < 0) { LOGP(DMSC, LOGL_ERROR, "msc %u: Could not create/connect/bind MGCP proxy socket: %d\n", msc->nr, rc); return rc; } + LOGP(DMSC, LOGL_INFO, "msc %u: Socket forwarding IPA-encapsulated MGCP messages towards MGW: %s\n", + msc->nr, osmo_sock_get_name2(msc->mgcp_ipa.ofd.fd)); return 0; } @@ -196,7 +409,6 @@ struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *net, int nr) struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr) { struct bsc_msc_data *msc_data; - unsigned int i; /* check if there is already one */ msc_data = osmo_msc_data_find(net, nr); @@ -240,24 +452,16 @@ struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr) /* Allow the full set of possible codecs by default */ msc_data->audio_length = 5; - msc_data->audio_support = - talloc_zero_array(msc_data, struct gsm_audio_support *, - msc_data->audio_length); - for (i = 0; i < msc_data->audio_length; i++) { - msc_data->audio_support[i] = - talloc_zero(msc_data->audio_support, - struct gsm_audio_support); - } - msc_data->audio_support[0]->ver = 1; - msc_data->audio_support[0]->hr = 0; - msc_data->audio_support[1]->ver = 1; - msc_data->audio_support[1]->hr = 1; - msc_data->audio_support[2]->ver = 2; - msc_data->audio_support[2]->hr = 0; - msc_data->audio_support[3]->ver = 3; - msc_data->audio_support[3]->hr = 0; - msc_data->audio_support[4]->ver = 3; - msc_data->audio_support[4]->hr = 1; + msc_data->audio_support[0].ver = 1; + msc_data->audio_support[0].hr = 0; + msc_data->audio_support[1].ver = 1; + msc_data->audio_support[1].hr = 1; + msc_data->audio_support[2].ver = 2; + msc_data->audio_support[2].hr = 0; + msc_data->audio_support[3].ver = 3; + msc_data->audio_support[3].hr = 0; + msc_data->audio_support[4].ver = 3; + msc_data->audio_support[4].hr = 1; osmo_fd_setup(&msc_data->mgcp_ipa.ofd, -1, OSMO_FD_READ, &bsc_sccplite_mgcp_proxy_cb, msc_data, 0); msc_data->mgcp_ipa.local_addr = NULL; /* = INADDR(6)_ANY */ diff --git a/src/osmo-bsc/osmo_bsc_sigtran.c b/src/osmo-bsc/osmo_bsc_sigtran.c index 2cb769470..a4d0f2d74 100644 --- a/src/osmo-bsc/osmo_bsc_sigtran.c +++ b/src/osmo-bsc/osmo_bsc_sigtran.c @@ -36,6 +36,7 @@ #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/bts.h> #include <osmocom/bsc/paging.h> +#include <osmocom/bsc/bssmap_reset.h> #include <osmocom/mgcp_client/mgcp_common.h> /* A pointer to a list with all involved MSCs @@ -45,33 +46,6 @@ static struct llist_head *msc_list; #define DEFAULT_ASP_LOCAL_IP "localhost" #define DEFAULT_ASP_REMOTE_IP "localhost" -/* Helper function to Check if the given connection id is already assigned */ -static struct gsm_subscriber_connection *get_bsc_conn_by_conn_id(int conn_id) -{ - conn_id &= 0xFFFFFF; - struct gsm_subscriber_connection *conn; - - llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) { - if (conn->sccp.conn_id == conn_id) - return conn; - } - - return NULL; -} - -struct gsm_subscriber_connection *bsc_conn_by_bsub(struct bsc_subscr *bsub) -{ - struct gsm_subscriber_connection *conn; - if (!bsub) - return NULL; - - llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) { - if (conn->bsub == bsub) - return conn; - } - return NULL; -} - /* Patch regular BSSMAP RESET to add extra T to announce Osmux support (osmocom extension) */ static void _gsm0808_extend_announce_osmux(struct msgb *msg) { @@ -94,7 +68,7 @@ void osmo_bsc_sigtran_tx_reset(const struct bsc_msc_data *msc) if (msc_is_aoip(msc) && msc->use_osmux != OSMUX_USAGE_OFF) _gsm0808_extend_announce_osmux(msg); - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_UDT_RESET]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_UDT_RESET)); osmo_sccp_tx_unitdata_msg(msc->a.sccp_user, &msc->a.bsc_addr, &msc->a.msc_addr, msg); } @@ -114,12 +88,12 @@ void osmo_bsc_sigtran_tx_reset_ack(const struct bsc_msc_data *msc) if (msc_is_aoip(msc) && msc->use_osmux != OSMUX_USAGE_OFF) _gsm0808_extend_announce_osmux(msg); - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_UDT_RESET_ACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_UDT_RESET_ACK)); osmo_sccp_tx_unitdata_msg(msc->a.sccp_user, &msc->a.bsc_addr, &msc->a.msc_addr, msg); } -/* Find an MSC by its sigtran point code */ +/* Find an MSC by its remote SCCP address */ static struct bsc_msc_data *get_msc_by_addr(const struct osmo_sccp_addr *msc_addr) { struct osmo_ss7_instance *ss7; @@ -134,6 +108,21 @@ static struct bsc_msc_data *get_msc_by_addr(const struct osmo_sccp_addr *msc_add return NULL; } +/* Find an MSC by its remote sigtran point code on a given cs7 instance. */ +static struct bsc_msc_data *get_msc_by_pc(struct osmo_ss7_instance *cs7, uint32_t pc) +{ + struct bsc_msc_data *msc; + llist_for_each_entry(msc, msc_list, entry) { + if (msc->a.cs7_instance != cs7->cfg.id) + continue; + if ((msc->a.msc_addr.presence & OSMO_SCCP_ADDR_T_PC) == 0) + continue; + if (msc->a.msc_addr.pc == pc) + return msc; + } + return NULL; +} + /* Received data from MSC, use the connection id which MSC it is */ static int handle_data_from_msc(struct gsm_subscriber_connection *conn, struct msgb *msg) { @@ -164,10 +153,12 @@ static int handle_unitdata_from_msc(const struct osmo_sccp_addr *msc_addr, struc static int handle_n_connect_from_msc(struct osmo_sccp_user *scu, struct osmo_scu_prim *scu_prim) { struct bsc_msc_data *msc = get_msc_by_addr(&scu_prim->u.connect.calling_addr); + struct osmo_sccp_instance *sccp = osmo_sccp_get_sccp(scu); + struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(sccp); struct gsm_subscriber_connection *conn; int rc = 0; - conn = get_bsc_conn_by_conn_id(scu_prim->u.connect.conn_id); + conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.connect.conn_id); if (conn) { LOGP(DMSC, LOGL_NOTICE, "(calling_addr=%s conn_id=%u) N-CONNECT.ind with already used conn_id, ignoring\n", @@ -195,7 +186,14 @@ static int handle_n_connect_from_msc(struct osmo_sccp_user *scu, struct osmo_scu if (!conn) return -ENOMEM; conn->sccp.msc = msc; - conn->sccp.conn_id = scu_prim->u.connect.conn_id; + conn->sccp.conn.conn_id = scu_prim->u.connect.conn_id; + if (bsc_sccp_inst_register_gscon(bsc_sccp, &conn->sccp.conn) < 0) { + LOGP(DMSC, LOGL_NOTICE, "(calling_addr=%s conn_id=%u) N-CONNECT.ind failed registering conn\n", + osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr), scu_prim->u.connect.conn_id); + osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REQUEST, NULL); + rc = -ENOENT; + goto refuse; + } /* Take actions asked for by the enclosed PDU */ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_IND, scu_prim); @@ -206,11 +204,101 @@ refuse: return rc; } +static void handle_pcstate_ind(struct osmo_ss7_instance *cs7, const struct osmo_scu_pcstate_param *pcst) +{ + struct bsc_msc_data *msc; + bool connected; + bool disconnected; + + LOGP(DMSC, LOGL_DEBUG, "N-PCSTATE ind: affected_pc=%u sp_status=%s remote_sccp_status=%s\n", + pcst->affected_pc, osmo_sccp_sp_status_name(pcst->sp_status), + osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); + + /* If we don't care about that point-code, ignore PCSTATE. */ + msc = get_msc_by_pc(cs7, pcst->affected_pc); + if (!msc) + return; + + /* See if this marks the point code to have become available, or to have been lost. + * + * I want to detect two events: + * - connection event (both indicators say PC is reachable). + * - disconnection event (at least one indicator says the PC is not reachable). + * + * There are two separate incoming indicators with various possible values -- the incoming events can be: + * + * - neither connection nor disconnection indicated -- just indicating congestion + * connected == false, disconnected == false --> do nothing. + * - both incoming values indicate that we are connected + * --> trigger connected + * - both indicate we are disconnected + * --> trigger disconnected + * - one value indicates 'connected', the other indicates 'disconnected' + * --> trigger disconnected + * + * Congestion could imply that we're connected, but it does not indicate that a PC's reachability changed, so no need to + * trigger on that. + */ + connected = false; + disconnected = false; + + switch (pcst->sp_status) { + case OSMO_SCCP_SP_S_ACCESSIBLE: + connected = true; + break; + case OSMO_SCCP_SP_S_INACCESSIBLE: + disconnected = true; + break; + default: + case OSMO_SCCP_SP_S_CONGESTED: + /* Neither connecting nor disconnecting */ + break; + } + + switch (pcst->remote_sccp_status) { + case OSMO_SCCP_REM_SCCP_S_AVAILABLE: + if (!disconnected) + connected = true; + break; + case OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN: + case OSMO_SCCP_REM_SCCP_S_UNEQUIPPED: + case OSMO_SCCP_REM_SCCP_S_INACCESSIBLE: + disconnected = true; + connected = false; + break; + default: + case OSMO_SCCP_REM_SCCP_S_CONGESTED: + /* Neither connecting nor disconnecting */ + break; + } + + if (disconnected && a_reset_conn_ready(msc)) { + LOGP(DMSC, LOGL_NOTICE, + "(msc%d) now unreachable: N-PCSTATE ind: pc=%u sp_status=%s remote_sccp_status=%s\n", + msc->nr, pcst->affected_pc, + osmo_sccp_sp_status_name(pcst->sp_status), + osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); + /* A previously usable MSC has disconnected. Kick the BSSMAP back to DISC state. */ + bssmap_reset_set_disconnected(msc->a.bssmap_reset); + } else if (connected && !a_reset_conn_ready(msc)) { + LOGP(DMSC, LOGL_NOTICE, + "(msc%d) now available: N-PCSTATE ind: pc=%u sp_status=%s remote_sccp_status=%s\n", + msc->nr, pcst->affected_pc, + osmo_sccp_sp_status_name(pcst->sp_status), + osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); + /* A previously unusable MSC has become reachable. Trigger immediate BSSMAP RESET -- we would resend a + * RESET either way, but we might as well do it now to speed up connecting. */ + bssmap_reset_resend_reset(msc->a.bssmap_reset); + } +} + /* Callback function, called by the SCCP stack when data arrives */ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) { struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *)oph; struct osmo_sccp_user *scu = _scu; + struct osmo_sccp_instance *sccp = osmo_sccp_get_sccp(scu); + struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(sccp); struct gsm_subscriber_connection *conn; int rc = 0; @@ -231,7 +319,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) /* Handle outbound connection confirmation */ DEBUGP(DMSC, "N-CONNECT.cnf(%u, %s)\n", scu_prim->u.connect.conn_id, osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg))); - conn = get_bsc_conn_by_conn_id(scu_prim->u.connect.conn_id); + conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.connect.conn_id); if (conn) { osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_CFM, scu_prim); conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; @@ -250,7 +338,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg))); /* Incoming data is a sign of a vital connection */ - conn = get_bsc_conn_by_conn_id(scu_prim->u.data.conn_id); + conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.data.conn_id); if (conn) { a_reset_conn_success(conn->sccp.msc); handle_data_from_msc(conn, oph->msg); @@ -262,7 +350,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)), scu_prim->u.disconnect.cause); /* indication of disconnect */ - conn = get_bsc_conn_by_conn_id(scu_prim->u.disconnect.conn_id); + conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.disconnect.conn_id); if (conn) { conn->sccp.state = SUBSCR_SCCP_ST_NONE; if (msgb_l2len(oph->msg) > 0) @@ -271,6 +359,10 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) } break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_PCSTATE, PRIM_OP_INDICATION): + handle_pcstate_ind(osmo_sccp_get_ss7(sccp), &scu_prim->u.pcstate); + break; + default: LOGP(DMSC, LOGL_ERROR, "Unhandled SIGTRAN operation %s on primitive %u\n", get_value_string(osmo_prim_op_names, oph->operation), oph->primitive); @@ -312,17 +404,19 @@ enum bsc_con osmo_bsc_sigtran_new_conn(struct gsm_subscriber_connection *conn, s } /* Open a new connection oriented sigtran connection */ -int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg) +/* Allow test to overwrite it */ +__attribute__((weak)) int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg) { struct osmo_ss7_instance *ss7; struct bsc_msc_data *msc; - int conn_id; + struct bsc_sccp_inst *bsc_sccp; + uint32_t conn_id; int rc; OSMO_ASSERT(conn); OSMO_ASSERT(msg); OSMO_ASSERT(conn->sccp.msc); - OSMO_ASSERT(conn->sccp.conn_id == -1); + OSMO_ASSERT(conn->sccp.conn.conn_id == SCCP_CONN_ID_UNSET); msc = conn->sccp.msc; @@ -331,15 +425,20 @@ int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct ms return -EINVAL; } - conn->sccp.conn_id = conn_id = bsc_sccp_inst_next_conn_id(conn->sccp.msc->a.sccp); - if (conn->sccp.conn_id < 0) { + bsc_sccp = osmo_sccp_get_priv(msc->a.sccp); + conn->sccp.conn.conn_id = conn_id = bsc_sccp_inst_next_conn_id(bsc_sccp); + if (conn->sccp.conn.conn_id == SCCP_CONN_ID_UNSET) { LOGP(DMSC, LOGL_ERROR, "Unable to allocate SCCP Connection ID\n"); return -1; } - LOGP(DMSC, LOGL_DEBUG, "Allocated new connection id: %d\n", conn->sccp.conn_id); + if (bsc_sccp_inst_register_gscon(bsc_sccp, &conn->sccp.conn) < 0) { + LOGP(DMSC, LOGL_ERROR, "Unable to register SCCP connection (id=%u)\n", conn->sccp.conn.conn_id); + return -1; + } + LOGP(DMSC, LOGL_DEBUG, "Allocated new connection id: %u\n", conn->sccp.conn.conn_id); ss7 = osmo_ss7_instance_find(msc->a.cs7_instance); OSMO_ASSERT(ss7); - LOGP(DMSC, LOGL_INFO, "Opening new SCCP connection (id=%i) to MSC %d: %s\n", conn_id, + LOGP(DMSC, LOGL_INFO, "Opening new SCCP connection (id=%u) to MSC %d: %s\n", conn_id, msc->nr, osmo_sccp_addr_name(ss7, &msc->a.msc_addr)); rc = osmo_sccp_tx_conn_req_msg(msc->a.sccp_user, conn_id, &msc->a.bsc_addr, @@ -374,32 +473,32 @@ int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *m if (msg->len >= 3) { switch (msg->data[0]) { case BSSAP_MSG_BSS_MANAGEMENT: - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_BSS_MANAGEMENT]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_BSS_MANAGEMENT)); LOGP(DMSC, LOGL_INFO, "Tx MSC: BSSMAP: %s\n", gsm0808_bssmap_name(msg->data[2])); break; case BSSAP_MSG_DTAP: - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DTAP]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DTAP)); LOGP(DMSC, LOGL_INFO, "Tx MSC: DTAP\n"); break; default: - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_UNKNOWN]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_UNKNOWN)); LOGP(DMSC, LOGL_ERROR, "Tx MSC: unknown message type: 0x%x\n", msg->data[0]); } } else { - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_SHORT]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_SHORT)); LOGP(DMSC, LOGL_ERROR, "Tx MSC: message too short: %u\n", msg->len); } if (a_reset_conn_ready(msc) == false) { - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_ERR_CONN_NOT_READY]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_ERR_CONN_NOT_READY)); LOGP(DMSC, LOGL_ERROR, "MSC is not connected. Dropping.\n"); msgb_free(msg); return -EINVAL; } - conn_id = conn->sccp.conn_id; + conn_id = conn->sccp.conn.conn_id; ss7 = osmo_ss7_instance_find(msc->a.cs7_instance); OSMO_ASSERT(ss7); @@ -408,9 +507,9 @@ int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *m rc = osmo_sccp_tx_data_msg(msc->a.sccp_user, conn_id, msg); if (rc >= 0) - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_SUCCESS]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_SUCCESS)); else - rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_ERR_SEND]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_ERR_SEND)); return rc; } @@ -513,6 +612,7 @@ int osmo_bsc_sigtran_init(struct llist_head *mscs) int prev_msc_nr; struct osmo_sccp_instance *sccp; + struct bsc_sccp_inst *bsc_sccp; llist_for_each_entry(msc, msc_list, entry) { /* An MSC with invalid cs7 instance id defaults to cs7 instance 0 */ @@ -556,6 +656,10 @@ int osmo_bsc_sigtran_init(struct llist_head *mscs) if (!sccp) return -EINVAL; + bsc_sccp = bsc_sccp_inst_alloc(tall_bsc_ctx); + bsc_sccp->sccp = sccp; + osmo_sccp_set_priv(sccp, bsc_sccp); + /* Now that the SCCP client is set up, configure all MSCs on this cs7 instance to use it */ llist_for_each_entry(msc, msc_list, entry) { char msc_name[32]; @@ -625,7 +729,7 @@ static int asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *m struct ipaccess_head *iph; struct ipaccess_head_ext *iph_ext; - if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA) { + if (osmo_ss7_asp_get_proto(asp) != OSMO_SS7_ASP_PROT_IPA) { msgb_free(msg); return 0; } diff --git a/src/osmo-bsc/paging.c b/src/osmo-bsc/paging.c index 15aca00ea..b7842dd52 100644 --- a/src/osmo-bsc/paging.c +++ b/src/osmo-bsc/paging.c @@ -54,21 +54,52 @@ #include <osmocom/bsc/gsm_04_08_rr.h> #include <osmocom/bsc/bsc_subscr_conn_fsm.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bsc_stats.h> void *tall_paging_ctx = NULL; -#define PAGING_TIMER 0, 500000 +/* How many paging requests to Tx on RSL at max before going back to main loop */ +#define MAX_PAGE_REQ_PER_ITER 10 + +/* How often to attempt sending new paging requests (initial, not retrans): 250ms */ +static const struct timespec initial_period = { + .tv_sec = 0, + .tv_nsec = 250 * 1000 * 1000, +}; + +/* Minimum period between retransmits of paging req to a subscriber: 500ms */ +static const struct timespec retrans_period = { + .tv_sec = 0, + .tv_nsec = 500 * 1000 * 1000, +}; + +/* If no CCCH Load Ind is received before this time period, the BTS is considered + * to have stopped sending CCCH Load Indication, probably due to being under Load + * Threshold: */ +#define bts_no_ccch_load_ind_timeout_sec(bts) ((bts)->ccch_load_ind_period * 2) /* * Kill one paging request update the internal list... */ -static void paging_remove_request(struct gsm_bts_paging_state *paging_bts, - struct gsm_paging_request *to_be_deleted) +static void paging_remove_request(struct gsm_paging_request *req) { - osmo_timer_del(&to_be_deleted->T3113); - llist_del(&to_be_deleted->entry); - bsc_subscr_put(to_be_deleted->bsub, BSUB_USE_PAGING_REQUEST); - talloc_free(to_be_deleted); + struct gsm_bts *bts = req->bts; + struct gsm_bts_paging_state *bts_pag_st = &bts->paging; + + osmo_timer_del(&req->T3113); + llist_del(&req->entry); + if (req->attempts == 0) { + bts_pag_st->initial_req_list_len--; + bts_pag_st->initial_req_pgroup_counts[req->pgroup]--; + } else { + bts_pag_st->retrans_req_list_len--; + } + osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_REQ_QUEUE_LENGTH), 1); + bsc_subscr_remove_active_paging_request(req->bsub, req); + talloc_free(req); + + if (llist_empty(&bts_pag_st->initial_req_list) && llist_empty(&bts_pag_st->retrans_req_list)) + osmo_timer_del(&bts_pag_st->work_timer); } static void page_ms(struct gsm_paging_request *request) @@ -79,9 +110,9 @@ static void page_ms(struct gsm_paging_request *request) log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub); - LOG_BTS(bts, DPAG, LOGL_INFO, "Going to send paging commands: %s" - " for ch. type %d (attempt %d)\n", bsc_subscr_name(request->bsub), - request->chan_type, request->attempts); + LOG_PAGING_BTS(request, bts, DPAG, LOGL_INFO, + "Going to send paging command for ch. type %d (attempt %d)\n", + request->chan_type, request->attempts); if (request->bsub->tmsi == GSM_RESERVED_TMSI) { mi = (struct osmo_mobile_identity){ @@ -101,24 +132,35 @@ static void page_ms(struct gsm_paging_request *request) log_set_context(LOG_CTX_BSC_SUBSCR, NULL); } +static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts); + static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts) { - if (llist_empty(&paging_bts->pending_requests)) - return; - + /* paging_handle_pending_requests() will schedule work_timer if work + * needs to be partitioned in several iterations. */ if (!osmo_timer_pending(&paging_bts->work_timer)) - osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER); + paging_handle_pending_requests(paging_bts); } +/* Placeholder to set the value and update the related osmo_stat: */ +static void paging_set_available_slots(struct gsm_bts *bts, uint16_t available_slots) +{ + bts->paging.available_slots = available_slots; + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_AVAILABLE_SLOTS), available_slots); +} -static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts); static void paging_give_credit(void *data) { - struct gsm_bts_paging_state *paging_bts = data; - - LOG_BTS(paging_bts->bts, DPAG, LOGL_NOTICE, "No PCH LOAD IND, adding 20 slots)\n"); - paging_bts->available_slots = 20; - paging_handle_pending_requests(paging_bts); + struct gsm_bts_paging_state *paging_bts_st = data; + struct gsm_bts *bts = paging_bts_st->bts; + unsigned int load_ind_timeout = bts_no_ccch_load_ind_timeout_sec(bts); + uint16_t estimated_slots = paging_estimate_available_slots(bts, load_ind_timeout); + LOG_BTS(bts, DPAG, LOGL_INFO, + "Timeout waiting for CCCH Load Indication, assuming BTS is below Load Threshold (available_slots %u -> %u)\n", + paging_bts_st->available_slots, estimated_slots); + paging_set_available_slots(bts, estimated_slots); + paging_schedule_if_needed(paging_bts_st); + osmo_timer_schedule(&bts->paging.credit_timer, load_ind_timeout, 0); } /*! count the number of free channels for given RSL channel type required @@ -171,63 +213,182 @@ count_tch: return bts->paging.free_chans_need > count; } +static void paging_req_timeout_retrans(struct gsm_paging_request *request, const struct timespec *now) +{ + struct gsm_bts_paging_state *bts_pag_st = &request->bts->paging; + page_ms(request); + paging_set_available_slots(request->bts, bts_pag_st->available_slots - 1); + + if (request->attempts == 0) { + /* req is removed from initial_req_list and inserted into retrans_req_list, update list lengths: */ + bts_pag_st->initial_req_list_len--; + bts_pag_st->initial_req_pgroup_counts[request->pgroup]--; + bts_pag_st->retrans_req_list_len++; + } + llist_del(&request->entry); + llist_add_tail(&request->entry, &bts_pag_st->retrans_req_list); + + request->last_attempt_ts = *now; + request->attempts++; +} + +/* Returns number of paged initial requests (up to max_page_req_per_iter). + * Returning work_done=false means the work timer has been scheduled internally and the caller should avoid processing + * further requests right now. + */ +static unsigned int step_page_initial_reqs(struct gsm_bts_paging_state *bts_pag_st, unsigned int max_page_req_per_iter, + const struct timespec *now, bool *work_done) +{ + struct gsm_paging_request *request, *request2; + unsigned int num_paged = 0; + + llist_for_each_entry_safe(request, request2, &bts_pag_st->initial_req_list, entry) { + /* We run out of available slots. Wait until next CCCH Load Ind + * arrives or credit_timer triggers to keep processing requests. + */ + if (bts_pag_st->available_slots == 0) { + LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_INFO, + "Paging delayed: waiting for available slots at BTS\n"); + *work_done = false; + return num_paged; + } + + if (num_paged == max_page_req_per_iter) { + goto sched_next_iter; + } + + /* we need to determine the number of free channels */ + if (bts_pag_st->free_chans_need != -1 && + can_send_pag_req(request->bts, request->chan_type) != 0) { + LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_INFO, + "Paging delayed: not enough free channels (<%d)\n", + bts_pag_st->free_chans_need); + goto sched_next_iter; + } + + /* handle the paging request now */ + paging_req_timeout_retrans(request, now); + num_paged++; + } + + *work_done = true; + return num_paged; + +sched_next_iter: + LOG_BTS(bts_pag_st->bts, DPAG, LOGL_DEBUG, "Scheduling next batch in %lld.%06lds (available_slots=%u)\n", + (long long)initial_period.tv_sec, initial_period.tv_nsec / 1000, + bts_pag_st->available_slots); + osmo_timer_schedule(&bts_pag_st->work_timer, initial_period.tv_sec, initial_period.tv_nsec / 1000); + *work_done = false; + return num_paged; +} + +static unsigned int step_page_retrans_reqs(struct gsm_bts_paging_state *bts_pag_st, unsigned int max_page_req_per_iter, + const struct timespec *now) +{ + struct gsm_paging_request *request, *initial_request; + unsigned int num_paged = 0; + struct timespec retrans_ts; + + /* do while loop: Try send at most first max_page_req_per_iter paging + * requests. Since transmitted requests are re-appended at the end of + * the list, we check until we find the first req again, in order to + * avoid retransmitting repeated requests until next time paging is + * scheduled. */ + initial_request = llist_first_entry_or_null(&bts_pag_st->retrans_req_list, + struct gsm_paging_request, entry); + if (!initial_request) + return num_paged; + + request = initial_request; + do { + /* We run out of available slots. Wait until next CCCH Load Ind + * arrives or credit_timer triggers to keep processing requests. + */ + if (bts_pag_st->available_slots == 0) { + LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_INFO, + "Paging delayed: waiting for available slots at BTS\n"); + return num_paged; + } + + /* we need to determine the number of free channels */ + if (bts_pag_st->free_chans_need != -1 && + can_send_pag_req(request->bts, request->chan_type) != 0) { + LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_INFO, + "Paging delayed: not enough free channels (<%d)\n", + bts_pag_st->free_chans_need); + goto sched_next_iter; + } + + /* Check if time to retransmit has elapsed. Otherwise, wait until its time to retransmit. */ + timespecadd(&request->last_attempt_ts, &retrans_period, &retrans_ts); + if (timespeccmp(now, &retrans_ts, <)) { + struct timespec tdiff; + timespecsub(&retrans_ts, now, &tdiff); + LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_DEBUG, + "Paging delayed: retransmission happens in %lld.%06lds\n", + (long long)tdiff.tv_sec, tdiff.tv_nsec / 1000); + osmo_timer_schedule(&bts_pag_st->work_timer, tdiff.tv_sec, tdiff.tv_nsec / 1000); + return num_paged; + } + + if (num_paged >= max_page_req_per_iter) + goto sched_next_iter; + + /* handle the paging request now */ + paging_req_timeout_retrans(request, now); + num_paged++; + + request = llist_first_entry(&bts_pag_st->retrans_req_list, + struct gsm_paging_request, entry); + } while (request != initial_request); + + /* Reaching this code paths means all retrans request have been scheduled (and intial_req_list is empty). + * Hence, reeschedule ourselves to now + retrans_period. */ + osmo_timer_schedule(&bts_pag_st->work_timer, retrans_period.tv_sec, retrans_period.tv_nsec / 1000); + return num_paged; + +sched_next_iter: + LOG_BTS(bts_pag_st->bts, DPAG, LOGL_DEBUG, "Scheduling next batch in %lld.%06lds (available_slots=%u)\n", + (long long)initial_period.tv_sec, initial_period.tv_nsec / 1000, + bts_pag_st->available_slots); + osmo_timer_schedule(&bts_pag_st->work_timer, initial_period.tv_sec, initial_period.tv_nsec / 1000); + return num_paged; +} + /* * This is kicked by the periodic PAGING LOAD Indicator * coming from abis_rsl.c * * We attempt to iterate once over the list of items but - * only upto available_slots. + * only up to available_slots. */ static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts) { - struct gsm_paging_request *request = NULL; + unsigned int num_paged_initial, num_paged_retrans = 0; + unsigned int max_page_req_per_iter = MAX_PAGE_REQ_PER_ITER; + struct timespec now; + bool work_done = false; /* * Determine if the pending_requests list is empty and * return then. */ - if (llist_empty(&paging_bts->pending_requests)) { - /* since the list is empty, no need to reschedule the timer */ + if (llist_empty(&paging_bts->initial_req_list) && + llist_empty(&paging_bts->retrans_req_list)) { + /* since the lists are empty, no need to reschedule the timer */ return; } - /* - * In case the BTS does not provide us with load indication and we - * ran out of slots, call an autofill routine. It might be that the - * BTS did not like our paging messages and then we have counted down - * to zero and we do not get any messages. - */ - if (paging_bts->available_slots == 0) { - osmo_timer_setup(&paging_bts->credit_timer, paging_give_credit, - paging_bts); - osmo_timer_schedule(&paging_bts->credit_timer, 5, 0); - return; - } - - request = llist_entry(paging_bts->pending_requests.next, - struct gsm_paging_request, entry); - - /* we need to determine the number of free channels */ - if (paging_bts->free_chans_need != -1) { - if (can_send_pag_req(request->bts, request->chan_type) != 0) - goto skip_paging; - } + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + paging_bts->last_sched_ts = now; - /* Skip paging if the bts is down. */ - if (!request->bts->oml_link) - goto skip_paging; + num_paged_initial = step_page_initial_reqs(paging_bts, max_page_req_per_iter, &now, &work_done); + if (work_done) /* All work done for initial requests, work on retransmissions now: */ + num_paged_retrans = step_page_retrans_reqs(paging_bts, max_page_req_per_iter - num_paged_initial, &now); - /* handle the paging request now */ - page_ms(request); - paging_bts->available_slots--; - request->attempts++; - - /* take the current and add it to the back */ - llist_del(&request->entry); - llist_add_tail(&request->entry, &paging_bts->pending_requests); - -skip_paging: - osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER); + LOG_BTS(paging_bts->bts, DPAG, LOGL_DEBUG, "Paged %u subscribers (%u initial, %u retrans) during last iteration\n", + num_paged_initial + num_paged_retrans, num_paged_initial, num_paged_retrans); } static void paging_worker(void *data) @@ -238,36 +399,23 @@ static void paging_worker(void *data) } /*! initialize the bts paging state, if it hasn't been initialized yet */ -static void paging_init_if_needed(struct gsm_bts *bts) +void paging_init(struct gsm_bts *bts) { - if (bts->paging.bts) - return; - bts->paging.bts = bts; - - /* This should be initialized only once. There is currently no code that sets bts->paging.bts - * back to NULL, so let's just assert this one instead of graceful handling. */ - OSMO_ASSERT(llist_empty(&bts->paging.pending_requests)); - - osmo_timer_setup(&bts->paging.work_timer, paging_worker, - &bts->paging); - - /* Large number, until we get a proper message */ - bts->paging.available_slots = 20; + bts->paging.free_chans_need = -1; + paging_set_available_slots(bts, 0); + INIT_LLIST_HEAD(&bts->paging.initial_req_list); + INIT_LLIST_HEAD(&bts->paging.retrans_req_list); + osmo_timer_setup(&bts->paging.work_timer, paging_worker, &bts->paging); + osmo_timer_setup(&bts->paging.credit_timer, paging_give_credit, &bts->paging); } -/*! do we have any pending paging requests for given subscriber? */ -static int paging_pending_request(struct gsm_bts_paging_state *bts, - struct bsc_subscr *bsub) +/* Called upon the bts struct being freed */ +void paging_destructor(struct gsm_bts *bts) { - struct gsm_paging_request *req; - - llist_for_each_entry(req, &bts->pending_requests, entry) { - if (bsub == req->bsub) - return 1; - } - - return 0; + paging_flush_bts(bts, NULL); + osmo_timer_del(&bts->paging.credit_timer); + osmo_timer_del(&bts->paging.work_timer); } /*! Call-back once T3113 (paging timeout) expires for given paging_request */ @@ -277,45 +425,72 @@ static void paging_T3113_expired(void *data) log_set_context(LOG_CTX_BSC_SUBSCR, req->bsub); - LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n", - req, bsc_subscr_name(req->bsub)); + LOG_PAGING_BTS(req, req->bts, DPAG, LOGL_INFO, "T3113 expired\n"); /* must be destroyed before calling cbfn, to prevent double free */ - rate_ctr_inc(&req->bts->bts_ctrs->ctr[BTS_CTR_PAGING_EXPIRED]); + rate_ctr_inc(rate_ctr_group_get_ctr(req->bts->bts_ctrs, BTS_CTR_PAGING_EXPIRED)); + + /* If last BTS paging times out (active_paging_requests will be + * decremented in paging_remove_request below): */ + if (req->bsub->active_paging_requests_len == 1) + rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_PAGING_EXPIRED)); /* destroy it now. Do not access req afterwards */ - paging_remove_request(&req->bts->paging, req); + paging_remove_request(req); log_set_context(LOG_CTX_BSC_SUBSCR, NULL); } -#define GSM_FRAME_DURATION_us 4615 -#define GSM51_MFRAME_DURATION_us (51 * GSM_FRAME_DURATION_us) /* 235365 us */ -static unsigned int calculate_timer_3113(struct gsm_bts *bts) +#define GSM51_MFRAME_DURATION_us (51 * GSM_TDMA_FN_DURATION_uS) /* 235365 us */ +static unsigned int paging_estimate_delay_us(struct gsm_bts *bts, unsigned int num_reqs, + unsigned int num_reqs_same_pgroup); + +static unsigned int calculate_timer_3113(struct gsm_paging_request *req, unsigned int reqs_before, + unsigned int reqs_before_same_pgroup, unsigned int max_dynamic_value) { - unsigned int to_us, to; + unsigned int to_us, estimated_to, to; + struct gsm_bts *bts = req->bts; struct osmo_tdef *d = osmo_tdef_get_entry(bts->network->T_defs, 3113); + unsigned int rach_max_trans, rach_tx_integer, bs_pa_mfrms; /* Note: d should always contain a valid pointer since all timers, * including 3113 are statically pre-defined in * struct osmo_tdef gsm_network_T_defs. */ OSMO_ASSERT(d); - if (!bts->T3113_dynamic) - return d->val; - - /* TODO: take into account load of paging group for req->bsub */ + if (!bts->T3113_dynamic) { + to = d->val; + goto ret; + } /* MFRMS defines repeat interval of paging messages for MSs that belong * to same paging group across multiple 51 frame multiframes. - * MAXTRANS defines maximum number of RACH retransmissions. + * MAXTRANS defines maximum number of RACH retransmissions, spread over + * TXINTEGER slots. */ - to_us = GSM51_MFRAME_DURATION_us * (bts->si_common.chan_desc.bs_pa_mfrms + 2) * - bts->si_common.rach_control.max_trans; + rach_max_trans = rach_max_trans_raw2val(bts->si_common.rach_control.max_trans); + rach_tx_integer = rach_tx_integer_raw2val(bts->si_common.rach_control.tx_integer); + bs_pa_mfrms = (bts->si_common.chan_desc.bs_pa_mfrms + 2); + to_us = GSM51_MFRAME_DURATION_us * bs_pa_mfrms + + GSM_TDMA_FN_DURATION_uS * rach_tx_integer * rach_max_trans; + + /* Now add some extra time based on how many requests need to be transmitted before this one: */ + to_us += paging_estimate_delay_us(bts, reqs_before, reqs_before_same_pgroup); /* ceiling in seconds + extra time */ - to = (to_us + 999999) / 1000000 + d->val; - LOG_BTS(bts, DPAG, LOGL_DEBUG, "Paging request: T3113 expires in %u seconds\n", to); + estimated_to = (to_us + 999999) / 1000000 + d->val; + + /* upper bound: see X3113, PAGING_THRESHOLD_X3113_DEFAULT_SEC */ + if (estimated_to > max_dynamic_value) + to = max_dynamic_value; + else + to = estimated_to; + + LOG_PAGING_BTS(req, bts, DPAG, LOGL_DEBUG, + "Paging request: T3113 expires in %u seconds (estimated %u)\n", + to, estimated_to); +ret: + osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_T3113), to); return to; } @@ -330,29 +505,83 @@ static int _paging_request(const struct bsc_paging_params *params, struct gsm_bt struct gsm_bts_paging_state *bts_entry = &bts->paging; struct gsm_paging_request *req; unsigned int t3113_timeout_s; + unsigned int x3113_s = osmo_tdef_get(bts->network->T_defs, -3113, OSMO_TDEF_S, -1); + uint8_t pgroup; + unsigned int reqs_before, reqs_before_same_pgroup; - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ATTEMPTED]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_ATTEMPTED)); - if (paging_pending_request(bts_entry, params->bsub)) { + /* Find if we already have one for the given subscriber on this BTS: */ + if (bsc_subscr_find_req_by_bts(params->bsub, bts)) { LOG_PAGING_BTS(params, bts, DPAG, LOGL_INFO, "Paging request already pending for this subscriber\n"); - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ALREADY]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_ALREADY)); return -EEXIST; } + /* Don't try to queue more requests than we can realistically handle within X3113 seconds, + * see PAGING_THRESHOLD_X3113_DEFAULT_SEC. */ + if (paging_pending_requests_nr(bts) > paging_estimate_available_slots(bts, x3113_s)) { + struct gsm_paging_request *first_retrans_req; + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_OVERLOAD)); + /* Need to drop a retrans from the queue if possible, in order to make space for the new initial req. */ + if (bts_entry->retrans_req_list_len == 0) { + /* There are no retrans to be replaced by this initial request, discard it. */ + return -ENOSPC; + } + first_retrans_req = llist_first_entry(&bts_entry->retrans_req_list, struct gsm_paging_request, entry); + paging_remove_request(first_retrans_req); + } + + pgroup = gsm0502_calc_paging_group(&bts->si_common.chan_desc, str_to_imsi(params->bsub->imsi)); + reqs_before = bts_entry->initial_req_list_len; + reqs_before_same_pgroup = bts_entry->initial_req_pgroup_counts[pgroup]; + LOG_PAGING_BTS(params, bts, DPAG, LOGL_DEBUG, "Start paging\n"); req = talloc_zero(tall_paging_ctx, struct gsm_paging_request); OSMO_ASSERT(req); req->reason = params->reason; req->bsub = params->bsub; - bsc_subscr_get(req->bsub, BSUB_USE_PAGING_REQUEST); req->bts = bts; req->chan_type = params->chan_needed; + req->pgroup = pgroup; req->msc = params->msc; osmo_timer_setup(&req->T3113, paging_T3113_expired, req); - t3113_timeout_s = calculate_timer_3113(bts); + bsc_subscr_add_active_paging_request(req->bsub, req); + + bts_entry->initial_req_list_len++; + bts_entry->initial_req_pgroup_counts[req->pgroup]++; + osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_REQ_QUEUE_LENGTH), 1); + llist_add_tail(&req->entry, &bts_entry->initial_req_list); + + t3113_timeout_s = calculate_timer_3113(req, reqs_before, reqs_before_same_pgroup, x3113_s); osmo_timer_schedule(&req->T3113, t3113_timeout_s, 0); - llist_add_tail(&req->entry, &bts_entry->pending_requests); - paging_schedule_if_needed(bts_entry); + + /* Trigger scheduler if needed: */ + if (!osmo_timer_pending(&bts_entry->work_timer)) { + paging_handle_pending_requests(bts_entry); + } else if (bts_entry->initial_req_list_len == 1) { + /* Worker timer is armed -> there was already one req before + * bts_entry->initial_req_list_len == 1 -> There were no initial requests + * in the list, aka the timer is waiting for retransmission, + * which is a longer period. + * Let's recaculate the time to adapt it to initial_period: */ + struct timespec now, elapsed, tdiff; + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &bts_entry->last_sched_ts, &elapsed); + if (timespeccmp(&elapsed, &initial_period, <)) { + timespecsub(&initial_period, &elapsed, &tdiff); + } else { + tdiff = (struct timespec){.tv_sec = 0, .tv_nsec = 0 }; + } + LOG_PAGING_BTS(req, req->bts, DPAG, LOGL_DEBUG, + "New req arrived: re-scheduling next batch in %lld.%06lds\n", + (long long)tdiff.tv_sec, tdiff.tv_nsec / 1000); + /* Avoid scheduling timer for short periods, run cb directly: */ + if (tdiff.tv_sec == 0 && tdiff.tv_nsec < 5000) + paging_worker(bts_entry); + else + osmo_timer_schedule(&bts_entry->work_timer, tdiff.tv_sec, tdiff.tv_nsec / 1000); + } /* else: worker is already ongoing submitting initial requests, nothing do be done */ return 0; } @@ -371,9 +600,6 @@ int paging_request_bts(const struct bsc_paging_params *params, struct gsm_bts *b if (!trx_is_usable(bts->c0)) return 0; - /* maybe it is the first time we use it */ - paging_init_if_needed(bts); - /* Trigger paging, pass any error to the caller */ rc = _paging_request(params, bts); if (rc < 0) @@ -381,128 +607,100 @@ int paging_request_bts(const struct bsc_paging_params *params, struct gsm_bts *b return 1; } -/*! Stop paging a given subscriber on a given BTS. - * \param[out] returns the MSC that paged the subscriber, if any. - * \param[out] returns the reason for a pending paging, if any. - * \param[in] bts BTS which has received a paging response. - * \param[in] bsub subscriber. - * \returns number of pending pagings. - */ -static int paging_request_stop_bts(struct bsc_msc_data **msc_p, enum bsc_paging_reason *reason_p, - struct gsm_bts *bts, struct bsc_subscr *bsub) -{ - struct gsm_bts_paging_state *bts_entry = &bts->paging; - struct gsm_paging_request *req, *req2; - - *msc_p = NULL; - *reason_p = BSC_PAGING_NONE; - - paging_init_if_needed(bts); - - llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests, - entry) { - if (req->bsub != bsub) - continue; - *msc_p = req->msc; - *reason_p = req->reason; - LOG_BTS(bts, DPAG, LOGL_DEBUG, "Stop paging %s\n", bsc_subscr_name(bsub)); - paging_remove_request(&bts->paging, req); - return 1; - } - - return 0; -} - /*! Stop paging on all cells and return the MSC that paged (if any) and all pending paging reasons. * \param[out] returns the MSC that paged the subscriber, if there was a pending request. * \param[out] returns the ORed bitmask of all reasons of pending pagings. * \param[in] bts BTS which has received a paging response * \param[in] bsub subscriber - * \returns number of pending pagings. */ -int paging_request_stop(struct bsc_msc_data **msc_p, enum bsc_paging_reason *reasons_p, +void paging_request_stop(struct bsc_msc_data **msc_p, enum bsc_paging_reason *reasons_p, struct gsm_bts *bts, struct bsc_subscr *bsub) { - struct gsm_bts *bts_i; - struct bsc_msc_data *paged_from_msc; - int count; - enum bsc_paging_reason reasons; + struct bsc_msc_data *paged_from_msc = NULL; + enum bsc_paging_reason reasons = BSC_PAGING_NONE; OSMO_ASSERT(bts); - - count = paging_request_stop_bts(&paged_from_msc, &reasons, bts, bsub); - if (paged_from_msc) { - count++; - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_RESPONDED]); - rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_RESPONDED]); + struct gsm_paging_request *req = bsc_subscr_find_req_by_bts(bsub, bts); + + /* Avoid accessing bsub after reaching 0 active_paging_request_len, + * since it could be freed during put(): */ + unsigned remaining = bsub->active_paging_requests_len; + + if (req) { + paged_from_msc = req->msc; + reasons = req->reason; + LOG_PAGING_BTS(req, bts, DPAG, LOGL_DEBUG, "Stop paging\n"); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_RESPONDED)); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->network->bsc_ctrs, BSC_CTR_PAGING_RESPONDED)); + paging_remove_request(req); + remaining--; } - llist_for_each_entry(bts_i, &bsc_gsmnet->bts_list, list) { - struct bsc_msc_data *paged_from_msc2; - enum bsc_paging_reason reason2; - count += paging_request_stop_bts(&paged_from_msc2, &reason2, bts_i, bsub); - if (paged_from_msc2) { - reasons |= reason2; - if (!paged_from_msc) { - /* If this happened, it would be a bit weird: it means there was no Paging Request - * pending on the BTS that sent the Paging Reponse, but there *is* a Paging Request - * pending on a different BTS. But why not return an MSC when we found one. */ - paged_from_msc = paged_from_msc2; - } + while (remaining > 0) { + struct gsm_paging_request *req; + req = llist_first_entry(&bsub->active_paging_requests, + struct gsm_paging_request, bsub_entry); + LOG_PAGING_BTS(req, req->bts, DPAG, LOGL_DEBUG, "Stop paging\n"); + reasons |= req->reason; + if (!paged_from_msc) { + /* If this happened, it would be a bit weird: it means there was no Paging Request + * pending on the BTS that sent the Paging Response, but there *is* a Paging Request + * pending on a different BTS. But why not return an MSC when we found one. */ + paged_from_msc = req->msc; } + paging_remove_request(req); + remaining--; } *msc_p = paged_from_msc; *reasons_p = reasons; - - return count; } /* Remove all paging requests, for specific reasons only. */ -int paging_request_cancel(struct bsc_subscr *bsub, enum bsc_paging_reason reasons) +void paging_request_cancel(struct bsc_subscr *bsub, enum bsc_paging_reason reasons) { - struct gsm_bts *bts; - int count = 0; - - llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { - struct gsm_paging_request *req, *req2; + struct gsm_paging_request *req, *req2; + OSMO_ASSERT(bsub); - paging_init_if_needed(bts); + /* Avoid accessing bsub after reaching 0 active_paging_request_len, + * since it could be freed during put(): */ + unsigned remaining = bsub->active_paging_requests_len; - llist_for_each_entry_safe(req, req2, &bts->paging.pending_requests, entry) { - if (req->bsub != bsub) - continue; - if (!(req->reason & reasons)) - continue; - LOG_BTS(bts, DPAG, LOGL_DEBUG, "Cancel paging %s\n", bsc_subscr_name(bsub)); - paging_remove_request(&bts->paging, req); - count++; + llist_for_each_entry_safe(req, req2, &bsub->active_paging_requests, bsub_entry) { + if (!(req->reason & reasons)) + continue; + LOG_PAGING_BTS(req, req->bts, DPAG, LOGL_DEBUG, "Cancel paging reasons=0x%x\n", + reasons); + if (req->reason & ~reasons) { + /* Other reasons are active, simply drop the reasons from func arg: */ + req->reason &= ~reasons; + continue; } + /* No reason to keep the paging, remove it: */ + paging_remove_request(req); + remaining--; + if (remaining == 0) + break; } - return count; } /*! Update the BTS paging buffer slots on given BTS */ -void paging_update_buffer_space(struct gsm_bts *bts, uint16_t free_slots) +static void paging_update_buffer_space(struct gsm_bts *bts, uint16_t free_slots) { - paging_init_if_needed(bts); - - osmo_timer_del(&bts->paging.credit_timer); - bts->paging.available_slots = free_slots; - paging_schedule_if_needed(&bts->paging); + LOG_BTS(bts, DPAG, LOGL_DEBUG, "Rx CCCH Load Indication from BTS (available_slots %u -> %u)\n", + bts->paging.available_slots, free_slots); + paging_set_available_slots(bts, free_slots); + /* Re-arm credit_timer if needed */ + if (trx_is_usable(bts->c0)) { + paging_schedule_if_needed(&bts->paging); + osmo_timer_schedule(&bts->paging.credit_timer, + bts_no_ccch_load_ind_timeout_sec(bts), 0); + } } /*! Count the number of pending paging requests on given BTS */ -unsigned int paging_pending_requests_nr(struct gsm_bts *bts) +unsigned int paging_pending_requests_nr(const struct gsm_bts *bts) { - unsigned int requests = 0; - struct gsm_paging_request *req; - - paging_init_if_needed(bts); - - llist_for_each_entry(req, &bts->paging.pending_requests, entry) - ++requests; - - return requests; + return bts->paging.initial_req_list_len + bts->paging.retrans_req_list_len; } /*! Flush all paging requests at a given BTS for a given MSC (or NULL if all MSC should be flushed). */ @@ -510,19 +708,22 @@ void paging_flush_bts(struct gsm_bts *bts, struct bsc_msc_data *msc) { struct gsm_paging_request *req, *req2; int num_cancelled = 0; + int i; - paging_init_if_needed(bts); + struct llist_head *lists[] = { &bts->paging.initial_req_list, &bts->paging.retrans_req_list }; - llist_for_each_entry_safe(req, req2, &bts->paging.pending_requests, entry) { - if (msc && req->msc != msc) - continue; - /* now give up the data structure */ - LOG_BTS(bts, DPAG, LOGL_DEBUG, "Stop paging %s (flush)\n", bsc_subscr_name(req->bsub)); - paging_remove_request(&bts->paging, req); - num_cancelled++; + for (i = 0; i < ARRAY_SIZE(lists); i++) { + llist_for_each_entry_safe(req, req2, lists[i], entry) { + if (msc && req->msc != msc) + continue; + /* now give up the data structure */ + LOG_PAGING_BTS(req, bts, DPAG, LOGL_DEBUG, "Stop paging (flush)\n"); + paging_remove_request(req); + num_cancelled++; + } } - rate_ctr_add(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_MSC_FLUSH], num_cancelled); + rate_ctr_add(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_MSC_FLUSH), num_cancelled); } /*! Flush all paging requests issued by \a msc on any BTS in \a net */ @@ -533,3 +734,131 @@ void paging_flush_network(struct gsm_network *net, struct bsc_msc_data *msc) llist_for_each_entry(bts, &net->bts_list, list) paging_flush_bts(bts, msc); } + +/* Shim to avoid problems when compiling against libosmocore <= 1.7.0, since + * gsm0502_get_n_pag_blocks() was not declared const despite being readonly. Once + * osmo-bsc depends on libosmocore > 1.7.0, this shim can be dropped. */ +static inline unsigned int _gsm0502_get_n_pag_blocks(const struct gsm48_control_channel_descr *chan_desc) +{ + return gsm0502_get_n_pag_blocks((struct gsm48_control_channel_descr *)chan_desc); +} + +/*! Estimate available_slots credit over a time period, used when below CCCH Load Indication Threshold */ +uint16_t paging_estimate_available_slots(const struct gsm_bts *bts, unsigned int time_span_s) +{ + unsigned int n_pag_blocks = _gsm0502_get_n_pag_blocks(&bts->si_common.chan_desc); + uint16_t available_slots = n_pag_blocks * time_span_s * 1000000 / GSM51_MFRAME_DURATION_us; + LOG_BTS(bts, DPAG, LOGL_DEBUG, "Estimated %u paging available_slots over %u seconds\n", + available_slots, time_span_s); + return available_slots; +} + +/*! Conservative estimate of time needed by BTS to schedule a number of paging + * requests (num_reqs), based on current load at the BSC queue (doesn't take into + * account BTs own buffer) */ +static unsigned int paging_estimate_delay_us(struct gsm_bts *bts, unsigned int num_reqs, + unsigned int num_reqs_same_pgroup) +{ + unsigned int n_pag_blocks, n_mframes, time_us = 0; + + n_pag_blocks = _gsm0502_get_n_pag_blocks(&bts->si_common.chan_desc); + + /* First of all, we need to extend the timeout in relation to the amount + * of paging requests in the BSC queue. In here we don't care about the + * paging group, because they are mixed in the same queue. If we don't + * take this into account, it could happen that if lots of requests are + * received at the BSC (from MSC) around the same time, they could time + * out in the BSC queue before arriving at the BTS. We already account of + * same-paging-group ones further below, so don't take them into account + * here: */ + unsigned int num_reqs_other_groups = num_reqs - num_reqs_same_pgroup; + time_us += ((num_reqs_other_groups * GSM51_MFRAME_DURATION_us) + (n_pag_blocks - 1)) / n_pag_blocks; + + /* Now we extend the timeout based on the amount of requests of the same + * paging group before the present one: */ + n_mframes = (num_reqs_same_pgroup + (n_pag_blocks - 1)) / n_pag_blocks; + time_us += n_mframes * GSM51_MFRAME_DURATION_us; + /* the multiframes are not consecutive for a paging group, let's add the spacing between: */ + if (n_mframes > 1) { + unsigned int bs_pa_mfrms = (bts->si_common.chan_desc.bs_pa_mfrms + 2); + time_us += (n_mframes - 1) * bs_pa_mfrms * GSM51_MFRAME_DURATION_us; + } + return time_us; +} + +/* Callback function to be called every time we receive a signal from NM */ +static int nm_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct nm_running_chg_signal_data *nsd; + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + unsigned int load_ind_timeout; + uint16_t estimated_slots; + + if (signal != S_NM_RUNNING_CHG) + return 0; + + nsd = signal_data; + bts = nsd->bts; + + switch (nsd->obj_class) { + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *)nsd->obj; + break; + case NM_OC_BASEB_TRANSC: + trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj); + break; + default: + return 0; + } + + /* We only care about state changes of C0. */ + if (trx != trx->bts->c0) + return 0; + + if (nsd->running) { + if (trx_is_usable(trx)) { + LOG_BTS(bts, DPAG, LOGL_INFO, "C0 becomes available for paging\n"); + /* Fill in initial credit */ + load_ind_timeout = bts_no_ccch_load_ind_timeout_sec(bts); + estimated_slots = paging_estimate_available_slots(bts, load_ind_timeout); + paging_set_available_slots(bts, estimated_slots); + /* Start scheduling credit_timer */ + osmo_timer_schedule(&bts->paging.credit_timer, + bts_no_ccch_load_ind_timeout_sec(bts), 0); + /* work_timer will be started when new paging requests arrive. */ + } + } else { + /* If credit timer was not pending it means C0 was already unavailable before (rcarrier||bbtransc) */ + if (osmo_timer_pending(&bts->paging.credit_timer)) { + LOG_BTS(bts, DPAG, LOGL_INFO, "C0 becomes unavailable for paging\n"); + /* Note: flushing will osmo_timer_del(&bts->paging.work_timer) when queue becomes empty */ + paging_flush_bts(bts, NULL); + osmo_timer_del(&bts->paging.credit_timer); + } + } + return 0; +} + +/* Callback function to be called every time we receive a signal from CCCH */ +static int ccch_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct ccch_signal_data *sd; + + if (signal != S_CCCH_PAGING_LOAD) + return 0; + + sd = signal_data; + + paging_update_buffer_space(sd->bts, sd->pg_buf_space); + return 0; +} + +/* To be called once at startup of the process: */ +void paging_global_init(void) +{ + osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL); + osmo_signal_register_handler(SS_CCCH, ccch_sig_cb, NULL); +} diff --git a/src/osmo-bsc/pcu_sock.c b/src/osmo-bsc/pcu_sock.c index d59df33bb..7b1aeae68 100644 --- a/src/osmo-bsc/pcu_sock.c +++ b/src/osmo-bsc/pcu_sock.c @@ -15,10 +15,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdio.h> @@ -37,6 +33,7 @@ #include <osmocom/core/logging.h> #include <osmocom/gsm/l1sap.h> #include <osmocom/gsm/gsm0502.h> +#include <osmocom/bsc/abis_nm.h> #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/pcu_if.h> @@ -46,30 +43,28 @@ #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/gsm_04_08_rr.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/bts_sm.h> +#include <osmocom/bsc/timeslot_fsm.h> -static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg); -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); -int pcu_direct = 1; +static int pcu_sock_send(struct gsm_network *net, struct msgb *msg); static const char *sapi_string[] = { [PCU_IF_SAPI_RACH] = "RACH", - [PCU_IF_SAPI_AGCH] = "AGCH", - [PCU_IF_SAPI_PCH] = "PCH", [PCU_IF_SAPI_BCCH] = "BCCH", [PCU_IF_SAPI_PDTCH] = "PDTCH", [PCU_IF_SAPI_PRACH] = "PRACH", - [PCU_IF_SAPI_PTCCH] = "PTCCH", - [PCU_IF_SAPI_AGCH_DT] = "AGCH_DT", + [PCU_IF_SAPI_PTCCH] = "PTCCH", + [PCU_IF_SAPI_PCH_2] = "PCH_2", + [PCU_IF_SAPI_AGCH_2] = "AGCH_2", }; -/* Check if BTS has a PCU connection */ -static bool pcu_connected(struct gsm_bts *bts) +bool pcu_connected(const struct gsm_network *net) { - struct pcu_sock_state *state = bts->pcu_state; + struct pcu_sock_state *state = net->pcu_state; if (!state) return false; - if (state->conn_bfd.fd <= 0) + if (state->upqueue.bfd.fd <= 0) return false; return true; } @@ -96,6 +91,33 @@ struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr) return msg; } +/* Check if the timeslot can be utilized as PDCH now + * (PDCH is currently active on BTS) */ +static bool ts_now_usable_as_pdch(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan_is) { + case GSM_PCHAN_PDCH: + /* NOTE: We currently only support Ericsson RBS as a BSC + * co-located BTS. This BTS only supports dynamic channels. */ + return true; + default: + return false; + } +} + +/* Check if it is possible to use the TS as PDCH (not now, but maybe later) */ +static bool ts_usable_as_pdch(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan_from_config) { + case GSM_PCHAN_TCH_F_PDCH: + case GSM_PCHAN_OSMO_DYN: + case GSM_PCHAN_PDCH: + return true; + default: + return false; + } +} + /* Fill the frequency hopping parameter */ static void info_ind_fill_fhp(struct gsm_pcu_if_info_trx_ts *ts_info, const struct gsm_bts_trx_ts *ts) @@ -108,6 +130,48 @@ static void info_ind_fill_fhp(struct gsm_pcu_if_info_trx_ts *ts_info, ts_info->ma_bit_len = ts->hopping.ma_len * 8 - ts->hopping.ma.cur_bit; } +/* Fill the TRX parameter */ +static void info_ind_fill_trx(struct gsm_pcu_if_info_trx *trx_info, const struct gsm_bts_trx *trx) +{ + unsigned int tn; + const struct gsm_bts_trx_ts *ts; + + trx_info->hlayer1 = 0x2342; + trx_info->pdch_mask = 0; + trx_info->arfcn = trx->arfcn; + + if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED || + trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) { + LOG_TRX(trx, DPCU, LOGL_INFO, "unavailable for PCU (op=%s adm=%s)\n", + abis_nm_opstate_name(trx->mo.nm_state.operational), + abis_nm_admin_name(trx->mo.nm_state.administrative)); + return; + } + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + ts = &trx->ts[tn]; + if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + if (!ts_now_usable_as_pdch(ts)) + continue; + + trx_info->pdch_mask |= (1 << tn); + trx_info->ts[tn].tsc = + (ts->tsc >= 0) ? ts->tsc : trx->bts->bsic & 7; + + if (ts->hopping.enabled) + info_ind_fill_fhp(&trx_info->ts[tn], ts); + + LOG_TRX(trx, DPCU, LOGL_INFO, "PDCH on ts=%u is available (tsc=%u ", ts->nr, + trx_info->ts[tn].tsc); + if (ts->hopping.enabled) + LOGPC(DPCU, LOGL_INFO, "hopping=yes hsn=%u maio=%u ma_bit_len=%u)\n", + ts->hopping.hsn, ts->hopping.maio, trx_info->ts[tn].ma_bit_len); + else + LOGPC(DPCU, LOGL_INFO, "hopping=no arfcn=%u)\n", trx->arfcn); + } +} + /* Send BTS properties to the PCU */ static int pcu_tx_info_ind(struct gsm_bts *bts) { @@ -115,15 +179,14 @@ static int pcu_tx_info_ind(struct gsm_bts *bts) struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_info_ind *info_ind; struct gprs_rlc_cfg *rlcc; - struct gsm_bts_gprs_nsvc *nsvc; + struct gsm_bts_sm *bts_sm; + struct gsm_gprs_nsvc *nsvc; struct gsm_bts_trx *trx; - struct gsm_bts_trx_ts *ts; - int i, tn; + int i; - OSMO_ASSERT(bts); - OSMO_ASSERT(bts->network); + bts_sm = bts->site_mgr; - LOGP(DPCU, LOGL_INFO, "Sending info for BTS %d\n",bts->nr); + LOG_BTS(bts, DPCU, LOGL_INFO, "Sending info for BTS\n"); rlcc = &bts->gprs.cell.rlc_cfg; @@ -135,9 +198,7 @@ static int pcu_tx_info_ind(struct gsm_bts *bts) info_ind = &pcu_prim->u.info_ind; info_ind->version = PCU_IF_VERSION; info_ind->flags |= PCU_IF_FLAG_ACTIVE; - - if (pcu_direct) - info_ind->flags |= PCU_IF_FLAG_SYSMO; + info_ind->flags |= PCU_IF_FLAG_DIRECT_PHY; /* RAI */ info_ind->mcc = bts->network->plmn.mcc; @@ -147,11 +208,12 @@ static int pcu_tx_info_ind(struct gsm_bts *bts) info_ind->rac = bts->gprs.rac; /* NSE */ - info_ind->nsei = bts->gprs.nse.nsei; - memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7); + info_ind->nsei = bts_sm->gprs.nse.nsei; + memcpy(info_ind->nse_timer, bts_sm->gprs.nse.timer, 7); memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11); /* cell attributes */ + info_ind->bsic = bts->bsic; info_ind->cell_id = bts->cell_identity; info_ind->repeat_time = rlcc->paging.repeat_time; info_ind->repeat_count = rlcc->paging.repeat_count; @@ -193,16 +255,16 @@ static int pcu_tx_info_ind(struct gsm_bts *bts) if (rlcc->cs_mask & (1 << GPRS_MCS9)) info_ind->flags |= PCU_IF_FLAG_MCS9; } -#warning "isn't dl_tbf_ext wrong?: * 10 and no ntohs" + /* TODO: isn't dl_tbf_ext wrong?: * 10 and no ntohs */ info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT]; -#warning "isn't ul_tbf_ext wrong?: * 10 and no ntohs" + /* TODO: isn't ul_tbf_ext wrong?: * 10 and no ntohs */ info_ind->ul_tbf_ext = rlcc->parameter[T_UL_TBF_EXT]; info_ind->initial_cs = rlcc->initial_cs; info_ind->initial_mcs = rlcc->initial_mcs; /* NSVC */ for (i = 0; i < ARRAY_SIZE(info_ind->nsvci); i++) { - nsvc = &bts->gprs.nsvc[i]; + nsvc = &bts->site_mgr->gprs.nsvc[i]; info_ind->nsvci[i] = nsvc->nsvci; info_ind->local_port[i] = nsvc->local_port; @@ -210,14 +272,14 @@ static int pcu_tx_info_ind(struct gsm_bts *bts) case AF_INET: info_ind->address_type[i] = PCU_IF_ADDR_TYPE_IPV4; info_ind->remote_ip[i].v4 = nsvc->remote.u.sin.sin_addr; - info_ind->remote_port[i] = nsvc->remote.u.sin.sin_port; + info_ind->remote_port[i] = ntohs(nsvc->remote.u.sin.sin_port); break; case AF_INET6: info_ind->address_type[i] = PCU_IF_ADDR_TYPE_IPV6; memcpy(&info_ind->remote_ip[i].v6, &nsvc->remote.u.sin6.sin6_addr, sizeof(struct in6_addr)); - info_ind->remote_port[i] = nsvc->remote.u.sin6.sin6_port; + info_ind->remote_port[i] = ntohs(nsvc->remote.u.sin6.sin6_port); break; default: info_ind->address_type[i] = PCU_IF_ADDR_TYPE_UNSPEC; @@ -229,39 +291,122 @@ static int pcu_tx_info_ind(struct gsm_bts *bts) trx = gsm_bts_trx_num(bts, i); if (!trx) continue; - info_ind->trx[i].hlayer1 = 0x2342; - info_ind->trx[i].pdch_mask = 0; - info_ind->trx[i].arfcn = trx->arfcn; - for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { - ts = &trx->ts[tn]; - if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED || - ts->pchan_is != GSM_PCHAN_PDCH) - continue; + if (trx->nr >= ARRAY_SIZE(info_ind->trx)) { + LOG_TRX(trx, DPCU, LOGL_NOTICE, "PCU interface (version %u) " + "cannot handle more than %zu transceivers => skipped\n", + PCU_IF_VERSION, ARRAY_SIZE(info_ind->trx)); + break; + } + info_ind_fill_trx(&info_ind->trx[trx->nr], trx); + } - info_ind->trx[i].pdch_mask |= (1 << tn); - info_ind->trx[i].ts[tn].tsc = - (ts->tsc >= 0) ? ts->tsc : bts->bsic & 7; + switch (bts->type) { + case GSM_BTS_TYPE_RBS2000: + info_ind->bts_model = PCU_IF_BTS_MODEL_RBS; + break; + default: + info_ind->bts_model = PCU_IF_BTS_MODEL_UNSPEC; + } - if (ts->hopping.enabled) - info_ind_fill_fhp(&info_ind->trx[i].ts[tn], ts); + return pcu_sock_send(bts->network, msg); +} + +static int pcu_tx_e1_ccu_ind(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct gsm_pcu_if_e1_ccu_ind *e1_ccu_ind; + int i; + + if (trx->nr >= PCU_IF_NUM_TRX) { + LOG_TRX(trx, DPCU, LOGL_NOTICE, "PCU interface (version %u) " + "cannot handle more than %u transceivers => skipped\n", + PCU_IF_VERSION, PCU_IF_NUM_TRX); + continue; + } + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_pcu_if *pcu_prim; + struct gsm_bts_trx_ts *ts; + struct msgb *msg; + int rc; + + ts = &trx->ts[i]; + + if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + if (!ts_usable_as_pdch(ts)) + continue; - LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: PDCH is available " - "(tsc=%u ", trx->nr, ts->nr, info_ind->trx[i].ts[tn].tsc); - if (ts->hopping.enabled) - LOGPC(DPCU, LOGL_INFO, "hopping=yes hsn=%u maio=%u ma_bit_len=%u)\n", - ts->hopping.hsn, ts->hopping.maio, trx->ts[tn].hopping.ma.data_len - trx->ts[tn].hopping.ma.cur_bit); - else - LOGPC(DPCU, LOGL_INFO, "hopping=no arfcn=%u)\n", trx->arfcn); + msg = pcu_msgb_alloc(PCU_IF_MSG_E1_CCU_IND, bts->nr); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *)msg->data; + e1_ccu_ind = &pcu_prim->u.e1_ccu_ind; + e1_ccu_ind->ts_nr = ts->nr; + e1_ccu_ind->trx_nr = trx->nr; + e1_ccu_ind->e1_nr = ts->e1_link.e1_nr; + e1_ccu_ind->e1_ts = ts->e1_link.e1_ts; + e1_ccu_ind->e1_ts_ss = ts->e1_link.e1_ts_ss; + + LOG_TRX(trx, DPCU, LOGL_INFO, "Sending E1 CCU info for TS %d\n", e1_ccu_ind->ts_nr); + rc = pcu_sock_send(bts->network, msg); + if (rc < 0) + return -EINVAL; } } - return pcu_sock_send(bts, msg); + return 0; } -void pcu_info_update(struct gsm_bts *bts) +/* Allow test to overwrite it */ +__attribute__((weak)) void pcu_info_update(struct gsm_bts *bts) { - if (pcu_connected(bts)) - pcu_tx_info_ind(bts); + if (pcu_connected(bts->network)) { + if (bsc_co_located_pcu(bts)) { + /* In cases where the CCU is connected via an E1 line, we transmit the connection parameters for the + * PDCH before we announce the other BTS related parameters. */ + if (is_e1_bts(bts)) + pcu_tx_e1_ccu_ind(bts); + pcu_tx_info_ind(bts); + } + } +} + +static int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn, + uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len, + int8_t rssi, uint16_t ber10k, int16_t bto, int16_t lqual) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_data *data_ind; + struct gsm_bts *bts = ts->trx->bts; + + LOGP(DPCU, LOGL_DEBUG, "Sending data indication: sapi=%s arfcn=%d block=%d data=%s\n", + sapi_string[sapi], arfcn, block_nr, osmo_hexdump(data, len)); + + msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_IND, bts->nr); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + data_ind = &pcu_prim->u.data_ind; + + data_ind->sapi = sapi; + data_ind->rssi = rssi; + data_ind->fn = fn; + data_ind->arfcn = arfcn; + data_ind->trx_nr = ts->trx->nr; + data_ind->ts_nr = ts->nr; + data_ind->block_nr = block_nr; + data_ind->ber10k = ber10k; + data_ind->ta_offs_qbits = bto; + data_ind->lqual_cb = lqual; + if (len) + memcpy(data_ind->data, data, len); + data_ind->len = len; + + return pcu_sock_send(bts->network, msg); } /* Forward rach indication to PCU */ @@ -273,13 +418,12 @@ int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn, struct gsm_pcu_if_rach_ind *rach_ind; /* Bail if no PCU is connected */ - if (!pcu_connected(bts)) { - LOGP(DRSL, LOGL_ERROR, "BTS %d CHAN RQD(GPRS) but PCU not " - "connected!\n", bts->nr); + if (!pcu_connected(bts->network)) { + LOG_BTS(bts, DRSL, LOGL_ERROR, "CHAN RQD(GPRS) but PCU not connected!\n"); return -ENODEV; } - LOGP(DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, " + LOG_BTS(bts, DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, " "fn=%d\n", qta, ra, fn); msg = pcu_msgb_alloc(PCU_IF_MSG_RACH_IND, bts->nr); @@ -295,37 +439,37 @@ int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn, rach_ind->is_11bit = is_11bit; rach_ind->burst_type = burst_type; - return pcu_sock_send(bts, msg); + return pcu_sock_send(bts->network, msg); } -/* Confirm the sending of an immediate assignment to the pcu */ -int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli) +int pcu_tx_data_cnf(struct gsm_bts *bts, uint32_t msg_id, uint8_t sapi) { struct msgb *msg; struct gsm_pcu_if *pcu_prim; - struct gsm_pcu_if_data_cnf_dt *data_cnf_dt; + struct gsm_pcu_if_data_cnf *data_cnf; - LOGP(DPCU, LOGL_INFO, "Sending PCH confirm with direct TLLI\n"); + LOGP(DPCU, LOGL_DEBUG, "Sending DATA.cnf: sapi=%s msg_id=%08x\n", + sapi_string[sapi], msg_id); - msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_DT, bts->nr); + msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_2, bts->nr); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; - data_cnf_dt = &pcu_prim->u.data_cnf_dt; + data_cnf = &pcu_prim->u.data_cnf2; - data_cnf_dt->sapi = PCU_IF_SAPI_PCH; - data_cnf_dt->tlli = tlli; + data_cnf->sapi = sapi; + data_cnf->msg_id = msg_id; - return pcu_sock_send(bts, msg); + return pcu_sock_send(bts->network, msg); } /* we need to decode the raw RR paging message (see PCU code * Encoding::write_paging_request) and extract the mobile identity * (P-TMSI) from it */ -static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group, - const uint8_t *raw_rr_msg) +static int pcu_rx_rr_paging_pch(struct gsm_bts *bts, uint8_t paging_group, + const struct gsm_pcu_if_pch *pch) { - struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) raw_rr_msg; + struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) pch->data; uint8_t chan_needed; struct osmo_mobile_identity mi; int rc; @@ -335,14 +479,14 @@ static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group, chan_needed = (p1->cneed2 << 2) | p1->cneed1; rc = osmo_mobile_identity_decode(&mi, p1->data+1, p1->data[0], false); if (rc) { - LOGP(DPCU, LOGL_ERROR, "PCU Sends paging " - "request type %02x (chan_needed=%02x): Unable to decode Mobile Identity\n", + LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU Sends paging " + "request type %02x (chan_needed=%02x): Unable to decode Mobile Identity\n", p1->msg_type, chan_needed); rc = -EINVAL; break; } - LOGP(DPCU, LOGL_ERROR, "PCU Sends paging " - "request type %02x (chan_needed=%02x, mi=%s)\n", + LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU Sends paging " + "request type %02x (chan_needed=%02x, mi=%s)\n", p1->msg_type, chan_needed, osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); /* NOTE: We will have to add 2 to mi_len and subtract 2 from * the mi pointer because rsl_paging_cmd() will perform the @@ -354,12 +498,12 @@ static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group, break; case GSM48_MT_RR_PAG_REQ_2: case GSM48_MT_RR_PAG_REQ_3: - LOGP(DPCU, LOGL_ERROR, "PCU Sends unsupported paging " + LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU Sends unsupported paging " "request type %02x\n", p1->msg_type); rc = -EINVAL; break; default: - LOGP(DPCU, LOGL_ERROR, "PCU Sends unknown paging " + LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU Sends unknown paging " "request type %02x\n", p1->msg_type); rc = -EINVAL; break; @@ -368,77 +512,76 @@ static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group, return rc; } +static int pcu_rx_rr_imm_ass_pch(struct gsm_bts *bts, uint8_t paging_group, + const struct gsm_pcu_if_pch *pch, bool confirm) +{ + LOG_BTS(bts, DPCU, LOGL_DEBUG, "PCU Sends immediate assignment via PCH (msg_id=0x%08x, IMSI=%s, Paging group=0x%02x)\n", + pch->msg_id, pch->imsi, paging_group); + + /* NOTE: Sending an IMMEDIATE ASSIGNMENT via PCH became necessary with GPRS in order to be able to + * assign downlink TBFs directly through the paging channel. However, this method never became part + * of the RSL specs. This means that each BTS vendor has to come up with a proprietary method. At + * the moment we only support Ericsson RBS here. */ + if (is_ericsson_bts(bts)) + return rsl_ericsson_imm_assign_cmd(bts, pch->msg_id, sizeof(pch->data), pch->data, paging_group, + confirm); + + LOG_BTS(bts, DPCU, LOGL_ERROR, "BTS model does not support sending immediate assignment via PCH!\n"); + return -ENOTSUP; +} + static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type, struct gsm_pcu_if_data *data_req) { - struct msgb *msg; - char imsi_digit_buf[4]; - uint32_t tlli = -1; uint8_t pag_grp; int rc = 0; + const struct gsm_pcu_if_pch *pch; + const struct gsm_pcu_if_agch *agch; + const struct gsm48_imm_ass *gsm48_imm_ass; - LOGP(DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d " + LOG_BTS(bts, DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d " "block=%d data=%s\n", sapi_string[data_req->sapi], data_req->arfcn, data_req->block_nr, osmo_hexdump(data_req->data, data_req->len)); switch (data_req->sapi) { - case PCU_IF_SAPI_PCH: - /* the first three bytes are the last three digits of - * the IMSI, which we need to compute the paging group */ - imsi_digit_buf[0] = data_req->data[0]; - imsi_digit_buf[1] = data_req->data[1]; - imsi_digit_buf[2] = data_req->data[2]; - imsi_digit_buf[3] = '\0'; - LOGP(DPCU, LOGL_DEBUG, "SAPI PCH imsi %s\n", imsi_digit_buf); - pag_grp = gsm0502_calc_paging_group(&bts->si_common.chan_desc, - str_to_imsi(imsi_digit_buf)); - pcu_rx_rr_paging(bts, pag_grp, data_req->data+3); - break; - case PCU_IF_SAPI_AGCH: - msg = msgb_alloc(data_req->len, "pcu_agch"); - if (!msg) { - rc = -ENOMEM; + case PCU_IF_SAPI_AGCH_2: + if (data_req->len < sizeof(struct gsm_pcu_if_agch)) { + LOG_BTS(bts, DPCU, LOGL_ERROR, "Received PCU data request with invalid/small length %d\n", + data_req->len); break; } - msg->l3h = msgb_put(msg, data_req->len); - memcpy(msg->l3h, data_req->data, data_req->len); - if (rsl_imm_assign_cmd(bts, msg->len, msg->data)) { - msgb_free(msg); - rc = -EIO; - } - break; - case PCU_IF_SAPI_AGCH_DT: - /* DT = direct tlli. A tlli is prefixed */ + agch = (struct gsm_pcu_if_agch *)data_req->data; + if (rsl_imm_assign_cmd(bts, GSM_MACBLOCK_LEN, agch->data)) + return -EIO; - if (data_req->len < 5) { - LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " - "invalid/small length %d\n", data_req->len); + /* Send the confirmation immediately. This is as accurate as we can get since from this point on the + * BTS hardware is responsible to schedule the sending of the IMMEDIATE ASSIGNMENT */ + if (agch->confirm) + return pcu_tx_data_cnf(bts, agch->msg_id, PCU_IF_SAPI_AGCH_2); + break; + case PCU_IF_SAPI_PCH_2: + if (data_req->len < sizeof(struct gsm_pcu_if_pch)) { + LOG_BTS(bts, DPCU, LOGL_ERROR, "Received PCU data request with invalid/small length %d\n", + data_req->len); break; } - memcpy(&tlli, data_req->data, 4); - msg = msgb_alloc(data_req->len - 4, "pcu_agch"); - if (!msg) { - rc = -ENOMEM; - break; - } - msg->l3h = msgb_put(msg, data_req->len - 4); - memcpy(msg->l3h, data_req->data + 4, data_req->len - 4); + pch = (struct gsm_pcu_if_pch *)data_req->data; + pag_grp = gsm0502_calc_paging_group(&bts->si_common.chan_desc, str_to_imsi(pch->imsi)); + gsm48_imm_ass = (struct gsm48_imm_ass *)pch->data; - if (bts->type == GSM_BTS_TYPE_RBS2000) - rc = rsl_ericsson_imm_assign_cmd(bts, tlli, msg->len, msg->data); - else - rc = rsl_imm_assign_cmd(bts, msg->len, msg->data); + if (gsm48_imm_ass->msg_type == GSM48_MT_RR_IMM_ASS) + return pcu_rx_rr_imm_ass_pch(bts, pag_grp, pch, pch->confirm); - if (rc) { - msgb_free(msg); - rc = -EIO; - } + if (pcu_rx_rr_paging_pch(bts, pag_grp, pch)) + return -EIO; + if (pch->confirm) + return pcu_tx_data_cnf(bts, pch->msg_id, PCU_IF_SAPI_PCH_2); break; default: - LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " + LOG_BTS(bts, DPCU, LOGL_ERROR, "Received PCU data request with " "unsupported sapi %d\n", data_req->sapi); rc = -EINVAL; } @@ -446,20 +589,121 @@ static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type, return rc; } +static int pcu_tx_si(const struct gsm_bts *bts, enum osmo_sysinfo_type si_type, bool enable) +{ + /* the SI is per-BTS so it doesn't matter which TRX we use */ + struct gsm_bts_trx *trx = bts->c0; + + uint8_t si_buf[GSM_MACBLOCK_LEN]; + uint8_t len; + int rc; + + if (enable) { + memcpy(si_buf, GSM_BTS_SI(bts, si_type), GSM_MACBLOCK_LEN); + len = GSM_MACBLOCK_LEN; + LOG_BTS(bts, DPCU, LOGL_DEBUG, "Updating SI%s to PCU: %s\n", + get_value_string(osmo_sitype_strs, si_type), + osmo_hexdump_nospc(si_buf, GSM_MACBLOCK_LEN)); + } else { + si_buf[0] = si_type; + len = 1; + + /* Note: SI13 is the only system information type that is revked + * by just sending a completely empty message. This is due to + * historical reasons */ + if (si_type != SYSINFO_TYPE_13) + len = 0; + + LOG_BTS(bts, DPCU, LOGL_DEBUG, "Revoking SI%s from PCU\n", + get_value_string(osmo_sitype_strs, si_buf[0])); + } + + /* The low-level data like FN, ARFCN etc will be ignored but we have to + * set lqual high enough to bypass the check at lower levels */ + rc = pcu_tx_data_ind(&trx->ts[0], PCU_IF_SAPI_BCCH, 0, 0, 0, si_buf, len, + 0, 0, 0, INT16_MAX); + if (rc < 0) + LOG_BTS(bts, DPCU, LOGL_NOTICE, "Failed to send SI%s to PCU: rc=%d\n", + get_value_string(osmo_sitype_strs, si_type), rc); + + return rc; +} + +static int pcu_tx_si_all(struct gsm_bts *bts) +{ + const enum osmo_sysinfo_type si_types[] = { SYSINFO_TYPE_1, SYSINFO_TYPE_2, SYSINFO_TYPE_3, SYSINFO_TYPE_13 }; + unsigned int i; + int rc = 0; + + for (i = 0; i < ARRAY_SIZE(si_types); i++) { + if (GSM_BTS_HAS_SI(bts, si_types[i])) { + rc = pcu_tx_si(bts, si_types[i], true); + if (rc < 0) + return rc; + } else { + LOG_BTS(bts, DPCU, LOGL_INFO, + "SI%s is not available on PCU connection\n", + get_value_string(osmo_sitype_strs, si_types[i])); + } + } + + return 0; +} + +static int pcu_rx_txt_ind(struct gsm_bts *bts, + const struct gsm_pcu_if_txt_ind *txt) +{ + int rc; + + switch (txt->type) { + case PCU_VERSION: + LOG_BTS(bts, DPCU, LOGL_INFO, "OsmoPCU version %s connected\n", + txt->text); + rc = pcu_tx_si_all(bts); + if (rc < 0) + return -EINVAL; + break; + case PCU_OML_ALERT: + LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU external alarm: %s\n", txt->text); + break; + default: + LOG_BTS(bts, DPCU, LOGL_ERROR, "Unknown TXT_IND type %u received\n", + txt->type); + return -EINVAL; + } + + return 0; +} + +#define CHECK_IF_MSG_SIZE(prim_len, prim_msg) \ + do { \ + size_t _len = PCUIF_HDR_SIZE + sizeof(prim_msg); \ + if (prim_len < _len) { \ + LOGP(DPCU, LOGL_ERROR, "Received %zu bytes on PCU Socket, but primitive %s " \ + "size is %zu, discarding\n", prim_len, #prim_msg, _len); \ + return -EINVAL; \ + } \ + } while (0) static int pcu_rx(struct gsm_network *net, uint8_t msg_type, - struct gsm_pcu_if *pcu_prim) + struct gsm_pcu_if *pcu_prim, size_t prim_len) { int rc = 0; struct gsm_bts *bts; - /* FIXME: allow multiple BTS */ - bts = llist_entry(net->bts_list.next, struct gsm_bts, list); + bts = gsm_bts_num(net, pcu_prim->bts_nr); + if (!bts) + return -EINVAL; switch (msg_type) { case PCU_IF_MSG_DATA_REQ: case PCU_IF_MSG_PAG_REQ: + CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.data_req); rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req); break; + case PCU_IF_MSG_TXT_IND: + CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.txt_ind); + rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind); + break; default: LOGP(DPCU, LOGL_ERROR, "Received unknown PCU msg type %d\n", msg_type); @@ -469,15 +713,18 @@ static int pcu_rx(struct gsm_network *net, uint8_t msg_type, return rc; } +static void pcu_sock_close(struct pcu_sock_state *state); + /* * PCU socket interface */ -static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg) +static int pcu_sock_send(struct gsm_network *net, struct msgb *msg) { - struct pcu_sock_state *state = bts->pcu_state; + struct pcu_sock_state *state = net->pcu_state; struct osmo_fd *conn_bfd; struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data; + int rc; if (!state) { if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) @@ -486,7 +733,7 @@ static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg) msgb_free(msg); return -EINVAL; } - conn_bfd = &state->conn_bfd; + conn_bfd = &state->upqueue.bfd; if (conn_bfd->fd <= 0) { if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, " @@ -494,31 +741,25 @@ static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg) msgb_free(msg); return -EIO; } - msgb_enqueue(&state->upqueue, msg); - conn_bfd->when |= OSMO_FD_WRITE; + rc = osmo_wqueue_enqueue(&state->upqueue, msg); + if (rc < 0) { + if (rc == -ENOSPC) + LOGP(DPCU, LOGL_NOTICE, + "PCU not reacting (more than %u messages waiting). Closing connection\n", + state->upqueue.max_length); + pcu_sock_close(state); + msgb_free(msg); + return rc; + } + return 0; } -static void pcu_sock_close(struct pcu_sock_state *state) +static void pdch_deact_bts(struct gsm_bts *bts) { - struct osmo_fd *bfd = &state->conn_bfd; - struct gsm_bts *bts; struct gsm_bts_trx *trx; - struct gsm_bts_trx_ts *ts; - int i, j; - - /* FIXME: allow multiple BTS */ - bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list); - - LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n"); - - close(bfd->fd); - bfd->fd = -1; - osmo_fd_unregister(bfd); - - /* re-enable the generation of ACCEPT for new connections */ - state->listen_bfd.when |= OSMO_FD_READ; + int j; #if 0 /* remove si13, ... */ @@ -527,24 +768,41 @@ static void pcu_sock_close(struct pcu_sock_state *state) #endif /* release PDCH */ - for (i = 0; i < 8; i++) { - trx = gsm_bts_trx_num(bts, i); - if (!trx) - break; - for (j = 0; j < 8; j++) { - ts = &trx->ts[j]; + llist_for_each_entry(trx, &bts->trx_list, list) { + for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { + struct gsm_bts_trx_ts *ts = &trx->ts[j]; + /* BSC co-located PCU applies only to Ericsson RBS, which supports only GSM_PCHAN_OSMO_DYN. + * So we need to deact only on this pchan kind. */ if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED - && ts->pchan_is == GSM_PCHAN_PDCH) { - printf("l1sap_chan_rel(trx,gsm_lchan2chan_nr(ts->lchan));\n"); + && ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) { + ts_pdch_deact(ts); } } } +} - /* flush the queue */ - while (!llist_empty(&state->upqueue)) { - struct msgb *msg = msgb_dequeue(&state->upqueue); - msgb_free(msg); +static void pcu_sock_close(struct pcu_sock_state *state) +{ + struct osmo_fd *bfd = &state->upqueue.bfd; + struct gsm_bts *bts; + + LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n"); + + osmo_fd_unregister(bfd); + close(bfd->fd); + bfd->fd = -1; + + /* re-enable the generation of ACCEPT for new connections */ + osmo_fd_read_enable(&state->listen_bfd); + + /* Disable all PDCHs on all BTSs that are served by the PCU */ + llist_for_each_entry(bts, &state->net->bts_list, list) { + if (bsc_co_located_pcu(bts)) + pdch_deact_bts(bts); } + + /* flush the queue */ + osmo_wqueue_clear(&state->upqueue); } static int pcu_sock_read(struct osmo_fd *bfd) @@ -554,7 +812,7 @@ static int pcu_sock_read(struct osmo_fd *bfd) struct msgb *msg; int rc; - msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx"); + msg = msgb_alloc(sizeof(*pcu_prim) + 1000, "pcu_sock_rx"); if (!msg) return -ENOMEM; @@ -565,12 +823,21 @@ static int pcu_sock_read(struct osmo_fd *bfd) goto close; if (rc < 0) { - if (errno == EAGAIN) + if (errno == EAGAIN) { + msgb_free(msg); return 0; + } goto close; } - rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim); + if (rc < PCUIF_HDR_SIZE) { + LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive hdr size " + "is %zu, discarding\n", rc, PCUIF_HDR_SIZE); + msgb_free(msg); + return 0; + } + + rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim, rc); /* as we always synchronously process the message in pcu_rx() and * its callbacks, we can free the message here. */ @@ -584,45 +851,22 @@ close: return -1; } -static int pcu_sock_write(struct osmo_fd *bfd) +static int pcu_sock_write(struct osmo_fd *bfd, struct msgb *msg) { struct pcu_sock_state *state = bfd->data; int rc; - while (!llist_empty(&state->upqueue)) { - struct msgb *msg, *msg2; - struct gsm_pcu_if *pcu_prim; - - /* peek at the beginning of the queue */ - msg = llist_entry(state->upqueue.next, struct msgb, list); - pcu_prim = (struct gsm_pcu_if *)msg->data; - - bfd->when &= ~OSMO_FD_WRITE; - - /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ - if (!msgb_length(msg)) { - LOGP(DPCU, LOGL_ERROR, "message type (%d) with ZERO " - "bytes!\n", pcu_prim->msg_type); - goto dontsend; - } - - /* try to send it over the socket */ - rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); - if (rc == 0) - goto close; - if (rc < 0) { - if (errno == EAGAIN) { - bfd->when |= OSMO_FD_WRITE; - break; - } - goto close; - } + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + OSMO_ASSERT(msgb_length(msg) > 0); + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (OSMO_UNLIKELY(rc == 0)) + goto close; + if (OSMO_UNLIKELY(rc < 0)) { + if (errno == EAGAIN) + return -EAGAIN; + return -1; -dontsend: - /* _after_ we send it, we can deueue */ - msg2 = msgb_dequeue(&state->upqueue); - assert(msg == msg2); - msgb_free(msg); } return 0; @@ -632,51 +876,53 @@ close: return -1; } -static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags) +static void pdch_act_bts(struct gsm_bts *bts) { - int rc = 0; - - if (flags & OSMO_FD_READ) - rc = pcu_sock_read(bfd); - if (rc < 0) - return rc; - - if (flags & OSMO_FD_WRITE) - rc = pcu_sock_write(bfd); + struct gsm_bts_trx *trx; + int j; - return rc; + /* activate PDCH */ + llist_for_each_entry(trx, &bts->trx_list, list) { + for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { + struct gsm_bts_trx_ts *ts = &trx->ts[j]; + /* (See comment in pdch_deact_bts above) */ + if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED + && ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) { + ts_pdch_act(ts); + } + } + } } /* accept connection coming from PCU */ static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags) { struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data; - struct osmo_fd *conn_bfd = &state->conn_bfd; + struct osmo_fd *conn_bfd = &state->upqueue.bfd; struct sockaddr_un un_addr; + struct gsm_bts *bts; socklen_t len; - int rc; + int fd; len = sizeof(un_addr); - rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); - if (rc < 0) { + fd = accept(bfd->fd, (struct sockaddr *)&un_addr, &len); + if (fd < 0) { LOGP(DPCU, LOGL_ERROR, "Failed to accept a new connection\n"); return -1; } if (conn_bfd->fd >= 0) { - LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have " - "another active connection ?!?\n"); + LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have another active connection ?!?\n"); /* We already have one PCU connected, this is all we support */ - state->listen_bfd.when &= ~OSMO_FD_READ; - close(rc); + osmo_fd_read_disable(&state->listen_bfd); + close(fd); return 0; } - osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, pcu_sock_cb, state, 0); + osmo_fd_setup(conn_bfd, fd, OSMO_FD_READ, osmo_wqueue_bfd_cb, state, 0); if (osmo_fd_register(conn_bfd) != 0) { - LOGP(DPCU, LOGL_ERROR, "Failed to register new connection " - "fd\n"); + LOGP(DPCU, LOGL_ERROR, "Failed to register new connection fd\n"); close(conn_bfd->fd); conn_bfd->fd = -1; return -1; @@ -684,11 +930,17 @@ static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags) LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n"); + /* Activate all PDCHs on all BTSs that are served by the PCU */ + llist_for_each_entry(bts, &state->net->bts_list, list) { + if (bsc_co_located_pcu(bts)) + pdch_act_bts(bts); + } + return 0; } /* Open connection to PCU */ -int pcu_sock_init(const char *path, struct gsm_bts *bts) +int pcu_sock_init(struct gsm_network *net) { struct pcu_sock_state *state; struct osmo_fd *bfd; @@ -698,13 +950,15 @@ int pcu_sock_init(const char *path, struct gsm_bts *bts) if (!state) return -ENOMEM; - INIT_LLIST_HEAD(&state->upqueue); - state->net = bts->network; - state->conn_bfd.fd = -1; + osmo_wqueue_init(&state->upqueue, net->pcu_sock_wqueue_len_max); + state->upqueue.read_cb = pcu_sock_read; + state->upqueue.write_cb = pcu_sock_write; + state->upqueue.bfd.fd = -1; + state->net = net; bfd = &state->listen_bfd; - rc = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND); + rc = osmo_sock_unix_init(SOCK_SEQPACKET, 0, net->pcu_sock_path, OSMO_SOCK_F_BIND); if (rc < 0) { LOGP(DPCU, LOGL_ERROR, "Could not create unix socket: %s\n", strerror(errno)); @@ -723,27 +977,28 @@ int pcu_sock_init(const char *path, struct gsm_bts *bts) return rc; } - LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket: %s\n", path); + LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket (PCU IF v%u): %s\n", + PCU_IF_VERSION, net->pcu_sock_path); - bts->pcu_state = state; + net->pcu_state = state; return 0; } /* Close connection to PCU */ -void pcu_sock_exit(struct gsm_bts *bts) +void pcu_sock_exit(struct gsm_network *net) { - struct pcu_sock_state *state = bts->pcu_state; + struct pcu_sock_state *state = net->pcu_state; struct osmo_fd *bfd, *conn_bfd; if (!state) return; - conn_bfd = &state->conn_bfd; + conn_bfd = &state->upqueue.bfd; if (conn_bfd->fd > 0) pcu_sock_close(state); bfd = &state->listen_bfd; - close(bfd->fd); osmo_fd_unregister(bfd); + close(bfd->fd); talloc_free(state); - bts->pcu_state = NULL; + net->pcu_state = NULL; } diff --git a/src/osmo-bsc/penalty_timers.c b/src/osmo-bsc/penalty_timers.c index 02cf2468a..124a36255 100644 --- a/src/osmo-bsc/penalty_timers.c +++ b/src/osmo-bsc/penalty_timers.c @@ -28,34 +28,22 @@ #include <osmocom/bsc/penalty_timers.h> #include <osmocom/bsc/gsm_data.h> -struct penalty_timers { - struct llist_head timers; -}; - -struct penalty_timer { - struct llist_head entry; - const void *for_object; - unsigned int timeout; -}; - static unsigned int time_now(void) { - time_t now; - time(&now); - /* FIXME: use monotonic clock */ - return (unsigned int)now; -} - -struct penalty_timers *penalty_timers_init(void *ctx) -{ - struct penalty_timers *pt = talloc_zero(ctx, struct penalty_timers); - if (!pt) - return NULL; - INIT_LLIST_HEAD(&pt->timers); - return pt; + struct timespec tp; + if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp)) + return 0; + return (unsigned int)tp.tv_sec; } -void penalty_timers_add(struct penalty_timers *pt, const void *for_object, int timeout) +/* Add a penalty timer for a target cell ID. + * \param ctx talloc context to allocate new struct penalty_timer from. + * \param penalty_timers llist head to add penalty timer to. + * \param for_target_cell Which handover target to penalize. + * \param timeout Penalty time in seconds. + */ +void penalty_timers_add(void *ctx, struct llist_head *penalty_timers, + const struct gsm0808_cell_id *for_target_cell, int timeout) { struct penalty_timer *timer; unsigned int now; @@ -67,9 +55,9 @@ void penalty_timers_add(struct penalty_timers *pt, const void *for_object, int t then = now + timeout; - /* timer already running for that BTS? */ - llist_for_each_entry(timer, &pt->timers, entry) { - if (timer->for_object != for_object) + /* timer already running for that target cell? */ + llist_for_each_entry(timer, penalty_timers, entry) { + if (!gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true)) continue; /* raise, if running timer will timeout earlier or has timed * out already, otherwise keep later timeout */ @@ -79,24 +67,49 @@ void penalty_timers_add(struct penalty_timers *pt, const void *for_object, int t } /* add new timer */ - timer = talloc_zero(pt, struct penalty_timer); + timer = talloc_zero(ctx, struct penalty_timer); if (!timer) return; - timer->for_object = for_object; + timer->for_target_cell = *for_target_cell; timer->timeout = then; - llist_add_tail(&timer->entry, &pt->timers); + llist_add_tail(&timer->entry, penalty_timers); } -unsigned int penalty_timers_remaining(struct penalty_timers *pt, const void *for_object) +/* Add a penalty timer for each target cell ID in the given list. + * \param ctx talloc context to allocate new struct penalty_timer from. + * \param penalty_timers llist head to add penalty timer to. + * \param for_target_cells Which handover targets to penalize. + * \param timeout Penalty time in seconds. + */ +void penalty_timers_add_list(void *ctx, struct llist_head *penalty_timers, + const struct gsm0808_cell_id_list2 *for_target_cells, int timeout) +{ + int i; + for (i = 0; i < for_target_cells->id_list_len; i++) { + struct gsm0808_cell_id add = { + .id_discr = for_target_cells->id_discr, + .id = for_target_cells->id_list[i], + }; + penalty_timers_add(ctx, penalty_timers, &add, timeout); + } +} + +/* Return the amount of penalty time in seconds remaining for a target cell. + * \param penalty_timers llist head to look up penalty time in. + * \param for_target_cell Which handover target to query. + * \returns seconds remaining until all penalty time has expired. + */ +unsigned int penalty_timers_remaining(struct llist_head *penalty_timers, + const struct gsm0808_cell_id *for_target_cell) { struct penalty_timer *timer; unsigned int now = time_now(); unsigned int max_remaining = 0; - llist_for_each_entry(timer, &pt->timers, entry) { + llist_for_each_entry(timer, penalty_timers, entry) { unsigned int remaining; - if (timer->for_object != for_object) + if (!gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true)) continue; if (now >= timer->timeout) continue; @@ -107,23 +120,39 @@ unsigned int penalty_timers_remaining(struct penalty_timers *pt, const void *for return max_remaining; } -void penalty_timers_clear(struct penalty_timers *pt, const void *for_object) +/* Return the largest amount of penalty time in seconds remaining for any one of the given target cells. + * Call penalty_timers_remaining() for each entry of for_target_cells and return the largest value encountered. + * \param penalty_timers llist head to look up penalty time in. + * \param for_target_cells Which handover targets to query. + * \returns seconds remaining until all penalty time has expired. + */ +unsigned int penalty_timers_remaining_list(struct llist_head *penalty_timers, + const struct gsm0808_cell_id_list2 *for_target_cells) +{ + int i; + unsigned int max_remaining = 0; + for (i = 0; i < for_target_cells->id_list_len; i++) { + unsigned int remaining; + struct gsm0808_cell_id query = { + .id_discr = for_target_cells->id_discr, + .id = for_target_cells->id_list[i], + }; + remaining = penalty_timers_remaining(penalty_timers, &query); + max_remaining = OSMO_MAX(max_remaining, remaining); + } + return max_remaining; +} + +/* Clear penalty timers for one target cell, or completely clear the entire list. + * \param penalty_timers llist head to add penalty timer to. + * \param for_target_cell Which handover target to clear timers for, or NULL to clear all timers. */ +void penalty_timers_clear(struct llist_head *penalty_timers, const struct gsm0808_cell_id *for_target_cell) { struct penalty_timer *timer, *timer2; - llist_for_each_entry_safe(timer, timer2, &pt->timers, entry) { - if (for_object && timer->for_object != for_object) + llist_for_each_entry_safe(timer, timer2, penalty_timers, entry) { + if (for_target_cell && !gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true)) continue; llist_del(&timer->entry); talloc_free(timer); } } - -void penalty_timers_free(struct penalty_timers **pt_p) -{ - struct penalty_timers *pt = *pt_p; - if (!pt) - return; - penalty_timers_clear(pt, NULL); - talloc_free(pt); - *pt_p = NULL; -} diff --git a/src/osmo-bsc/power_control.c b/src/osmo-bsc/power_control.c new file mode 100644 index 000000000..8bf0783bf --- /dev/null +++ b/src/osmo-bsc/power_control.c @@ -0,0 +1,476 @@ +/* MS Power Control Loop L1 */ + +/* (C) 2014 by Holger Hans Peter Freyther + * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <inttypes.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/meas_rep.h> +#include <osmocom/bsc/power_control.h> + +/* We don't want to deal with floating point, so we scale up */ +#define EWMA_SCALE_FACTOR 100 +/* EWMA_SCALE_FACTOR/2 = +50: Round to nearest value when downscaling, otherwise floor() is applied. */ +#define EWMA_ROUND_FACTOR (EWMA_SCALE_FACTOR / 2) + +/* Base Low-Pass Single-Pole IIR Filter (EWMA) formula: + * + * Avg[n] = a * Val[n] + (1 - a) * Avg[n - 1] + * + * where parameter 'a' determines how much weight of the latest measurement value + * 'Val[n]' carries vs the weight of the accumulated average 'Avg[n - 1]'. The + * value of 'a' is usually a float in range 0 .. 1, so: + * + * - value 0.5 gives equal weight to both 'Val[n]' and 'Avg[n - 1]'; + * - value 1.0 means no filtering at all (pass through); + * - value 0.0 makes no sense. + * + * Further optimization: + * + * Avg[n] = a * Val[n] + Avg[n - 1] - a * Avg[n - 1] + * ^^^^^^ ^^^^^^^^^^ + * + * a) this can be implemented in C using '+=' operator: + * + * Avg += a * Val - a * Avg + * Avg += a * (Val - Avg) + * + * b) everything is scaled up by 100 to avoid floating point stuff: + * + * Avg100 += A * (Val - Avg) + * + * where 'Avg100' is 'Avg * 100' and 'A' is 'a * 100'. + * + * For more details, see: + * + * https://en.wikipedia.org/wiki/Moving_average + * https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter + * https://tomroelandts.com/articles/low-pass-single-pole-iir-filter + */ +static int do_pf_ewma(const struct gsm_power_ctrl_meas_params *mp, + struct gsm_power_ctrl_meas_proc_state *mps, + const int Val) +{ + const uint8_t A = mp->ewma.alpha; + int *Avg100 = &mps->ewma.Avg100; + + /* We don't have 'Avg[n - 1]' if this is the first run */ + if (mps->meas_num++ == 0) { + *Avg100 = Val * EWMA_SCALE_FACTOR; + return Val; + } + + *Avg100 += A * (Val - (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR); + return (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR; +} + +/* Calculate target RxLev value from lower/upper thresholds */ +#define CALC_TARGET(mp) \ + ((mp).lower_thresh + (mp).upper_thresh) / 2 + +static int do_avg_algo(const struct gsm_power_ctrl_meas_params *mp, + struct gsm_power_ctrl_meas_proc_state *mps, + const int val) +{ + int val_avg; + switch (mp->algo) { + case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: + val_avg = do_pf_ewma(mp, mps, val); + break; + /* TODO: implement other pre-processing methods */ + case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE: + default: + /* No filtering (pass through) */ + val_avg = val; + } + return val_avg; +} +/* Calculate a 'delta' value (for the given MS/BS power control parameters) + * to be applied to the current Tx power level to approach the target level. */ +static int calc_delta_rxlev(const struct gsm_power_ctrl_params *params, const uint8_t rxlev) +{ + int delta; + + /* Check if RxLev is within the threshold window */ + if (rxlev >= params->rxlev_meas.lower_thresh && + rxlev <= params->rxlev_meas.upper_thresh) + return 0; + + /* How many dBs measured power should be increased (+) or decreased (-) + * to reach expected power. */ + delta = CALC_TARGET(params->rxlev_meas) - rxlev; + + /* Don't ever change more than PWR_{LOWER,RAISE}_MAX_DBM during one loop + * iteration, i.e. reduce the speed at which the MS transmit power can + * change. A higher value means a lower level (and vice versa) */ + if (delta > params->inc_step_size_db) + delta = params->inc_step_size_db; + else if (delta < -params->red_step_size_db) + delta = -params->red_step_size_db; + + return delta; +} + +/* Shall we skip current block based on configured interval? */ +static bool ctrl_interval_skip_block(const struct gsm_power_ctrl_params *params, + struct lchan_power_ctrl_state *state) +{ + /* Power control interval: how many blocks do we skip? */ + if (state->skip_block_num-- > 0) + return true; + + /* Can we be sure if ONE Report is always going to correspond + * to ONE SACCH block at the BTS? - If not this is as approximation + * but it should not hurt. */ + + /* Reset the number of SACCH blocks to be skipped: + * ctrl_interval=0 => 0 blocks to skip, + * ctrl_interval=1 => 1 blocks to skip, + * ctrl_interval=2 => 3 blocks to skip, + * so basically ctrl_interval * 2 - 1. */ + state->skip_block_num = params->ctrl_interval * 2 - 1; + return false; +} + +int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, const struct gsm_meas_rep *mr) +{ + struct lchan_power_ctrl_state *state = &lchan->ms_power_ctrl; + struct gsm_bts_trx *trx = lchan->ts->trx; + struct gsm_bts *bts = trx->bts; + enum gsm_band band = bts->band; + const struct gsm_power_ctrl_params *params = &bts->ms_power_ctrl; + int8_t new_power_lvl; /* TS 05.05 power level */ + int8_t ms_dbm, new_dbm, current_dbm, bsc_max_dbm; + uint8_t rxlev_avg; + uint8_t ms_power_lvl = ms_pwr_ctl_lvl(band, mr->ms_l1.pwr); + int8_t ul_rssi_dbm; + bool ignore; + + if (params == NULL) + return 0; + /* Not doing the power loop here if we are not handling it */ + if (params->mode != GSM_PWR_CTRL_MODE_DYN_BSC) + return 0; + + /* Shall we skip current block based on configured interval? */ + if (ctrl_interval_skip_block(params, state)) + return 0; + + /* If DTx is active on Uplink, + * use the '-SUB', otherwise '-FULL': */ + if (mr->flags & MEAS_REP_F_UL_DTX) + ul_rssi_dbm = rxlev2dbm(mr->ul.sub.rx_lev); + else + ul_rssi_dbm = rxlev2dbm(mr->ul.full.rx_lev); + + ms_dbm = ms_pwr_dbm(band, ms_power_lvl); + if (ms_dbm < 0) { + LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE, + "Failed to calculate dBm for power ctl level %" PRIu8 " on band %s\n", + ms_power_lvl, gsm_band_name(band)); + return 0; + } + + bsc_max_dbm = bts->ms_max_power; + rxlev_avg = do_avg_algo(¶ms->rxlev_meas, &state->rxlev_meas_proc, dbm2rxlev(ul_rssi_dbm)); + new_dbm = ms_dbm + calc_delta_rxlev(params, rxlev_avg); + + /* Make sure new_dbm is never negative. ms_pwr_ctl_lvl() can later on + cope with any unsigned dbm value, regardless of band minimal value. */ + if (new_dbm < 0) + new_dbm = 0; + /* Don't ask for smaller ms power level than the one set by ms max power for this BTS */ + if (new_dbm > bsc_max_dbm) + new_dbm = bsc_max_dbm; + + new_power_lvl = ms_pwr_ctl_lvl(band, new_dbm); + if (new_power_lvl < 0) { + LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE, + "Failed to retrieve power level for %" PRId8 " dBm on band %d\n", + new_dbm, band); + return 0; + } + + current_dbm = ms_pwr_dbm(band, lchan->ms_power); + + /* In this Power Control Loop, we infer a new good MS Power Level based + * on the previous MS Power Level announced by the MS (not the previous + * one we requested!) together with the related computed measurements. + * Hence, and since we allow for several good MS Power Levels falling into our + * thresholds, we could finally converge into an oscillation loop where + * the MS bounces between 2 different correct MS Power levels all the + * time, due to the fact that we "accept" and "request back" whatever + * good MS Power Level we received from the MS, but at that time the MS + * will be transmitting using the previous MS Power Level we + * requested, which we will later "accept" and "request back" on next loop + * iteration. As a result MS effectively bounces between those 2 MS + * Power Levels. + * In order to fix this permanent oscillation, if current MS_PWR used/announced + * by MS is good ("ms_dbm == new_dbm", hence within thresholds and no change + * required) but has higher Tx power than the one we last requested, we ignore + * it and keep requesting for one with lower Tx power. This way we converge to + * the lowest good Tx power avoiding oscillating over values within thresholds. + */ + ignore = (ms_dbm == new_dbm && ms_dbm > current_dbm); + + if (lchan->ms_power == new_power_lvl || ignore) { + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Keeping MS power at control level %d (%d dBm): " + "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm\n", + new_power_lvl, ms_dbm, ms_power_lvl, bsc_max_dbm, ul_rssi_dbm, rxlev2dbm(rxlev_avg), + rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh)); + return 0; + } + + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "%s MS power control level %d (%d dBm) => %d (%d dBm): " + "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm\n", + (new_dbm > current_dbm) ? "Raising" : "Lowering", + lchan->ms_power, current_dbm, new_power_lvl, new_dbm, ms_power_lvl, + bsc_max_dbm, ul_rssi_dbm, rxlev2dbm(rxlev_avg), + rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh)); + + lchan_update_ms_power_ctrl_level(lchan, new_dbm); + + return 1; + +} + +/* Default MS/BS Power Control parameters (see 3GPP TS 45.008, table A.1) */ +const struct gsm_power_ctrl_params power_ctrl_params_def = { + /* Static Power Control is the safe default */ + .mode = GSM_PWR_CTRL_MODE_STATIC, + + /* BS Power reduction value / maximum (in dB) */ + .bs_power_val_db = 0, /* no attenuation in static mode */ + .bs_power_max_db = 12, /* up to 12 dB in dynamic mode */ + + /* Power increasing/reducing step size */ + .inc_step_size_db = 4, /* 2, 4, or 6 dB */ + .red_step_size_db = 2, /* 2 or 4 dB */ + + /* RxLev measurement parameters */ + .rxlev_meas = { + .enabled = true, + /* Thresholds for RxLev (see 3GPP TS 45.008, A.3.2.1) */ + .lower_thresh = 32, /* L_RXLEV_XX_P (-78 dBm) */ + .upper_thresh = 38, /* U_RXLEV_XX_P (-72 dBm) */ + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_RXLEV_XX_P */ + .lower_cmp_p = 10, /* P1 as in 3GPP TS 45.008, A.3.2.1 (case a) */ + .lower_cmp_n = 12, /* N1 as in 3GPP TS 45.008, A.3.2.1 (case a) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_RXLEV_XX_P */ + .upper_cmp_p = 19, /* P2 as in 3GPP TS 45.008, A.3.2.1 (case b) */ + .upper_cmp_n = 20, /* N2 as in 3GPP TS 45.008, A.3.2.1 (case b) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + + /* RxQual measurement parameters */ + .rxqual_meas = { + .enabled = true, + /* Thresholds for RxQual (see 3GPP TS 45.008, A.3.2.1) */ + .lower_thresh = 3, /* L_RXQUAL_XX_P (0.8% <= BER < 1.6%) */ + .upper_thresh = 0, /* U_RXQUAL_XX_P (BER < 0.2%) */ + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_RXQUAL_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_RXQUAL_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + + /* C/I measurement parameters. + * Target C/I retrieved from "GSM/EDGE: Evolution and Performance" Table 10.3. + * Set lower and upper so that (lower + upper) / 2 is equal or slightly + * above the target. + */ + .ci_fr_meas = { /* FR: Target C/I = 15 dB, Soft blocking threshold = 10 dB */ + .enabled = false, + .lower_thresh = 13, + .upper_thresh = 17, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_FR_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_FR_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_hr_meas = { /* HR: Target C/I = 18 dB, Soft blocking threshold = 13 dB */ + .enabled = false, + .lower_thresh = 16, + .upper_thresh = 21, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_HR_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_HR_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_amr_fr_meas = { /* AMR-FR: Target C/I = 9 dB, Soft blocking threshold = 4 dB */ + .enabled = false, + .lower_thresh = 7, + .upper_thresh = 11, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_AMR_FR_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_AMR_FR_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_amr_hr_meas = { /* AMR-HR: Target C/I = 15 dB, Soft blocking threshold = 10 dB */ + .enabled = false, + .lower_thresh = 13, + .upper_thresh = 17, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_AMR_HR_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_AMR_HR_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_sdcch_meas = { /* SDCCH: Target C/I = 14 dB, Soft blocking threshold = 9 dB */ + .enabled = false, + .lower_thresh = 12, + .upper_thresh = 16, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_SDCCH_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_SDCCH_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_gprs_meas = { /* GPRS: Target C/I = 20 dB, Soft blocking threshold = 15 dB */ + .enabled = false, + .lower_thresh = 18, + .upper_thresh = 24, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_GPRS_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_GPRS_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, +}; + +void power_ctrl_params_def_reset(struct gsm_power_ctrl_params *params, + enum gsm_power_ctrl_dir dir) +{ + *params = power_ctrl_params_def; + params->dir = dir; + + /* Trigger loop every N-th SACCH block. See 3GPP TS 45.008 section 4.7.1. */ + if (dir == GSM_PWR_CTRL_DIR_UL) + params->ctrl_interval = 2; /* N=4 (1.92s) */ + else + params->ctrl_interval = 1; /* N=2 (0.960) */ +} diff --git a/src/osmo-bsc/rest_octets.c b/src/osmo-bsc/rest_octets.c deleted file mode 100644 index 7307cb888..000000000 --- a/src/osmo-bsc/rest_octets.c +++ /dev/null @@ -1,899 +0,0 @@ -/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface, - * rest octet handling according to - * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ - -/* (C) 2009 by Harald Welte <laforge@gnumonks.org> - * - * 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 <http://www.gnu.org/licenses/>. - * - */ - -#include <string.h> -#include <stdlib.h> -#include <errno.h> -#include <stdbool.h> - -#include <osmocom/bsc/debug.h> -#include <osmocom/bsc/gsm_data.h> -#include <osmocom/core/bitvec.h> -#include <osmocom/gsm/bitvec_gsm.h> -#include <osmocom/bsc/rest_octets.h> -#include <osmocom/bsc/arfcn_range_encode.h> -#include <osmocom/bsc/system_information.h> -#include <osmocom/bsc/bts.h> - -/* generate SI1 rest octets */ -int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net) -{ - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data = data; - bv.data_len = 1; - - if (nch_pos) { - bitvec_set_bit(&bv, H); - bitvec_set_uint(&bv, *nch_pos, 5); - } else - bitvec_set_bit(&bv, L); - - if (is1800_net) - bitvec_set_bit(&bv, L); - else - bitvec_set_bit(&bv, H); - - bitvec_spare_padding(&bv, 6); - return bv.data_len; -} - -/* Append Repeated E-UTRAN Neighbour Cell to bitvec: see 3GPP TS 44.018 Table 10.5.2.33b.1 */ -static inline bool append_eutran_neib_cell(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget) -{ - const struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; - unsigned i, skip = 0; - size_t offset = bts->e_offset; - int16_t rem = budget - 6; /* account for mandatory stop bit and THRESH_E-UTRAN_high */ - uint8_t earfcn_budget; - - if (budget <= 6) - return false; - - OSMO_ASSERT(budget <= SI2Q_MAX_LEN); - - /* first we have to properly adjust budget requirements */ - if (e->prio_valid) /* E-UTRAN_PRIORITY: 3GPP TS 45.008*/ - rem -= 4; - else - rem--; - - if (e->thresh_lo_valid) /* THRESH_E-UTRAN_low: */ - rem -= 6; - else - rem--; - - if (e->qrxlm_valid) /* E-UTRAN_QRXLEVMIN: */ - rem -= 6; - else - rem--; - - if (rem < 0) - return false; - - /* now we can proceed with actually adding EARFCNs within adjusted budget limit */ - for (i = 0; i < e->length; i++) { - if (e->arfcn[i] != OSMO_EARFCN_INVALID) { - if (skip < offset) { - skip++; /* ignore EARFCNs added on previous calls */ - } else { - earfcn_budget = 17; /* compute budget per-EARFCN */ - if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i]) - earfcn_budget++; - else - earfcn_budget += 4; - - if (rem - earfcn_budget < 0) - break; - else { - bts->e_offset++; - rem -= earfcn_budget; - - if (rem < 0) - return false; - - bitvec_set_bit(bv, 1); /* EARFCN: */ - bitvec_set_uint(bv, e->arfcn[i], 16); - - if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i]) - bitvec_set_bit(bv, 0); - else { /* Measurement Bandwidth: 9.1.54 */ - bitvec_set_bit(bv, 1); - bitvec_set_uint(bv, e->meas_bw[i], 3); - } - } - } - } - } - - /* stop bit - end of EARFCN + Measurement Bandwidth sequence */ - bitvec_set_bit(bv, 0); - - /* Note: we don't support different EARFCN arrays each with different priority, threshold etc. */ - - if (e->prio_valid) { - /* E-UTRAN_PRIORITY: 3GPP TS 45.008*/ - bitvec_set_bit(bv, 1); - bitvec_set_uint(bv, e->prio, 3); - } else - bitvec_set_bit(bv, 0); - - /* THRESH_E-UTRAN_high */ - bitvec_set_uint(bv, e->thresh_hi, 5); - - if (e->thresh_lo_valid) { - /* THRESH_E-UTRAN_low: */ - bitvec_set_bit(bv, 1); - bitvec_set_uint(bv, e->thresh_lo, 5); - } else - bitvec_set_bit(bv, 0); - - if (e->qrxlm_valid) { - /* E-UTRAN_QRXLEVMIN: */ - bitvec_set_bit(bv, 1); - bitvec_set_uint(bv, e->qrxlm, 5); - } else - bitvec_set_bit(bv, 0); - - return true; -} - -static inline void append_earfcn(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget) -{ - bool appended; - unsigned int old = bv->cur_bit; /* save current position to make rollback possible */ - int rem = ((int)budget) - 40; - if (rem <= 0) - return; - - OSMO_ASSERT(budget <= SI2Q_MAX_LEN); - - /* Additions in Rel-5: */ - bitvec_set_bit(bv, H); - /* No 3G Additional Measurement Param. Descr. */ - bitvec_set_bit(bv, 0); - /* No 3G ADDITIONAL MEASUREMENT Param. Descr. 2 */ - bitvec_set_bit(bv, 0); - /* Additions in Rel-6: */ - bitvec_set_bit(bv, H); - /* 3G_CCN_ACTIVE */ - bitvec_set_bit(bv, 0); - /* Additions in Rel-7: */ - bitvec_set_bit(bv, H); - /* No 700_REPORTING_OFFSET */ - bitvec_set_bit(bv, 0); - /* No 810_REPORTING_OFFSET */ - bitvec_set_bit(bv, 0); - /* Additions in Rel-8: */ - bitvec_set_bit(bv, H); - - /* Priority and E-UTRAN Parameters Description */ - bitvec_set_bit(bv, 1); - - /* budget: 10 bits used above */ - - /* Serving Cell Priority Parameters Descr. is Present, - * see also: 3GPP TS 44.018, Table 10.5.2.33b.1 */ - bitvec_set_bit(bv, 1); - - /* GERAN_PRIORITY */ - bitvec_set_uint(bv, 0, 3); - - /* THRESH_Priority_Search */ - bitvec_set_uint(bv, 0, 4); - - /* THRESH_GSM_low */ - bitvec_set_uint(bv, 0, 4); - - /* H_PRIO */ - bitvec_set_uint(bv, 0, 2); - - /* T_Reselection */ - bitvec_set_uint(bv, 0, 2); - - /* budget: 26 bits used above */ - - /* No 3G Priority Parameters Description */ - bitvec_set_bit(bv, 0); - /* E-UTRAN Parameters Description */ - bitvec_set_bit(bv, 1); - - /* E-UTRAN_CCN_ACTIVE */ - bitvec_set_bit(bv, 0); - /* E-UTRAN_Start: 9.1.54 */ - bitvec_set_bit(bv, 1); - /* E-UTRAN_Stop: 9.1.54 */ - bitvec_set_bit(bv, 1); - - /* No E-UTRAN Measurement Parameters Descr. */ - bitvec_set_bit(bv, 0); - /* No GPRS E-UTRAN Measurement Param. Descr. */ - bitvec_set_bit(bv, 0); - - /* Note: each of next 3 "repeated" structures might be repeated any - (0, 1, 2...) times - we only support 1 and 0 */ - - /* Repeated E-UTRAN Neighbour Cells */ - bitvec_set_bit(bv, 1); - - /* budget: 34 bits used above */ - - appended = append_eutran_neib_cell(bv, bts, rem); - if (!appended) { /* appending is impossible within current budget: rollback */ - bv->cur_bit = old; - return; - } - - /* budget: further 6 bits used below, totalling 40 bits */ - - /* stop bit - end of Repeated E-UTRAN Neighbour Cells sequence: */ - bitvec_set_bit(bv, 0); - - /* Note: following 2 repeated structs are not supported ATM */ - /* stop bit - end of Repeated E-UTRAN Not Allowed Cells sequence: */ - bitvec_set_bit(bv, 0); - /* stop bit - end of Repeated E-UTRAN PCID to TA mapping sequence: */ - bitvec_set_bit(bv, 0); - - /* Priority and E-UTRAN Parameters Description ends here */ - /* No 3G CSG Description */ - bitvec_set_bit(bv, 0); - /* No E-UTRAN CSG Description */ - bitvec_set_bit(bv, 0); - /* No Additions in Rel-9: */ - bitvec_set_bit(bv, L); -} - -static inline int f0_helper(int *sc, size_t length, uint8_t *chan_list) -{ - int w[RANGE_ENC_MAX_ARFCNS] = { 0 }; - - return range_encode(ARFCN_RANGE_1024, sc, length, w, 0, chan_list); -} - -/* Estimate how many bits it'll take to append single FDD UARFCN */ -static inline int append_utran_fdd_length(uint16_t u, const int *sc, size_t sc_len, size_t length) -{ - uint8_t chan_list[16] = { 0 }; - int tmp[sc_len], f0; - - memcpy(tmp, sc, sizeof(tmp)); - - f0 = f0_helper(tmp, length, chan_list); - if (f0 < 0) - return f0; - - return 21 + range1024_p(length); -} - -/* Append single FDD UARFCN */ -static inline int append_utran_fdd(struct bitvec *bv, uint16_t u, int *sc, size_t length) -{ - uint8_t chan_list[16] = { 0 }; - int f0 = f0_helper(sc, length, chan_list); - - if (f0 < 0) - return f0; - - /* Repeated UTRAN FDD Neighbour Cells */ - bitvec_set_bit(bv, 1); - - /* FDD-ARFCN */ - bitvec_set_bit(bv, 0); - bitvec_set_uint(bv, u, 14); - - /* FDD_Indic0: parameter value '0000000000' is a member of the set? */ - bitvec_set_bit(bv, f0); - /* NR_OF_FDD_CELLS */ - bitvec_set_uint(bv, length, 5); - - f0 = bv->cur_bit; - bitvec_add_range1024(bv, (struct gsm48_range_1024 *)chan_list); - bv->cur_bit = f0 + range1024_p(length); - - return 21 + range1024_p(length); -} - -static inline int try_adding_uarfcn(struct bitvec *bv, struct gsm_bts *bts, uint16_t uarfcn, - uint8_t num_sc, uint8_t start_pos, uint8_t budget) -{ - int i, k, rc, a[bts->si_common.uarfcn_length]; - - if (budget < 23) - return -ENOMEM; - - /* copy corresponding Scrambling Codes: range encoder make in-place modifications */ - for (i = start_pos, k = 0; i < num_sc; a[k++] = bts->si_common.data.scramble_list[i++]); - - /* estimate bit length requirements */ - rc = append_utran_fdd_length(uarfcn, a, bts->si_common.uarfcn_length, k); - if (rc < 0) - return rc; /* range encoder failure */ - - if (budget - rc <= 0) - return -ENOMEM; /* we have ran out of budget in current SI2q */ - - /* compute next offset */ - bts->u_offset += k; - - return budget - append_utran_fdd(bv, uarfcn, a, k); -} - -/* Append multiple FDD UARFCNs */ -static inline void append_uarfcns(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget) -{ - const uint16_t *u = bts->si_common.data.uarfcn_list; - int i, rem = budget - 7, st = bts->u_offset; /* account for constant bits right away */ - uint16_t cu = u[bts->u_offset]; /* caller ensures that length is positive */ - - OSMO_ASSERT(budget <= SI2Q_MAX_LEN); - - if (budget <= 7) - return; - - /* 3G Neighbour Cell Description */ - bitvec_set_bit(bv, 1); - /* No Index_Start_3G */ - bitvec_set_bit(bv, 0); - /* No Absolute_Index_Start_EMR */ - bitvec_set_bit(bv, 0); - - /* UTRAN FDD Description */ - bitvec_set_bit(bv, 1); - /* No Bandwidth_FDD */ - bitvec_set_bit(bv, 0); - - for (i = bts->u_offset; i <= bts->si_common.uarfcn_length; i++) - if (u[i] != cu) { /* we've reached new UARFCN */ - rem = try_adding_uarfcn(bv, bts, cu, i, st, rem); - if (rem < 0) - break; - - if (i < bts->si_common.uarfcn_length) { - cu = u[i]; - st = i; - } else - break; - } - - /* stop bit - end of Repeated UTRAN FDD Neighbour Cells */ - bitvec_set_bit(bv, 0); - - /* UTRAN TDD Description */ - bitvec_set_bit(bv, 0); -} - -/* generate SI2quater rest octets: 3GPP TS 44.018 § 10.5.2.33b */ -int rest_octets_si2quater(uint8_t *data, struct gsm_bts *bts) -{ - int rc; - struct bitvec bv; - - if (bts->si2q_count < bts->si2q_index) - return -EINVAL; - - bv.data = data; - bv.data_len = 20; - bitvec_zero(&bv); - - /* BA_IND: Set to '0' as that's what we use for SI2xxx type, - * whereas '1' is used for SI5xxx type messages. The point here - * is to be able to correlate whether a given MS measurement - * report was using the neighbor cells advertised in SI2 or in - * SI5, as those two could very well be different */ - bitvec_set_bit(&bv, 0); - /* 3G_BA_IND */ - bitvec_set_bit(&bv, 1); - /* MP_CHANGE_MARK */ - bitvec_set_bit(&bv, 0); - - /* SI2quater_INDEX */ - bitvec_set_uint(&bv, bts->si2q_index, 4); - /* SI2quater_COUNT */ - bitvec_set_uint(&bv, bts->si2q_count, 4); - - /* No Measurement_Parameters Description */ - bitvec_set_bit(&bv, 0); - /* No GPRS_Real Time Difference Description */ - bitvec_set_bit(&bv, 0); - /* No GPRS_BSIC Description */ - bitvec_set_bit(&bv, 0); - /* No GPRS_REPORT PRIORITY Description */ - bitvec_set_bit(&bv, 0); - /* No GPRS_MEASUREMENT_Parameters Description */ - bitvec_set_bit(&bv, 0); - /* No NC Measurement Parameters */ - bitvec_set_bit(&bv, 0); - /* No extension (length) */ - bitvec_set_bit(&bv, 0); - - rc = SI2Q_MAX_LEN - (bv.cur_bit + 3); - if (rc > 0 && bts->si_common.uarfcn_length - bts->u_offset > 0) - append_uarfcns(&bv, bts, rc); - else /* No 3G Neighbour Cell Description */ - bitvec_set_bit(&bv, 0); - - /* No 3G Measurement Parameters Description */ - bitvec_set_bit(&bv, 0); - /* No GPRS_3G_MEASUREMENT Parameters Descr. */ - bitvec_set_bit(&bv, 0); - - rc = SI2Q_MAX_LEN - bv.cur_bit; - if (rc > 0 && si2q_earfcn_count(&bts->si_common.si2quater_neigh_list) - bts->e_offset > 0) - append_earfcn(&bv, bts, rc); - else /* No Additions in Rel-5: */ - bitvec_set_bit(&bv, L); - - bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); - return bv.data_len; -} - -/* Append selection parameters to bitvec */ -static void append_selection_params(struct bitvec *bv, - const struct gsm48_si_selection_params *sp) -{ - if (sp->present) { - bitvec_set_bit(bv, H); - bitvec_set_bit(bv, sp->cbq); - bitvec_set_uint(bv, sp->cell_resel_off, 6); - bitvec_set_uint(bv, sp->temp_offs, 3); - bitvec_set_uint(bv, sp->penalty_time, 5); - } else - bitvec_set_bit(bv, L); -} - -/* Append power offset to bitvec */ -static void append_power_offset(struct bitvec *bv, - const struct gsm48_si_power_offset *po) -{ - if (po->present) { - bitvec_set_bit(bv, H); - bitvec_set_uint(bv, po->power_offset, 2); - } else - bitvec_set_bit(bv, L); -} - -/* Append GPRS indicator to bitvec */ -static void append_gprs_ind(struct bitvec *bv, - const struct gsm48_si3_gprs_ind *gi) -{ - if (gi->present) { - bitvec_set_bit(bv, H); - bitvec_set_uint(bv, gi->ra_colour, 3); - /* 0 == SI13 in BCCH Norm, 1 == SI13 sent on BCCH Ext */ - bitvec_set_bit(bv, gi->si13_position); - } else - bitvec_set_bit(bv, L); -} - -/* Generate SI2ter Rest Octests 3GPP TS 44.018 Table 10.5.2.33a.1 */ -int rest_octets_si2ter(uint8_t *data) -{ - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data = data; - bv.data_len = 4; - - /* No SI2ter_MP_CHANGE_MARK */ - bitvec_set_bit(&bv, L); - - bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); - - return bv.data_len; -} - -/* Generate SI2bis Rest Octests 3GPP TS 44.018 Table 10.5.2.33.1 */ -int rest_octets_si2bis(uint8_t *data) -{ - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data = data; - bv.data_len = 1; - - bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); - - return bv.data_len; -} - -/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */ -int rest_octets_si3(uint8_t *data, const struct gsm48_si_ro_info *si3) -{ - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data = data; - bv.data_len = 4; - - /* Optional Selection Parameters */ - append_selection_params(&bv, &si3->selection_params); - - /* Optional Power Offset */ - append_power_offset(&bv, &si3->power_offset); - - /* Do we have a SI2ter on the BCCH? */ - if (si3->si2ter_indicator) - bitvec_set_bit(&bv, H); - else - bitvec_set_bit(&bv, L); - - /* Early Classmark Sending Control */ - if (si3->early_cm_ctrl) - bitvec_set_bit(&bv, H); - else - bitvec_set_bit(&bv, L); - - /* Do we have a SI Type 9 on the BCCH? */ - if (si3->scheduling.present) { - bitvec_set_bit(&bv, H); - bitvec_set_uint(&bv, si3->scheduling.where, 3); - } else - bitvec_set_bit(&bv, L); - - /* GPRS Indicator */ - append_gprs_ind(&bv, &si3->gprs_ind); - - /* 3G Early Classmark Sending Restriction. If H, then controlled by - * early_cm_ctrl above */ - if (si3->early_cm_restrict_3g) - bitvec_set_bit(&bv, L); - else - bitvec_set_bit(&bv, H); - - if (si3->si2quater_indicator) { - bitvec_set_bit(&bv, H); /* indicator struct present */ - bitvec_set_uint(&bv, 0, 1); /* message is sent on BCCH Norm */ - } - - bitvec_spare_padding(&bv, (bv.data_len*8)-1); - return bv.data_len; -} - -static int append_lsa_params(struct bitvec *bv, - const struct gsm48_lsa_params *lsa_params) -{ - /* FIXME */ - return -1; -} - -/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */ -int rest_octets_si4(uint8_t *data, const struct gsm48_si_ro_info *si4, int len) -{ - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data = data; - bv.data_len = len; - - /* SI4 Rest Octets O */ - append_selection_params(&bv, &si4->selection_params); - append_power_offset(&bv, &si4->power_offset); - append_gprs_ind(&bv, &si4->gprs_ind); - - if (0 /* FIXME */) { - /* H and SI4 Rest Octets S */ - bitvec_set_bit(&bv, H); - - /* LSA Parameters */ - if (si4->lsa_params.present) { - bitvec_set_bit(&bv, H); - append_lsa_params(&bv, &si4->lsa_params); - } else - bitvec_set_bit(&bv, L); - - /* Cell Identity */ - if (1) { - bitvec_set_bit(&bv, H); - bitvec_set_uint(&bv, si4->cell_id, 16); - } else - bitvec_set_bit(&bv, L); - - /* LSA ID Information */ - if (0) { - bitvec_set_bit(&bv, H); - /* FIXME */ - } else - bitvec_set_bit(&bv, L); - } else { - /* L and break indicator */ - bitvec_set_bit(&bv, L); - bitvec_set_bit(&bv, si4->break_ind ? H : L); - } - - return bv.data_len; -} - - -/* GSM 04.18 ETSI TS 101 503 V8.27.0 (2006-05) - -<SI6 rest octets> ::= -{L | H <PCH and NCH info>} -{L | H <VBS/VGCS options : bit(2)>} -{ < DTM_support : bit == L > I < DTM_support : bit == H > -< RAC : bit (8) > -< MAX_LAPDm : bit (3) > } -< Band indicator > -{ L | H < GPRS_MS_TXPWR_MAX_CCH : bit (5) > } -<implicit spare >; -*/ -int rest_octets_si6(uint8_t *data, bool is1800_net) -{ - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data = data; - bv.data_len = 1; - - /* no PCH/NCH info */ - bitvec_set_bit(&bv, L); - /* no VBS/VGCS options */ - bitvec_set_bit(&bv, L); - /* no DTM_support */ - bitvec_set_bit(&bv, L); - /* band indicator */ - if (is1800_net) - bitvec_set_bit(&bv, L); - else - bitvec_set_bit(&bv, H); - /* no GPRS_MS_TXPWR_MAX_CCH */ - bitvec_set_bit(&bv, L); - - bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); - return bv.data_len; -} - -/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a: - < GPRS Mobile Allocation IE > ::= - < HSN : bit (6) > - { 0 | 1 < RFL number list : < RFL number list struct > > } - { 0 < MA_LENGTH : bit (6) > - < MA_BITMAP: bit (val(MA_LENGTH) + 1) > - | 1 { 0 | 1 <ARFCN index list : < ARFCN index list struct > > } } ; - - < RFL number list struct > :: = - < RFL_NUMBER : bit (4) > - { 0 | 1 < RFL number list struct > } ; - < ARFCN index list struct > ::= - < ARFCN_INDEX : bit(6) > - { 0 | 1 < ARFCN index list struct > } ; - */ -static int append_gprs_mobile_alloc(struct bitvec *bv) -{ - /* Hopping Sequence Number */ - bitvec_set_uint(bv, 0, 6); - - if (0) { - /* We want to use a RFL number list */ - bitvec_set_bit(bv, 1); - /* FIXME: RFL number list */ - } else - bitvec_set_bit(bv, 0); - - if (0) { - /* We want to use a MA_BITMAP */ - bitvec_set_bit(bv, 0); - /* FIXME: MA_LENGTH, MA_BITMAP, ... */ - } else { - bitvec_set_bit(bv, 1); - if (0) { - /* We want to provide an ARFCN index list */ - bitvec_set_bit(bv, 1); - /* FIXME */ - } else - bitvec_set_bit(bv, 0); - } - return 0; -} - -static int encode_t3192(unsigned int t3192) -{ - /* See also 3GPP TS 44.060 - Table 12.24.2: GPRS Cell Options information element details */ - if (t3192 == 0) - return 3; - else if (t3192 <= 80) - return 4; - else if (t3192 <= 120) - return 5; - else if (t3192 <= 160) - return 6; - else if (t3192 <= 200) - return 7; - else if (t3192 <= 500) - return 0; - else if (t3192 <= 1000) - return 1; - else if (t3192 <= 1500) - return 2; - else - return -EINVAL; -} - -static int encode_drx_timer(unsigned int drx) -{ - if (drx == 0) - return 0; - else if (drx == 1) - return 1; - else if (drx == 2) - return 2; - else if (drx <= 4) - return 3; - else if (drx <= 8) - return 4; - else if (drx <= 16) - return 5; - else if (drx <= 32) - return 6; - else if (drx <= 64) - return 7; - else - return -EINVAL; -} - -/* GPRS Cell Options as per TS 04.60 Chapter 12.24 - < GPRS Cell Options IE > ::= - < NMO : bit(2) > - < T3168 : bit(3) > - < T3192 : bit(3) > - < DRX_TIMER_MAX: bit(3) > - < ACCESS_BURST_TYPE: bit > - < CONTROL_ACK_TYPE : bit > - < BS_CV_MAX: bit(4) > - { 0 | 1 < PAN_DEC : bit(3) > - < PAN_INC : bit(3) > - < PAN_MAX : bit(3) > - { 0 | 1 < Extension Length : bit(6) > - < bit (val(Extension Length) + 1 - & { < Extension Information > ! { bit ** = <no string> } } ; - < Extension Information > ::= - { 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit > - < BEP_PERIOD : bit(4) > } - < PFC_FEATURE_MODE : bit > - < DTM_SUPPORT : bit > - <BSS_PAGING_COORDINATION: bit > - <spare bit > ** ; - */ -static int append_gprs_cell_opt(struct bitvec *bv, - const struct gprs_cell_options *gco) -{ - int t3192, drx_timer_max; - - t3192 = encode_t3192(gco->t3192); - if (t3192 < 0) - return t3192; - - drx_timer_max = encode_drx_timer(gco->drx_timer_max); - if (drx_timer_max < 0) - return drx_timer_max; - - bitvec_set_uint(bv, gco->nmo, 2); - - /* See also 3GPP TS 44.060 - Table 12.24.2: GPRS Cell Options information element details */ - bitvec_set_uint(bv, gco->t3168 / 500 - 1, 3); - - bitvec_set_uint(bv, t3192, 3); - bitvec_set_uint(bv, drx_timer_max, 3); - /* ACCESS_BURST_TYPE: Hard-code 8bit */ - bitvec_set_bit(bv, 0); - /* CONTROL_ACK_TYPE: */ - bitvec_set_bit(bv, gco->ctrl_ack_type_use_block); - bitvec_set_uint(bv, gco->bs_cv_max, 4); - - if (0) { - /* hard-code no PAN_{DEC,INC,MAX} */ - bitvec_set_bit(bv, 0); - } else { - /* copied from ip.access BSC protocol trace */ - bitvec_set_bit(bv, 1); - bitvec_set_uint(bv, 1, 3); /* DEC */ - bitvec_set_uint(bv, 1, 3); /* INC */ - bitvec_set_uint(bv, 15, 3); /* MAX */ - } - - if (!gco->ext_info_present) { - /* no extension information */ - bitvec_set_bit(bv, 0); - } else { - /* extension information */ - bitvec_set_bit(bv, 1); - if (!gco->ext_info.egprs_supported) { - /* 6bit length of extension */ - bitvec_set_uint(bv, (1 + 3)-1, 6); - /* EGPRS supported in the cell */ - bitvec_set_bit(bv, 0); - } else { - /* 6bit length of extension */ - bitvec_set_uint(bv, (1 + 5 + 3)-1, 6); - /* EGPRS supported in the cell */ - bitvec_set_bit(bv, 1); - - /* 1bit EGPRS PACKET CHANNEL REQUEST (inverted logic) */ - bitvec_set_bit(bv, !gco->ext_info.use_egprs_p_ch_req); - - /* 4bit BEP PERIOD */ - bitvec_set_uint(bv, gco->ext_info.bep_period, 4); - } - bitvec_set_bit(bv, gco->ext_info.pfc_supported); - bitvec_set_bit(bv, gco->ext_info.dtm_supported); - bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination); - } - - return 0; -} - -static void append_gprs_pwr_ctrl_pars(struct bitvec *bv, - const struct gprs_power_ctrl_pars *pcp) -{ - bitvec_set_uint(bv, pcp->alpha, 4); - bitvec_set_uint(bv, pcp->t_avg_w, 5); - bitvec_set_uint(bv, pcp->t_avg_t, 5); - bitvec_set_uint(bv, pcp->pc_meas_chan, 1); - bitvec_set_uint(bv, pcp->n_avg_i, 4); -} - -/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */ -int rest_octets_si13(uint8_t *data, const struct gsm48_si13_info *si13) -{ - struct bitvec bv; - - memset(&bv, 0, sizeof(bv)); - bv.data = data; - bv.data_len = 20; - - if (0) { - /* No rest octets */ - bitvec_set_bit(&bv, L); - } else { - bitvec_set_bit(&bv, H); - bitvec_set_uint(&bv, si13->bcch_change_mark, 3); - bitvec_set_uint(&bv, si13->si_change_field, 4); - if (1) { - bitvec_set_bit(&bv, 0); - } else { - bitvec_set_bit(&bv, 1); - bitvec_set_uint(&bv, si13->bcch_change_mark, 2); - append_gprs_mobile_alloc(&bv); - } - /* PBCCH not present in cell: - it shall never be indicated according to 3GPP TS 44.018 Table 10.5.2.37b.1 */ - bitvec_set_bit(&bv, 0); - bitvec_set_uint(&bv, si13->rac, 8); - bitvec_set_bit(&bv, si13->spgc_ccch_sup); - bitvec_set_uint(&bv, si13->prio_acc_thr, 3); - bitvec_set_uint(&bv, si13->net_ctrl_ord, 2); - append_gprs_cell_opt(&bv, &si13->cell_opts); - append_gprs_pwr_ctrl_pars(&bv, &si13->pwr_ctrl_pars); - - /* 3GPP TS 44.018 Release 6 / 10.5.2.37b */ - bitvec_set_bit(&bv, H); /* added Release 99 */ - /* claim our SGSN is compatible with Release 99, as EDGE and EGPRS - * was only added in this Release */ - bitvec_set_bit(&bv, 1); - } - bitvec_spare_padding(&bv, (bv.data_len*8)-1); - return bv.data_len; -} diff --git a/src/osmo-bsc/smscb.c b/src/osmo-bsc/smscb.c index b34780b6d..8e1c6348e 100644 --- a/src/osmo-bsc/smscb.c +++ b/src/osmo-bsc/smscb.c @@ -22,10 +22,12 @@ #include <limits.h> #include <osmocom/core/stats.h> +#include <osmocom/core/utils.h> #include <osmocom/core/select.h> #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> #include <osmocom/core/byteswap.h> +#include <osmocom/core/signal.h> #include <osmocom/gsm/cbsp.h> #include <osmocom/gsm/protocol/gsm_23_041.h> @@ -37,11 +39,11 @@ #include <osmocom/bsc/debug.h> #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/smscb.h> -#include <osmocom/bsc/vty.h> #include <osmocom/bsc/gsm_04_08_rr.h> #include <osmocom/bsc/lchan_fsm.h> #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/signal.h> /********************************************************************************* * Helper Functions @@ -58,8 +60,6 @@ static void llist_replace_head(struct llist_head *new, struct llist_head *old) INIT_LLIST_HEAD(old); } -#define ETWS_PRIM_NOTIF_SIZE 56 - /* Build a ETWS Primary Notification message as per TS 23.041 9.4.1.3 */ static int gen_etws_primary_notification(uint8_t *out, uint16_t serial_nr, uint16_t msg_id, uint16_t warn_type, const uint8_t *sec_info) @@ -71,13 +71,24 @@ static int gen_etws_primary_notification(uint8_t *out, uint16_t serial_nr, uint1 osmo_store16be(serial_nr, out); etws->msg_id = osmo_htons(msg_id); etws->warning_type = osmo_htons(warn_type); - - if (sec_info) - memcpy(etws->data, sec_info, ETWS_PRIM_NOTIF_SIZE - sizeof(*etws)); + memcpy(etws->data, sec_info, ETWS_PRIM_NOTIF_SIZE - sizeof(*etws)); return ETWS_PRIM_NOTIF_SIZE; } +static void bts_cbch_init_state(struct bts_smscb_chan_state *cstate, struct gsm_bts *bts) +{ + cstate->bts = bts; + INIT_LLIST_HEAD(&cstate->messages); +} + +void bts_cbch_init(struct gsm_bts *bts) +{ + bts_cbch_init_state(&bts->cbch_basic, bts); + bts_cbch_init_state(&bts->cbch_extended, bts); + osmo_timer_setup(&bts->cbch_timer, &bts_cbch_timer_cb, bts); +} + /*! Obtain SMSCB Channel State for given BTS (basic or extended CBCH) */ struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended) { @@ -110,6 +121,8 @@ static void __bts_smscb_add(struct bts_smscb_chan_state *cstate, struct bts_smsc return; } } + /* we didn't find any messages with longer period than us, insert us at tail */ + llist_add_tail(&new->list, &cstate->messages); } /* stringify a SMSCB for logging */ @@ -242,6 +255,12 @@ static void append_bcast_compl(struct response_state *r_state, struct gsm_bts *b llist_add_tail(¢->list, &r_state->num_completed.list); } +static bool etws_msg_id_matches(uint16_t a, uint16_t b) +{ + /* ETWS messages are identified by the twelve most significant bits of the Message ID */ + return (a & 0xFFF0) == (b & 0xFFF0); +} + /*! Iterate over all BTSs, find matching ones, execute command on BTS, add result * to succeeded/failed lists. * \param[in] net GSM network in which we operate @@ -366,7 +385,7 @@ static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts, smscb->input.dcs = wrepl->u.cbs.dcs; smscb->num_pages = llist_count(&wrepl->u.cbs.msg_content); if (smscb->num_pages > ARRAY_SIZE(smscb->page)) { - LOG_BTS(bts, DCBS, LOGL_ERROR, "SMSCB with too many pages (%u > %lu)\n", + LOG_BTS(bts, DCBS, LOGL_ERROR, "SMSCB with too many pages (%u > %zu)\n", smscb->num_pages, ARRAY_SIZE(smscb->page)); talloc_free(smscb); return NULL; @@ -382,6 +401,9 @@ static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts, page = &smscb->page[i++]; msg_param = (struct gsm23041_msg_param_gsm *) &page->data[0]; + /* ensure we don't overflow in the memcpy below */ + osmo_static_assert(sizeof(*page) > sizeof(*msg_param) + sizeof(cont->data), smscb_space); + /* build 6 byte header according to TS 23.041 9.4.1.2 */ osmo_store16be(wrepl->new_serial_nr, &msg_param->serial_nr); osmo_store16be(wrepl->msg_id, &msg_param->message_id); @@ -391,7 +413,9 @@ static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts, OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(cont->data)); OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(page->data) - sizeof(*msg_param)); - memcpy(&msg_param->content, cont->data, cont->user_len); + /* we must not use cont->user_len as length here, as it would truncate any + * possible 7-bit padding at the end. Always copy the whole page */ + memcpy(&msg_param->content, cont->data, sizeof(cont->data)); bytes_used = sizeof(*msg_param) + cont->user_len; /* compute number of valid blocks in page */ page->num_blocks = bytes_used / 22; @@ -452,6 +476,44 @@ int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg) return cbsp_tx_decoded(cbc, cbsp); } +/* transmit a CBSP RESTART-INDICATION message stating a cell is operative again */ +int cbsp_tx_restart_bts(struct bsc_cbc_link *cbc, bool is_emerg, struct gsm_bts *bts) +{ + struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESTART); + struct osmo_cbsp_cell_ent cell_ent; + struct osmo_cell_global_id *cgi; + + if (is_emerg) + cbsp->u.restart.bcast_msg_type = 0x01; + cbsp->u.restart.recovery_ind = 0x00; /* message data available */ + cbsp->u.restart.cell_list.id_discr = CELL_IDENT_WHOLE_GLOBAL; + + cgi = bts_get_cgi(bts); + cell_ent.cell_id.global = *cgi; + llist_add(&cell_ent.list, &cbsp->u.restart.cell_list.list); + + return cbsp_tx_decoded(cbc, cbsp); +} + +/* transmit a CBSP FAILURE-INDICATION message stating all message data was lost for one cell */ +int cbsp_tx_failure_bts(struct bsc_cbc_link *cbc, bool is_emerg, struct gsm_bts *bts) +{ + struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_FAILURE); + struct osmo_cbsp_fail_ent fail_ent; + struct osmo_cell_global_id *cgi; + + if (is_emerg) + cbsp->u.failure.bcast_msg_type = 0x01; + + cgi = bts_get_cgi(bts); + fail_ent.id_discr = CELL_IDENT_WHOLE_GLOBAL; + fail_ent.cell_id.global = *cgi; + fail_ent.cause = OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_OPERATIONAL; + llist_add(&fail_ent.list, &cbsp->u.failure.fail_list); + + return cbsp_tx_decoded(cbc, cbsp); +} + /* transmit a CBSP KEEPALIVE COMPLETE to the CBC */ static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc) { @@ -463,25 +525,34 @@ static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc) * Per-BTS Processing of CBSP from CBC, called via cbsp_per_bts() *********************************************************************************/ +static void etws_pn_stop(struct gsm_bts *bts, bool timeout) +{ + if (osmo_bts_has_feature(&bts->features, BTS_FEAT_ETWS_PN)) { + LOG_BTS(bts, DCBS, LOGL_NOTICE, "ETWS PN broadcast via PCH disabled (cause=%s)\n", + timeout ? "timeout" : "request"); + rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, NULL, 0); + } + bts->etws.active = false; + if (!timeout) + osmo_timer_del(&bts->etws.timer); +} + /* timer call-back once ETWS warning period has expired */ static void etws_pn_cb(void *data) { struct gsm_bts *bts = (struct gsm_bts *)data; - LOG_BTS(bts, DCBS, LOGL_NOTICE, "ETWS PN Timeout; disabling broadcast via PCH\n"); - rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, NULL, 0); + etws_pn_stop(bts, true); } -static void etws_primary_to_bts(struct gsm_bts *bts, const struct osmo_cbsp_write_replace *wrepl) + +/* the actual "execution" part: Send ETWS to all active lchan in the BTS and via PCH */ +static void bts_send_etws(struct gsm_bts *bts) { - uint8_t etws_primary[ETWS_PRIM_NOTIF_SIZE]; + struct bts_etws_state *bes = &bts->etws; struct gsm_bts_trx *trx; unsigned int count = 0; int i, j; - gen_etws_primary_notification(etws_primary, wrepl->new_serial_nr, wrepl->msg_id, - wrepl->u.emergency.warning_type, - wrepl->u.emergency.warning_sec_info); - /* iterate over all lchan in each TS in each TRX of this BTS */ llist_for_each_entry(trx, &bts->trx_list, list) { for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { @@ -490,8 +561,8 @@ static void etws_primary_to_bts(struct gsm_bts *bts, const struct osmo_cbsp_writ struct gsm_lchan *lchan = &ts->lchan[j]; if (!lchan_may_receive_data(lchan)) continue; - gsm48_send_rr_app_info(lchan, 0x1, 0x0, etws_primary, - sizeof(etws_primary)); + gsm48_send_rr_app_info(lchan, 0x1, 0x0, bes->primary, + sizeof(bes->primary)); count++; } } @@ -502,18 +573,64 @@ static void etws_primary_to_bts(struct gsm_bts *bts, const struct osmo_cbsp_writ /* Notify BTS of primary ETWS notification via vendor-specific Abis message */ if (osmo_bts_has_feature(&bts->features, BTS_FEAT_ETWS_PN)) { - rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, etws_primary, sizeof(etws_primary)); + rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, bes->primary, sizeof(bes->primary)); LOG_BTS(bts, DCBS, LOGL_NOTICE, "Sent ETWS Primary Notification via common channel\n"); - if (wrepl->u.emergency.warning_period != 0xffffffff) { - osmo_timer_setup(&bts->etws_timer, etws_pn_cb, bts); - osmo_timer_schedule(&bts->etws_timer, wrepl->u.emergency.warning_period, 0); - } else - LOG_BTS(bts, DCBS, LOGL_NOTICE, "Unlimited ETWS PN broadcast, this breaks " - "normal network operation due to PCH blockage\n"); } else LOG_BTS(bts, DCBS, LOGL_ERROR, "BTS doesn't support RSL command for ETWS PN\n"); } +static int etws_primary_to_bts(struct gsm_bts *bts, const struct osmo_cbsp_write_replace *wrepl) +{ + struct bts_etws_state *bes = &bts->etws; + + if (bes->active) { + /* we were already broadcasting emergency before receiving this WRITE-REPLACE */ + + /* If only the New Serial Number IE, and not the Old Serial Number IE, is included in the + * WRITE-REPLACE message, then the BSC shall interpret the message as a write request, i.e. a + * broadcast request of a new emergency message without replacing an ongoing emergency message + * broadcast. */ + if (!wrepl->old_serial_nr) { + /* If a write request is received for a cell where an emergency message broadcast is + * currently ongoing, the write request is considered as failed */ + LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP WRITE rejected due to ongoing emergency " + "while no Old Serial Nr IE present in CBSP WRITE\n"); + return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED; + } + + if (!etws_msg_id_matches(*wrepl->old_serial_nr, bes->input.serial_nr)) { + LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP WRITE-REPLACE old_serial 0x%04x doesn't match " + "current serial 0x%04x. Is the CBC confused?\n", + *wrepl->old_serial_nr, bes->input.serial_nr); + /* we allow the WRITE-REPLACE to continue, TS 48.049 doesn't specify how to + * handle situations like this */ + } + } + + /* copy over all the data to per-BTS private state */ + bes->input.msg_id = wrepl->msg_id; + bes->input.serial_nr = wrepl->new_serial_nr; + bes->input.warn_type = wrepl->u.emergency.warning_type; + memcpy(bes->input.sec_info, wrepl->u.emergency.warning_sec_info, sizeof(bes->input.sec_info)); + + /* generate the encoded ETWS PN */ + gen_etws_primary_notification(bes->primary, bes->input.serial_nr, bes->input.msg_id, + bes->input.warn_type, bes->input.sec_info); + + bes->active = true; + + bts_send_etws(bts); + + /* start the expiration timer, if any */ + if (wrepl->u.emergency.warning_period != 0xffffffff) { + osmo_timer_schedule(&bts->etws.timer, wrepl->u.emergency.warning_period, 0); + } else + LOG_BTS(bts, DCBS, LOGL_NOTICE, "Unlimited ETWS PN broadcast, this breaks " + "normal network operation due to PCH blockage\n"); + + return 0; +} + /*! Try to execute a write-replace operation; roll-back if it fails. * \param[in] chan_state BTS CBCH channel state * \param[in] extended_cbch Basic (false) or Extended (true) CBCH @@ -583,8 +700,7 @@ static int bts_rx_write_replace(struct gsm_bts *bts, const struct osmo_cbsp_deco int rc; if (!wrepl->is_cbs) { - etws_primary_to_bts(bts, wrepl); - return 0; + return etws_primary_to_bts(bts, wrepl); } /* check if cell has a CBCH at all */ @@ -630,21 +746,53 @@ static int bts_rx_kill(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec, struct response_state *r_state, void *priv) { const struct osmo_cbsp_kill *kill = &dec->u.kill; - struct bts_smscb_chan_state *chan_state; - struct bts_smscb_message *smscb; - bool extended = false; - if (kill->channel_ind && *kill->channel_ind == 0x01) - extended = true; - chan_state = bts_get_smscb_chan(bts, extended); + if (kill->channel_ind) { + /* KILL for CBS message */ + struct bts_smscb_chan_state *chan_state; + struct bts_smscb_message *smscb; + bool extended = false; - /* Find message by msg_id + old_serial_nr */ - smscb = bts_find_smscb(chan_state, kill->msg_id, kill->old_serial_nr); - if (!smscb) - return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED; + if (*kill->channel_ind == 0x01) + extended = true; + + chan_state = bts_get_smscb_chan(bts, extended); + + /* Find message by msg_id + old_serial_nr */ + smscb = bts_find_smscb(chan_state, kill->msg_id, kill->old_serial_nr); + if (!smscb) + return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED; + + append_bcast_compl(r_state, chan_state->bts, smscb); + + /* Remove it */ + bts_smscb_del(smscb, chan_state, "KILL"); + } else { + /* KILL for Emergency */ + struct bts_etws_state *bes = &bts->etws; + + if (!bes->active) { + LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP KILL (Emerg) but no emergency " + "broadcast is currently active in this cell\n"); + return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED; + } + + if (kill->msg_id != bes->input.msg_id) { + LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP KILL (Emerg) for msg_id 0x%04x, but " + "current emergency msg_id is 0x%04x\n", kill->msg_id, bes->input.msg_id); + return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED; + } + + if (!etws_msg_id_matches(kill->old_serial_nr, bes->input.serial_nr)) { + LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP KILL (Emerg) for old_serial_nr 0x%04x, but " + "current emergency serial_nr is 0x%04x\n", + kill->old_serial_nr, bes->input.serial_nr); + return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED; + } - /* Remove it */ - bts_smscb_del(smscb, chan_state, "KILL"); + /* stop broadcasting the PN in this BTS */ + etws_pn_stop(bts, false); + } return 0; } @@ -666,13 +814,36 @@ static int bts_rx_reset(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list) bts_smscb_del(smscb, chan_state, "RESET"); - osmo_timer_del(&bts->etws_timer); + osmo_timer_del(&bts->etws.timer); /* Make sure that broadcast is disabled */ rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, NULL, 0); return 0; } +static int bts_rx_status_query(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec, + struct response_state *r_state, void *priv) +{ + const struct osmo_cbsp_msg_status_query *query = &dec->u.msg_status_query; + struct bts_smscb_chan_state *chan_state; + struct bts_smscb_message *smscb; + bool extended = false; + + if (query->channel_ind == 0x01) + extended = true; + chan_state = bts_get_smscb_chan(bts, extended); + + /* Find message by msg_id + old_serial_nr */ + smscb = bts_find_smscb(chan_state, query->msg_id, query->old_serial_nr); + if (!smscb) + return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED; + + append_bcast_compl(r_state, chan_state->bts, smscb); + + return 0; +} + + /********************************************************************************* * Receive of CBSP from CBC *********************************************************************************/ @@ -758,11 +929,16 @@ static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded fail->channel_ind = kill->channel_ind; llist_replace_head(&fail->fail_list, &r_state->fail); - fail->cell_list.id_discr = r_state->success.id_discr; - llist_replace_head(&fail->cell_list.list, &r_state->success.list); - - fail->num_compl_list.id_discr = r_state->num_completed.id_discr; - llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list); + /* if the KILL relates to CBS, the "Channel Indicator" IE is present */ + if (kill->channel_ind) { + /* only if it was CBS */ + fail->num_compl_list.id_discr = r_state->num_completed.id_discr; + llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list); + } else { + /* only if it was emergency */ + fail->cell_list.id_discr = r_state->success.id_discr; + llist_replace_head(&fail->cell_list.list, &r_state->success.list); + } } else { resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_COMPL); struct osmo_cbsp_kill_complete *compl = &resp->u.kill_compl; @@ -770,11 +946,16 @@ static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded compl->old_serial_nr = kill->old_serial_nr; compl->channel_ind = kill->channel_ind; - compl->cell_list.id_discr = r_state->success.id_discr; - llist_replace_head(&compl->cell_list.list, &r_state->success.list); - - compl->num_compl_list.id_discr = r_state->num_completed.id_discr; - llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list); + /* if the KILL relates to CBS, the "Channel Indicator" IE is present */ + if (kill->channel_ind) { + /* only if it was CBS */ + compl->num_compl_list.id_discr = r_state->num_completed.id_discr; + llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list); + } else { + /* only if it was emergency */ + compl->cell_list.id_discr = r_state->success.id_discr; + llist_replace_head(&compl->cell_list.list, &r_state->success.list); + } } cbsp_tx_decoded(cbc, resp); @@ -816,6 +997,48 @@ static int cbsp_rx_reset(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decode return rc; } +static int cbsp_rx_status_query(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec) +{ + const struct osmo_cbsp_msg_status_query *query = &dec->u.msg_status_query; + struct gsm_network *net = cbc->net; + struct response_state *r_state = talloc_zero(cbc, struct response_state); + struct osmo_cbsp_decoded *resp; + int rc; + + LOGP(DCBS, LOGL_DEBUG, "CBSP Rx MESSAGE STATUS QUERY\n"); + + rc = cbsp_per_bts(net, r_state, &dec->u.msg_status_query.cell_list, bts_rx_status_query, dec, NULL); + if (rc < 0) { + resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_MSG_STATUS_QUERY_FAIL); + struct osmo_cbsp_msg_status_query_failure *fail = &resp->u.msg_status_query_fail; + fail->msg_id = query->msg_id; + fail->old_serial_nr = query->old_serial_nr; + fail->channel_ind = query->channel_ind; + llist_replace_head(&fail->fail_list, &r_state->fail); + + fail->num_compl_list.id_discr = r_state->num_completed.id_discr; + llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list); + } else { + resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_MSG_STATUS_QUERY_COMPL); + struct osmo_cbsp_msg_status_query_complete *compl = &resp->u.msg_status_query_compl; + compl->msg_id = query->msg_id; + compl->old_serial_nr = query->old_serial_nr; + compl->channel_ind = query->channel_ind; + + if (dec->u.msg_status_query.cell_list.id_discr == CELL_IDENT_BSS) { + /* replace the list of individual cell identities with CELL_IDENT_BSS */ + compl->num_compl_list.id_discr = CELL_IDENT_BSS; + /* no need to free num_completed_list entries, hierarchical talloc works */ + } else { + compl->num_compl_list.id_discr = r_state->num_completed.id_discr; + llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list); + } + } + cbsp_tx_decoded(cbc, resp); + talloc_free(r_state); + return rc; +} + /*! process an incoming, already decoded CBSP message from the CBC. * \param[in] cbc link to the CBC @@ -838,8 +1061,10 @@ int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *de case CBSP_MSGT_RESET: /* stop broadcasting of all messages */ rc = cbsp_rx_reset(cbc, dec); break; - case CBSP_MSGT_LOAD_QUERY: case CBSP_MSGT_MSG_STATUS_QUERY: + rc = cbsp_rx_status_query(cbc, dec); + break; + case CBSP_MSGT_LOAD_QUERY: case CBSP_MSGT_SET_DRX: LOGP(DCBS, LOGL_ERROR, "Received Unimplemented CBSP Message Type %s", get_value_string(cbsp_msg_type_names, dec->msg_type)); @@ -853,54 +1078,76 @@ int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *de return rc; } -/********************************************************************************* - * VTY Interface (Introspection) - *********************************************************************************/ - -static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs) +/* initialize the ETWS state of a BTS */ +void bts_etws_init(struct gsm_bts *bts) { - const struct bts_smscb_message *sm; - - vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE); + bts->etws.active = false; + osmo_timer_setup(&bts->etws.timer, etws_pn_cb, bts); +} - vty_out(vty, " MsgId | SerNo | Pg | Category | Perd | #Tx | #Req | DCS%s", VTY_NEWLINE); - vty_out(vty, "-------|-------|----|---------------|------|------|------|----%s", VTY_NEWLINE); - llist_for_each_entry(sm, &cs->messages, list) { - vty_out(vty, " %04x | %04x | %2u | %13s | %4u | %4u | %4u | %02x%s", - sm->input.msg_id, sm->input.serial_nr, sm->num_pages, - get_value_string(cbsp_category_names, sm->input.category), - sm->input.rep_period, sm->bcast_count, sm->input.num_bcast_req, - sm->input.dcs, VTY_NEWLINE); - } - vty_out(vty, "%s", VTY_NEWLINE); +/* BSC is bootstrapping a BTS; install any currently active ETWS PN */ +static void bts_etws_bootstrap(struct gsm_bts *bts) +{ + if (bts->etws.active) + bts_send_etws(bts); } -DEFUN(bts_show_cbs, bts_show_cbs_cmd, - "show bts <0-255> smscb [(basic|extended)]", - SHOW_STR "Display information about a BTS\n" "BTS number\n" - "SMS Cell Broadcast State\n" - "Show only information related to CBCH BASIC\n" - "Show only information related to CBCH EXTENDED\n") +/* Callback function to be called every time we receive a signal from NM */ +static int nm_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) { - struct gsm_network *net = gsmnet_from_vty(vty); - int bts_nr = atoi(argv[0]); + struct nm_running_chg_signal_data *nsd; struct gsm_bts *bts; + struct gsm_bts_trx *trx; + + if (signal != S_NM_RUNNING_CHG) + return 0; + + nsd = signal_data; + bts = nsd->bts; - if (bts_nr >= net->num_bts) { - vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE); - return CMD_WARNING; + switch (nsd->obj_class) { + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *)nsd->obj; + break; + case NM_OC_BASEB_TRANSC: + trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj); + break; + default: + return 0; } - bts = gsm_bts_num(net, bts_nr); - if (argc < 2 || !strcmp(argv[1], "basic")) - vty_dump_smscb_chan_state(vty, &bts->cbch_basic); - if (argc < 2 || !strcmp(argv[1], "extended")) - vty_dump_smscb_chan_state(vty, &bts->cbch_extended); + struct gsm_lchan *cbch = gsm_bts_get_cbch(bts); + if (!cbch) + return 0; + /* We only care about state changes of TRX holding the CBCH. */ + if (trx != cbch->ts->trx) + return 0; - return CMD_SUCCESS; + if (nsd->running) { + if (trx_is_usable(trx)) { + LOG_BTS(bts, DCBS, LOGL_INFO, "BTS becomes available for CBCH\n"); + /* Start CBCH transmit timer if CBCH is present */ + bts_cbch_timer_schedule(trx->bts); + /* Start ETWS/PWS Primary Notification, if active */ + bts_etws_bootstrap(trx->bts); + cbsp_tx_restart_bts(bts->network->cbc, true, bts); + cbsp_tx_restart_bts(bts->network->cbc, false, bts); + } + } else { + if (osmo_timer_pending(&bts->cbch_timer)) { + /* If timer is ongoing it means CBCH was available */ + LOG_BTS(bts, DCBS, LOGL_INFO, "BTS becomes unavailable for CBCH\n"); + osmo_timer_del(&bts->cbch_timer); + cbsp_tx_failure_bts(bts->network->cbc, true, bts); + cbsp_tx_failure_bts(bts->network->cbc, false, bts); + } /* else: CBCH was already unavailable before */ + } + return 0; } -void smscb_vty_init(void) +/* To be called once at startup of the process: */ +void smscb_global_init(void) { - install_element_ve(&bts_show_cbs_cmd); + osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL); } diff --git a/src/osmo-bsc/smscb_vty.c b/src/osmo-bsc/smscb_vty.c new file mode 100644 index 000000000..b13d2db07 --- /dev/null +++ b/src/osmo-bsc/smscb_vty.c @@ -0,0 +1,421 @@ +/* CBSP (Cell Broadcast Service Protocol) Handling for OsmoBSC */ +/* + * (C) 2019 by Harald Welte <laforge@gnumonks.org> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/vty.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/smscb.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/bts.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/socket.h> +#include <osmocom/gsm/cbsp.h> + +/********************************************************************************* + * cbc + *********************************************************************************/ +static struct bsc_cbc_link *vty_cbc_data(struct vty *vty) +{ + return bsc_gsmnet->cbc; +} + +DEFUN(cfg_cbc, cfg_cbc_cmd, + "cbc", "Configure CBSP Link to Cell Broadcast Centre\n") +{ + vty->node = CBC_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_mode, cfg_cbc_mode_cmd, + "mode (server|client|disabled)", + "Set OsmoBSC as CBSP server or client\n" + "CBSP Server: listen for inbound TCP connections from a remote Cell Broadcast Centre\n" + "CBSP Client: establish outbound TCP connection to a remote Cell Broadcast Centre\n" + "Disable CBSP link\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->mode = get_string_value(bsc_cbc_link_mode_names, argv[0]); + OSMO_ASSERT(cbc->mode >= 0); + + /* Immediately restart/stop CBSP only when coming from a telnet session. The settings from the config file take + * effect in osmo_bsc_main.c's invocation of bsc_cbc_link_restart(). */ + if (vty->type != VTY_FILE) + bsc_cbc_link_restart(); + + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_server, cfg_cbc_server_cmd, + "server", "Configure OsmoBSC's CBSP server role\n") +{ + vty->node = CBC_SERVER_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_server_local_ip, cfg_cbc_server_local_ip_cmd, + "local-ip " VTY_IPV46_CMD, + "Set IP Address to listen on for inbound CBSP from a Cell Broadcast Centre\n" + "IPv4 address\n" "IPv6 address\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + osmo_sockaddr_str_from_str(&cbc->server.local_addr, argv[0], cbc->server.local_addr.port); + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_server_local_port, cfg_cbc_server_local_port_cmd, + "local-port <1-65535>", + "Set TCP port to listen on for inbound CBSP from a Cell Broadcast Centre\n" + "CBSP port number (Default: " OSMO_STRINGIFY_VAL(CBSP_TCP_PORT) ")\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->server.local_addr.port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_client, cfg_cbc_client_cmd, + "client", "Configure OsmoBSC's CBSP client role\n") +{ + vty->node = CBC_CLIENT_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_client_remote_ip, cfg_cbc_client_remote_ip_cmd, + "remote-ip " VTY_IPV46_CMD, + "Set IP Address of the Cell Broadcast Centre, to establish CBSP link to\n" + "IPv4 address\n" "IPv6 address\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + osmo_sockaddr_str_from_str(&cbc->client.remote_addr, argv[0], cbc->client.remote_addr.port); + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_client_remote_port, cfg_cbc_client_remote_port_cmd, + "remote-port <1-65535>", + "Set TCP port of the Cell Broadcast Centre, to establish CBSP link to\n" + "CBSP port number (Default: " OSMO_STRINGIFY_VAL(CBSP_TCP_PORT) ")\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->client.remote_addr.port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_client_local_ip, cfg_cbc_client_local_ip_cmd, + "local-ip " VTY_IPV46_CMD, + "Set local bind address for the outbound CBSP link to the Cell Broadcast Centre\n" + "IPv4 address\n" "IPv6 address\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + osmo_sockaddr_str_from_str(&cbc->client.local_addr, argv[0], cbc->client.local_addr.port); + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_client_local_port, cfg_cbc_client_local_port_cmd, + "local-port <1-65535>", + "Set local bind port for the outbound CBSP link to the Cell Broadcast Centre\n" + "port number\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->client.local_addr.port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_client_no_local_ip, cfg_cbc_client_no_local_ip_cmd, + "no local-ip", + NO_STR "Remove local IP address bind config for the CBSP client mode\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->client.local_addr = (struct osmo_sockaddr_str){ .port = cbc->client.local_addr.port }; + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_client_no_local_port, cfg_cbc_client_no_local_port_cmd, + "no local-port", + NO_STR "Remove local TCP port bind config for the CBSP client mode\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->client.local_addr.port = 0; + return CMD_SUCCESS; +} + +static struct cmd_node cbc_node = { + CBC_NODE, + "%s(config-cbc)# ", + 1, +}; + +static struct cmd_node cbc_server_node = { + CBC_SERVER_NODE, + "%s(config-cbc-server)# ", + 1, +}; + +static struct cmd_node cbc_client_node = { + CBC_CLIENT_NODE, + "%s(config-cbc-client)# ", + 1, +}; + +static int config_write_cbc(struct vty *vty) +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + + bool default_server_local; + bool default_client_remote; + bool default_client_local; + + default_server_local = !osmo_sockaddr_str_cmp(&cbc->server.local_addr, + &bsc_cbc_default_server_local_addr); + default_client_remote = !osmo_sockaddr_str_is_set(&cbc->client.remote_addr); + default_client_local = !osmo_sockaddr_str_is_set(&cbc->client.local_addr); + + /* If all reflects default values, skip the 'cbc' section */ + if (cbc->mode == BSC_CBC_LINK_MODE_DISABLED + && default_server_local + && default_client_remote && default_client_local) + return 0; + + vty_out(vty, "cbc%s", VTY_NEWLINE); + vty_out(vty, " mode %s%s", bsc_cbc_link_mode_name(cbc->mode), VTY_NEWLINE); + + if (!default_server_local) { + vty_out(vty, " server%s", VTY_NEWLINE); + + if (strcmp(cbc->server.local_addr.ip, bsc_cbc_default_server_local_addr.ip)) + vty_out(vty, " local-ip %s%s", cbc->server.local_addr.ip, VTY_NEWLINE); + if (cbc->server.local_addr.port != bsc_cbc_default_server_local_addr.port) + vty_out(vty, " local-port %u%s", cbc->server.local_addr.port, VTY_NEWLINE); + } + + if (!(default_client_remote && default_client_local)) { + vty_out(vty, " client%s", VTY_NEWLINE); + + if (osmo_sockaddr_str_is_set(&cbc->client.remote_addr)) { + vty_out(vty, " remote-ip %s%s", cbc->client.remote_addr.ip, VTY_NEWLINE); + if (cbc->client.remote_addr.port != CBSP_TCP_PORT) + vty_out(vty, " remote-port %u%s", cbc->client.remote_addr.port, VTY_NEWLINE); + } + + if (cbc->client.local_addr.ip[0]) + vty_out(vty, " local-ip %s%s", cbc->client.local_addr.ip, VTY_NEWLINE); + if (cbc->client.local_addr.port) + vty_out(vty, " local-port %u%s", cbc->client.local_addr.port, VTY_NEWLINE); + } + + return 0; +} + +DEFUN(show_cbc, show_cbc_cmd, + "show cbc", + SHOW_STR "Display state of CBC / CBSP\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + + switch (cbc->mode) { + case BSC_CBC_LINK_MODE_DISABLED: + vty_out(vty, "CBSP link is disabled%s", VTY_NEWLINE); + break; + + case BSC_CBC_LINK_MODE_SERVER: + vty_out(vty, "OsmoBSC is configured as CBSP Server on " OSMO_SOCKADDR_STR_FMT "%s", + OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->server.local_addr), VTY_NEWLINE); + vty_out(vty, "CBSP Server Connection: %s%s", + cbc->server.sock_name ? cbc->server.sock_name : "Disconnected", VTY_NEWLINE); + break; + + case BSC_CBC_LINK_MODE_CLIENT: + vty_out(vty, "OsmoBSC is configured as CBSP Client to remote CBC at " OSMO_SOCKADDR_STR_FMT "%s", + OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->client.remote_addr), VTY_NEWLINE); + vty_out(vty, "CBSP Client Connection: %s%s", + cbc->client.sock_name ? cbc->client.sock_name : "Disconnected", VTY_NEWLINE); + break; + } + return CMD_SUCCESS; +} + +/* --- Deprecated 'cbc' commands for backwards compat --- */ + +DEFUN_DEPRECATED(cfg_cbc_remote_ip, cfg_cbc_remote_ip_cmd, + "remote-ip A.B.C.D", + "IP Address of the Cell Broadcast Centre\n" + "IP Address of the Cell Broadcast Centre\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + vty_out(vty, "%% cbc/remote-ip config is deprecated, instead use cbc/client/remote-ip and cbc/ mode%s", + VTY_NEWLINE); + osmo_sockaddr_str_from_str(&cbc->client.remote_addr, argv[0], cbc->client.remote_addr.port); + cbc->mode = BSC_CBC_LINK_MODE_CLIENT; + if (vty->type != VTY_FILE) + bsc_cbc_link_restart(); + return CMD_SUCCESS; +} +DEFUN_DEPRECATED(cfg_cbc_no_remote_ip, cfg_cbc_no_remote_ip_cmd, + "no remote-ip", + NO_STR "Remove IP address of CBC; disables outbound CBSP connections\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + vty_out(vty, "%% cbc/remote-ip config is deprecated, instead use cbc/client/remote-ip and cbc/mode%s", + VTY_NEWLINE); + if (cbc->mode == BSC_CBC_LINK_MODE_CLIENT) { + cbc->mode = BSC_CBC_LINK_MODE_DISABLED; + if (vty->type != VTY_FILE) + bsc_cbc_link_restart(); + } + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_cbc_remote_port, cfg_cbc_remote_port_cmd, + "remote-port <1-65535>", + "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n" + "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + vty_out(vty, "%% cbc/remote-port config is deprecated, instead use cbc/client/remote-port%s", + VTY_NEWLINE); + cbc->client.remote_addr.port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_cbc_listen_port, cfg_cbc_listen_port_cmd, + "listen-port <1-65535>", + "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n" + "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + vty_out(vty, "%% cbc/listen-port config is deprecated, instead use cbc/server/local-port and cbc/mode%s", + VTY_NEWLINE); + cbc->mode = BSC_CBC_LINK_MODE_SERVER; + cbc->server.local_addr.port = atoi(argv[0]); + if (vty->type != VTY_FILE) + bsc_cbc_link_restart(); + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_cbc_no_listen_port, cfg_cbc_no_listen_port_cmd, + "no listen-port", + NO_STR "Remove CBSP Listen Port; disables inbound CBSP connections\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + vty_out(vty, "%% cbc/listen-port config is deprecated, instead use cbc/server/local-port and cbc/mode%s", + VTY_NEWLINE); + if (cbc->mode == BSC_CBC_LINK_MODE_SERVER) { + cbc->mode = BSC_CBC_LINK_MODE_DISABLED; + if (vty->type != VTY_FILE) + bsc_cbc_link_restart(); + } + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_cbc_listen_ip, cfg_cbc_listen_ip_cmd, + "listen-ip A.B.C.D", + "Local IP Address where BSC listens for incoming CBC connections (Default: 127.0.0.1)\n" + "Local IP Address where BSC listens for incoming CBC connections\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + vty_out(vty, "%% cbc/listen-ip config is deprecated, instead use cbc/server/local-ip%s", + VTY_NEWLINE); + osmo_sockaddr_str_from_str(&cbc->server.local_addr, argv[0], cbc->server.local_addr.port); + return CMD_SUCCESS; +} + +void cbc_vty_init(void) +{ + install_element_ve(&show_cbc_cmd); + + install_element(CONFIG_NODE, &cfg_cbc_cmd); + install_node(&cbc_node, config_write_cbc); + install_element(CBC_NODE, &cfg_cbc_mode_cmd); + + install_element(CBC_NODE, &cfg_cbc_server_cmd); + install_node(&cbc_server_node, NULL); + install_element(CBC_SERVER_NODE, &cfg_cbc_server_local_ip_cmd); + install_element(CBC_SERVER_NODE, &cfg_cbc_server_local_port_cmd); + + install_element(CBC_NODE, &cfg_cbc_client_cmd); + install_node(&cbc_client_node, NULL); + install_element(CBC_CLIENT_NODE, &cfg_cbc_client_remote_ip_cmd); + install_element(CBC_CLIENT_NODE, &cfg_cbc_client_remote_port_cmd); + install_element(CBC_CLIENT_NODE, &cfg_cbc_client_local_ip_cmd); + install_element(CBC_CLIENT_NODE, &cfg_cbc_client_local_port_cmd); + install_element(CBC_CLIENT_NODE, &cfg_cbc_client_no_local_ip_cmd); + install_element(CBC_CLIENT_NODE, &cfg_cbc_client_no_local_port_cmd); + + /* Deprecated, for backwards compat */ + install_element(CBC_NODE, &cfg_cbc_remote_ip_cmd); + install_element(CBC_NODE, &cfg_cbc_no_remote_ip_cmd); + install_element(CBC_NODE, &cfg_cbc_remote_port_cmd); + install_element(CBC_NODE, &cfg_cbc_listen_port_cmd); + install_element(CBC_NODE, &cfg_cbc_no_listen_port_cmd); + install_element(CBC_NODE, &cfg_cbc_listen_ip_cmd); +} + + +/********************************************************************************* + * smscb + *********************************************************************************/ +static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs) +{ + const struct bts_smscb_message *sm; + + vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE); + + vty_out(vty, " MsgId | SerNo | Pg | Category | Perd | #Tx | #Req | DCS%s", VTY_NEWLINE); + vty_out(vty, "-------|-------|----|---------------|------|------|------|----%s", VTY_NEWLINE); + llist_for_each_entry(sm, &cs->messages, list) { + vty_out(vty, " %04x | %04x | %2u | %13s | %4u | %4u | %4u | %02x%s", + sm->input.msg_id, sm->input.serial_nr, sm->num_pages, + get_value_string(cbsp_category_names, sm->input.category), + sm->input.rep_period, sm->bcast_count, sm->input.num_bcast_req, + sm->input.dcs, VTY_NEWLINE); + } + vty_out(vty, "%s", VTY_NEWLINE); +} + +DEFUN(bts_show_cbs, bts_show_cbs_cmd, + "show bts <0-255> smscb [(basic|extended)]", + SHOW_STR "Display information about a BTS\n" "BTS number\n" + "SMS Cell Broadcast State\n" + "Show only information related to CBCH BASIC\n" + "Show only information related to CBCH EXTENDED\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int bts_nr = atoi(argv[0]); + struct gsm_bts *bts; + + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + + if (argc < 2 || !strcmp(argv[1], "basic")) + vty_dump_smscb_chan_state(vty, &bts->cbch_basic); + if (argc < 2 || !strcmp(argv[1], "extended")) + vty_dump_smscb_chan_state(vty, &bts->cbch_extended); + + return CMD_SUCCESS; +} + +void smscb_vty_init(void) +{ + install_element_ve(&bts_show_cbs_cmd); +} diff --git a/src/osmo-bsc/system_information.c b/src/osmo-bsc/system_information.c index b9699899f..cc2e788d7 100644 --- a/src/osmo-bsc/system_information.c +++ b/src/osmo-bsc/system_information.c @@ -31,13 +31,13 @@ #include <osmocom/core/utils.h> #include <osmocom/gsm/sysinfo.h> #include <osmocom/gsm/gsm48_ie.h> +#include <osmocom/gsm/gsm48_rest_octets.h> #include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/gsm48_arfcn_range_encode.h> #include <osmocom/bsc/debug.h> #include <osmocom/bsc/gsm_data.h> #include <osmocom/bsc/abis_rsl.h> -#include <osmocom/bsc/rest_octets.h> -#include <osmocom/bsc/arfcn_range_encode.h> #include <osmocom/bsc/gsm_04_08_rr.h> #include <osmocom/bsc/acc.h> #include <osmocom/bsc/neighbor_ident.h> @@ -52,7 +52,7 @@ struct gsm0808_cell_id_list2; * array. DCS1800 and PCS1900 can not be used at the same time so conserve * memory and do the below. */ -static int band_compatible(const struct gsm_bts *bts, int arfcn) +int band_compatible(const struct gsm_bts *bts, int arfcn) { enum gsm_band band; @@ -169,7 +169,12 @@ static int make_si2quaters(struct gsm_bts *bts, bool counting) si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater; } - rc = rest_octets_si2quater(si2q->rest_octets, bts); + rc = osmo_gsm48_rest_octets_si2quater_encode(si2q->rest_octets, bts->si2q_index, + bts->si2q_count, bts->si_common.data.uarfcn_list, + &bts->u_offset, bts->si_common.uarfcn_length, + bts->si_common.data.scramble_list, + &bts->si_common.si2quater_neigh_list, + &bts->e_offset); if (rc < 0) return rc; @@ -211,11 +216,40 @@ static inline uint16_t encode_fdd(uint16_t scramble, bool diversity) return scramble; } +int bts_earfcn_del(struct gsm_bts *bts, uint16_t earfcn) +{ + struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; + int r; + + r = osmo_earfcn_del(e, earfcn); + + if (r < 0) + return r; + + /* If the last earfcn was removed, invlidate common neighbours limitations */ + if (si2q_earfcn_count(e) == 0) { + e->thresh_lo_valid = false; + e->qrxlm_valid = false; + e->prio_valid = false; + } + + return r; +} + int bts_earfcn_add(struct gsm_bts *bts, uint16_t earfcn, uint8_t thresh_hi, uint8_t thresh_lo, uint8_t prio, uint8_t qrx, uint8_t meas_bw) { struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; - int r = osmo_earfcn_add(e, earfcn, (meas_bw < EARFCN_MEAS_BW_INVALID) ? meas_bw : OSMO_EARFCN_MEAS_INVALID); + int r; + + /* EARFCN may already exist, so we delete it to avoid duplicates */ + if (bts_earfcn_del(bts, earfcn) == 0) + LOGP(DRR, LOGL_NOTICE, "EARFCN %u is already in the list, modifying\n", earfcn); + + if (meas_bw < EARFCN_MEAS_BW_INVALID) + r = osmo_earfcn_add(e, earfcn, meas_bw); + else + r = osmo_earfcn_add(e, earfcn, OSMO_EARFCN_MEAS_INVALID); if (r < 0) return r; @@ -293,16 +327,21 @@ int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool size_t len = bts->si_common.uarfcn_length, i; uint8_t si2q; int pos = uarfcn_sc_pos(bts, arfcn, scramble); - uint16_t scr = diversity ? encode_fdd(scramble, true) : encode_fdd(scramble, false), + uint16_t scr = encode_fdd(scramble, diversity), *ual = bts->si_common.data.uarfcn_list, *scl = bts->si_common.data.scramble_list; + if (pos >= 0) { + LOGP(DRR, LOGL_NOTICE, + "EARFCN (%u, %u) is already in the list, modifying\n", + arfcn, scramble); + scl[pos] = scr; + return 0; + } + if (len == MAX_EARFCN_LIST) return -ENOMEM; - if (pos >= 0) - return -EADDRINUSE; - /* find the suitable position for arfcn if any */ pos = uarfcn_sc_pos(bts, arfcn, SC_BOUND); i = (pos < 0) ? len : pos; @@ -426,7 +465,7 @@ static inline int enc_freq_lst_var_bitmap(uint8_t *chan_list, return 0; } -int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w, +int range_encode(enum osmo_gsm48_range r, int *arfcns, int arfcns_used, int *w, int f0, uint8_t *chan_list) { /* @@ -435,22 +474,22 @@ int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w, */ int rc, f0_included; - range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included); + osmo_gsm48_range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included); - rc = range_enc_arfcns(r, arfcns, arfcns_used, w, 0); + rc = osmo_gsm48_range_enc_arfcns(r, arfcns, arfcns_used, w, 0); if (rc < 0) return rc; /* Select the range and the amount of bits needed */ switch (r) { - case ARFCN_RANGE_128: - return range_enc_range128(chan_list, f0, w); - case ARFCN_RANGE_256: - return range_enc_range256(chan_list, f0, w); - case ARFCN_RANGE_512: - return range_enc_range512(chan_list, f0, w); - case ARFCN_RANGE_1024: - return range_enc_range1024(chan_list, f0, f0_included, w); + case OSMO_GSM48_ARFCN_RANGE_128: + return osmo_gsm48_range_enc_128(chan_list, f0, w); + case OSMO_GSM48_ARFCN_RANGE_256: + return osmo_gsm48_range_enc_256(chan_list, f0, w); + case OSMO_GSM48_ARFCN_RANGE_512: + return osmo_gsm48_range_enc_512(chan_list, f0, w); + case OSMO_GSM48_ARFCN_RANGE_1024: + return osmo_gsm48_range_enc_1024(chan_list, f0, f0_included, w); default: return -ERANGE; }; @@ -463,8 +502,8 @@ static inline int enc_freq_lst_range(uint8_t *chan_list, const struct bitvec *bv, const struct gsm_bts *bts, bool bis, bool ter, bool pgsm) { - int arfcns[RANGE_ENC_MAX_ARFCNS]; - int w[RANGE_ENC_MAX_ARFCNS]; + int arfcns[OSMO_GSM48_RANGE_ENC_MAX_ARFCNS]; + int w[OSMO_GSM48_RANGE_ENC_MAX_ARFCNS]; int arfcns_used = 0; int i, range, f0; @@ -483,8 +522,8 @@ static inline int enc_freq_lst_range(uint8_t *chan_list, /* * Check if the given list of ARFCNs can be encoded. */ - range = range_enc_determine_range(arfcns, arfcns_used, &f0); - if (range == ARFCN_RANGE_INVALID) + range = osmo_gsm48_range_enc_determine_range(arfcns, arfcns_used, &f0); + if (range == OSMO_GSM48_ARFCN_RANGE_INVALID) return -2; memset(w, 0, sizeof(w)); @@ -499,20 +538,29 @@ static int bitvec2freq_list(uint8_t *chan_list, const struct bitvec *bv, bool pgsm = false; memset(chan_list, 0, 16); - if (bts->band == GSM_BAND_900 - && bts->c0->arfcn >= 1 && bts->c0->arfcn <= 124) + /* According to 3GPP TS 44.018, section 10.5.2.1b.2, only ARFCN values + * in range 1..124 can be encoded using the 'bit map 0' format. */ + if (bts->band == GSM_BAND_900) pgsm = true; + /* Check presence of E-GSM ARFCN 0 */ + if (pgsm && bitvec_get_bit_pos(bv, 0) == ONE) + pgsm = false; + /* Check presence of R-GSM / E-GSM ARFCNs 955..1023 */ + for (i = 955; pgsm && i <= 1023; i++) { + if (bitvec_get_bit_pos(bv, i) == ONE) + pgsm = false; + } + /* P-GSM-only handsets only support 'bit map 0 format' */ if (!bis && !ter && pgsm) { chan_list[0] = 0; - for (i = 0; i < bv->data_len*8; i++) { - if (i >= 1 && i <= 124 - && bitvec_get_bit_pos(bv, i)) { - rc = freq_list_bm0_set_arfcn(chan_list, i); - if (rc < 0) - return rc; - } + for (i = 1; i <= 124; i++) { + if (!bitvec_get_bit_pos(bv, i)) + continue; + rc = freq_list_bm0_set_arfcn(chan_list, i); + if (rc < 0) + return rc; } return 0; } @@ -548,7 +596,7 @@ static int bitvec2freq_list(uint8_t *chan_list, const struct bitvec *bv, max = i; } - if (max == -1) { + if (arfcns == 0) { /* Empty set, use 'bit map 0 format' */ chan_list[0] = 0; return 0; @@ -569,51 +617,63 @@ static int bitvec2freq_list(uint8_t *chan_list, const struct bitvec *bv, return -EINVAL; } -/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ -int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts) +/* (Re)generate Cell Allocation (basically a bit-vector of all cell channels) */ +int generate_cell_chan_alloc(struct gsm_bts *bts) { - struct gsm_bts_trx *trx; - struct bitvec *bv = &bts->si_common.cell_alloc; - - /* Zero-initialize the bit-vector */ - memset(bv->data, 0, bv->data_len); + const struct gsm_bts_trx *trx; + unsigned int chan, chan_num; + unsigned int tn; + + /* Temporary Cell Allocation bit-vector */ + uint8_t ca[1024 / 8] = { 0 }; + struct bitvec bv = { + .data_len = sizeof(ca), + .data = &ca[0], + }; - /* first we generate a bitvec of all TRX ARFCN's in our BTS */ + /* Calculate a bit-mask of all assigned ARFCNs */ llist_for_each_entry(trx, &bts->trx_list, list) { - unsigned int i, j; /* Always add the TRX's ARFCN */ - bitvec_set_bit_pos(bv, trx->arfcn, 1); - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; + bitvec_set_bit_pos(&bv, trx->arfcn, 1); + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + const struct gsm_bts_trx_ts *ts = &trx->ts[tn]; /* Add any ARFCNs present in hopping channels */ - for (j = 0; j < 1024; j++) { - if (bitvec_get_bit_pos(&ts->hopping.arfcns, j)) - bitvec_set_bit_pos(bv, j, 1); + for (chan = 0; chan < sizeof(ca) * 8; chan++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, chan) == ONE) + bitvec_set_bit_pos(&bv, chan, ONE); } } } - /* then we generate a GSM 04.08 frequency list from the bitvec */ - return bitvec2freq_list(chan_list, bv, bts, false, false); -} + /* Calculate the overall number of assigned ARFCNs */ + for (chan_num = 0, chan = 0; chan < sizeof(ca) * 8; chan++) { + if (bitvec_get_bit_pos(&bv, chan) == ONE) + chan_num++; + } -struct generate_bcch_chan_list__ni_iter_data { - struct gsm_bts *bts; - struct bitvec *bv; -}; + /* The Mobile Allocation IE may contain up to 64 bits, so here we + * cannot allow more than 64 channels in Cell Allocation. */ + if (chan_num > 64) { + LOGP(DRR, LOGL_ERROR, "Failed to (re)generate Cell Allocation: " + "number of channels (%u) exceeds the maximum number of " + "ARFCNs in Mobile Allocation (64)\n", chan_num); + return -E2BIG; + } -static bool generate_bcch_chan_list__ni_iter_cb(const struct neighbor_ident_key *key, - const struct gsm0808_cell_id_list2 *val, - void *cb_data) -{ - struct generate_bcch_chan_list__ni_iter_data *data = cb_data; + /* Commit the new Channel Allocation */ + memcpy(&bts->si_common.data.cell_alloc[0], ca, sizeof(ca)); + bts->si_common.cell_chan_num = chan_num; - if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS - && key->from_bts != data->bts->nr) - return true; + return 0; +} - bitvec_set_bit_pos(data->bv, key->arfcn, 1); - return true; +/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ +int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts) +{ + const struct bitvec *bv = &bts->si_common.cell_alloc; + + /* generate a Frequency List from the Cell Allocation */ + return bitvec2freq_list(chan_list, bv, bts, false, false); } /*! generate a cell channel list as per Section 10.5.2.22 of 04.08 @@ -641,7 +701,7 @@ static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts, /* Zero-initialize the bit-vector */ memset(bv->data, 0, bv->data_len); - if (llist_empty(&bts->local_neighbors)) { + if (llist_empty(&bts->neighbors)) { /* There are no explicit neighbors, assume all BTS are. */ llist_for_each_entry(cur_bts, &bts->network->bts_list, list) { if (cur_bts == bts) @@ -650,21 +710,23 @@ static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts, } } else { /* Only add explicit neighbor cells */ - struct gsm_bts_ref *neigh; - llist_for_each_entry(neigh, &bts->local_neighbors, entry) { - bitvec_set_bit_pos(bv, neigh->bts->c0->arfcn, 1); + struct neighbor *n; + llist_for_each_entry(n, &bts->neighbors, entry) { + if (n->type == NEIGHBOR_TYPE_CELL_ID && n->cell_id.ab_present) { + bitvec_set_bit_pos(bv, n->cell_id.ab.arfcn, 1); + } else { + struct gsm_bts *neigh_bts; + if (resolve_local_neighbor(&neigh_bts, bts, n)) { + LOGP(DHO, LOGL_ERROR, + "Neither local nor remote neighbor: BTS %u -> %s\n", + bts->nr, neighbor_to_str_c(OTC_SELECT, n)); + continue; + } + if (neigh_bts->c0) + bitvec_set_bit_pos(bv, neigh_bts->c0->arfcn, 1); + } } } - - /* Also add neighboring BSS cells' ARFCNs */ - { - struct generate_bcch_chan_list__ni_iter_data data = { - .bv = bv, - .bts = bts, - }; - neighbor_ident_iter(bts->network->neighbor_bss_cells, - generate_bcch_chan_list__ni_iter_cb, &data); - } } /* then we generate a GSM 04.08 frequency list from the bitvec */ @@ -690,7 +752,7 @@ static int list_arfcn(uint8_t *chan_list, uint8_t mask, char *text) struct gsm_sysinfo_freq freq[1024]; memset(freq, 0, sizeof(freq)); - gsm48_decode_freq_list(freq, chan_list, 16, 0xce, 1); + gsm48_decode_freq_list(freq, chan_list, 16, mask, 1); for (i = 0; i < 1024; i++) { if (freq[i].mask) { if (!n) @@ -729,7 +791,19 @@ static int generate_si1(enum osmo_sysinfo_type t, struct gsm_bts *bts) * SI1 Rest Octets (10.5.2.32), contains NCH position and band * indicator but that is not in the 04.08. */ - rc = rest_octets_si1(si1->rest_octets, NULL, is_dcs_net(bts)); + if (bts->nch.num_blocks) { + rc = osmo_gsm48_si1ro_nch_pos_encode(bts->nch.num_blocks, bts->nch.first_block); + if (rc < 0) { + LOGP(DRR, LOGL_ERROR, "Unable to encode NCH position (num_blocks=%u, first_block=%u)\n", + bts->nch.num_blocks, bts->nch.first_block); + rc = osmo_gsm48_rest_octets_si1_encode(si1->rest_octets, NULL, is_dcs_net(bts)); + } else { + uint8_t nch_pos = rc; + rc = osmo_gsm48_rest_octets_si1_encode(si1->rest_octets, &nch_pos, is_dcs_net(bts)); + } + } else { + rc = osmo_gsm48_rest_octets_si1_encode(si1->rest_octets, NULL, is_dcs_net(bts)); + } return sizeof(*si1) + rc; } @@ -759,6 +833,20 @@ static int generate_si2(enum osmo_sysinfo_type t, struct gsm_bts *bts) return sizeof(*si2); } +/* Generate SI2bis Rest Octests 3GPP TS 44.018 Table 10.5.2.33.1 */ +static int rest_octets_si2bis(uint8_t *data) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 1; + + bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); + + return bv.data_len; +} + static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) { int rc; @@ -796,6 +884,24 @@ static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) return sizeof(*si2b) + rc; } + +/* Generate SI2ter Rest Octests 3GPP TS 44.018 Table 10.5.2.33a.1 */ +static int rest_octets_si2ter(uint8_t *data) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 4; + + /* No SI2ter_MP_CHANGE_MARK */ + bitvec_set_bit(&bv, L); + + bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); + + return bv.data_len; +} + static int generate_si2ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) { int rc; @@ -865,7 +971,7 @@ static int generate_si2quater(enum osmo_sysinfo_type t, struct gsm_bts *bts) return sizeof(*si2q) + rc; } -static struct gsm48_si_ro_info si_info = { +static struct osmo_gsm48_si_ro_info si_info = { .selection_params = { .present = 0, }, @@ -903,6 +1009,10 @@ static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts) si3->header.skip_indicator = 0; si3->header.system_information = GSM48_MT_RR_SYSINFO_3; + /* The value in bts->si_common.chan_desc may get out of sync with the actual value + * in net->T_defs (e.g. after changing it via the VTY), so we need to sync it here. */ + bts->si_common.chan_desc.t3212 = osmo_tdef_get(bts->network->T_defs, 3212, OSMO_TDEF_CUSTOM, 0); + si3->cell_identity = htons(bts->cell_identity); gsm48_generate_lai2(&si3->lai, bts_lai(bts)); si3->control_channel_desc = bts->si_common.chan_desc; @@ -934,7 +1044,7 @@ static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts) CBQ, CELL_RESELECT_OFFSET, TEMPORARY_OFFSET, PENALTY_TIME Power Offset, 2ter Indicator, Early Classmark Sending, Scheduling if and WHERE, GPRS Indicator, SI13 position */ - rc = rest_octets_si3(si3->rest_octets, &si_info); + rc = osmo_gsm48_rest_octets_si3_encode(si3->rest_octets, &si_info); return sizeof(*si3) + rc; } @@ -966,8 +1076,11 @@ static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts) const struct gsm_bts_trx_ts *ts = cbch_lchan->ts; struct gsm48_chan_desc cd; - /* 10.5.2.5 (TV) CBCH Channel Description IE */ - gsm48_lchan2chan_desc_as_configured(&cd, cbch_lchan); + /* 10.5.2.5 (TV) CBCH Channel Description IE. + * CBCH is never in VAMOS mode, so just pass allow_osmo_cbits == false. */ + if (gsm48_lchan_and_pchan2chan_desc(&cd, cbch_lchan, cbch_lchan->ts->pchan_from_config, + gsm_ts_tsc(cbch_lchan->ts), false)) + return -EINVAL; tail = tv_fixed_put(tail, GSM48_IE_CBCH_CHAN_DESC, sizeof(cd), (uint8_t *) &cd); l2_plen += 1 + sizeof(cd); @@ -989,7 +1102,7 @@ static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts) /* SI4 Rest Octets (10.5.2.35), containing Optional Power offset, GPRS Indicator, Cell Identity, LSA ID, Selection Parameter */ - rc = rest_octets_si4(tail, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - tail); + rc = osmo_gsm48_rest_octets_si4_encode(tail, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - tail); return l2_plen + 1 + rc; } @@ -1002,15 +1115,10 @@ static int generate_si5(enum osmo_sysinfo_type t, struct gsm_bts *bts) memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - /* ip.access nanoBTS needs l2_plen!! */ - switch (bts->type) { - case GSM_BTS_TYPE_NANOBTS: - case GSM_BTS_TYPE_OSMOBTS: + /* Abis/IP needs l2_plen!! */ + if (is_ipa_abisip_bts(bts)) { *output++ = GSM48_LEN2PLEN(l2_plen); l2_plen++; - break; - default: - break; } si5 = (struct gsm48_system_information_type_5 *) output; @@ -1038,15 +1146,10 @@ static int generate_si5bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - /* ip.access nanoBTS needs l2_plen!! */ - switch (bts->type) { - case GSM_BTS_TYPE_NANOBTS: - case GSM_BTS_TYPE_OSMOBTS: + /* Abis/IP needs l2_plen!! */ + if (is_ipa_abisip_bts(bts)) { *output++ = GSM48_LEN2PLEN(l2_plen); l2_plen++; - break; - default: - break; } si5b = (struct gsm48_system_information_type_5bis *) output; @@ -1082,15 +1185,10 @@ static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - /* ip.access nanoBTS needs l2_plen!! */ - switch (bts->type) { - case GSM_BTS_TYPE_NANOBTS: - case GSM_BTS_TYPE_OSMOBTS: + /* Abis/IP needs l2_plen!! */ + if (is_ipa_abisip_bts(bts)) { *output++ = GSM48_LEN2PLEN(l2_plen); l2_plen++; - break; - default: - break; } si5t = (struct gsm48_system_information_type_5ter *) output; @@ -1114,21 +1212,18 @@ static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts) { struct gsm48_system_information_type_6 *si6; + struct osmo_gsm48_si6_ro_info si6_ro_info; uint8_t *output = GSM_BTS_SI(bts, t); int l2_plen = 11; int rc; memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + memset(&si6_ro_info, 0, sizeof(si6_ro_info)); - /* ip.access nanoBTS needs l2_plen!! */ - switch (bts->type) { - case GSM_BTS_TYPE_NANOBTS: - case GSM_BTS_TYPE_OSMOBTS: + /* Abis/IP needs l2_plen!! */ + if (is_ipa_abisip_bts(bts)) { *output++ = GSM48_LEN2PLEN(l2_plen); l2_plen++; - break; - default: - break; } si6 = (struct gsm48_system_information_type_6 *) output; @@ -1145,48 +1240,280 @@ static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts) gsm48_set_dtx(&si6->cell_options, bts->dtxu, bts->dtxu, false); /* SI6 Rest Octets: 10.5.2.35a: PCH / NCH info, VBS/VGCS options */ - rc = rest_octets_si6(si6->rest_octets, is_dcs_net(bts)); + si6_ro_info.band_indicator_1900 = !is_dcs_net(bts); + rc = osmo_gsm48_rest_octets_si6_encode(si6->rest_octets, &si6_ro_info); return l2_plen + rc; } -static struct gsm48_si13_info si13_default = { - .cell_opts = { - .nmo = GPRS_NMO_II, - .t3168 = 2000, - .t3192 = 1500, - .drx_timer_max = 3, - .bs_cv_max = 15, - .ctrl_ack_type_use_block = true, - .ext_info_present = 0, - .ext_info = { - .egprs_supported = 0, /* overridden in gsm_generate_si() */ - .use_egprs_p_ch_req = 0, /* overridden in generate_si13() */ - .bep_period = 5, - .pfc_supported = 0, - .dtm_supported = 0, - .bss_paging_coordination = 0, - }, - }, - .pwr_ctrl_pars = { - .alpha = 0, /* a = 0.0 */ - .t_avg_w = 16, - .t_avg_t = 16, - .pc_meas_chan = 0, /* downling measured on CCCH */ - .n_avg_i = 8, - }, - .bcch_change_mark = 1, - .si_change_field = 0, - .rac = 0, /* needs to be patched */ - .spgc_ccch_sup = 0, - .net_ctrl_ord = 0, - .prio_acc_thr = 6, -}; +static int si10_rest_octets_encode(struct gsm_bts *s_bts, struct bitvec *bv, struct gsm_bts *n_bts, uint8_t index) +{ + /* The BA-IND must be equal to the BA-IND in SI5*. */ + /* <BA ind : bit(1)> */ + bitvec_set_bit(bv, 1); + + /* Do we have neighbor cells ? */ + /* { L <spare padding> | H <neighbour information> } */ + if (!n_bts) + return 0; + bitvec_set_bit(bv, H); + + /* <first frequency: bit(5)> */ + bitvec_set_uint(bv, index, 5); + + /* <bsic : bit(6)> */ + bitvec_set_uint(bv, n_bts->bsic, 6); + + /* We do not provide empty cell information. */ + /* { H <cell parameters> | L } */ + bitvec_set_bit(bv, H); + + /* If cell is barred, we do not need further cell info. */ + /* <cell barred> | L <further cell info> */ + if (n_bts->si_common.rach_control.cell_bar) { + /* H */ + bitvec_set_bit(bv, H); + /* We are done with the first cell info. */ + return 0; + } + bitvec_set_bit(bv, L); + + /* If LA is different for serving cell, we need to add CRH. */ + /* { H <cell reselect hysteresis : bit(3)> | L } */ + if (s_bts->location_area_code != n_bts->location_area_code) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.cell_resel_hyst, 3); + } else + bitvec_set_bit(bv, L); + + /* <ms txpwr max cch : bit(5)> */ + bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.ms_txpwr_max_ccch, 5); + + /* <rxlev access min : bit(6)> */ + bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.rxlev_acc_min, 6); + + /* <cell reselect offset : bit(6)> */ + if (n_bts->si_common.cell_ro_sel_par.present) + bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.cell_resel_off, 6); + else + bitvec_set_uint(bv, 0, 6); + + /* <temporary offset : bit(3)> */ + if (n_bts->si_common.cell_ro_sel_par.present) + bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.temp_offs, 3); + else + bitvec_set_uint(bv, 0, 3); + + /* <penalty time : bit(5)> */ + if (n_bts->si_common.cell_ro_sel_par.present) + bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.penalty_time, 5); + else + bitvec_set_uint(bv, 0, 5); + + /* We are done with the first cell info. */ + return 0; +} + +static int si10_rest_octets_encode_other(struct gsm_bts *s_bts, struct bitvec *bv, struct gsm_bts *l_bts, + struct gsm_bts *n_bts, uint8_t last_index, uint8_t index) +{ + int rc; + + /* If the bv buffer would overflow, then the bits are not written. Each bitvec_set_* call will return + * an error code. This error is returned with this function. */ + + /* { H <info field> } */ + bitvec_set_bit(bv, H); + + /* How many frequency indices do we skip? */ + /* <next frequency>** L <differential cell info> */ + while ((last_index = (last_index + 1) & 0x1f) != index) { + /* H */ + bitvec_set_bit(bv, H); + } + bitvec_set_bit(bv, L); + + /* If NCC is equal, just send BCC, otherwise send complete BSIC. */ + /* { H <BCC : bit(3)> | L <bsic : bit(6)> } */ + if ((l_bts->bsic & 0x38) == (n_bts->bsic & 0x38)) + bitvec_set_uint(bv, n_bts->bsic & 0x07, 3); + else + bitvec_set_uint(bv, n_bts->bsic, 6); + + /* We do not provide empty cell information. */ + /* { H <cell parameters> | L } */ + bitvec_set_bit(bv, H); + + /* If cell is barred, we do not need further cell info. */ + /* <cell barred> | L <further cell info> */ + if (n_bts->si_common.rach_control.cell_bar) { + /* H */ + rc = bitvec_set_bit(bv, H); + /* We are done with the other cell info. */ + return rc; + } + bitvec_set_bit(bv, L); + + /* If LA is different for serving cell, we need to add CRH. */ + /* { H <cell reselect hysteresis : bit(3)> | L } */ + if (s_bts->location_area_code != n_bts->location_area_code) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.cell_resel_hyst, 3); + } else + bitvec_set_bit(bv, L); + + /* { H <ms txpwr max cch : bit(5)> | L } */ + if (l_bts->si_common.cell_sel_par.ms_txpwr_max_ccch != n_bts->si_common.cell_sel_par.ms_txpwr_max_ccch) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.ms_txpwr_max_ccch, 5); + } else + bitvec_set_bit(bv, L); + + /* { H <rxlev access min : bit(6)> | L } */ + if (l_bts->si_common.cell_sel_par.rxlev_acc_min != n_bts->si_common.cell_sel_par.rxlev_acc_min) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.rxlev_acc_min, 6); + } else + bitvec_set_bit(bv, L); + + /* { H <cell reselect offset : bit(6)> | L } */ + if (l_bts->si_common.cell_ro_sel_par.present != n_bts->si_common.cell_ro_sel_par.present || + (n_bts->si_common.cell_ro_sel_par.present && + l_bts->si_common.cell_ro_sel_par.cell_resel_off != n_bts->si_common.cell_ro_sel_par.cell_resel_off)) { + bitvec_set_bit(bv, H); + if (n_bts->si_common.cell_ro_sel_par.present) + bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.cell_resel_off, 6); + else + bitvec_set_uint(bv, 0, 6); + } else + bitvec_set_bit(bv, L); + + /* { H <temporary offset : bit(3)> | L } */ + if (l_bts->si_common.cell_ro_sel_par.present != n_bts->si_common.cell_ro_sel_par.present || + (n_bts->si_common.cell_ro_sel_par.present && + l_bts->si_common.cell_ro_sel_par.temp_offs != n_bts->si_common.cell_ro_sel_par.temp_offs)) { + bitvec_set_bit(bv, H); + if (n_bts->si_common.cell_ro_sel_par.present) + bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.temp_offs, 3); + else + bitvec_set_uint(bv, 0, 3); + } else + bitvec_set_bit(bv, L); + + /* { H <penalty time : bit(5)> | L } */ + if (l_bts->si_common.cell_ro_sel_par.present != n_bts->si_common.cell_ro_sel_par.present || + (n_bts->si_common.cell_ro_sel_par.present && + l_bts->si_common.cell_ro_sel_par.penalty_time != n_bts->si_common.cell_ro_sel_par.penalty_time)) { + bitvec_set_bit(bv, H); + if (n_bts->si_common.cell_ro_sel_par.present) + rc = bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.penalty_time, 5); + else + rc = bitvec_set_uint(bv, 0, 5); + } else + rc = bitvec_set_bit(bv, L); + + /* We are done with the other cell info. */ + return rc; +} + +/* Generate SI 10 and return the number of bits used in the rest octet. */ +int gsm_generate_si10(struct gsm48_system_information_type_10 *si10, size_t len, + const struct gsm_subscriber_connection *conn) +{ + struct bitvec *nbv; + struct gsm_bts *s_bts = conn->lchan->ts->trx->bts, *l_bts = NULL; + int i, last_i = -1; + bool any_neighbor = false; + int rc; + + struct bitvec bv = { + .data_len = len - sizeof(*si10), + .data = si10->rest_octets, + }; + + si10->rr_short_pd = 0; /* 3GPP TS 24.007 §11.3.2.1 */ + si10->msg_type = GSM48_MT_RR_SH_SI10; + si10->l2_header = 0; /* 3GPP TS 44.006 §6.4a */ + + /* If we have gernerated SI5 with separate SI5 list, the used frequency indexes refer to it. */ + if (s_bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) + nbv = &s_bts->si_common.si5_neigh_list; + else + nbv = &s_bts->si_common.neigh_list; + + /* Get up to 32 possible neighbor frequencies that SI10 can refer to. */ + for (i = 0; i < 32; i++) { + struct gsm_bts *c_bts, *n_bts; + struct gsm_subscriber_connection *c; + unsigned int save_cur_bit; + int16_t arfcn; + arfcn = neigh_list_get_arfcn(s_bts, nbv, i); + /* End of list */ + if (arfcn < 0) + break; + /* Is this neighbour used for this group call? */ + n_bts = NULL; + llist_for_each_entry(c, &conn->vgcs_chan.call->vgcs_call.chan_list, vgcs_chan.list) { + if (c == conn) + continue; + if (!c->lchan) + continue; + c_bts = c->lchan->ts->trx->bts; + if (c_bts->c0->arfcn != arfcn) + continue; + n_bts = c_bts; + break; + } + if (n_bts) { + if (!any_neighbor) { + /* First neighbor, so generate rest octets with first cell info. */ + LOGP(DRR, LOGL_INFO, "SI 10 with cell ID %d.\n", n_bts->cell_identity); + rc = si10_rest_octets_encode(s_bts, &bv, n_bts, i); + if (rc < 0) + return rc; + any_neighbor = true; + } else { + /* Save current position, so we can restore to last position in case of failure. */ + save_cur_bit = bv.cur_bit; + /* Nth neighbor, so add rest octets with differential cell info. */ + LOGP(DRR, LOGL_INFO, "Append cell ID %d to SI 10.\n", n_bts->cell_identity); + OSMO_ASSERT(l_bts && last_i >= 0); + rc = si10_rest_octets_encode_other(s_bts, &bv, l_bts, n_bts, last_i, i); + if (rc < 0) { + LOGP(DRR, LOGL_INFO, "Skip cell ID %d, SI 10 would overflow.\n", + n_bts->cell_identity); + /* Resore last position. */ + bv.cur_bit = save_cur_bit; + break; + } + } + last_i = i; + l_bts = n_bts; + } + } + + /* If no neighbor exists, generate rest octets without any neighbor info. */ + if (!any_neighbor) { + LOGP(DRR, LOGL_INFO, "SI 10 without any neighbor cell.\n"); + rc = si10_rest_octets_encode(s_bts, &bv, NULL, 0); + if (rc < 0) + return rc; + } + + /* Do spare padding. We cannot do it earlier, because encoding might corrupt it if differential cell info + * does not fit into the message. */ + while ((bv.cur_bit & 7)) + bitvec_set_bit(&bv, L); + memset(bv.data + bv.cur_bit / 8, GSM_MACBLOCK_PADDING, bv.data_len - bv.cur_bit / 8); + + return len; +} static int generate_si13(enum osmo_sysinfo_type t, struct gsm_bts *bts) { struct gsm48_system_information_type_13 *si13 = (struct gsm48_system_information_type_13 *) GSM_BTS_SI(bts, t); + struct osmo_gsm48_si13_info si13_info; int ret; memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); @@ -1195,27 +1522,68 @@ static int generate_si13(enum osmo_sysinfo_type t, struct gsm_bts *bts) si13->header.skip_indicator = 0; si13->header.system_information = GSM48_MT_RR_SYSINFO_13; - si13_default.rac = bts->gprs.rac; - si13_default.net_ctrl_ord = bts->gprs.net_ctrl_ord; - - si13_default.cell_opts.ctrl_ack_type_use_block = - bts->gprs.ctrl_ack_type_use_block; - - /* Information about the other SIs */ - si13_default.bcch_change_mark = bts->bcch_change_mark; + si13_info = (struct osmo_gsm48_si13_info){ + .cell_opts = { + .nmo = GPRS_NMO_II, + .t3168 = 2000, + .t3192 = 1500, + /* 3GPP TS 45.002 6.5.6: + * "On BCCH, the operator should limit DRX_TIMER_MAX [...] to 4 seconds" */ + .drx_timer_max = 4, + .bs_cv_max = 15, + .ctrl_ack_type_use_block = bts->gprs.ctrl_ack_type_use_block, + .ext_info_present = true, + .ext_info = { + .egprs_supported = 0, /* overridden below */ + .use_egprs_p_ch_req = 0, /* overridden below */ + .bep_period = 5, + .pfc_supported = 0, + .dtm_supported = 0, + .bss_paging_coordination = 0, /* overridden below */ + .ccn_active = false, /* overridden below */ + }, + }, + .pwr_ctrl_pars = { + .alpha = bts->gprs.pwr_ctrl.alpha, /* a = 0.0 */ + .t_avg_w = 16, + .t_avg_t = 16, + .pc_meas_chan = 0, /* downling measured on CCCH */ + .n_avg_i = 8, + }, + .bcch_change_mark = bts->bcch_change_mark, /* Information about the other SIs */ + .si_change_field = 0, + .rac = bts->gprs.rac, + .spgc_ccch_sup = 0, + .net_ctrl_ord = bts->gprs.net_ctrl_ord, + .prio_acc_thr = 6, + }; - /* Whether EGPRS capable MSs shall use EGPRS PACKET CHANNEL REQUEST */ - if (bts->gprs.egprs_pkt_chan_request) - si13_default.cell_opts.ext_info.use_egprs_p_ch_req = 1; - else - si13_default.cell_opts.ext_info.use_egprs_p_ch_req = 0; + switch (bts->gprs.mode) { + case BTS_GPRS_EGPRS: + si13_info.cell_opts.ext_info.egprs_supported = 1; + /* Whether EGPRS capable MSs shall use EGPRS PACKET CHANNEL REQUEST */ + if (bts->gprs.egprs_pkt_chan_request) + si13_info.cell_opts.ext_info.use_egprs_p_ch_req = 1; + else + si13_info.cell_opts.ext_info.use_egprs_p_ch_req = 0; + break; + case BTS_GPRS_GPRS: + case BTS_GPRS_NONE: + si13_info.cell_opts.ext_info.egprs_supported = 0; + si13_info.cell_opts.ext_info.use_egprs_p_ch_req = 0; + break; + } if (osmo_bts_has_feature(&bts->features, BTS_FEAT_PAGING_COORDINATION)) - si13_default.cell_opts.ext_info.bss_paging_coordination = 1; + si13_info.cell_opts.ext_info.bss_paging_coordination = 1; else - si13_default.cell_opts.ext_info.bss_paging_coordination = 0; + si13_info.cell_opts.ext_info.bss_paging_coordination = 0; + + si13_info.cell_opts.ext_info.ccn_active = bts->gprs.ccn.forced_vty ? + bts->gprs.ccn.active : + osmo_bts_has_feature(&bts->features, BTS_FEAT_CCN); - ret = rest_octets_si13(si13->rest_octets, &si13_default); + ret = osmo_gsm48_rest_octets_si13_encode(si13->rest_octets, &si13_info); if (ret < 0) return ret; @@ -1249,9 +1617,6 @@ int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) switch (bts->gprs.mode) { case BTS_GPRS_EGPRS: - si13_default.cell_opts.ext_info_present = 1; - si13_default.cell_opts.ext_info.egprs_supported = 1; - /* fallthrough */ case BTS_GPRS_GPRS: si_info.gprs_ind.present = 1; break; @@ -1262,7 +1627,7 @@ int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) memcpy(&si_info.selection_params, &bts->si_common.cell_ro_sel_par, - sizeof(struct gsm48_si_selection_params)); + sizeof(struct osmo_gsm48_si_selection_params)); gen_si = gen_si_fn[si_type]; if (!gen_si) { diff --git a/src/osmo-bsc/timeslot_fsm.c b/src/osmo-bsc/timeslot_fsm.c index 106e6a1fa..9618f9280 100644 --- a/src/osmo-bsc/timeslot_fsm.c +++ b/src/osmo-bsc/timeslot_fsm.c @@ -29,6 +29,7 @@ #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/pcu_if.h> #include <osmocom/bsc/bts.h> +#include <osmocom/bsc/lchan.h> static struct osmo_fsm ts_fsm; @@ -48,11 +49,11 @@ struct gsm_bts_trx_ts *ts_fi_ts(struct osmo_fsm_inst *fi) 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)); + osmo_fsm_inst_update_id_f_sanitize(ts->fi, '_', "%u-%u-%u-%s", ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan_on_init)); } -void ts_fsm_init() +static __attribute__((constructor)) void ts_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&ts_fsm) == 0); } @@ -66,6 +67,14 @@ void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) ts_fsm_update_id(ts); } +void ts_fsm_free(struct gsm_bts_trx_ts *ts) +{ + if (ts->fi) { + osmo_fsm_inst_free(ts->fi); + ts->fi = NULL; + } +} + enum lchan_sanity { LCHAN_IS_INSANE = -1, LCHAN_IS_READY_TO_GO, @@ -85,7 +94,9 @@ static enum lchan_sanity is_lchan_sane(struct gsm_bts_trx_ts *ts, struct gsm_lch return LCHAN_IS_READY_TO_GO; switch (ts->pchan_on_init) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: + if (lchan->type == GSM_LCHAN_SDCCH) + return LCHAN_NEEDS_PCHAN_CHANGE; if (lchan->type == GSM_LCHAN_TCH_H) return LCHAN_NEEDS_PCHAN_CHANGE; /* fall thru */ @@ -112,7 +123,7 @@ 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) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { if (lchan->fi->state == LCHAN_ST_UNUSED) continue; count++; @@ -125,7 +136,7 @@ static void ts_lchans_dispatch(struct gsm_bts_trx_ts *ts, int lchan_state, uint3 { struct gsm_lchan *lchan; - ts_for_each_potential_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { if (lchan_state >= 0 && !lchan_state_is(lchan, lchan_state)) continue; @@ -137,7 +148,7 @@ static void ts_terminate_lchan_fsms(struct gsm_bts_trx_ts *ts) { struct gsm_lchan *lchan; - ts_for_each_potential_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { osmo_fsm_inst_term(lchan->fi, OSMO_FSM_TERM_REQUEST, NULL); } } @@ -146,7 +157,7 @@ static int ts_lchans_waiting(struct gsm_bts_trx_ts *ts) { struct gsm_lchan *lchan; int count = 0; - ts_for_each_potential_lchan(lchan, ts) + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) if (lchan->fi->state == LCHAN_ST_WAIT_TS_READY) count++; return count; @@ -187,17 +198,60 @@ static void ts_fsm_err_ready_to_go_in_pdch(struct osmo_fsm_inst *fi, struct gsm_ osmo_fsm_inst_state_name(fi), gsm_lchan_name(lchan)); } +void ts_set_pchan_is(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan_is) +{ + int i; + struct gsm_lchan *lchan; + uint8_t max_lchans_possible_vamos; + + ts->pchan_is = pchan_is; + ts->max_primary_lchans = pchan_subslots(ts->pchan_is); + max_lchans_possible_vamos = pchan_subslots_vamos(ts->pchan_is); + LOG_TS(ts, LOGL_DEBUG, "pchan_is=%s max_primary_lchans=%d max_lchans_possible=%d (%u VAMOS)\n", + gsm_pchan_name(ts->pchan_is), ts->max_primary_lchans, ts->max_lchans_possible, + max_lchans_possible_vamos); + switch (ts->pchan_is) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + for (i = 0; i < ts->max_lchans_possible; i++) { + lchan = &ts->lchan[i]; + if (i >= ts->max_primary_lchans && + (i - ts->max_primary_lchans) < (int)max_lchans_possible_vamos) + lchan->vamos.is_secondary = true; + else + lchan->vamos.is_secondary = false; + lchan_fsm_update_id(lchan); + } + break; + default: + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { + lchan->vamos.is_secondary = false; + lchan_fsm_update_id(lchan); + } + break; + } + chan_counts_ts_update(ts); +} + static void ts_setup_lchans(struct gsm_bts_trx_ts *ts) { int i, max_lchans; + int max_lchans_vamos; 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 (osmo_bts_has_feature(&ts->trx->bts->features, BTS_FEAT_VAMOS)) + max_lchans_vamos = pchan_subslots_vamos(ts->pchan_on_init); + else + max_lchans_vamos = 0; + LOG_TS(ts, LOGL_DEBUG, "max lchans: %d + %d VAMOS secondaries\n", max_lchans, max_lchans_vamos); + ts->max_lchans_possible = max_lchans + max_lchans_vamos; + ts->max_primary_lchans = 0; + + OSMO_ASSERT(ts->max_lchans_possible <= TS_MAX_LCHAN); + for (i = 0; i < ts->max_lchans_possible; i++) { /* If we receive more than one Channel OPSTART ACK, don't fail on the second init. */ if (ts->lchan[i].fi) continue; @@ -205,18 +259,16 @@ static void ts_setup_lchans(struct gsm_bts_trx_ts *ts) } switch (ts->pchan_on_init) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - ts->pchan_is = GSM_PCHAN_NONE; + case GSM_PCHAN_OSMO_DYN: + ts_set_pchan_is(ts, GSM_PCHAN_NONE); break; case GSM_PCHAN_TCH_F_PDCH: - ts->pchan_is = GSM_PCHAN_TCH_F; + ts_set_pchan_is(ts, GSM_PCHAN_TCH_F); break; default: - ts->pchan_is = ts->pchan_on_init; + ts_set_pchan_is(ts, 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) @@ -265,11 +317,45 @@ static void ts_fsm_not_initialized(struct osmo_fsm_inst *fi, uint32_t event, voi 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) +static void ts_fsm_not_initialized_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + chan_counts_ts_clear(ts); +} + +static void ts_fsm_unused_pdch_act(struct osmo_fsm_inst *fi) { struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); struct gsm_bts *bts = ts->trx->bts; + 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; + } + + if (bsc_co_located_pcu(bts) && !pcu_connected(bts->network)) { + LOG_TS(ts, LOGL_DEBUG, "PCU not connected: not activating PDCH.\n"); + return; + } + + osmo_fsm_inst_state_chg(fi, TS_ST_WAIT_PDCH_ACT, CHAN_ACT_DEACT_TIMEOUT, + T_CHAN_ACT_DEACT); +} + +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); + + chan_counts_ts_update(ts); + /* We are entering the unused state. There must by definition not be any lchans waiting to be * activated. */ if (ts_lchans_waiting(ts)) { @@ -280,21 +366,9 @@ static void ts_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) } switch (ts->pchan_on_init) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: 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); + ts_fsm_unused_pdch_act(fi); break; case GSM_PCHAN_CCCH_SDCCH4_CBCH: @@ -339,6 +413,10 @@ static void ts_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *data) /* ignored. */ return; + case TS_EV_PDCH_ACT: + ts_fsm_unused_pdch_act(fi); + return; + default: OSMO_ASSERT(false); } @@ -366,6 +444,7 @@ static void ts_fsm_wait_pdch_act_onenter(struct osmo_fsm_inst *fi, uint32_t prev 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); + struct rate_ctr_group *bts_ctrs = ts->trx->bts->bts_ctrs; switch (event) { case TS_EV_PDCH_ACT_ACK: @@ -374,9 +453,9 @@ static void ts_fsm_wait_pdch_act(struct osmo_fsm_inst *fi, uint32_t event, void 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]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_RSL_IPA_NACK)); else - rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_CHAN_ACT_NACK)); ts->pdch_act_allowed = false; ts_fsm_error(fi, TS_ST_UNUSED, "Received PDCH activation NACK"); return; @@ -416,10 +495,10 @@ static void ts_fsm_pdch_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) /* Set pchan = PDCH status, but double check. */ switch (ts->pchan_on_init) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: case GSM_PCHAN_TCH_F_PDCH: case GSM_PCHAN_PDCH: - ts->pchan_is = GSM_PCHAN_PDCH; + ts_set_pchan_is(ts, GSM_PCHAN_PDCH); break; default: ts_fsm_error(fi, TS_ST_BORKEN, "pchan %s is incapable of activating PDCH", @@ -461,6 +540,10 @@ static void ts_fsm_pdch(struct osmo_fsm_inst *fi, uint32_t event, void *data) } } + case TS_EV_PDCH_DEACT: + ts_fsm_pdch_deact(fi); + return; + case TS_EV_LCHAN_UNUSED: /* ignored */ return; @@ -491,11 +574,11 @@ static void ts_fsm_wait_pdch_deact(struct osmo_fsm_inst *fi, uint32_t event, voi 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; + case GSM_PCHAN_OSMO_DYN: + ts_set_pchan_is(ts, GSM_PCHAN_NONE); break; case GSM_PCHAN_TCH_F_PDCH: - ts->pchan_is = GSM_PCHAN_TCH_F; + ts_set_pchan_is(ts, GSM_PCHAN_TCH_F); break; default: ts_fsm_error(fi, TS_ST_BORKEN, "pchan %s is incapable of deactivating PDCH", @@ -511,7 +594,7 @@ static void ts_fsm_wait_pdch_deact(struct osmo_fsm_inst *fi, uint32_t event, voi 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]); + rate_ctr_inc(rate_ctr_group_get_ctr(ts->trx->bts->bts_ctrs, 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"); @@ -556,7 +639,7 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) ts->pdch_act_allowed = true; /* For static TS, check validity. For dyn TS, figure out which PCHAN this should become. */ - ts_for_each_potential_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { if (lchan_state_is(lchan, LCHAN_ST_UNUSED)) continue; @@ -573,7 +656,7 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) 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_chan_t_name(lchan->type), gsm_pchan_name(ts->pchan_on_init), gsm_lchan_name(lchan)); lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); } @@ -585,7 +668,7 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) 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_chan_t_name(lchan->type), gsm_chan_t_name(activating_type), gsm_lchan_name(lchan)); lchan_dispatch(lchan, LCHAN_EV_TS_ERROR); } @@ -604,7 +687,7 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) break; default: - LOG_TS(ts, LOGL_ERROR, "cannot use timeslot as %s\n", gsm_lchant_name(activating_type)); + LOG_TS(ts, LOGL_ERROR, "cannot use timeslot as %s\n", gsm_chan_t_name(activating_type)); ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_ERROR); break; } @@ -614,10 +697,12 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) return; } + chan_counts_ts_update(ts); + /* 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); + * for Osmocom style TCH/F_TCH/H_SDCCH8_PDCH the pchan_is == NONE until an lchan is activated. */ + if (ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) + ts_set_pchan_is(ts, gsm_pchan_by_lchan_type(activating_type)); ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_READY); } @@ -659,6 +744,7 @@ static void ts_fsm_in_use(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void ts_fsm_borken_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; enum bts_counter_id ctr; switch (prev_state) { case TS_ST_NOT_INITIALIZED: @@ -685,8 +771,8 @@ static void ts_fsm_borken_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) default: ctr = BTS_CTR_TS_BORKEN_FROM_UNKNOWN; } - rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[ctr]); - osmo_stat_item_inc(ts->trx->bts->bts_statg->items[BTS_STAT_TS_BORKEN], 1); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, ctr)); + osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_TS_BORKEN), 1); } static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -710,8 +796,8 @@ static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data) struct gsm_bts *bts = ts->trx->bts; /* Late PDCH activation ACK/NACK is not a crime. * Just process them as normal. */ - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_TS_BORKEN_EV_PDCH_ACT_ACK_NACK]); - osmo_stat_item_dec(bts->bts_statg->items[BTS_STAT_TS_BORKEN], 1); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_TS_BORKEN_EV_PDCH_ACT_ACK_NACK)); + osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_TS_BORKEN), 1); ts_fsm_wait_pdch_act(fi, event, data); return; } @@ -723,8 +809,8 @@ static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data) struct gsm_bts *bts = ts->trx->bts; /* Late PDCH deactivation ACK/NACK is also not a crime. * Just process them as normal. */ - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_TS_BORKEN_EV_PDCH_DEACT_ACK_NACK]); - osmo_stat_item_dec(bts->bts_statg->items[BTS_STAT_TS_BORKEN], 1); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_TS_BORKEN_EV_PDCH_DEACT_ACK_NACK)); + osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_TS_BORKEN), 1); ts_fsm_wait_pdch_deact(fi, event, data); return; } @@ -762,7 +848,8 @@ static void ts_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data 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->pchan_on_init = GSM_PCHAN_NONE; + ts_set_pchan_is(ts, GSM_PCHAN_NONE); ts_fsm_update_id(ts); break; @@ -771,7 +858,7 @@ static void ts_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data 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_set_pchan_is(ts, GSM_PCHAN_NONE); ts_lchans_dispatch(ts, -1, LCHAN_EV_TS_ERROR); break; @@ -783,9 +870,10 @@ static void ts_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data static void ts_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct gsm_bts_trx_ts *ts = ts_fi_ts(fi); + struct gsm_bts *bts = ts->trx->bts; if (ts->fi->state == TS_ST_BORKEN) { - rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_TS_BORKEN_EV_TEARDOWN]); - osmo_stat_item_dec(ts->trx->bts->bts_statg->items[BTS_STAT_TS_BORKEN], 1); + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_TS_BORKEN_EV_TEARDOWN)); + osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_TS_BORKEN), 1); } } @@ -794,6 +882,7 @@ static void ts_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause ca static const struct osmo_fsm_state ts_fsm_states[] = { [TS_ST_NOT_INITIALIZED] = { .name = "NOT_INITIALIZED", + .onenter = ts_fsm_not_initialized_onenter, .action = ts_fsm_not_initialized, .in_event_mask = 0 | S(TS_EV_OML_READY) @@ -813,6 +902,7 @@ static const struct osmo_fsm_state ts_fsm_states[] = { .in_event_mask = 0 | S(TS_EV_LCHAN_REQUESTED) | S(TS_EV_LCHAN_UNUSED) + | S(TS_EV_PDCH_ACT) , .out_state_mask = 0 | S(TS_ST_WAIT_PDCH_ACT) @@ -845,6 +935,7 @@ static const struct osmo_fsm_state ts_fsm_states[] = { .in_event_mask = 0 | S(TS_EV_LCHAN_REQUESTED) | S(TS_EV_LCHAN_UNUSED) + | S(TS_EV_PDCH_DEACT) , .out_state_mask = 0 | S(TS_ST_WAIT_PDCH_DEACT) @@ -865,7 +956,6 @@ static const struct osmo_fsm_state ts_fsm_states[] = { .out_state_mask = 0 | S(TS_ST_IN_USE) | S(TS_ST_UNUSED) - | S(TS_ST_BORKEN) | S(TS_ST_NOT_INITIALIZED) | S(TS_ST_BORKEN) , @@ -917,6 +1007,8 @@ static const struct value_string ts_fsm_event_names[] = { OSMO_VALUE_STRING(TS_EV_PDCH_ACT_NACK), OSMO_VALUE_STRING(TS_EV_PDCH_DEACT_ACK), OSMO_VALUE_STRING(TS_EV_PDCH_DEACT_NACK), + OSMO_VALUE_STRING(TS_EV_PDCH_DEACT), + OSMO_VALUE_STRING(TS_EV_PDCH_ACT), {} }; @@ -940,7 +1032,7 @@ static struct osmo_fsm ts_fsm = { 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_potential_lchan(lchan, ts) { + ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { if (lchan->fi->state == LCHAN_ST_WAIT_TS_READY) { if (target_pchan) *target_pchan = gsm_pchan_by_lchan_type(lchan->type); @@ -981,7 +1073,7 @@ bool ts_is_pchan_switching(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config * waiting for PDCH DEACT (N)ACK */ if (target_pchan) { switch (ts->pchan_on_init) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: if (target_pchan) *target_pchan = GSM_PCHAN_NONE; break; @@ -1004,7 +1096,7 @@ bool ts_is_pchan_switching(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config /* 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) +bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan, bool allow_pchan_switch) { enum gsm_phys_chan_config target_pchan; @@ -1022,5 +1114,31 @@ bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_ if (ts_is_lchan_waiting_for_pchan(ts, &target_pchan)) return target_pchan == as_pchan; - return ts_is_capable_of_pchan(ts, as_pchan); + if (!ts_is_capable_of_pchan(ts, as_pchan)) + return false; + + if (!allow_pchan_switch && ts->pchan_is != as_pchan) + return false; + + return true; +} + +void ts_pdch_act(struct gsm_bts_trx_ts *ts) +{ + /* We send the activation event only when the timeslot is UNUSED. In all other cases the timeslot FSM + * will automatically handle the activation at some later point. */ + if (ts->fi->state != TS_ST_UNUSED) + return; + + osmo_fsm_inst_dispatch(ts->fi, TS_EV_PDCH_ACT, NULL); +} + +void ts_pdch_deact(struct gsm_bts_trx_ts *ts) +{ + /* We send the deactivation event only when the timeslot is active as PDCH. The timeslot FSM will check + * if the PCU is available before activating the PDCH again. */ + if (ts->fi->state != TS_ST_PDCH) + return; + + osmo_fsm_inst_dispatch(ts->fi, TS_EV_PDCH_DEACT, NULL); } diff --git a/src/osmo-bsc/vgcs_fsm.c b/src/osmo-bsc/vgcs_fsm.c new file mode 100644 index 000000000..1f2bbefec --- /dev/null +++ b/src/osmo-bsc/vgcs_fsm.c @@ -0,0 +1,1318 @@ +/* Handle VGCS/VBCS calls. (Voice Group/Broadcast Call Service). */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Andreas Eversberg + * + * 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/>. + */ + +/* The process consists of two state machnes: + * + * The VGCS call state machine handles the voice group/broadcast call control. + * There is one instance for every call. It controls the uplink states of the + * call. They will be reported to the MSC or can be changed by the MSC. + * One SCCP connection for is associated with the state machine. This is used + * to talk to the MSC about state changes. + * + * The VGCS channel state machine handles the channel states in each cell. + * There is one instance for every cell and every call. The instances are + * linked to the call state process. It controls the uplink states of the + * channel. They will be reported to the call state machine or can be changed + * by the call state machine. + * One SCCP connection for every cell is associated with the state machine. + * It is used to perform VGCS channel assignment. + * + */ + +#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> +#include <osmocom/bsc/osmo_bsc.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/vgcs_fsm.h> +#include <osmocom/bsc/handover_fsm.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/lchan_select.h> +#include <osmocom/bsc/bsc_subscr_conn_fsm.h> +#include <osmocom/bsc/assignment_fsm.h> +#include <osmocom/bsc/gsm_08_08.h> +#include <osmocom/bsc/gsm_04_08_rr.h> +#include <osmocom/bsc/bts_trx.h> +#include <osmocom/bsc/bts.h> +#include <osmocom/bsc/system_information.h> + +#define S(x) (1 << (x)) + +#define LOG_CALL(conn, level, fmt, args...) \ + LOGP(DASCI, level, \ + (conn->vgcs_call.sf == GSM0808_SF_VGCS) ? ("VGCS callref %s: " fmt) : ("VBS callref %s: " fmt), \ + gsm44068_group_id_string(conn->vgcs_call.call_ref), ##args) +#define LOG_CHAN(conn, level, fmt, args...) \ + LOGP(DASCI, level, \ + (conn->vgcs_chan.sf == GSM0808_SF_VGCS) ? ("VGCS callref %s, cell %s: " fmt) \ + : ("VBS callref %s, cell %s: " fmt), \ + gsm44068_group_id_string(conn->vgcs_chan.call_ref), conn->vgcs_chan.ci_str, ##args) + +const char *gsm44068_group_id_string(uint32_t callref) +{ + static char string[9]; + + snprintf(string, sizeof(string), "%08u", callref); + + return string; +} + +static struct osmo_fsm vgcs_call_fsm; +static struct osmo_fsm vgcs_chan_fsm; + +static __attribute__((constructor)) void vgcs_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&vgcs_call_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&vgcs_chan_fsm) == 0); +} + +static const struct value_string vgcs_fsm_event_names[] = { + OSMO_VALUE_STRING(VGCS_EV_SETUP), + OSMO_VALUE_STRING(VGCS_EV_ASSIGN_REQ), + OSMO_VALUE_STRING(VGCS_EV_TALKER_DET), + OSMO_VALUE_STRING(VGCS_EV_LISTENER_DET), + OSMO_VALUE_STRING(VGCS_EV_MSC_ACK), + OSMO_VALUE_STRING(VGCS_EV_MSC_REJECT), + OSMO_VALUE_STRING(VGCS_EV_MSC_SEIZE), + OSMO_VALUE_STRING(VGCS_EV_MSC_RELEASE), + OSMO_VALUE_STRING(VGCS_EV_MSC_DTAP), + OSMO_VALUE_STRING(VGCS_EV_LCHAN_ACTIVE), + OSMO_VALUE_STRING(VGCS_EV_LCHAN_ERROR), + OSMO_VALUE_STRING(VGCS_EV_MGW_OK), + OSMO_VALUE_STRING(VGCS_EV_MGW_FAIL), + OSMO_VALUE_STRING(VGCS_EV_TALKER_EST), + OSMO_VALUE_STRING(VGCS_EV_TALKER_DATA), + OSMO_VALUE_STRING(VGCS_EV_TALKER_REL), + OSMO_VALUE_STRING(VGCS_EV_TALKER_FAIL), + OSMO_VALUE_STRING(VGCS_EV_BLOCK), + OSMO_VALUE_STRING(VGCS_EV_REJECT), + OSMO_VALUE_STRING(VGCS_EV_UNBLOCK), + OSMO_VALUE_STRING(VGCS_EV_CLEANUP), + OSMO_VALUE_STRING(VGCS_EV_CALLING_ASSIGNED), + { } +}; + +static struct gsm_subscriber_connection *find_calling_subscr_conn(struct gsm_subscriber_connection *conn) +{ + struct gsm_subscriber_connection *c; + + llist_for_each_entry(c, &conn->network->subscr_conns, entry) { + if (!c->assignment.fi) + continue; + if (c->assignment.req.target_lchan != conn->lchan) + continue; + return c; + } + + return NULL; +} + +/* + * VGCS call FSM + */ + +/* Add/update SI10. It must be called whenever a channel is activated or failed. */ +static void si10_update(struct gsm_subscriber_connection *conn) +{ + struct gsm_subscriber_connection *c; + uint8_t si10[SI10_LENGTH]; + int rc; + + /* Skip SI10 update, if not all channels have been activated or failed. */ + llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) { + if (c->vgcs_chan.fi->state == VGCS_CHAN_ST_WAIT_EST) { + LOG_CALL(conn, LOGL_DEBUG, "There is a channel, not yet active. No SI10 update now.\n"); + return; + } + } + + LOG_CALL(conn, LOGL_DEBUG, "New channel(s) added, updating SI10 for all channels.\n"); + + /* Go through all channels. */ + llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) { + /* Skip all channels that failed to activate or have not been aktivated yet. + * There shouldn't be any channel in that state now. */ + if (!c->lchan) + continue; + /* Encode SI 10 for this channel. Skip, if it fails. */ + rc = gsm_generate_si10((struct gsm48_system_information_type_10 *)si10, sizeof(si10), c); + if (rc < 0) + continue; + /* Add SI 10 to SACCH of this channel c. */ + rsl_sacch_info_modify(c->lchan, RSL_SYSTEM_INFO_10, si10, sizeof(si10)); + } +} + +static void vgcs_call_detach_and_destroy(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv, *c; + struct msgb *msg; + + /* Flush message queue. */ + while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue))) + msgb_free(msg); + + /* Detach all cell instances. */ + while (!llist_empty(&conn->vgcs_call.chan_list)) { + c = llist_entry(conn->vgcs_call.chan_list.next, struct gsm_subscriber_connection, vgcs_chan.list); + c->vgcs_chan.call = NULL; + llist_del(&c->vgcs_chan.list); + } + + /* No Talker. */ + conn->vgcs_call.talker = NULL; + + /* Remove pointer of FSM. */ + conn->vgcs_call.fi = NULL; +} + +static void vgcs_call_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + switch (event) { + case VGCS_EV_SETUP: + LOG_CALL(conn, LOGL_DEBUG, "VGCS/VBS SETUP from MSC.\n"); + /* MSC sends VGCS/VBS SETUP for a new call. */ + osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0); + /* Remove unsupported features. */ + conn->vgcs_call.ff.tp_ind = 0; + conn->vgcs_call.ff.as_ind_circuit = 0; + conn->vgcs_call.ff.as_ind_link = 0; + conn->vgcs_call.ff.bss_res = 0; + conn->vgcs_call.ff.tcp = 0; + /* Acknowlege the call. */ + bsc_tx_setup_ack(conn, &conn->vgcs_call.ff); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_call_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv, *c; + struct handover_rr_detect_data *d = data; + struct msgb *msg; + + switch (event) { + case VGCS_EV_TALKER_DET: + LOG_CALL(conn, LOGL_DEBUG, "Talker detected.\n"); + /* Talker detected on a channel, call becomes busy. */ + osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BUSY, 0, 0); + conn->vgcs_call.talker = d->msg->lchan->conn; + /* Reset pending states. */ + while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue))) + msgb_free(msg); + conn->vgcs_call.msc_ack = false; + conn->vgcs_call.talker_rel = false; + /* Report busy uplink to the MSC. */ + bsc_tx_uplink_req(conn); + /* Block all other channels. */ + llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) { + if (c == conn->vgcs_call.talker) + continue; + osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL); + } + break; + case VGCS_EV_LISTENER_DET: + LOG_CALL(conn, LOGL_DEBUG, "Listener detected.\n"); + // Listener detection not supported. + break; + case VGCS_EV_MSC_SEIZE: + LOG_CALL(conn, LOGL_DEBUG, "MSC seizes all channels.\n"); + /* MSC seizes call (talker on a different BSS), call becomes blocked. */ + osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BLOCKED, 0, 0); + /* Block all channels. */ + llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) + osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL); + break; + case VGCS_EV_MSC_RELEASE: + /* Ignore, because there is no blocked channel in this state. */ + break; + case VGCS_EV_MSC_REJECT: + LOG_CALL(conn, LOGL_DEBUG, "MSC rejects talker on uplink.\n"); + /* Race condition: Talker released before the MSC rejects the talker. Ignore! */ + break; + case VGCS_EV_CLEANUP: + LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +/* Get L3 info from message, if exists. Return the length or otherwise return 0. */ +int l3_data_from_msg(struct msgb *msg, uint8_t **l3_info) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + + /* No space for L3 info */ + if (msgb_l2len(msg) < sizeof(*rllh) + 3 || rllh->data[0] != RSL_IE_L3_INFO) + return 0; + + *l3_info = msg->l3h = &rllh->data[3]; + return msgb_l3len(msg); +} + +static void vgcs_call_fsm_busy(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv, *c; + struct msgb *msg = data; + uint8_t cause = (data) ? *(uint8_t *)data : 0; + uint8_t *l3_info; + int l3_len; + int rc; + + switch (event) { + case VGCS_EV_TALKER_EST: + LOG_CALL(conn, LOGL_DEBUG, "Talker established uplink.\n"); + /* Talker established L2 connection. Sent L3 info to MSC, if MSC already acked, otherwise enqueue. */ + if (conn->vgcs_call.msc_ack) { + LOG_CALL(conn, LOGL_DEBUG, "Sending establishment messages to MSC.\n"); + l3_len = l3_data_from_msg(msg, &l3_info); + if (conn->vgcs_call.talker) + bsc_tx_uplink_req_conf(conn, &conn->vgcs_call.talker->vgcs_chan.ci, l3_info, l3_len); + else + LOG_CALL(conn, LOGL_ERROR, "Talker establishes, but talker not set, please fix!\n"); + } else { + LOG_CALL(conn, LOGL_DEBUG, "No uplink request ack from MSC yet, queue message.\n"); + msg = msgb_copy(msg, "Queued Talker establishment"); + if (msg) + msgb_enqueue(&conn->vgcs_call.l3_queue, msg); + } + break; + case VGCS_EV_TALKER_DATA: + LOG_CALL(conn, LOGL_DEBUG, "Talker sent data on uplink.\n"); + /* Talker sends data. Sent L3 info to MSC, if MSC already acked, otherwise enqueue. */ + if (conn->vgcs_call.msc_ack) { + LOG_CALL(conn, LOGL_DEBUG, "Sending data messages to MSC.\n"); + bsc_dtap(conn, 0, msg); + } else { + LOG_CALL(conn, LOGL_DEBUG, "No uplink request ack from MSC yet, queue message.\n"); + msg = msgb_copy(msg, "Queued DTAP"); + if (msg) + msgb_enqueue(&conn->vgcs_call.l3_queue, msg); + } + break; + case VGCS_EV_MSC_DTAP: + LOG_CALL(conn, LOGL_DEBUG, "MSC sends DTAP message to talker.\n"); + if (!conn->vgcs_call.talker) { + msgb_free(data); + break; + } + rc = osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_MSC_DTAP, data); + if (rc < 0) + msgb_free(data); + break; + case VGCS_EV_TALKER_REL: + LOG_CALL(conn, LOGL_DEBUG, "Talker released on uplink.\n"); + if (!conn->vgcs_call.msc_ack) { + LOG_CALL(conn, LOGL_DEBUG, "Talker released before MSC acknowleded or rejected.\n"); + conn->vgcs_call.talker_rel = true; + conn->vgcs_call.talker_cause = cause; + break; + } +talker_released: + /* Talker released channel, call becomes idle. */ + osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0); + conn->vgcs_call.talker = NULL; + /* Report free uplink to the MSC. */ + bsc_tx_uplink_release_ind(conn, cause); + /* Unblock all other channels. */ + llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) { + if (c == conn->vgcs_call.talker) + continue; + osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL); + } + break; + case VGCS_EV_MSC_SEIZE: + LOG_CALL(conn, LOGL_DEBUG, "MSC seizes all channels. (channels are blocked)\n"); + /* Race condition: MSC seizes call (talker on a different BSS), call becomes blocked. */ + osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BLOCKED, 0, 0); + /* Reject talker. (Forward to chan FSM.) */ + if (conn->vgcs_call.talker) { + osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_REJECT, NULL); + conn->vgcs_call.talker = NULL; + } + /* Block all channels. */ + llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) + osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL); + break; + case VGCS_EV_MSC_ACK: + LOG_CALL(conn, LOGL_DEBUG, "MSC acks talker on uplink.\n"); + /* MSC acknowledges uplink. Send L3 info to MSC, if talker already established. */ + conn->vgcs_call.msc_ack = true; + /* Send establish message via UPLINK REQUEST CONFIRM, if already received. */ + msg = msgb_dequeue(&conn->vgcs_call.l3_queue); + if (msg) { + LOG_CALL(conn, LOGL_DEBUG, "Sending queued establishment messages to MSC.\n"); + l3_len = l3_data_from_msg(msg, &l3_info); + if (conn->vgcs_call.talker) + bsc_tx_uplink_req_conf(conn, &conn->vgcs_call.talker->vgcs_chan.ci, l3_info, l3_len); + else + LOG_CALL(conn, LOGL_ERROR, "MSC acks taker, but talker not set, please fix!\n"); + msgb_free(msg); + } + /* Send data messages via UPLINK APPLICATION DATA, if already received. */ + while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue))) { + LOG_CALL(conn, LOGL_DEBUG, "Sending queued DTAP messages to MSC.\n"); + bsc_dtap(conn, 0, msg); + msgb_free(msg); + } + /* If there is a pending talker release. */ + if (conn->vgcs_call.talker_rel) { + LOG_CALL(conn, LOGL_DEBUG, "Sending queued talker release messages to MSC.\n"); + cause = conn->vgcs_call.talker_cause; + goto talker_released; + } + break; + case VGCS_EV_MSC_REJECT: + LOG_CALL(conn, LOGL_DEBUG, "MSC rejects talker on uplink.\n"); + /* MSC rejects talker, call becomes idle. */ + osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0); + /* Reject talker. (Forward to chan FSM.) */ + if (conn->vgcs_call.talker) + osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_REJECT, NULL); + else + LOG_CALL(conn, LOGL_ERROR, "MSC rejects, but talker not set, please fix!\n"); + conn->vgcs_call.talker = NULL; + /* Unblock all other channels. */ + llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) { + if (c == conn->vgcs_call.talker) + continue; + osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL); + } + break; + case VGCS_EV_CLEANUP: + LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_call_fsm_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv, *c; + struct msgb *msg; + + switch (event) { + case VGCS_EV_CALLING_ASSIGNED: + LOG_CALL(conn, LOGL_DEBUG, "Calling subscriber assigned and now on uplink.\n"); + /* Talker detected on a channel, call becomes busy. */ + osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BUSY, 0, 0); + conn->vgcs_call.talker = data; + /* Reset pending states, but imply that MSC acked this uplink session. */ + while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue))) + msgb_free(msg); + conn->vgcs_call.msc_ack = true; + break; + case VGCS_EV_TALKER_REL: + LOG_CALL(conn, LOGL_DEBUG, "Talker released on uplink.\n"); + /* Talker release was complete. Ignore. */ + break; + case VGCS_EV_MSC_RELEASE: + LOG_CALL(conn, LOGL_DEBUG, "MSC releases all channels. (channels are free)\n"); + /* MSC releases call (no mor talker on a different BSS), call becomes idle */ + osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0); + /* Unblock all channels. */ + llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) + osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL); + break; + case VGCS_EV_CLEANUP: + LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +static const struct osmo_fsm_state vgcs_call_fsm_states[] = { + [VGCS_CALL_ST_NULL] = { + .name = "NULL", + .in_event_mask = S(VGCS_EV_SETUP), + .out_state_mask = S(VGCS_CALL_ST_IDLE), + .action = vgcs_call_fsm_null, + }, + [VGCS_CALL_ST_IDLE] = { + .name = "IDLE", + .in_event_mask = S(VGCS_EV_TALKER_DET) | + S(VGCS_EV_MSC_SEIZE) | + S(VGCS_EV_MSC_RELEASE) | + S(VGCS_EV_MSC_REJECT) | + S(VGCS_EV_CLEANUP), + .out_state_mask = S(VGCS_CALL_ST_BUSY) | + S(VGCS_CALL_ST_BLOCKED) | + S(VGCS_CALL_ST_NULL), + .action = vgcs_call_fsm_idle, + }, + [VGCS_CALL_ST_BUSY] = { + .name = "BUSY", + .in_event_mask = S(VGCS_EV_TALKER_EST) | + S(VGCS_EV_TALKER_DATA) | + S(VGCS_EV_MSC_DTAP) | + S(VGCS_EV_TALKER_REL) | + S(VGCS_EV_MSC_SEIZE) | + S(VGCS_EV_MSC_ACK) | + S(VGCS_EV_MSC_REJECT) | + S(VGCS_EV_CLEANUP), + .out_state_mask = S(VGCS_CALL_ST_IDLE) | + S(VGCS_CALL_ST_BLOCKED) | + S(VGCS_CALL_ST_NULL), + .action = vgcs_call_fsm_busy, + }, + [VGCS_CALL_ST_BLOCKED] = { + .name = "BLOCKED", + .in_event_mask = S(VGCS_EV_CALLING_ASSIGNED) | + S(VGCS_EV_TALKER_REL) | + S(VGCS_EV_MSC_RELEASE) | + S(VGCS_EV_CLEANUP), + .out_state_mask = S(VGCS_CALL_ST_IDLE) | + S(VGCS_CALL_ST_BUSY) | + S(VGCS_CALL_ST_NULL), + .action = vgcs_call_fsm_blocked, + }, +}; + +static struct osmo_fsm vgcs_call_fsm = { + .name = "vgcs_call", + .states = vgcs_call_fsm_states, + .num_states = ARRAY_SIZE(vgcs_call_fsm_states), + .log_subsys = DASCI, + .event_names = vgcs_fsm_event_names, + .cleanup = vgcs_call_detach_and_destroy, +}; + +/* Handle VGCS/VBS SETUP message. + * + * See 3GPP TS 48.008 §3.2.1.50 + */ +int vgcs_vbs_call_start(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + int payload_length = msg->tail - msg->l4h; + struct tlv_parsed tp; + struct gsm_subscriber_connection *c; + struct gsm0808_group_callref *gc = &conn->vgcs_call.gc_ie; + int rc; + uint8_t cause; + + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS; + goto reject; + } + + /* Check for mandatory Group Call Reference. */ + if (!TLVP_PRESENT(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)) { + LOGPFSML(conn->fi, LOGL_ERROR, "Mandatory group call reference not present.\n"); + cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + goto reject; + } + + /* Decode Group Call Reference. */ + rc = gsm0808_dec_group_callref(gc, TLVP_VAL(&tp, GSM0808_IE_GROUP_CALL_REFERENCE), + TLVP_LEN(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)); + if (rc < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode group call reference.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + conn->vgcs_call.sf = gc->sf; + conn->vgcs_call.call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo; + + /* Check for duplicated callref. */ + llist_for_each_entry(c, &conn->network->subscr_conns, entry) { + if (!c->vgcs_call.fi) + continue; + if (c == conn) + continue; + if (conn->vgcs_call.sf == c->vgcs_call.sf + && conn->vgcs_call.call_ref == c->vgcs_call.call_ref) { + LOG_CALL(conn, LOGL_ERROR, "A %s call with callref %s already exists.\n", + (conn->vgcs_call.sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS", + gsm44068_group_id_string(conn->vgcs_call.call_ref)); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + } + + /* Decode VGCS Feature Flags */ + if (TLVP_PRESENT(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS)) { + rc = gsm0808_dec_vgcs_feature_flags(&conn->vgcs_call.ff, + TLVP_VAL(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS), + TLVP_LEN(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS)); + if (rc < 0) { + LOG_CALL(conn, LOGL_ERROR, "Unable to decode feature flags.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + conn->vgcs_call.ff_present = true; + } + + /* Create VGCS FSM. */ + conn->vgcs_call.fi = osmo_fsm_inst_alloc(&vgcs_call_fsm, conn->network, conn, LOGL_DEBUG, NULL); + if (!conn->vgcs_call.fi) { + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + + /* Init list of cells that are used by the call. */ + INIT_LLIST_HEAD(&conn->vgcs_call.chan_list); + + /* Init L3 queue. */ + INIT_LLIST_HEAD(&conn->vgcs_call.l3_queue); + + osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_SETUP, NULL); + return 0; +reject: + bsc_tx_setup_refuse(conn, cause); + return -EINVAL; +} + +/* + * VGCS chan FSM + */ + +static void vgcs_chan_detach_and_destroy(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + if (conn->vgcs_chan.fi->state != VGCS_CHAN_ST_WAIT_EST) { + /* Remove call from notification channel. */ + if (conn->lchan) + rsl_notification_cmd(conn->lchan->ts->trx->bts, NULL, &conn->vgcs_chan.gc_ie, NULL); + else + LOG_CHAN(conn, LOGL_ERROR, "Unable to remove notification, lchan is already gone.\n"); + } + + /* Detach from call, if not already. */ + if (conn->vgcs_chan.call) { + llist_del(&conn->vgcs_chan.list); + conn->vgcs_chan.call = NULL; + } + + /* Remove pointer of FSM. */ + conn->vgcs_chan.fi = NULL; +} + +static void uplink_released(struct gsm_subscriber_connection *conn) +{ + LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n"); + /* Go into blocked or free state. */ + if (conn->vgcs_chan.call && conn->vgcs_chan.call->vgcs_call.fi + && conn->vgcs_chan.call->vgcs_call.fi->state == VGCS_CALL_ST_IDLE) + osmo_fsm_inst_state_chg(conn->vgcs_chan.fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0); + else + osmo_fsm_inst_state_chg(conn->vgcs_chan.fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0); +} + +static void vgcs_chan_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct lchan_activate_info info; + + switch (event) { + case VGCS_EV_ASSIGN_REQ: + LOG_CHAN(conn, LOGL_DEBUG, "MSC assigns channel.\n"); + /* MSC requests channel assignment. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_WAIT_EST, 0, 0); + /* Requesting channel from BTS. */ + info = (struct lchan_activate_info){ + .activ_for = ACTIVATE_FOR_VGCS_CHANNEL, + .for_conn = conn, + .chreq_reason = GSM_CHREQ_REASON_OTHER, + .ch_mode_rate = conn->vgcs_chan.ch_mode_rate, + .ch_indctr = conn->vgcs_chan.ct.ch_indctr, + /* TSC is used from TS config. */ + .encr = conn->vgcs_chan.new_lchan->encr, + /* Timing advance of 0 is used until channel is activated for uplink. */ + .ta_known = true, + .ta = 0, + }; + if (conn->vgcs_chan.call->vgcs_call.sf == GSM0808_SF_VGCS) + info.type_for = LCHAN_TYPE_FOR_VGCS; + else + info.type_for = LCHAN_TYPE_FOR_VBS; + /* Activate lchan. If an error occurs, this the function call may trigger VGCS_EV_LCHAN_ERROR event. + * This means that this must be the last action in this handler. */ + lchan_activate(conn->vgcs_chan.new_lchan, &info); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_chan_fsm_wait_est(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + const struct mgcp_conn_peer *mgw_info; + + switch (event) { + case VGCS_EV_LCHAN_ACTIVE: + LOG_CHAN(conn, LOGL_DEBUG, "lchan is active.\n"); + /* If no MGW is used. */ + if (!gscon_is_aoip(conn)) { + LOG_CHAN(conn, LOGL_DEBUG, "Not connecting MGW endpoint, no AoIP connection.\n"); + goto no_aoip; + } + /* Send activation to MGW. */ + LOG_CHAN(conn, LOGL_DEBUG, "Connecting MGW endpoint to the MSC's RTP port: %s:%u\n", + conn->vgcs_chan.msc_rtp_addr, conn->vgcs_chan.msc_rtp_port); + /* Connect MGW. The function call may trigger VGCS_EV_MGW_OK event. + * This means that this must be the last action in this handler. + * If this function fails, VGCS_EV_MGW_FAIL will not trigger. */ + if (!gscon_connect_mgw_to_msc(conn, + conn->vgcs_chan.new_lchan, + conn->vgcs_chan.msc_rtp_addr, + conn->vgcs_chan.msc_rtp_port, + fi, + VGCS_EV_MGW_OK, + VGCS_EV_MGW_FAIL, + NULL, + NULL)) { + /* Report failure to MSC. */ + bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + break; + } + break; + case VGCS_EV_LCHAN_ERROR: + LOG_CHAN(conn, LOGL_DEBUG, "lchan failed.\n"); + /* BTS reports failure on channel request. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_NULL, 0, 0); + /* Add/update SI10. */ + if (conn->vgcs_chan.call) + si10_update(conn->vgcs_chan.call); + /* Report failure to MSC. */ + bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + break; + case VGCS_EV_MGW_OK: + LOG_CHAN(conn, LOGL_DEBUG, "MGW endpoint connected.\n"); + /* MGW reports success. */ + mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc); + if (!mgw_info) { + LOG_CHAN(conn, LOGL_ERROR, "Unable to retrieve RTP port info allocated by MGW for" + " the MSC side."); + /* Report failure to MSC. */ + bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + break; + } + LOG_CHAN(conn, LOGL_DEBUG, "MGW's MSC side CI: %s:%u\n", mgw_info->addr, mgw_info->port); +no_aoip: + /* Channel established from BTS. */ + gscon_change_primary_lchan(conn, conn->vgcs_chan.new_lchan); + /* Change state according to call state. */ + if (conn->vgcs_chan.call && conn->vgcs_chan.call->vgcs_call.fi + && conn->vgcs_chan.call->vgcs_call.fi->state == VGCS_CALL_ST_IDLE) + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0); + else + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0); + if (conn->vgcs_chan.call) { + /* Add call to notification channel. */ + rsl_notification_cmd(conn->lchan->ts->trx->bts, conn->lchan, &conn->vgcs_chan.gc_ie, NULL); + /* Add/update SI10. */ + si10_update(conn->vgcs_chan.call); + } + /* Report result to MSC. */ + bsc_tx_vgcs_vbs_assignment_result(conn, &conn->vgcs_chan.ct, &conn->vgcs_chan.ci, + conn->vgcs_chan.call_id); + break; + case VGCS_EV_MGW_FAIL: + LOG_CHAN(conn, LOGL_DEBUG, "MGW endpoint failed.\n"); + /* MGW reports failure. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_NULL, 0, 0); + /* Add/update SI10. */ + if (conn->vgcs_chan.call) + si10_update(conn->vgcs_chan.call); + /* Report failure to MSC. */ + bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + break; + case VGCS_EV_CLEANUP: + LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + /* MSC wants to terminate. */ + osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL); + break; + case VGCS_EV_BLOCK: + case VGCS_EV_UNBLOCK: + /* Ignore, because channel is not yet ready. */ + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_chan_fsm_active_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv, *cc; + + switch (event) { + case VGCS_EV_UNBLOCK: + LOG_CHAN(conn, LOGL_DEBUG, "Unblocking channel.\n"); + /* No uplink is used in other cell. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0); + break; + case VGCS_EV_TALKER_DET: + LOG_CHAN(conn, LOGL_DEBUG, "Talker detected on blocked channel.\n"); + if (conn->vgcs_chan.call->vgcs_call.sf == GSM0808_SF_VBS) + LOG_CHAN(conn, LOGL_ERROR, "Talker detection not allowed on VBS channel.\n"); + /* Race condition: BTS detected a talker. Waiting for talker to establish or fail. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0); + break; + case VGCS_EV_TALKER_EST: + cc = find_calling_subscr_conn(conn); + if (!cc) { + LOG_CHAN(conn, LOGL_ERROR, "No assignment requested from MSC!\n"); + /* Uplink is used while blocked. Waiting for channel to be release. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0); + /* Send UPLINK RELEASE to MS. */ + gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL); + /* Go into blocked or free state. */ + uplink_released(conn); + break; + } + /* Talker is assigning to this channel. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_EST, 0, 0); + /* Report talker detection to call state machine. */ + if (conn->vgcs_chan.call) + osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_CALLING_ASSIGNED, conn); + /* Repeat notification for the MS that has been assigned. */ + rsl_notification_cmd(conn->lchan->ts->trx->bts, conn->lchan, &conn->vgcs_chan.gc_ie, NULL); + break; + case VGCS_EV_CLEANUP: + LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + /* MSC wants to terminate. */ + osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_chan_fsm_enter_active_free(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* Send UPLINK FREE message to BTS. This hits on every state change (and or timer start). */ + LOG_CHAN(conn, LOGL_DEBUG, "Sending UPLINK FREE message to channel.\n"); + gsm48_send_uplink_free(conn->lchan, 0, NULL); +} + +static void vgcs_chan_fsm_active_free(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + switch (event) { + case VGCS_EV_BLOCK: + LOG_CHAN(conn, LOGL_DEBUG, "Blocking channel.\n"); + /* Uplink is used in other cell. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0); + /* Send UPLINK BUSY to MS. */ + LOG_CHAN(conn, LOGL_DEBUG, "Sending UPLINK BUSY message to channel.\n"); + gsm48_send_uplink_busy(conn->lchan); + break; + case VGCS_EV_TALKER_DET: + LOG_CHAN(conn, LOGL_DEBUG, "Talker detected on free channel.\n"); + /* BTS detected a talker. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_INIT, 0, 0); + /* Report talker detection to call state machine. */ + if (conn->vgcs_chan.call) + osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_DET, data); + break; + case VGCS_EV_CLEANUP: + LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + /* MSC wants to terminate. */ + osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_chan_fsm_active_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + uint8_t cause = (data) ? *(uint8_t *)data : 0; + + switch (event) { + case VGCS_EV_BLOCK: + case VGCS_EV_REJECT: + LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n"); + /* Uplink is used in other cell. Waiting for channel to be established and then released. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0); + break; + case VGCS_EV_TALKER_EST: + LOG_CHAN(conn, LOGL_DEBUG, "Talker established uplink.\n"); + /* Uplink has been established */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_EST, 0, 0); + /* Report talker establishment to call state machine. */ + if (conn->vgcs_chan.call) + osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_EST, data); + break; + case VGCS_EV_TALKER_FAIL: + LOG_CHAN(conn, LOGL_NOTICE, "Uplink failed, establishment timeout.\n"); + /* Release datalink */ + rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END); + /* fall thru */ + case VGCS_EV_TALKER_REL: + LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n"); + /* Uplink establishment failed. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0); + /* Report release indication to call state machine. */ + if (conn->vgcs_chan.call) + osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL, &cause); + break; + case VGCS_EV_CLEANUP: + LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + /* MSC wants to terminate. */ + osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_chan_fsm_active_est(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + uint8_t cause = (data) ? *(uint8_t *)data : 0; + struct msgb *msg = data; + + switch (event) { + case VGCS_EV_BLOCK: + case VGCS_EV_REJECT: + LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n"); + /* Uplink is used in other cell. Waiting for channel to be release. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0); + /* Send UPLINK RELEASE to MS. */ + gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL); + /* Go into blocked or free state. */ + uplink_released(conn); + break; + case VGCS_EV_TALKER_DATA: + LOG_CHAN(conn, LOGL_DEBUG, "Talker sends data on uplink.\n"); + if (msg) { + struct gsm48_hdr *gh; + uint8_t pdisc; + uint8_t msg_type; + if (msgb_l3len(msg) < sizeof(*gh)) { + LOG_LCHAN(msg->lchan, LOGL_ERROR, + "Message too short for a GSM48 header (%u)\n", msgb_l3len(msg)); + break; + } + gh = msgb_l3(msg); + pdisc = gsm48_hdr_pdisc(gh); + msg_type = gsm48_hdr_msg_type(gh); + if (pdisc == GSM48_PDISC_RR && msg_type == GSM48_MT_RR_UPLINK_RELEASE) { + LOG_CHAN(conn, LOGL_DEBUG, "Uplink is released by UPLINK RELEASE message.\n"); + /* Release datalink */ + rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END); + /* Talker released the uplink. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0); + /* Report talker release to call state machine. */ + if (conn->vgcs_chan.call) { + cause = GSM0808_CAUSE_CALL_CONTROL; + osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL, + &cause); + } + break; + } + if (pdisc == GSM48_PDISC_RR && msg_type == GSM48_MT_RR_ASS_COMPL) { + LOG_CHAN(conn, LOGL_DEBUG, "Asssignment complete.\n"); + struct gsm_subscriber_connection *cc; + cc = find_calling_subscr_conn(conn); + if (!cc) { + LOG_CHAN(conn, LOGL_ERROR, "No assignment requested from MSC!\n"); + break; + } + LOG_CHAN(conn, LOGL_DEBUG, "Trigger State machine.\n"); + osmo_fsm_inst_dispatch(cc->assignment.fi, ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE, msg); + break; + } + } + /* Report talker data to call state machine. */ + if (conn->vgcs_chan.call) + osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_DATA, data); + break; + case VGCS_EV_MSC_DTAP: + LOG_CHAN(conn, LOGL_DEBUG, "MSC sends DTAP message to talker.\n"); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, data); + break; + case VGCS_EV_TALKER_FAIL: + LOG_CHAN(conn, LOGL_NOTICE, "Uplink failed after establishment.\n"); + /* Release datalink */ + rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END); + /* fall thru */ + case VGCS_EV_TALKER_REL: + LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n"); + /* Talker released the uplink. */ + osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0); + /* Report talker release to call state machine. */ + if (conn->vgcs_chan.call) + osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL, &cause); + break; + case VGCS_EV_CLEANUP: + LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + /* MSC wants to terminate. */ + osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_chan_fsm_active_rel(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + switch (event) { + case VGCS_EV_BLOCK: + case VGCS_EV_REJECT: + LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n"); + /* Race condition: Uplink is used in other cell, we are already releasing. */ + break; + case VGCS_EV_TALKER_EST: + LOG_CHAN(conn, LOGL_DEBUG, "Talker established uplink, releasing.\n"); + /* Finally the talker established the connection. Send UPLINK RELEASE to MS. */ + gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL); + /* Release datalink */ + rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END); + /* fall thru */ + case VGCS_EV_TALKER_FAIL: + /* Release datalink */ + rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END); + /* fall thru */ + case VGCS_EV_TALKER_REL: + /* Go into blocked or free state. */ + uplink_released(conn); + break; + case VGCS_EV_CLEANUP: + LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n"); + /* MSC wants to terminate. */ + osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +static const struct osmo_fsm_state vgcs_chan_fsm_states[] = { + [VGCS_CHAN_ST_NULL] = { + .name = "NULL", + .in_event_mask = S(VGCS_EV_ASSIGN_REQ), + .out_state_mask = S(VGCS_CHAN_ST_WAIT_EST), + .action = vgcs_chan_fsm_null, + }, + [VGCS_CHAN_ST_WAIT_EST] = { + .name = "WAIT_EST", + .in_event_mask = S(VGCS_EV_LCHAN_ACTIVE) | + S(VGCS_EV_LCHAN_ERROR) | + S(VGCS_EV_MGW_OK) | + S(VGCS_EV_MGW_FAIL) | + S(VGCS_EV_CLEANUP) | + S(VGCS_EV_BLOCK) | + S(VGCS_EV_UNBLOCK), + .out_state_mask = S(VGCS_CHAN_ST_NULL) | + S(VGCS_CHAN_ST_ACTIVE_BLOCKED) | + S(VGCS_CHAN_ST_ACTIVE_FREE), + .action = vgcs_chan_fsm_wait_est, + }, + [VGCS_CHAN_ST_ACTIVE_BLOCKED] = { + .name = "ACTIVE/BLOCKED", + .in_event_mask = S(VGCS_EV_UNBLOCK) | + S(VGCS_EV_TALKER_DET) | + S(VGCS_EV_TALKER_EST) | + S(VGCS_EV_CLEANUP), + .out_state_mask = S(VGCS_CHAN_ST_NULL) | + S(VGCS_CHAN_ST_ACTIVE_EST) | + S(VGCS_CHAN_ST_ACTIVE_FREE) | + S(VGCS_CHAN_ST_ACTIVE_REL), + .action = vgcs_chan_fsm_active_blocked, + }, + [VGCS_CHAN_ST_ACTIVE_FREE] = { + .name = "ACTIVE/FREE", + .in_event_mask = S(VGCS_EV_BLOCK) | + S(VGCS_EV_TALKER_DET) | + S(VGCS_EV_CLEANUP), + .out_state_mask = S(VGCS_CHAN_ST_NULL) | + S(VGCS_CHAN_ST_ACTIVE_BLOCKED) | + S(VGCS_CHAN_ST_ACTIVE_INIT) | + S(VGCS_CHAN_ST_ACTIVE_FREE), + .action = vgcs_chan_fsm_active_free, + .onenter = vgcs_chan_fsm_enter_active_free, + }, + [VGCS_CHAN_ST_ACTIVE_INIT] = { + .name = "ACTIVE/INIT", + .in_event_mask = S(VGCS_EV_BLOCK) | + S(VGCS_EV_REJECT) | + S(VGCS_EV_TALKER_EST) | + S(VGCS_EV_TALKER_FAIL) | + S(VGCS_EV_CLEANUP), + .out_state_mask = S(VGCS_CHAN_ST_NULL) | + S(VGCS_CHAN_ST_ACTIVE_EST) | + S(VGCS_CHAN_ST_ACTIVE_REL) | + S(VGCS_CHAN_ST_ACTIVE_FREE), + .action = vgcs_chan_fsm_active_init, + }, + [VGCS_CHAN_ST_ACTIVE_EST] = { + .name = "ACTIVE/ESTABLISHED", + .in_event_mask = S(VGCS_EV_BLOCK) | + S(VGCS_EV_REJECT) | + S(VGCS_EV_TALKER_DATA) | + S(VGCS_EV_MSC_DTAP) | + S(VGCS_EV_TALKER_REL) | + S(VGCS_EV_TALKER_FAIL) | + S(VGCS_EV_CLEANUP), + .out_state_mask = S(VGCS_CHAN_ST_NULL) | + S(VGCS_CHAN_ST_ACTIVE_FREE) | + S(VGCS_CHAN_ST_ACTIVE_REL), + .action = vgcs_chan_fsm_active_est, + }, + [VGCS_CHAN_ST_ACTIVE_REL] = { + .name = "ACTIVE/RELEASE", + .in_event_mask = S(VGCS_EV_BLOCK) | + S(VGCS_EV_REJECT) | + S(VGCS_EV_TALKER_EST) | + S(VGCS_EV_TALKER_REL) | + S(VGCS_EV_TALKER_FAIL) | + S(VGCS_EV_CLEANUP), + .out_state_mask = S(VGCS_CHAN_ST_NULL) | + S(VGCS_CHAN_ST_ACTIVE_BLOCKED) | + S(VGCS_CHAN_ST_ACTIVE_FREE), + .action = vgcs_chan_fsm_active_rel, + }, +}; + +static struct osmo_fsm vgcs_chan_fsm = { + .name = "vgcs_chan", + .states = vgcs_chan_fsm_states, + .num_states = ARRAY_SIZE(vgcs_chan_fsm_states), + .log_subsys = DASCI, + .event_names = vgcs_fsm_event_names, + .cleanup = vgcs_chan_detach_and_destroy, +}; + +/* Handle VGCS/VBS ASSIGNMENT REQUEST message. + * + * See 3GPP TS 48.008 §3.2.1.53 + */ +int vgcs_vbs_chan_start(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + int payload_length = msg->tail - msg->l4h; + struct tlv_parsed tp; + struct gsm_subscriber_connection *c; + struct gsm0808_group_callref *gc = &conn->vgcs_chan.gc_ie; + struct assignment_request req = { + .aoip = gscon_is_aoip(conn), + }; + uint8_t cause; + struct gsm_bts *bts; + struct gsm_lchan *lchan = NULL; + int rc; + int i; + + if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__); + cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS; + goto reject; + } + + /* Check for mandatory IEs. */ + if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE) + || !TLVP_PRESENT(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT) + || !TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER) + || !TLVP_PRESENT(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)) { + LOGPFSML(conn->fi, LOGL_ERROR, "Mandatory IE not present.\n"); + cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + goto reject; + } + + /* Decode Channel Type element. */ + rc = gsm0808_dec_channel_type(&conn->vgcs_chan.ct, TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE), + TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE)); + if (rc < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Channel Type.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + + /* Only speech is supported. */ + if (conn->vgcs_chan.ct.ch_indctr != GSM0808_CHAN_SPEECH) { + cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS; + goto reject; + } + + /* Decode Assignment Requirement element. */ + rc = gsm0808_dec_assign_req(&conn->vgcs_chan.ar, TLVP_VAL(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT), + TLVP_LEN(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT)); + if (rc < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Assignment Requirement.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + + /* Decode Cell Identifier element. */ + rc = gsm0808_dec_cell_id(&conn->vgcs_chan.ci, TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER), + TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER)); + if (rc < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Cell Identifier.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + gsm0808_cell_id_u_name(conn->vgcs_chan.ci_str, sizeof(conn->vgcs_chan.ci_str), conn->vgcs_chan.ci.id_discr, + &conn->vgcs_chan.ci.id); + + /* Decode Group Call Reference element. */ + rc = gsm0808_dec_group_callref(gc, TLVP_VAL(&tp, GSM0808_IE_GROUP_CALL_REFERENCE), + TLVP_LEN(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)); + if (rc < 0) { + LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Group Call Reference.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + conn->vgcs_chan.sf = gc->sf; + conn->vgcs_chan.call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo; + + /* Find BTS from Cell Identity. */ + bts = gsm_bts_by_cell_id(conn->network, &conn->vgcs_chan.ci, 0); + if (!bts) { + LOG_CHAN(conn, LOGL_ERROR, "No cell found that matches the given Cell Identifier.\n"); + cause = GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE; + goto reject; + } + + /* If Cell Identity is ambiguous. */ + if (gsm_bts_by_cell_id(conn->network, &conn->vgcs_chan.ci, 1)) + LOG_CHAN(conn, LOGL_NOTICE, "More thant one cell found that match the given Cell Identifier.\n"); + + /* Decode channel related elements. + * This must be done after selecting the BTS, because codec selection requires relation to BTS. */ + rc = bssmap_handle_ass_req_ct_speech(conn, bts, &tp, &conn->vgcs_chan.ct, &req, &cause); + if (rc < 0) + goto reject; + + /* Store AoIP elements. */ + osmo_strlcpy(conn->vgcs_chan.msc_rtp_addr, req.msc_rtp_addr, sizeof(conn->vgcs_chan.msc_rtp_addr)); + conn->vgcs_chan.msc_rtp_port = req.msc_rtp_port; + if (TLVP_PRESENT(&tp, GSM0808_IE_CALL_ID)) { + /* Decode Call Identifier element. */ + rc = gsm0808_dec_call_id(&conn->vgcs_chan.call_id, TLVP_VAL(&tp, GSM0808_IE_CALL_ID), + TLVP_LEN(&tp, GSM0808_IE_CALL_ID)); + if (rc < 0) { + LOG_CHAN(conn, LOGL_ERROR, "Unable to decode Call Identifier.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + } + + /* Try to allocate a new lchan in order of preference. */ + for (i = 0; i < req.n_ch_mode_rate; i++) { + lchan = lchan_select_by_chan_mode(bts, + req.ch_mode_rate_list[i].chan_mode, + req.ch_mode_rate_list[i].chan_rate, + SELECT_FOR_VGCS, NULL); + if (!lchan) + continue; + LOG_CHAN(conn, LOGL_DEBUG, "Selected new lchan %s for mode[%d] = %s channel_rate=%d\n", + gsm_lchan_name(lchan), i, gsm48_chan_mode_name(req.ch_mode_rate_list[i].chan_mode), + req.ch_mode_rate_list[i].chan_rate); + + conn->vgcs_chan.ch_mode_rate = req.ch_mode_rate_list[i]; + break; + } + if (!lchan) { + LOG_CHAN(conn, LOGL_ERROR, "Requested lchan not available.\n"); + cause = GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE; + goto reject; + } + conn->vgcs_chan.new_lchan = lchan; + + /* Create VGCS FSM. */ + conn->vgcs_chan.fi = osmo_fsm_inst_alloc(&vgcs_chan_fsm, conn->network, conn, LOGL_DEBUG, NULL); + if (!conn->vgcs_chan.fi) + goto reject; + + /* Attach to call control instance, if a call with same callref exists. */ + llist_for_each_entry(c, &conn->network->subscr_conns, entry) { + if (!c->vgcs_call.fi) + continue; + if (c->vgcs_call.sf == conn->vgcs_chan.sf + && c->vgcs_call.call_ref == conn->vgcs_chan.call_ref) { + llist_add_tail(&conn->vgcs_chan.list, &c->vgcs_call.chan_list); + conn->vgcs_chan.call = c; + break; + } + } + if (!conn->vgcs_chan.call) { + LOG_CHAN(conn, LOGL_ERROR, "A %s call with callref %s does not exist.\n", + (conn->vgcs_chan.sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS", + gsm44068_group_id_string(conn->vgcs_chan.call_ref)); + cause = GSM0808_CAUSE_VGCS_VBS_CALL_NON_EXISTENT; + osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL); + goto reject; + } + + osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_ASSIGN_REQ, NULL); + return 0; +reject: + bsc_tx_vgcs_vbs_assignment_fail(conn, cause); + return -EINVAL; +} + +/* Return lchan of group call that exists in the same BTS. */ +struct gsm_lchan *vgcs_vbs_find_lchan(struct gsm_bts *bts, struct gsm0808_group_callref *gc) +{ + struct gsm_subscriber_connection *call = NULL, *c; + struct gsm_lchan *lchan = NULL; + uint32_t call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo; + + /* Find group call. */ + llist_for_each_entry(c, &bts->network->subscr_conns, entry) { + if (!c->vgcs_call.fi) + continue; + if (c->vgcs_call.sf == gc->sf + && c->vgcs_call.call_ref == call_ref) { + call = c; + break; + } + } + if (!call) { + LOGP(DASCI, LOGL_ERROR, "Cannot assign to channel, %s channel with callref %s does not exist.\n", + (gc->sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS", gsm44068_group_id_string(call_ref)); + return NULL; + } + + /* Find channel in same BTS. */ + llist_for_each_entry(c, &call->vgcs_call.chan_list, vgcs_chan.list) { + if (c->lchan && c->lchan->ts->trx->bts == bts) + lchan = c->lchan; + } + if (!call) { + LOGP(DASCI, LOGL_ERROR, "Cannot assign to channel, caller's BTS has no %s channel with callref %s.\n", + (gc->sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS", gsm44068_group_id_string(call_ref)); + return NULL; + } + + return lchan; +} |