From 958f259f9592e3729a2ebf4084d8f37bdba6bf3c Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Sun, 27 May 2018 01:26:31 +0200 Subject: dissolve libbsc: move all to src/osmo-bsc, link .o files Move all of libbsc/ into osmo-bsc/, and separate/move some implementations to allow linking from utils/* and ipaccess/* without pulling in unccessary dependencies. Some utilities use gsm_network and gsm_bts structs, which already include data structures for fairly advanced uses. Move initialization that only osmo-bsc needs into new bsc_network_init() and bsc_bts_alloc_register() functions, so that the leaner tools can use the old gsm_* versions without the need to link everything (e.g. handover and lchan alloc code). In some instances, there need to be stubs if to cut off linking "just before the RSL level" and prevent dependencies from creeping in. - abis_rsl_rcvmsg(): the only program currently interpreting RSL messages is osmo-bsc, the utils are merely concerned with OML, if at all. - paging_flush_bts(): ip.access nanobts models call this when the RSL link is dropped. Only osmo-bsc actually needs to do anything there. - on_gsm_ts_init(): the mechanism to trigger timeslot initialization is related to OML, while this action to take on init would pull in RSL dependencies. utils/ and ipaccess/ each have a stubs.c file to implement these stubs. Tests implement stubs inline where required. From src/utils/, src/ipaccess/ and tests/*/, link in .o files from osmo-bsc/. In order for this to work, the osmo-bsc subdir must be built before the other source trees. (An alternative would be to include the .c files as sources, but that would re-compile them in every source tree. Not a large burden really, but unless linking .o files gives problems, let's have the quicker build.) Minor obvious cleanups creep in with this patch, I will not bother to name them individually now unless code review asks me to. Rationale: 1) libbsc has been separate to use it for osmo-nitb and osmo-bsc in the old openbsc.git. This is no longer required, and spreading over libbsc and osmo-bsc is distracting. 2) Recently, ridiculous linking requirements have made adding new functions cumbersome, because libbsc has started depending on osmo-bsc/*.c implementations: on gscon FSM and bssap functions. For example, neither bs11_config nor ipaccess-config nor bts_test need handover_cfg or BSSMAP message composition. It makes no sense to link the entire osmo-bsc to it, nor do we want to keep adding stubs to each linking realm. Change-Id: I36a586726f5818121abe54d25654819fc451d3bf --- configure.ac | 1 - include/osmocom/bsc/Makefile.am | 1 - include/osmocom/bsc/bss.h | 1 - include/osmocom/bsc/chan_alloc.h | 1 - include/osmocom/bsc/common_bsc.h | 5 - include/osmocom/bsc/gsm_data.h | 11 +- src/Makefile.am | 8 +- src/ipaccess/Makefile.am | 12 +- src/ipaccess/ipaccess-config.c | 7 +- src/ipaccess/stubs.c | 43 + src/libbsc/Makefile.am | 70 - src/libbsc/a_reset.c | 205 -- src/libbsc/abis_nm.c | 2978 --------------- src/libbsc/abis_nm_ipaccess.c | 87 - src/libbsc/abis_nm_vty.c | 188 - src/libbsc/abis_om2000.c | 2769 -------------- src/libbsc/abis_om2000_vty.c | 604 ---- src/libbsc/abis_rsl.c | 3070 ---------------- src/libbsc/acc_ramp.c | 363 -- src/libbsc/arfcn_range_encode.c | 336 -- src/libbsc/bsc_api.c | 841 ----- src/libbsc/bsc_ctrl_commands.c | 500 --- src/libbsc/bsc_ctrl_lookup.c | 123 - src/libbsc/bsc_dyn_ts.c | 60 - src/libbsc/bsc_init.c | 591 --- src/libbsc/bsc_rf_ctrl.c | 534 --- src/libbsc/bsc_rll.c | 139 - src/libbsc/bsc_subscr_conn_fsm.c | 1204 ------- src/libbsc/bsc_subscriber.c | 168 - src/libbsc/bsc_vty.c | 5003 -------------------------- src/libbsc/bts_ericsson_rbs2000.c | 212 -- src/libbsc/bts_init.c | 30 - src/libbsc/bts_ipaccess_nanobts.c | 591 --- src/libbsc/bts_ipaccess_nanobts_omlattr.c | 240 -- src/libbsc/bts_nokia_site.c | 1744 --------- src/libbsc/bts_siemens_bs11.c | 604 ---- src/libbsc/bts_sysmobts.c | 60 - src/libbsc/bts_unknown.c | 40 - src/libbsc/chan_alloc.c | 746 ---- src/libbsc/e1_config.c | 299 -- src/libbsc/gsm_04_08_utils.c | 725 ---- src/libbsc/gsm_04_80_utils.c | 40 - src/libbsc/gsm_data.c | 1234 ------- src/libbsc/handover_cfg.c | 86 - src/libbsc/handover_decision.c | 343 -- src/libbsc/handover_decision_2.c | 1830 ---------- src/libbsc/handover_logic.c | 473 --- src/libbsc/handover_vty.c | 177 - src/libbsc/meas_feed.c | 185 - src/libbsc/meas_rep.c | 134 - src/libbsc/net_init.c | 117 - src/libbsc/osmo_bsc_lcls.c | 766 ---- src/libbsc/paging.c | 473 --- src/libbsc/pcu_sock.c | 740 ---- src/libbsc/penalty_timers.c | 129 - src/libbsc/rest_octets.c | 878 ----- src/libbsc/system_information.c | 1210 ------- src/osmo-bsc/Makefile.am | 58 +- src/osmo-bsc/a_reset.c | 205 ++ src/osmo-bsc/abis_bs11.c | 21 + src/osmo-bsc/abis_nm.c | 2971 +++++++++++++++ src/osmo-bsc/abis_nm_ipaccess.c | 89 + src/osmo-bsc/abis_nm_vty.c | 188 + src/osmo-bsc/abis_om2000.c | 2769 ++++++++++++++ src/osmo-bsc/abis_om2000_vty.c | 604 ++++ src/osmo-bsc/abis_rsl.c | 3040 ++++++++++++++++ src/osmo-bsc/acc_ramp.c | 363 ++ src/osmo-bsc/arfcn_range_encode.c | 336 ++ src/osmo-bsc/bsc_api.c | 841 +++++ src/osmo-bsc/bsc_ctrl_commands.c | 500 +++ src/osmo-bsc/bsc_ctrl_lookup.c | 123 + src/osmo-bsc/bsc_dyn_ts.c | 60 + src/osmo-bsc/bsc_init.c | 288 ++ src/osmo-bsc/bsc_rf_ctrl.c | 534 +++ src/osmo-bsc/bsc_rll.c | 139 + src/osmo-bsc/bsc_subscr_conn_fsm.c | 1204 +++++++ src/osmo-bsc/bsc_subscriber.c | 168 + src/osmo-bsc/bsc_vty.c | 5003 ++++++++++++++++++++++++++ src/osmo-bsc/bts_ericsson_rbs2000.c | 212 ++ src/osmo-bsc/bts_init.c | 30 + src/osmo-bsc/bts_ipaccess_nanobts.c | 591 +++ src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c | 240 ++ src/osmo-bsc/bts_nokia_site.c | 1744 +++++++++ src/osmo-bsc/bts_siemens_bs11.c | 604 ++++ src/osmo-bsc/bts_sysmobts.c | 60 + src/osmo-bsc/bts_unknown.c | 40 + src/osmo-bsc/chan_alloc.c | 734 ++++ src/osmo-bsc/e1_config.c | 299 ++ src/osmo-bsc/gsm_04_08_utils.c | 705 ++++ src/osmo-bsc/gsm_04_80_utils.c | 40 + src/osmo-bsc/gsm_data.c | 1305 +++++++ src/osmo-bsc/handover_cfg.c | 86 + src/osmo-bsc/handover_decision.c | 343 ++ src/osmo-bsc/handover_decision_2.c | 1830 ++++++++++ src/osmo-bsc/handover_logic.c | 473 +++ src/osmo-bsc/handover_vty.c | 177 + src/osmo-bsc/meas_feed.c | 185 + src/osmo-bsc/meas_rep.c | 134 + src/osmo-bsc/net_init.c | 69 + src/osmo-bsc/osmo_bsc_lcls.c | 766 ++++ src/osmo-bsc/osmo_bsc_main.c | 369 ++ src/osmo-bsc/paging.c | 473 +++ src/osmo-bsc/pcu_sock.c | 740 ++++ src/osmo-bsc/penalty_timers.c | 129 + src/osmo-bsc/rest_octets.c | 878 +++++ src/osmo-bsc/system_information.c | 1210 +++++++ src/utils/Makefile.am | 13 +- src/utils/bs11_config.c | 3 +- src/utils/stubs.c | 36 + tests/abis/Makefile.am | 4 +- tests/abis/abis_test.c | 2 + tests/bsc/Makefile.am | 21 +- tests/bsc/bsc_test.c | 3 +- tests/gsm0408/Makefile.am | 6 +- tests/gsm0408/gsm0408_test.c | 8 +- tests/handover/Makefile.am | 30 +- tests/handover/handover_test.c | 8 +- tests/nanobts_omlattr/Makefile.am | 4 +- tests/nanobts_omlattr/nanobts_omlattr_test.c | 3 + tests/subscr/Makefile.am | 2 +- 120 files changed, 34148 insertions(+), 33994 deletions(-) delete mode 100644 include/osmocom/bsc/common_bsc.h create mode 100644 src/ipaccess/stubs.c delete mode 100644 src/libbsc/Makefile.am delete mode 100644 src/libbsc/a_reset.c delete mode 100644 src/libbsc/abis_nm.c delete mode 100644 src/libbsc/abis_nm_ipaccess.c delete mode 100644 src/libbsc/abis_nm_vty.c delete mode 100644 src/libbsc/abis_om2000.c delete mode 100644 src/libbsc/abis_om2000_vty.c delete mode 100644 src/libbsc/abis_rsl.c delete mode 100644 src/libbsc/acc_ramp.c delete mode 100644 src/libbsc/arfcn_range_encode.c delete mode 100644 src/libbsc/bsc_api.c delete mode 100644 src/libbsc/bsc_ctrl_commands.c delete mode 100644 src/libbsc/bsc_ctrl_lookup.c delete mode 100644 src/libbsc/bsc_dyn_ts.c delete mode 100644 src/libbsc/bsc_init.c delete mode 100644 src/libbsc/bsc_rf_ctrl.c delete mode 100644 src/libbsc/bsc_rll.c delete mode 100644 src/libbsc/bsc_subscr_conn_fsm.c delete mode 100644 src/libbsc/bsc_subscriber.c delete mode 100644 src/libbsc/bsc_vty.c delete mode 100644 src/libbsc/bts_ericsson_rbs2000.c delete mode 100644 src/libbsc/bts_init.c delete mode 100644 src/libbsc/bts_ipaccess_nanobts.c delete mode 100644 src/libbsc/bts_ipaccess_nanobts_omlattr.c delete mode 100644 src/libbsc/bts_nokia_site.c delete mode 100644 src/libbsc/bts_siemens_bs11.c delete mode 100644 src/libbsc/bts_sysmobts.c delete mode 100644 src/libbsc/bts_unknown.c delete mode 100644 src/libbsc/chan_alloc.c delete mode 100644 src/libbsc/e1_config.c delete mode 100644 src/libbsc/gsm_04_08_utils.c delete mode 100644 src/libbsc/gsm_04_80_utils.c delete mode 100644 src/libbsc/gsm_data.c delete mode 100644 src/libbsc/handover_cfg.c delete mode 100644 src/libbsc/handover_decision.c delete mode 100644 src/libbsc/handover_decision_2.c delete mode 100644 src/libbsc/handover_logic.c delete mode 100644 src/libbsc/handover_vty.c delete mode 100644 src/libbsc/meas_feed.c delete mode 100644 src/libbsc/meas_rep.c delete mode 100644 src/libbsc/net_init.c delete mode 100644 src/libbsc/osmo_bsc_lcls.c delete mode 100644 src/libbsc/paging.c delete mode 100644 src/libbsc/pcu_sock.c delete mode 100644 src/libbsc/penalty_timers.c delete mode 100644 src/libbsc/rest_octets.c delete mode 100644 src/libbsc/system_information.c create mode 100644 src/osmo-bsc/a_reset.c create mode 100644 src/osmo-bsc/abis_bs11.c create mode 100644 src/osmo-bsc/abis_nm.c create mode 100644 src/osmo-bsc/abis_nm_ipaccess.c create mode 100644 src/osmo-bsc/abis_nm_vty.c create mode 100644 src/osmo-bsc/abis_om2000.c create mode 100644 src/osmo-bsc/abis_om2000_vty.c create mode 100644 src/osmo-bsc/abis_rsl.c create mode 100644 src/osmo-bsc/acc_ramp.c create mode 100644 src/osmo-bsc/arfcn_range_encode.c create mode 100644 src/osmo-bsc/bsc_api.c create mode 100644 src/osmo-bsc/bsc_ctrl_commands.c create mode 100644 src/osmo-bsc/bsc_ctrl_lookup.c create mode 100644 src/osmo-bsc/bsc_dyn_ts.c create mode 100644 src/osmo-bsc/bsc_init.c create mode 100644 src/osmo-bsc/bsc_rf_ctrl.c create mode 100644 src/osmo-bsc/bsc_rll.c create mode 100644 src/osmo-bsc/bsc_subscr_conn_fsm.c create mode 100644 src/osmo-bsc/bsc_subscriber.c create mode 100644 src/osmo-bsc/bsc_vty.c create mode 100644 src/osmo-bsc/bts_ericsson_rbs2000.c create mode 100644 src/osmo-bsc/bts_init.c create mode 100644 src/osmo-bsc/bts_ipaccess_nanobts.c create mode 100644 src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c create mode 100644 src/osmo-bsc/bts_nokia_site.c create mode 100644 src/osmo-bsc/bts_siemens_bs11.c create mode 100644 src/osmo-bsc/bts_sysmobts.c create mode 100644 src/osmo-bsc/bts_unknown.c create mode 100644 src/osmo-bsc/chan_alloc.c create mode 100644 src/osmo-bsc/e1_config.c create mode 100644 src/osmo-bsc/gsm_04_08_utils.c create mode 100644 src/osmo-bsc/gsm_04_80_utils.c create mode 100644 src/osmo-bsc/gsm_data.c create mode 100644 src/osmo-bsc/handover_cfg.c create mode 100644 src/osmo-bsc/handover_decision.c create mode 100644 src/osmo-bsc/handover_decision_2.c create mode 100644 src/osmo-bsc/handover_logic.c create mode 100644 src/osmo-bsc/handover_vty.c create mode 100644 src/osmo-bsc/meas_feed.c create mode 100644 src/osmo-bsc/meas_rep.c create mode 100644 src/osmo-bsc/net_init.c create mode 100644 src/osmo-bsc/osmo_bsc_lcls.c create mode 100644 src/osmo-bsc/paging.c create mode 100644 src/osmo-bsc/pcu_sock.c create mode 100644 src/osmo-bsc/penalty_timers.c create mode 100644 src/osmo-bsc/rest_octets.c create mode 100644 src/osmo-bsc/system_information.c create mode 100644 src/utils/stubs.c diff --git a/configure.ac b/configure.ac index 62a7dd06d..25bcad706 100644 --- a/configure.ac +++ b/configure.ac @@ -168,7 +168,6 @@ AC_OUTPUT( include/osmocom/Makefile include/osmocom/bsc/Makefile src/Makefile - src/libbsc/Makefile src/libfilter/Makefile src/osmo-bsc/Makefile src/ipaccess/Makefile diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am index 0987be9dd..5fa39ebc5 100644 --- a/include/osmocom/bsc/Makefile.am +++ b/include/osmocom/bsc/Makefile.am @@ -12,7 +12,6 @@ noinst_HEADERS = \ bss.h \ bts_ipaccess_nanobts_omlattr.h \ chan_alloc.h \ - common_bsc.h \ ctrl.h \ debug.h \ e1_config.h \ diff --git a/include/osmocom/bsc/bss.h b/include/osmocom/bsc/bss.h index 9891f5f1d..ecb68d627 100644 --- a/include/osmocom/bsc/bss.h +++ b/include/osmocom/bsc/bss.h @@ -7,7 +7,6 @@ struct msgb; /* start and stop network */ extern int bsc_network_alloc(void); -extern int bsc_network_configure(const char *cfg_file); extern int bsc_shutdown_net(struct gsm_network *net); /* register all supported BTS */ diff --git a/include/osmocom/bsc/chan_alloc.h b/include/osmocom/bsc/chan_alloc.h index 98568a50f..f3aec9dd5 100644 --- a/include/osmocom/bsc/chan_alloc.h +++ b/include/osmocom/bsc/chan_alloc.h @@ -45,7 +45,6 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts); void network_chan_load(struct pchan_load *pl, struct gsm_network *net); void bts_update_t3122_chan_load(struct gsm_bts *bts); -bool trx_is_usable(const struct gsm_bts_trx *trx); bool ts_is_usable(const struct gsm_bts_trx_ts *ts); #endif /* _CHAN_ALLOC_H */ diff --git a/include/osmocom/bsc/common_bsc.h b/include/osmocom/bsc/common_bsc.h deleted file mode 100644 index c23d20cb5..000000000 --- a/include/osmocom/bsc/common_bsc.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -struct gsm_network *bsc_network_init(void *ctx); diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h index b1fceb3ea..a8d1f92b9 100644 --- a/include/osmocom/bsc/gsm_data.h +++ b/include/osmocom/bsc/gsm_data.h @@ -997,6 +997,8 @@ struct gsm_bts { }; +struct gsm_network *gsm_network_init(void *ctx); + struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num); struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num); @@ -1378,9 +1380,8 @@ struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *ne struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network); void msc_subscr_con_free(struct gsm_subscriber_connection *conn); -struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, - enum gsm_bts_type type, - uint8_t bsic); +struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, uint8_t bsic); +struct gsm_bts *bsc_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, uint8_t bsic); void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr, uint8_t e1_ts, uint8_t e1_ts_ss); @@ -1418,4 +1419,8 @@ void gsm_ts_check_init(struct gsm_bts_trx_ts *ts); void gsm_trx_mark_all_ts_uninitialized(struct gsm_bts_trx *trx); void gsm_bts_mark_all_ts_uninitialized(struct gsm_bts *bts); +bool trx_is_usable(const struct gsm_bts_trx *trx); + +bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts); + #endif /* _GSM_DATA_H */ diff --git a/src/Makefile.am b/src/Makefile.am index fc9bf8e1a..6c63eead8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,15 +19,9 @@ AM_LDFLAGS = \ $(COVERAGE_LDFLAGS) \ $(NULL) -# Libraries SUBDIRS = \ - libbsc \ libfilter \ - $(NULL) - -# Programs -SUBDIRS += \ + osmo-bsc \ utils \ ipaccess \ - osmo-bsc \ $(NULL) diff --git a/src/ipaccess/Makefile.am b/src/ipaccess/Makefile.am index 0f9045fd0..2c6282d11 100644 --- a/src/ipaccess/Makefile.am +++ b/src/ipaccess/Makefile.am @@ -33,31 +33,37 @@ bin_PROGRAMS = \ $(NULL) abisip_find_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ $(OSMO_LIBS) \ $(NULL) abisip_find_SOURCES = \ abisip-find.c \ + stubs.c \ $(NULL) ipaccess_config_SOURCES = \ ipaccess-config.c \ ipaccess-firmware.c \ network_listen.c \ + stubs.c \ $(NULL) # FIXME: resolve the bogus dependencies patched around here: ipaccess_config_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/abis_nm.o \ + $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts.o \ + $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.o \ + $(top_builddir)/src/osmo-bsc/gsm_data.o \ + $(top_builddir)/src/osmo-bsc/net_init.o \ $(OSMO_LIBS) \ $(NULL) ipaccess_proxy_SOURCES = \ ipaccess-proxy.c \ + stubs.c \ + $(top_srcdir)/src/osmo-bsc/gsm_data.c \ $(NULL) ipaccess_proxy_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ $(OSMO_LIBS) \ $(NULL) diff --git a/src/ipaccess/ipaccess-config.c b/src/ipaccess/ipaccess-config.c index 223606373..079bae2b9 100644 --- a/src/ipaccess/ipaccess-config.c +++ b/src/ipaccess/ipaccess-config.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -54,6 +53,7 @@ #include #include #include +#include struct gsm_network *bsc_gsmnet; @@ -873,8 +873,6 @@ static void print_options(void) print_value_string(&ipa_test_strs[0], ARRAY_SIZE(ipa_test_strs)); } -extern void bts_model_nanobts_init(); - static const struct log_info_cat log_categories[] = { [DNM] = { .name = "DNM", @@ -898,6 +896,7 @@ int main(int argc, char **argv) int rc, option_index = 0, stream_id = 0xff; tall_ctx_config = talloc_named_const(NULL, 0, "ipaccess-config"); + tall_bsc_ctx = tall_ctx_config; msgb_talloc_ctx_init(tall_ctx_config, 0); osmo_init_logging2(tall_ctx_config, &log_info); @@ -1034,7 +1033,7 @@ int main(int argc, char **argv) libosmo_abis_init(tall_ctx_config); - bsc_gsmnet = bsc_network_init(tall_bsc_ctx); + bsc_gsmnet = gsm_network_init(tall_ctx_config); if (!bsc_gsmnet) exit(1); diff --git a/src/ipaccess/stubs.c b/src/ipaccess/stubs.c new file mode 100644 index 000000000..c52d52b77 --- /dev/null +++ b/src/ipaccess/stubs.c @@ -0,0 +1,43 @@ +/* Stubs required for linking */ + +/* (C) 2018 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 . + * + */ + +#include +struct gsm_bts; +struct gsm_bts_trx_ts; +struct msgb; +struct bsc_msc_data; + +bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts) +{ + /* No TS init required here. */ + return true; +} + +int abis_rsl_rcvmsg(struct msgb *msg) +{ + /* No RSL handling here */ + return 0; +} + +void paging_flush_bts(struct gsm_bts *bts, struct bsc_msc_data *msc) +{ + /* No paging flushing */ +} diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am deleted file mode 100644 index 2e447292f..000000000 --- a/src/libbsc/Makefile.am +++ /dev/null @@ -1,70 +0,0 @@ -AM_CPPFLAGS = \ - $(all_includes) \ - -I$(top_srcdir)/include \ - -I$(top_builddir) \ - $(NULL) - -AM_CFLAGS = \ - -Wall \ - $(LIBOSMOCORE_CFLAGS) \ - $(LIBOSMOGSM_CFLAGS) \ - $(LIBOSMOVTY_CFLAGS) \ - $(LIBOSMOABIS_CFLAGS) \ - $(LIBOSMOMGCP_CFLAGS) \ - $(LIBOSMOSIGTRAN_CFLAGS) \ - $(COVERAGE_CFLAGS) \ - $(LIBOSMOMGCPCLIENT_CFLAGS) \ - $(NULL) - -noinst_LIBRARIES = \ - libbsc.a \ - $(NULL) - -libbsc_a_SOURCES = \ - abis_nm.c \ - abis_nm_vty.c \ - abis_om2000.c \ - abis_om2000_vty.c \ - abis_rsl.c \ - a_reset.c \ - acc_ramp.c \ - bsc_rll.c \ - bsc_subscriber.c \ - paging.c \ - bts_ericsson_rbs2000.c \ - bts_ipaccess_nanobts.c \ - bts_siemens_bs11.c \ - bts_nokia_site.c \ - bts_unknown.c \ - bts_sysmobts.c \ - chan_alloc.c \ - gsm_data.c \ - handover_decision.c \ - handover_logic.c \ - meas_rep.c \ - pcu_sock.c \ - rest_octets.c \ - system_information.c \ - e1_config.c \ - bsc_api.c \ - bsc_vty.c \ - gsm_04_08_utils.c \ - gsm_04_80_utils.c \ - bsc_init.c \ - bts_init.c \ - bsc_rf_ctrl.c \ - arfcn_range_encode.c \ - bsc_ctrl_commands.c \ - bsc_ctrl_lookup.c \ - net_init.c \ - bsc_dyn_ts.c \ - bts_ipaccess_nanobts_omlattr.c \ - handover_vty.c \ - handover_cfg.c \ - penalty_timers.c \ - handover_decision_2.c \ - bsc_subscr_conn_fsm.c \ - meas_feed.c \ - osmo_bsc_lcls.c \ - $(NULL) - diff --git a/src/libbsc/a_reset.c b/src/libbsc/a_reset.c deleted file mode 100644 index b8f8c8cbd..000000000 --- a/src/libbsc/a_reset.c +++ /dev/null @@ -1,205 +0,0 @@ -/* (C) 2017 by sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Philipp Maier - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define RESET_RESEND_INTERVAL 2 /* sec */ -#define RESET_RESEND_TIMER_NO 4 /* See also 3GPP TS 48.008 Chapter 3.1.4.1.3.1 */ -#define BAD_CONNECTION_THRESOLD 3 /* connection failures */ - -/* Reset context data (callbacks, state machine etc...) */ -struct reset_ctx { - /* Connection failure counter. When this counter - * reaches a certain threshold, the reset procedure - * will be triggered */ - int conn_loss_counter; - - /* Callback function to be called when a connection - * failure is detected and a rest must occur */ - void (*cb)(void *priv); - - /* Privated data for the callback function */ - void *priv; -}; - -enum reset_fsm_states { - ST_DISC, /* Disconnected from remote end */ - ST_CONN, /* We have a confirmed connection */ -}; - -enum reset_fsm_evt { - EV_RESET_ACK, /* got reset acknowlegement from remote end */ - EV_N_DISCONNECT, /* lost a connection */ - EV_N_CONNECT, /* made a successful connection */ -}; - -static const struct value_string fsm_event_names[] = { - OSMO_VALUE_STRING(EV_RESET_ACK), - OSMO_VALUE_STRING(EV_N_DISCONNECT), - OSMO_VALUE_STRING(EV_N_CONNECT), - {0, NULL} -}; - -/* Disconnected state */ -static void fsm_disc_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; - OSMO_ASSERT(reset_ctx); - LOGPFSML(fi, LOGL_NOTICE, "SIGTRAN connection succeded.\n"); - - reset_ctx->conn_loss_counter = 0; - osmo_fsm_inst_state_chg(fi, ST_CONN, 0, 0); -} - -/* Connected state */ -static void fsm_conn_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; - OSMO_ASSERT(reset_ctx); - - switch (event) { - case EV_N_DISCONNECT: - if (reset_ctx->conn_loss_counter >= BAD_CONNECTION_THRESOLD) { - LOGPFSML(fi, LOGL_NOTICE, "SIGTRAN connection down, reconnecting...\n"); - osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); - } else - reset_ctx->conn_loss_counter++; - break; - case EV_N_CONNECT: - reset_ctx->conn_loss_counter = 0; - break; - } -} - -/* Timer callback to retransmit the reset signal */ -static int fsm_reset_ack_timeout_cb(struct osmo_fsm_inst *fi) -{ - struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; - OSMO_ASSERT(reset_ctx); - - LOGPFSML(fi, LOGL_NOTICE, "(re)sending BSSMAP RESET message...\n"); - - reset_ctx->cb(reset_ctx->priv); - - osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); - return 0; -} - -static struct osmo_fsm_state reset_fsm_states[] = { - [ST_DISC] = { - .in_event_mask = (1 << EV_RESET_ACK), - .out_state_mask = (1 << ST_DISC) | (1 << ST_CONN), - .name = "DISC", - .action = fsm_disc_cb, - }, - [ST_CONN] = { - .in_event_mask = (1 << EV_N_DISCONNECT) | (1 << EV_N_CONNECT), - .out_state_mask = (1 << ST_DISC) | (1 << ST_CONN), - .name = "CONN", - .action = fsm_conn_cb, - }, -}; - -/* State machine definition */ -static struct osmo_fsm fsm = { - .name = "A-RESET", - .states = reset_fsm_states, - .num_states = ARRAY_SIZE(reset_fsm_states), - .log_subsys = DMSC, - .timer_cb = fsm_reset_ack_timeout_cb, - .event_names = fsm_event_names, -}; - -/* Create and start state machine which handles the reset/reset-ack procedure */ -struct osmo_fsm_inst *a_reset_alloc(void *ctx, const char *name, void *cb, void *priv) -{ - OSMO_ASSERT(name); - - struct reset_ctx *reset_ctx; - struct osmo_fsm_inst *reset_fsm; - - /* Register the fsm description (if not already done) */ - if (osmo_fsm_find_by_name(fsm.name) != &fsm) - osmo_fsm_register(&fsm); - - /* Allocate and configure a new fsm instance */ - reset_ctx = talloc_zero(ctx, struct reset_ctx); - OSMO_ASSERT(reset_ctx); - reset_ctx->priv = priv; - reset_ctx->cb = cb; - reset_ctx->conn_loss_counter = 0; - reset_fsm = osmo_fsm_inst_alloc(&fsm, ctx, reset_ctx, LOGL_DEBUG, name); - OSMO_ASSERT(reset_fsm); - - /* kick off reset-ack sending mechanism */ - osmo_fsm_inst_state_chg(reset_fsm, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); - - return reset_fsm; -} - -/* Confirm that we sucessfully received a reset acknowlege message */ -void a_reset_ack_confirm(struct osmo_fsm_inst *reset_fsm) -{ - OSMO_ASSERT(reset_fsm); - osmo_fsm_inst_dispatch(reset_fsm, EV_RESET_ACK, NULL); -} - -/* Report a failed connection */ -void a_reset_conn_fail(struct osmo_fsm_inst *reset_fsm) -{ - /* If no reset context is supplied, just drop the info */ - if (!reset_fsm) - return; - - osmo_fsm_inst_dispatch(reset_fsm, EV_N_DISCONNECT, NULL); -} - -/* Report a successful connection */ -void a_reset_conn_success(struct osmo_fsm_inst *reset_fsm) -{ - /* If no reset context is supplied, just drop the info */ - if (!reset_fsm) - return; - - osmo_fsm_inst_dispatch(reset_fsm, EV_N_CONNECT, NULL); -} - -/* Check if we have a connection to a specified msc */ -bool a_reset_conn_ready(struct osmo_fsm_inst *reset_fsm) -{ - /* If no reset context is supplied, we assume that - * the connection can't be ready! */ - if (!reset_fsm) - return false; - - if (reset_fsm->state == ST_CONN) - return true; - - return false; -} diff --git a/src/libbsc/abis_nm.c b/src/libbsc/abis_nm.c deleted file mode 100644 index ea94d3766..000000000 --- a/src/libbsc/abis_nm.c +++ /dev/null @@ -1,2978 +0,0 @@ -/* GSM Network Management (OML) messages on the A-bis interface - * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */ - -/* (C) 2008-2018 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define OM_ALLOC_SIZE 1024 -#define OM_HEADROOM_SIZE 128 -#define IPACC_SEGMENT_SIZE 245 - -#define LOGPFOH(ss, lvl, foh, fmt, args ...) LOGP(ss, lvl, "%s: " fmt, abis_nm_dump_foh(foh), ## args) -#define DEBUGPFOH(ss, foh, fmt, args ...) LOGPFOH(ss, LOGL_DEBUG, foh, fmt, ## args) - -int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len) -{ - if (!bts->model) - return -EIO; - return tlv_parse(tp, &bts->model->nm_att_tlvdef, buf, len, 0, 0); -} - -static int is_in_arr(enum abis_nm_msgtype mt, const enum abis_nm_msgtype *arr, int size) -{ - int i; - - for (i = 0; i < size; i++) { - if (arr[i] == mt) - return 1; - } - - return 0; -} - -#if 0 -/* is this msgtype the usual ACK/NACK type ? */ -static int is_ack_nack(enum abis_nm_msgtype mt) -{ - return !is_in_arr(mt, no_ack_nack, ARRAY_SIZE(no_ack_nack)); -} -#endif - -/* is this msgtype a report ? */ -static int is_report(enum abis_nm_msgtype mt) -{ - return is_in_arr(mt, abis_nm_reports, ARRAY_SIZE(abis_nm_reports)); -} - -#define MT_ACK(x) (x+1) -#define MT_NACK(x) (x+2) - -static void fill_om_hdr(struct abis_om_hdr *oh, uint8_t len) -{ - oh->mdisc = ABIS_OM_MDISC_FOM; - oh->placement = ABIS_OM_PLACEMENT_ONLY; - oh->sequence = 0; - oh->length = len; -} - -static struct abis_om_fom_hdr *fill_om_fom_hdr(struct abis_om_hdr *oh, uint8_t len, - uint8_t msg_type, uint8_t obj_class, - uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr) -{ - struct abis_om_fom_hdr *foh = - (struct abis_om_fom_hdr *) oh->data; - - fill_om_hdr(oh, len+sizeof(*foh)); - foh->msg_type = msg_type; - foh->obj_class = obj_class; - foh->obj_inst.bts_nr = bts_nr; - foh->obj_inst.trx_nr = trx_nr; - foh->obj_inst.ts_nr = ts_nr; - return foh; -} - -static struct msgb *nm_msgb_alloc(void) -{ - return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, - "OML"); -} - -int _abis_nm_sendmsg(struct msgb *msg) -{ - msg->l2h = msg->data; - - if (!msg->dst) { - LOGP(DNM, LOGL_ERROR, "%s: msg->dst == NULL\n", __func__); - return -EINVAL; - } - - return abis_sendmsg(msg); -} - -/* Send a OML NM Message from BSC to BTS */ -static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg) -{ - msg->dst = bts->oml_link; - - /* queue OML messages */ - if (llist_empty(&bts->abis_queue) && !bts->abis_nm_pend) { - bts->abis_nm_pend = OBSC_NM_W_ACK_CB(msg); - return _abis_nm_sendmsg(msg); - } else { - msgb_enqueue(&bts->abis_queue, msg); - return 0; - } - -} - -int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg) -{ - OBSC_NM_W_ACK_CB(msg) = 1; - return abis_nm_queue_msg(bts, msg); -} - -static int abis_nm_sendmsg_direct(struct gsm_bts *bts, struct msgb *msg) -{ - OBSC_NM_W_ACK_CB(msg) = 0; - return abis_nm_queue_msg(bts, msg); -} - -static int abis_nm_rcvmsg_sw(struct msgb *mb); - -bool nm_is_running(const struct gsm_nm_state *s) { - return (s->operational == NM_OPSTATE_ENABLED) && ( - (s->availability == NM_AVSTATE_OK) || - (s->availability == 0xff) - ); -} - -/* Update the administrative state of a given object in our in-memory data - * structures and send an event to the higher layer */ -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 nm_statechg_signal_data nsd; - - memset(&nsd, 0, sizeof(nsd)); - - nsd.obj = gsm_objclass2obj(bts, obj_class, obj_inst); - if (!nsd.obj) - return -EINVAL; - nm_state = gsm_objclass2nmstate(bts, obj_class, obj_inst); - 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.obj_inst = obj_inst; - osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd); - - nm_state->administrative = adm_state; - - return 0; -} - -static int abis_nm_rx_statechg_rep(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 = sign_link->trx->bts; - struct tlv_parsed tp; - struct gsm_nm_state *nm_state, new_state; - - memset(&new_state, 0, sizeof(new_state)); - - 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; - - 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); - DEBUGPC(DNM, "OP_STATE=%s ", - abis_nm_opstate_name(new_state.operational)); - } - if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) { - if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0) - new_state.availability = 0xff; - else - 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); - } else - new_state.availability = 0xff; - if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) { - 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)); - } - DEBUGPC(DNM, "\n"); - - if ((new_state.administrative != 0 && nm_state->administrative == 0) || - new_state.operational != nm_state->operational || - new_state.availability != nm_state->availability) { - /* Update the operational 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; - } -#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; -} - -static inline void log_oml_fail_rep(const struct gsm_bts *bts, const char *type, - const char *severity, const uint8_t *p_val, - const char *text) -{ - enum abis_nm_pcause_type pcause = p_val[0]; - enum abis_mm_event_causes cause = osmo_load16be(p_val + 1); - - LOGPC(DNM, LOGL_ERROR, "BTS %u: Failure Event Report: ", bts->nr); - if (type) - LOGPC(DNM, LOGL_ERROR, "Type=%s, ", type); - if (severity) - LOGPC(DNM, LOGL_ERROR, "Severity=%s, ", severity); - - LOGPC(DNM, LOGL_ERROR, "Probable cause=%s: ", - get_value_string(abis_nm_pcause_type_names, pcause)); - - if (pcause == NM_PCAUSE_T_MANUF) - LOGPC(DNM, LOGL_ERROR, "%s, ", - get_value_string(abis_mm_event_cause_names, cause)); - else - LOGPC(DNM, LOGL_ERROR, "%02X %02X ", p_val[1], p_val[2]); - - if (text) { - LOGPC(DNM, LOGL_ERROR, "Additional Text=%s. ", text); - } - - LOGPC(DNM, LOGL_ERROR, "\n"); -} - -static inline void handle_manufact_report(struct gsm_bts *bts, const uint8_t *p_val, const char *type, - const char *severity, const char *text) -{ - enum abis_mm_event_causes cause = osmo_load16be(p_val + 1); - - switch (cause) { - case OSMO_EVT_PCU_VERS: - if (text) { - LOGPC(DNM, LOGL_NOTICE, "BTS %u reported connected PCU version %s\n", bts->nr, text); - osmo_strlcpy(bts->pcu_version, text, sizeof(bts->pcu_version)); - } else { - LOGPC(DNM, LOGL_ERROR, "BTS %u reported PCU disconnection.\n", bts->nr); - bts->pcu_version[0] = '\0'; - } - break; - default: - log_oml_fail_rep(bts, type, severity, p_val, text); - }; -} - -static int rx_fail_evt_rep(struct msgb *mb, struct gsm_bts *bts) -{ - 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 tlv_parsed tp; - int rc = 0; - const uint8_t *p_val = NULL; - char *p_text = NULL; - const char *e_type = NULL, *severity = NULL; - - abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, - oh->length-sizeof(*foh)); - - if (TLVP_PRESENT(&tp, NM_ATT_ADD_TEXT)) { - p_val = TLVP_VAL(&tp, NM_ATT_ADD_TEXT); - p_text = talloc_strndup(tall_bsc_ctx, (const char *) p_val, - TLVP_LEN(&tp, NM_ATT_ADD_TEXT)); - } - - if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE)) - e_type = abis_nm_event_type_name(*TLVP_VAL(&tp, - NM_ATT_EVENT_TYPE)); - - if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY)) - severity = abis_nm_severity_name(*TLVP_VAL(&tp, - NM_ATT_SEVERITY)); - - if (TLVP_PRESENT(&tp, NM_ATT_PROB_CAUSE)) { - p_val = TLVP_VAL(&tp, NM_ATT_PROB_CAUSE); - - switch (p_val[0]) { - case NM_PCAUSE_T_MANUF: - handle_manufact_report(bts, p_val, e_type, severity, - p_text); - break; - default: - log_oml_fail_rep(bts, e_type, severity, p_val, p_text); - }; - } else { - LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: Failure Event Report without " - "Probable Cause?!\n", bts->nr); - rc = -EINVAL; - } - - if (p_text) - talloc_free(p_text); - - return rc; -} - -static int abis_nm_rcvmsg_report(struct msgb *mb, struct gsm_bts *bts) -{ - struct abis_om_fom_hdr *foh = msgb_l3(mb); - uint8_t mt = foh->msg_type; - - switch (mt) { - case NM_MT_STATECHG_EVENT_REP: - return abis_nm_rx_statechg_rep(mb); - break; - case NM_MT_SW_ACTIVATED_REP: - DEBUGPFOH(DNM, foh, "Software Activated Report\n"); - osmo_signal_dispatch(SS_NM, S_NM_SW_ACTIV_REP, mb); - break; - case NM_MT_FAILURE_EVENT_REP: - rx_fail_evt_rep(mb, bts); - osmo_signal_dispatch(SS_NM, S_NM_FAIL_REP, mb); - break; - case NM_MT_TEST_REP: - DEBUGPFOH(DNM, foh, "Test Report\n"); - osmo_signal_dispatch(SS_NM, S_NM_TEST_REP, mb); - break; - default: - LOGPFOH(DNM, LOGL_NOTICE, foh, "unknown NM report MT 0x%02x\n", mt); - break; - }; - - return 0; -} - -/* Activate the specified software into the BTS */ -static int ipacc_sw_activate(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, - uint8_t i2, const struct abis_nm_sw_desc *sw_desc) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint16_t len = abis_nm_sw_desc_len(sw_desc, true); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, obj_class, i0, i1, i2); - abis_nm_put_sw_desc(msg, sw_desc, true); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_select_newest_sw(const struct abis_nm_sw_desc *sw_descr, - const size_t size) -{ - int res = 0; - int i; - - for (i = 1; i < size; ++i) { - if (memcmp(sw_descr[res].file_version, sw_descr[i].file_version, - OSMO_MIN(sw_descr[i].file_version_len, - sw_descr[res].file_version_len)) < 0) { - res = i; - } - } - - return res; -} - -static inline bool handle_attr(const struct gsm_bts *bts, enum bts_attribute id, uint8_t *val, uint8_t len) -{ - switch (id) { - case BTS_TYPE_VARIANT: - LOGP(DNM, LOGL_NOTICE, "BTS%u reported variant: %s\n", bts->nr, val); - break; - case BTS_SUB_MODEL: - LOGP(DNM, LOGL_NOTICE, "BTS%u reported submodel: %s\n", bts->nr, val); - break; - default: - return false; - } - return true; -} - -/* Parse Attribute Response Info - return pointer to the actual content */ -static inline const uint8_t *parse_attr_resp_info_unreported(uint8_t bts_nr, const uint8_t *ari, uint16_t ari_len, uint16_t *out_len) -{ - uint8_t num_unreported = ari[0], i; - - DEBUGP(DNM, "BTS%u Get Attributes Response Info: %u bytes total with %u unreported attributes\n", - bts_nr, ari_len, num_unreported); - - /* +1 because we have to account for number of unreported attributes, prefixing the list: */ - for (i = 0; i < num_unreported; i++) - LOGP(DNM, LOGL_ERROR, "BTS%u Attribute %s is unreported\n", - bts_nr, get_value_string(abis_nm_att_names, ari[i + 1])); - - /* the data starts right after the list of unreported attributes + space for length of that list */ - *out_len = ari_len - (num_unreported + 2); - - 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 inline const uint8_t *parse_attr_resp_info_manuf_id(struct gsm_bts *bts, const uint8_t *data, uint16_t *data_len) -{ - struct tlv_parsed tp; - uint16_t m_id_len = 0; - uint8_t adjust = 0, i; - - abis_nm_tlv_parse(&tp, bts, data, *data_len); - if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_ID, 2)) { - m_id_len = TLVP_LEN(&tp, NM_ATT_MANUF_ID); - - /* log potential BTS feature vector overflow */ - if (m_id_len > sizeof(bts->_features_data)) - LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: feature vector is truncated to %u bytes\n", - bts->nr, MAX_BTS_FEATURES/8); - - /* check that max. expected BTS attribute is above given feature vector length */ - if (m_id_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, m_id_len); - - memcpy(bts->_features_data, TLVP_VAL(&tp, NM_ATT_MANUF_ID), sizeof(bts->_features_data)); - adjust = m_id_len + 3; /* adjust for parsed TL16V struct */ - - 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)); - } - - *data_len -= adjust; - - return data + adjust; -} - -/* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.28 Manufacturer Dependent State */ -static inline const uint8_t *parse_attr_resp_info_manuf_state(const struct gsm_bts_trx *trx, const uint8_t *data, uint16_t *data_len) -{ - struct tlv_parsed tp; - const uint8_t *power; - uint8_t adjust = 0; - - if (!trx) /* this attribute does not make sense on BTS level, only on TRX level */ - return data; - - abis_nm_tlv_parse(&tp, trx->bts, data, *data_len); - if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_STATE, 1)) { - power = TLVP_VAL(&tp, NM_ATT_MANUF_STATE); - LOGP(DNM, LOGL_NOTICE, "%s Get Attributes Response: nominal power is %u\n", gsm_trx_name(trx), *power); - adjust = 2; /* adjust for parsed TV struct */ - } - - *data_len -= adjust; - - return data + adjust; -} - -/* Handle 3GPP TS 52.021 §9.4.64 Get Attribute Response Info */ -static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *trx) -{ - 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 tlv_parsed tp; - const uint8_t *data; - int i; - uint16_t data_len; - int rc; - struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR]; - - DEBUGPFOH(DNM, foh, "Get Attributes Response for BTS%u\n", bts->nr); - - abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); - if (!TLVP_PRES_LEN(&tp, NM_ATT_GET_ARI, 1)) { - LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: Get Attr Response without Response Info?!\n", - bts->nr); - return -EINVAL; - } - - data = parse_attr_resp_info_unreported(bts->nr, TLVP_VAL(&tp, NM_ATT_GET_ARI), TLVP_LEN(&tp, NM_ATT_GET_ARI), - &data_len); - - data = parse_attr_resp_info_manuf_state(trx, data, &data_len); - data = parse_attr_resp_info_manuf_id(bts, data, &data_len); - - /* after parsing manufacturer-specific attributes there's list of replies in form of sw-conf structure: */ - rc = abis_nm_get_sw_conf(data, data_len, &sw_descr[0], ARRAY_SIZE(sw_descr)); - if (rc > 0) { - for (i = 0; i < rc; i++) { - if (!handle_attr(bts, str2btsattr((const char *)sw_descr[i].file_id), - sw_descr[i].file_version, sw_descr[i].file_version_len)) - LOGPFOH(DNM, LOGL_NOTICE, foh, "BTS%u: ARI reported sw[%d/%d]: %s " - "is %s\n", bts->nr, i, rc, sw_descr[i].file_id, - sw_descr[i].file_version); - } - } else { - LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: failed to parse SW-Config part of " - "Get Attribute Response Info: %s\n", bts->nr, strerror(-rc)); - } - - return 0; -} - -/* 3GPP TS 52.021 §6.2.5 */ -static int abis_nm_rx_sw_act_req(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 tlv_parsed tp; - const uint8_t *sw_config; - int ret, sw_config_len, len; - struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR]; - - DEBUGPFOH(DNM, foh, "Software Activate Request, ACKing and Activating\n"); - - 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)); - 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)); - 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)) { - LOGPFOH(DNM, LOGL_ERROR, foh, "SW config not found! Can't continue.\n"); - return -EINVAL; - } else { - DEBUGP(DNM, "Found SW config: %s\n", osmo_hexdump(sw_config, sw_config_len)); - } - - /* Parse up to two sw descriptions from the data */ - len = abis_nm_get_sw_conf(sw_config, sw_config_len, &sw_descr[0], - ARRAY_SIZE(sw_descr)); - if (len <= 0) { - LOGPFOH(DNM, LOGL_ERROR, foh, "Failed to parse SW Config.\n"); - return -EINVAL; - } - - ret = abis_nm_select_newest_sw(&sw_descr[0], len); - DEBUGPFOH(DNM, foh, "Selected sw description %d of %d\n", ret, len); - - return ipacc_sw_activate(sign_link->trx->bts, foh->obj_class, - foh->obj_inst.bts_nr, - foh->obj_inst.trx_nr, - foh->obj_inst.ts_nr, - &sw_descr[ret]); -} - -/* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */ -static int abis_nm_rx_chg_adm_state_ack(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 tlv_parsed tp; - uint8_t adm_state; - - abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); - if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) - return -EINVAL; - - adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); - - return update_admstate(sign_link->trx->bts, foh->obj_class, &foh->obj_inst, adm_state); -} - -static int abis_nm_rx_lmt_event(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 tlv_parsed tp; - - 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); - DEBUGPC(DNM, "LOG%s ", onoff ? "ON" : "OFF"); - } - if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) && - TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) >= 1) { - uint8_t level = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV); - DEBUGPC(DNM, "Level=%u ", level); - } - if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_NAME) && - TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_NAME) >= 1) { - char *name = (char *) TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_NAME); - DEBUGPC(DNM, "Username=%s ", name); - } - DEBUGPC(DNM, "\n"); - /* FIXME: parse LMT LOGON TIME */ - return 0; -} - -static int abis_nm_rx_opstart_ack(struct msgb *mb) -{ - struct abis_om_fom_hdr *foh = msgb_l3(mb); - DEBUGPFOH(DNM, foh, "Opstart ACK\n"); - osmo_signal_dispatch(SS_NM, S_NM_OPSTART_ACK, foh); - return 0; -} - -bool all_trx_rsl_connected_unlocked(const struct gsm_bts *bts) -{ - const struct gsm_bts_trx *trx; - - if (bts->mo.nm_state.administrative == NM_STATE_LOCKED) - return false; - - if (bts->gprs.mode != BTS_GPRS_NONE) { - if (bts->gprs.cell.mo.nm_state.administrative == NM_STATE_LOCKED) - return false; - - if (bts->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) - return false; - } - - llist_for_each_entry(trx, &bts->trx_list, list) { - if (!trx->rsl_link) - return false; - - if (!trx_is_usable(trx)) - return false; - - if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) - return false; - } - - return true; -} - -char *get_model_oml_status(const struct gsm_bts *bts) -{ - if (bts->model->oml_status) - return bts->model->oml_status(bts); - - return "unknown"; -} - -void abis_nm_queue_send_next(struct gsm_bts *bts) -{ - int wait = 0; - struct msgb *msg; - /* the queue is empty */ - while (!llist_empty(&bts->abis_queue)) { - msg = msgb_dequeue(&bts->abis_queue); - wait = OBSC_NM_W_ACK_CB(msg); - _abis_nm_sendmsg(msg); - - if (wait) - break; - } - - bts->abis_nm_pend = wait; -} - -/* Receive a OML NM Message from BTS */ -static int abis_nm_rcvmsg_fom(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; - uint8_t mt = foh->msg_type; - /* sign_link might get deleted via osmo_signal_dispatch -> save bts */ - struct gsm_bts *bts = sign_link->trx->bts; - int ret = 0; - - /* check for unsolicited message */ - if (is_report(mt)) - return abis_nm_rcvmsg_report(mb, bts); - - if (is_in_arr(mt, abis_nm_sw_load_msgs, ARRAY_SIZE(abis_nm_sw_load_msgs))) - return abis_nm_rcvmsg_sw(mb); - - if (is_in_arr(mt, abis_nm_nacks, ARRAY_SIZE(abis_nm_nacks))) { - struct nm_nack_signal_data nack_data; - struct tlv_parsed tp; - - LOGPFOH(DNM, LOGL_NOTICE, foh, "%s NACK ", abis_nm_nack_name(mt)); - - abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); - if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) - DEBUGPC(DNM, "CAUSE=%s\n", - abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); - else - DEBUGPC(DNM, "\n"); - - nack_data.msg = mb; - nack_data.mt = mt; - nack_data.bts = bts; - osmo_signal_dispatch(SS_NM, S_NM_NACK, &nack_data); - abis_nm_queue_send_next(bts); - return 0; - } -#if 0 - /* check if last message is to be acked */ - if (is_ack_nack(nmh->last_msgtype)) { - if (mt == MT_ACK(nmh->last_msgtype)) { - DEBUGP(DNM, "received ACK (0x%x)\n", foh->msg_type); - /* we got our ACK, continue sending the next msg */ - } else if (mt == MT_NACK(nmh->last_msgtype)) { - /* we got a NACK, signal this to the caller */ - DEBUGP(DNM, "received NACK (0x%x)\n", foh->msg_type); - /* FIXME: somehow signal this to the caller */ - } else { - /* really strange things happen */ - return -EINVAL; - } - } -#endif - - switch (mt) { - case NM_MT_CHG_ADM_STATE_ACK: - ret = abis_nm_rx_chg_adm_state_ack(mb); - break; - case NM_MT_SW_ACT_REQ: - ret = abis_nm_rx_sw_act_req(mb); - break; - case NM_MT_BS11_LMT_SESSION: - ret = abis_nm_rx_lmt_event(mb); - break; - case NM_MT_OPSTART_ACK: - abis_nm_rx_opstart_ack(mb); - break; - case NM_MT_SET_CHAN_ATTR_ACK: - DEBUGPFOH(DNM, foh, "Set Channel Attributes ACK\n"); - break; - case NM_MT_SET_RADIO_ATTR_ACK: - DEBUGPFOH(DNM, foh, "Set Radio Carrier Attributes ACK\n"); - break; - case NM_MT_CONN_MDROP_LINK_ACK: - DEBUGPFOH(DNM, foh, "CONN MDROP LINK ACK\n"); - break; - case NM_MT_IPACC_RESTART_ACK: - DEBUGPFOH(DNM, foh, "IPA Restart ACK\n"); - osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_ACK, NULL); - break; - case NM_MT_IPACC_RESTART_NACK: - LOGPFOH(DNM, LOGL_NOTICE, foh, "IPA Restart NACK\n"); - osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_NACK, NULL); - break; - case NM_MT_SET_BTS_ATTR_ACK: - DEBUGPFOH(DNM, foh, "Set BTS Attribute ACK\n"); - 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)); - break; - default: - LOGPFOH(DNM, LOGL_ERROR, foh, "Unhandled message %s\n", - get_value_string(abis_nm_msgtype_names, mt)); - } - - abis_nm_queue_send_next(bts); - return ret; -} - -static int abis_nm_rx_ipacc(struct msgb *mb); - -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; - - 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); - break; - default: - LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this " - "BTS type (%u)\n", bts_type); - rc = 0; - break; - } - - return rc; -} - -/* High-Level API */ -/* Entry-point where L2 OML from BTS enters the NM code */ -int abis_nm_rcvmsg(struct msgb *msg) -{ - struct abis_om_hdr *oh = msgb_l2(msg); - int rc = 0; - - /* Various consistency checks */ - if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { - LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", - oh->placement); - if (oh->placement != ABIS_OM_PLACEMENT_FIRST) { - rc = -EINVAL; - goto err; - } - } - if (oh->sequence != 0) { - LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", - oh->sequence); - rc = -EINVAL; - goto err; - } -#if 0 - unsigned int l2_len = msg->tail - (uint8_t *)msgb_l2(msg); - unsigned int hlen = sizeof(*oh) + sizeof(struct abis_om_fom_hdr); - if (oh->length + hlen > l2_len) { - LOGP(DNM, LOGL_ERROR, "ABIS OML truncated message (%u > %u)\n", - oh->length + sizeof(*oh), l2_len); - return -EINVAL; - } - if (oh->length + hlen < l2_len) - LOGP(DNM, LOGL_ERROR, "ABIS OML message with extra trailer?!? (oh->len=%d, sizeof_oh=%d l2_len=%d\n", oh->length, sizeof(*oh), l2_len); -#endif - msg->l3h = (unsigned char *)oh + sizeof(*oh); - - switch (oh->mdisc) { - case ABIS_OM_MDISC_FOM: - rc = abis_nm_rcvmsg_fom(msg); - break; - case ABIS_OM_MDISC_MANUF: - rc = abis_nm_rcvmsg_manuf(msg); - break; - case ABIS_OM_MDISC_MMI: - case ABIS_OM_MDISC_TRAU: - LOGP(DNM, LOGL_ERROR, "unimplemented ABIS OML message discriminator 0x%x\n", - oh->mdisc); - break; - default: - LOGP(DNM, LOGL_ERROR, "unknown ABIS OML message discriminator 0x%x\n", - oh->mdisc); - rc = -EINVAL; - break; - } -err: - msgb_free(msg); - return rc; -} - -#if 0 -/* initialized all resources */ -struct abis_nm_h *abis_nm_init(struct abis_nm_cfg *cfg) -{ - struct abis_nm_h *nmh; - - nmh = malloc(sizeof(*nmh)); - if (!nmh) - return NULL; - - nmh->cfg = cfg; - - return nmh; -} - -/* free all resources */ -void abis_nm_fini(struct abis_nm_h *nmh) -{ - free(nmh); -} -#endif - -/* Here we are trying to define a high-level API that can be used by - * the actual BSC implementation. However, the architecture is currently - * still under design. Ideally the calls to this API would be synchronous, - * while the underlying stack behind the APi runs in a traditional select - * based state machine. - */ - -/* 6.2 Software Load: */ -enum sw_state { - SW_STATE_NONE, - SW_STATE_WAIT_INITACK, - SW_STATE_WAIT_SEGACK, - SW_STATE_WAIT_ENDACK, - SW_STATE_WAIT_ACTACK, - SW_STATE_ERROR, -}; - -struct abis_nm_sw { - struct gsm_bts *bts; - int trx_nr; - gsm_cbfn *cbfn; - void *cb_data; - int forced; - - /* this will become part of the SW LOAD INITIATE */ - uint8_t obj_class; - uint8_t obj_instance[3]; - - uint8_t file_id[255]; - uint8_t file_id_len; - - uint8_t file_version[255]; - uint8_t file_version_len; - - uint8_t window_size; - uint8_t seg_in_window; - - int fd; - FILE *stream; - enum sw_state state; - int last_seg; -}; - -static struct abis_nm_sw g_sw; - -static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg) -{ - if (sw->bts->type == GSM_BTS_TYPE_NANOBTS) { - msgb_v_put(msg, NM_ATT_SW_DESCR); - msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); - msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, - sw->file_version); - } else if (sw->bts->type == GSM_BTS_TYPE_BS11) { - msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); - 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"); - } -} - -/* 6.2.1 / 8.3.1: Load Data Initiate */ -static int sw_load_init(struct abis_nm_sw *sw) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t len = 3*2 + sw->file_id_len + sw->file_version_len; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, len, NM_MT_LOAD_INIT, sw->obj_class, - sw->obj_instance[0], sw->obj_instance[1], - sw->obj_instance[2]); - - sw_add_file_id_and_ver(sw, msg); - msgb_tv_put(msg, NM_ATT_WINDOW_SIZE, sw->window_size); - - return abis_nm_sendmsg(sw->bts, msg); -} - -static int is_last_line(FILE *stream) -{ - char next_seg_buf[256]; - long pos; - - /* check if we're sending the last line */ - pos = ftell(stream); - - /* Did ftell fail? Then we are at the end for sure */ - if (pos < 0) - return 1; - - if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) { - int rc = fseek(stream, pos, SEEK_SET); - if (rc < 0) - return rc; - return 1; - } - - fseek(stream, pos, SEEK_SET); - return 0; -} - -/* 6.2.2 / 8.3.2 Load Data Segment */ -static int sw_load_segment(struct abis_nm_sw *sw) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - char seg_buf[256]; - char *line_buf = seg_buf+2; - unsigned char *tlv; - int len; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - - switch (sw->bts->type) { - case GSM_BTS_TYPE_BS11: - if (fgets(line_buf, sizeof(seg_buf)-2, sw->stream) == NULL) { - perror("fgets reading segment"); - return -EINVAL; - } - seg_buf[0] = 0x00; - - /* check if we're sending the last line */ - sw->last_seg = is_last_line(sw->stream); - if (sw->last_seg) - seg_buf[1] = 0; - else - seg_buf[1] = 1 + sw->seg_in_window++; - - len = strlen(line_buf) + 2; - tlv = msgb_put(msg, TLV_GROSS_LEN(len)); - tlv_put(tlv, NM_ATT_BS11_FILE_DATA, len, (uint8_t *)seg_buf); - /* BS11 wants CR + LF in excess of the TLV length !?! */ - tlv[1] -= 2; - - /* we only now know the exact length for the OM hdr */ - len = strlen(line_buf)+2; - break; - case GSM_BTS_TYPE_NANOBTS: { - osmo_static_assert(sizeof(seg_buf) >= IPACC_SEGMENT_SIZE, buffer_big_enough); - len = read(sw->fd, &seg_buf, IPACC_SEGMENT_SIZE); - if (len < 0) { - perror("read failed"); - return -EINVAL; - } - - if (len != IPACC_SEGMENT_SIZE) - sw->last_seg = 1; - - ++sw->seg_in_window; - msgb_tl16v_put(msg, NM_ATT_IPACC_FILE_DATA, len, (const uint8_t *) seg_buf); - len += 3; - break; - } - default: - LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n"); - /* FIXME: Other BTS types */ - return -1; - } - - fill_om_fom_hdr(oh, len, NM_MT_LOAD_SEG, sw->obj_class, - sw->obj_instance[0], sw->obj_instance[1], - sw->obj_instance[2]); - - return abis_nm_sendmsg_direct(sw->bts, msg); -} - -/* 6.2.4 / 8.3.4 Load Data End */ -static int sw_load_end(struct abis_nm_sw *sw) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, len, NM_MT_LOAD_END, sw->obj_class, - sw->obj_instance[0], sw->obj_instance[1], - sw->obj_instance[2]); - - sw_add_file_id_and_ver(sw, msg); - return abis_nm_sendmsg(sw->bts, msg); -} - -/* Activate the specified software into the BTS */ -static int sw_activate(struct abis_nm_sw *sw) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, sw->obj_class, - sw->obj_instance[0], sw->obj_instance[1], - sw->obj_instance[2]); - - /* FIXME: this is BS11 specific format */ - msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); - msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, - sw->file_version); - - 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) -{ - 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"); - return -1; - } - - if (strncmp(firmware_header.magic, " SDP", 4) != 0) { - LOGP(DNM, LOGL_ERROR, "The magic number1 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"); - return -1; - } - - - if (fstat(sw->fd, &stat) == -1) { - LOGP(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"); - 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.\n" - "There might be checksums in the file that are not\n" - "verified and incomplete firmware might be flashed.\n" - "There is absolutely no WARRANTY that flashing will\n" - "work.\n"); - return 0; -} - -static int sw_open_file(struct abis_nm_sw *sw, const char *fname) -{ - char file_id[12+1]; - char file_version[80+1]; - int rc; - - sw->fd = open(fname, O_RDONLY); - if (sw->fd < 0) - return sw->fd; - - switch (sw->bts->type) { - case GSM_BTS_TYPE_BS11: - sw->stream = fdopen(sw->fd, "r"); - if (!sw->stream) { - perror("fdopen"); - return -1; - } - /* read first line and parse file ID and VERSION */ - rc = fscanf(sw->stream, "@(#)%12s:%80s\r\n", - file_id, file_version); - if (rc != 2) { - perror("parsing header line of software file"); - return -1; - } - strcpy((char *)sw->file_id, file_id); - sw->file_id_len = strlen(file_id); - strcpy((char *)sw->file_version, file_version); - sw->file_version_len = strlen(file_version); - /* rewind to start of file */ - rewind(sw->stream); - break; - case GSM_BTS_TYPE_NANOBTS: - /* TODO: extract that from the filename or content */ - rc = parse_sdp_header(sw); - if (rc < 0) { - fprintf(stderr, "Could not parse the ipaccess SDP header\n"); - return -1; - } - - strcpy((char *)sw->file_id, "id"); - sw->file_id_len = 3; - strcpy((char *)sw->file_version, "version"); - sw->file_version_len = 8; - break; - default: - /* We don't know how to treat them yet */ - close(sw->fd); - return -EINVAL; - } - - return 0; -} - -static void sw_close_file(struct abis_nm_sw *sw) -{ - switch (sw->bts->type) { - case GSM_BTS_TYPE_BS11: - fclose(sw->stream); - break; - default: - close(sw->fd); - break; - } -} - -/* Fill the window */ -static int sw_fill_window(struct abis_nm_sw *sw) -{ - int rc; - - while (sw->seg_in_window < sw->window_size) { - rc = sw_load_segment(sw); - if (rc < 0) - return rc; - if (sw->last_seg) - break; - } - return 0; -} - -/* callback function from abis_nm_rcvmsg() handler */ -static int abis_nm_rcvmsg_sw(struct msgb *mb) -{ - struct abis_om_fom_hdr *foh = msgb_l3(mb); - struct e1inp_sign_link *sign_link = mb->dst; - int rc = -1; - struct abis_nm_sw *sw = &g_sw; - enum sw_state old_state = sw->state; - - //DEBUGP(DNM, "state %u, NM MT 0x%02x\n", sw->state, foh->msg_type); - - switch (sw->state) { - case SW_STATE_WAIT_INITACK: - switch (foh->msg_type) { - case NM_MT_LOAD_INIT_ACK: - /* fill window with segments */ - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_LOAD_INIT_ACK, mb, - sw->cb_data, NULL); - rc = sw_fill_window(sw); - sw->state = SW_STATE_WAIT_SEGACK; - abis_nm_queue_send_next(sign_link->trx->bts); - break; - case NM_MT_LOAD_INIT_NACK: - if (sw->forced) { - DEBUGPFOH(DNM, foh, "FORCED: Ignoring Software Load Init NACK\n"); - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_LOAD_INIT_ACK, mb, - sw->cb_data, NULL); - rc = sw_fill_window(sw); - sw->state = SW_STATE_WAIT_SEGACK; - } else { - LOGPFOH(DNM, LOGL_NOTICE, foh, "Software Load Init NACK\n"); - /* FIXME: cause */ - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_LOAD_INIT_NACK, mb, - sw->cb_data, NULL); - sw->state = SW_STATE_ERROR; - } - abis_nm_queue_send_next(sign_link->trx->bts); - break; - } - break; - case SW_STATE_WAIT_SEGACK: - switch (foh->msg_type) { - case NM_MT_LOAD_SEG_ACK: - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_LOAD_SEG_ACK, mb, - sw->cb_data, NULL); - sw->seg_in_window = 0; - if (!sw->last_seg) { - /* fill window with more segments */ - rc = sw_fill_window(sw); - sw->state = SW_STATE_WAIT_SEGACK; - } else { - /* end the transfer */ - sw->state = SW_STATE_WAIT_ENDACK; - rc = sw_load_end(sw); - } - abis_nm_queue_send_next(sign_link->trx->bts); - break; - case NM_MT_LOAD_ABORT: - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_LOAD_ABORT, mb, - sw->cb_data, NULL); - break; - } - break; - case SW_STATE_WAIT_ENDACK: - switch (foh->msg_type) { - case NM_MT_LOAD_END_ACK: - sw_close_file(sw); - DEBUGPFOH(DNM, foh, "Software Load End (BTS %u)\n", sw->bts->nr); - sw->state = SW_STATE_NONE; - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_LOAD_END_ACK, mb, - sw->cb_data, NULL); - rc = 0; - abis_nm_queue_send_next(sign_link->trx->bts); - break; - case NM_MT_LOAD_END_NACK: - if (sw->forced) { - DEBUGPFOH(DNM, foh, "FORCED: Ignoring Software Load End NACK\n"); - sw->state = SW_STATE_NONE; - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_LOAD_END_ACK, mb, - sw->cb_data, NULL); - } else { - LOGPFOH(DNM, LOGL_NOTICE, foh, "Software Load End NACK\n"); - /* FIXME: cause */ - sw->state = SW_STATE_ERROR; - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_LOAD_END_NACK, mb, - sw->cb_data, NULL); - } - abis_nm_queue_send_next(sign_link->trx->bts); - break; - } - case SW_STATE_WAIT_ACTACK: - switch (foh->msg_type) { - case NM_MT_ACTIVATE_SW_ACK: - /* we're done */ - LOGPFOH(DNM, LOGL_INFO, foh, "Activate Software DONE!\n"); - sw->state = SW_STATE_NONE; - rc = 0; - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_ACTIVATE_SW_ACK, mb, - sw->cb_data, NULL); - abis_nm_queue_send_next(sign_link->trx->bts); - break; - case NM_MT_ACTIVATE_SW_NACK: - LOGPFOH(DNM, LOGL_ERROR, foh, "Activate Software NACK\n"); - /* FIXME: cause */ - sw->state = SW_STATE_ERROR; - if (sw->cbfn) - sw->cbfn(GSM_HOOK_NM_SWLOAD, - NM_MT_ACTIVATE_SW_NACK, mb, - sw->cb_data, NULL); - abis_nm_queue_send_next(sign_link->trx->bts); - break; - } - case SW_STATE_NONE: - switch (foh->msg_type) { - case NM_MT_ACTIVATE_SW_ACK: - rc = 0; - break; - } - break; - case SW_STATE_ERROR: - break; - } - - if (rc) - LOGPFOH(DNM, LOGL_ERROR, foh, "unexpected NM MT 0x%02x in state %u -> %u\n", - foh->msg_type, old_state, sw->state); - - return rc; -} - -/* Load the specified software into the BTS */ -int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname, - uint8_t win_size, int forced, - gsm_cbfn *cbfn, void *cb_data) -{ - struct abis_nm_sw *sw = &g_sw; - int rc; - - DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n", bts->nr, fname); - - if (sw->state != SW_STATE_NONE) - return -EBUSY; - - sw->bts = bts; - sw->trx_nr = trx_nr; - - switch (bts->type) { - case GSM_BTS_TYPE_BS11: - sw->obj_class = NM_OC_SITE_MANAGER; - sw->obj_instance[0] = 0xff; - sw->obj_instance[1] = 0xff; - sw->obj_instance[2] = 0xff; - break; - case GSM_BTS_TYPE_NANOBTS: - sw->obj_class = NM_OC_BASEB_TRANSC; - sw->obj_instance[0] = sw->bts->nr; - sw->obj_instance[1] = sw->trx_nr; - sw->obj_instance[2] = 0xff; - break; - case GSM_BTS_TYPE_UNKNOWN: - default: - LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n"); - return -1; - break; - } - sw->window_size = win_size; - sw->state = SW_STATE_WAIT_INITACK; - sw->cbfn = cbfn; - sw->cb_data = cb_data; - sw->forced = forced; - - rc = sw_open_file(sw, fname); - if (rc < 0) { - sw->state = SW_STATE_NONE; - return rc; - } - - return sw_load_init(sw); -} - -int abis_nm_software_load_status(struct gsm_bts *bts) -{ - struct abis_nm_sw *sw = &g_sw; - struct stat st; - int rc, percent; - - rc = fstat(sw->fd, &st); - if (rc < 0) { - perror("ERROR during stat"); - return rc; - } - - if (sw->stream) - percent = (ftell(sw->stream) * 100) / st.st_size; - else - percent = (lseek(sw->fd, 0, SEEK_CUR) * 100) / st.st_size; - return percent; -} - -/* Activate the specified software into the BTS */ -int abis_nm_software_activate(struct gsm_bts *bts, const char *fname, - gsm_cbfn *cbfn, void *cb_data) -{ - struct abis_nm_sw *sw = &g_sw; - int rc; - - DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n", bts->nr, fname); - - if (sw->state != SW_STATE_NONE) - return -EBUSY; - - sw->bts = bts; - sw->obj_class = NM_OC_SITE_MANAGER; - sw->obj_instance[0] = 0xff; - sw->obj_instance[1] = 0xff; - sw->obj_instance[2] = 0xff; - sw->state = SW_STATE_WAIT_ACTACK; - sw->cbfn = cbfn; - sw->cb_data = cb_data; - - /* Open the file in order to fill some sw struct members */ - rc = sw_open_file(sw, fname); - if (rc < 0) { - sw->state = SW_STATE_NONE; - return rc; - } - sw_close_file(sw); - - return sw_activate(sw); -} - -static void fill_nm_channel(struct abis_nm_channel *ch, uint8_t bts_port, - uint8_t ts_nr, uint8_t subslot_nr) -{ - ch->attrib = NM_ATT_ABIS_CHANNEL; - ch->bts_port = bts_port; - ch->timeslot = ts_nr; - ch->subslot = subslot_nr; -} - -int abis_nm_establish_tei(struct gsm_bts *bts, uint8_t trx_nr, - uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot, - uint8_t tei) -{ - struct abis_om_hdr *oh; - struct abis_nm_channel *ch; - uint8_t len = sizeof(*ch) + 2; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, len, NM_MT_ESTABLISH_TEI, NM_OC_RADIO_CARRIER, - bts->bts_nr, trx_nr, 0xff); - - msgb_tv_put(msg, NM_ATT_TEI, tei); - - ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); - fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); - - return abis_nm_sendmsg(bts, msg); -} - -/* connect signalling of one (BTS,TRX) to a particular timeslot on the E1 */ -int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx, - uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot) -{ - struct gsm_bts *bts = trx->bts; - struct abis_om_hdr *oh; - struct abis_nm_channel *ch; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_SIGN, - NM_OC_RADIO_CARRIER, bts->bts_nr, trx->nr, 0xff); - - ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); - fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); - - return abis_nm_sendmsg(bts, msg); -} - -#if 0 -int abis_nm_disc_terr_sign(struct abis_nm_h *h, struct abis_om_obj_inst *inst, - struct abis_nm_abis_channel *chan) -{ -} -#endif - -int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts, - uint8_t e1_port, uint8_t e1_timeslot, - uint8_t e1_subslot) -{ - struct gsm_bts *bts = ts->trx->bts; - struct abis_om_hdr *oh; - struct abis_nm_channel *ch; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_TRAF, - NM_OC_CHANNEL, bts->bts_nr, ts->trx->nr, ts->nr); - - 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); - - return abis_nm_sendmsg(bts, msg); -} - -#if 0 -int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst, - struct abis_nm_abis_channel *chan, - uint8_t subchan) -{ -} -#endif - -/* 3GPP TS 52.021 § 8.11.1 */ -int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, - const uint8_t *attr, uint8_t attr_len) -{ - struct abis_om_hdr *oh; - struct msgb *msg; - - if (bts->type != GSM_BTS_TYPE_OSMOBTS) { - LOGPC(DNM, LOGL_NOTICE, "Getting attributes from BTS%d type %s is not supported.\n", - bts->nr, btstype2str(bts->type)); - return -EINVAL; - } - - DEBUGP(DNM, "Get Attr (bts=%d)\n", bts->nr); - - msg = nm_msgb_alloc(); - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, attr_len, NM_MT_GET_ATTR, obj_class, - bts_nr, trx_nr, ts_nr); - msgb_tl16v_put(msg, NM_ATT_LIST_REQ_ATTR, attr_len, attr); - - return abis_nm_sendmsg(bts, msg); -} - -/* Chapter 8.6.1 */ -int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t *cur; - - DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr); - - 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); - cur = msgb_put(msg, attr_len); - memcpy(cur, attr, attr_len); - - return abis_nm_sendmsg(bts, msg); -} - -/* Chapter 8.6.2 */ -int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, uint8_t *attr, int attr_len) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t *cur; - - DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER, - trx->bts->bts_nr, trx->nr, 0xff); - cur = msgb_put(msg, attr_len); - memcpy(cur, attr, attr_len); - - return abis_nm_sendmsg(trx->bts, msg); -} - -int abis_nm_update_max_power_red(struct gsm_bts_trx *trx) -{ - uint8_t attr[] = { NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2 }; - return abis_nm_set_radio_attr(trx, attr, ARRAY_SIZE(attr)); -} - -static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb, - const char **reason) -{ - int i; - - *reason = "Reason unknown"; - - /* As it turns out, the BS-11 has some very peculiar restrictions - * on the channel combinations it allows */ - switch (ts->trx->bts->type) { - case GSM_BTS_TYPE_BS11: - switch (chan_comb) { - case NM_CHANC_TCHHalf: - case NM_CHANC_TCHHalf2: - case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: - /* not supported */ - *reason = "TCH/H is not supported."; - return -EINVAL; - case NM_CHANC_SDCCH: - /* only one SDCCH/8 per TRX */ - for (i = 0; i < TRX_NR_TS; i++) { - if (i == ts->nr) - continue; - if (ts->trx->ts[i].nm_chan_comb == - NM_CHANC_SDCCH) { - *reason = "Only one SDCCH/8 per TRX allowed."; - return -EINVAL; - } - } - /* not allowed for TS0 of BCCH-TRX */ - if (ts->trx == ts->trx->bts->c0 && - ts->nr == 0) { - *reason = "SDCCH/8 must be on TS0."; - return -EINVAL; - } - - /* not on the same TRX that has a BCCH+SDCCH4 - * combination */ - if (ts->trx != ts->trx->bts->c0 && - (ts->trx->ts[0].nm_chan_comb == 5 || - ts->trx->ts[0].nm_chan_comb == 8)) { - *reason = "SDCCH/8 and BCCH must be on the same TRX."; - return -EINVAL; - } - break; - case NM_CHANC_mainBCCH: - case NM_CHANC_BCCHComb: - /* allowed only for TS0 of C0 */ - if (ts->trx != ts->trx->bts->c0 || ts->nr != 0) { - *reason = "Main BCCH must be on TS0."; - return -EINVAL; - } - break; - case NM_CHANC_BCCH: - /* allowed only for TS 2/4/6 of C0 */ - if (ts->trx != ts->trx->bts->c0) { - *reason = "BCCH must be on C0."; - return -EINVAL; - } - if (ts->nr != 2 && ts->nr != 4 && ts->nr != 6) { - *reason = "BCCH must be on TS 2/4/6."; - return -EINVAL; - } - break; - case 8: /* this is not like 08.58, but in fact - * FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */ - /* FIXME: only one CBCH allowed per cell */ - break; - } - break; - case GSM_BTS_TYPE_NANOBTS: - switch (ts->nr) { - case 0: - if (ts->trx->nr == 0) { - /* only on TRX0 */ - switch (chan_comb) { - case NM_CHANC_BCCH: - case NM_CHANC_mainBCCH: - case NM_CHANC_BCCHComb: - return 0; - break; - default: - *reason = "TS0 of TRX0 must carry a BCCH."; - return -EINVAL; - } - } else { - switch (chan_comb) { - case NM_CHANC_TCHFull: - case NM_CHANC_TCHHalf: - case NM_CHANC_IPAC_TCHFull_TCHHalf: - return 0; - default: - *reason = "TS0 must carry a TCH/F or TCH/H."; - return -EINVAL; - } - } - break; - case 1: - if (ts->trx->nr == 0) { - switch (chan_comb) { - case NM_CHANC_SDCCH_CBCH: - if (ts->trx->ts[0].nm_chan_comb == - NM_CHANC_mainBCCH) - return 0; - *reason = "TS0 must be the main BCCH for CBCH."; - return -EINVAL; - case NM_CHANC_SDCCH: - case NM_CHANC_TCHFull: - case NM_CHANC_TCHHalf: - case NM_CHANC_IPAC_TCHFull_TCHHalf: - case NM_CHANC_IPAC_TCHFull_PDCH: - case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: - return 0; - default: - *reason = "TS1 must carry a CBCH, SDCCH or TCH."; - return -EINVAL; - } - } else { - switch (chan_comb) { - case NM_CHANC_SDCCH: - case NM_CHANC_TCHFull: - case NM_CHANC_TCHHalf: - case NM_CHANC_IPAC_TCHFull_TCHHalf: - return 0; - default: - *reason = "TS1 must carry a SDCCH or TCH."; - return -EINVAL; - } - } - break; - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - switch (chan_comb) { - case NM_CHANC_TCHFull: - case NM_CHANC_TCHHalf: - case NM_CHANC_IPAC_TCHFull_TCHHalf: - return 0; - case NM_CHANC_IPAC_PDCH: - case NM_CHANC_IPAC_TCHFull_PDCH: - case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: - if (ts->trx->nr == 0) - return 0; - else { - *reason = "PDCH must be on TRX0."; - return -EINVAL; - } - } - break; - } - *reason = "Unknown combination"; - return -EINVAL; - case GSM_BTS_TYPE_OSMOBTS: - /* no known restrictions */ - return 0; - default: - /* unknown BTS type */ - return 0; - } - return 0; -} - -/* Chapter 8.6.3 */ -int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb) -{ - struct gsm_bts *bts = ts->trx->bts; - struct abis_om_hdr *oh; - struct abis_om_fom_hdr *foh; - uint8_t zero = 0x00; - struct msgb *msg = nm_msgb_alloc(); - uint8_t len = 2 + 2; - const char *reason = NULL; - - if (bts->type == GSM_BTS_TYPE_BS11) - len += 4 + 2 + 2 + 3; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - foh = fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR, NM_OC_CHANNEL, bts->bts_nr, - ts->trx->nr, ts->nr); - - DEBUGPFOH(DNM, foh, "Set Chan Attr %s\n", gsm_ts_name(ts)); - if (verify_chan_comb(ts, chan_comb, &reason) < 0) { - msgb_free(msg); - LOGPFOH(DNM, LOGL_ERROR, foh, "Invalid Channel Combination %d on %s. Reason: %s\n", - chan_comb, gsm_ts_name(ts), reason); - return -EINVAL; - } - ts->nm_chan_comb = chan_comb; - - msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb); - if (ts->hopping.enabled) { - unsigned int i; - uint8_t *len; - - msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn); - msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio); - - /* build the ARFCN list */ - msgb_put_u8(msg, NM_ATT_ARFCN_LIST); - len = msgb_put(msg, 1); - *len = 0; - for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { - if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) { - msgb_put_u16(msg, i); - /* At least BS-11 wants a TLV16 here */ - if (bts->type == GSM_BTS_TYPE_BS11) - *len += 1; - else - *len += sizeof(uint16_t); - } - } - } - msgb_tv_put(msg, NM_ATT_TSC, gsm_ts_tsc(ts)); /* training sequence */ - if (bts->type == GSM_BTS_TYPE_BS11) - msgb_tlv_put(msg, 0x59, 1, &zero); - - DEBUGPFOH(DNM, foh, "%s(): sending %s\n", __func__, msgb_hexdump(msg)); - return abis_nm_sendmsg(bts, msg); -} - -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) -{ - 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; - - if (nack) { - len += 2; - msgtype = NM_MT_SW_ACT_REQ_NACK; - } - - 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); - - if (attr) { - uint8_t *ptr = msgb_put(msg, att_len); - memcpy(ptr, attr, att_len); - } - if (nack) - msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP); - - return abis_nm_sendmsg_direct(bts, msg); -} - -int abis_nm_raw_msg(struct gsm_bts *bts, int len, uint8_t *rawmsg) -{ - struct msgb *msg = nm_msgb_alloc(); - struct abis_om_hdr *oh; - uint8_t *data; - - oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh)); - fill_om_hdr(oh, len); - data = msgb_put(msg, len); - memcpy(data, rawmsg, len); - - return abis_nm_sendmsg(bts, msg); -} - -/* Siemens specific commands */ -static int __simple_cmd(struct gsm_bts *bts, uint8_t msg_type) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 0, msg_type, NM_OC_SITE_MANAGER, - 0xff, 0xff, 0xff); - - return abis_nm_sendmsg(bts, msg); -} - -/* Chapter 8.9.2 */ -int abis_nm_opstart(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, uint8_t i2) -{ - struct abis_om_hdr *oh; - struct abis_om_fom_hdr *foh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - foh = fill_om_fom_hdr(oh, 0, NM_MT_OPSTART, obj_class, i0, i1, i2); - - DEBUGPFOH(DNM, foh, "Sending OPSTART\n"); - - return abis_nm_sendmsg(bts, msg); -} - -/* Chapter 8.8.5 */ -int abis_nm_chg_adm_state(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, - uint8_t i1, uint8_t i2, enum abis_nm_adm_state adm_state) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 2, NM_MT_CHG_ADM_STATE, obj_class, i0, i1, i2); - msgb_tv_put(msg, NM_ATT_ADM_STATE, adm_state); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_conn_mdrop_link(struct gsm_bts *bts, uint8_t e1_port0, uint8_t ts0, - uint8_t e1_port1, uint8_t ts1) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t *attr; - - DEBUGP(DNM, "CONNECT MDROP LINK E1=(%u,%u) -> E1=(%u, %u)\n", - e1_port0, ts0, e1_port1, ts1); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 6, NM_MT_CONN_MDROP_LINK, - NM_OC_SITE_MANAGER, 0x00, 0x00, 0x00); - - attr = msgb_put(msg, 3); - attr[0] = NM_ATT_MDROP_LINK; - attr[1] = e1_port0; - attr[2] = ts0; - - attr = msgb_put(msg, 3); - attr[0] = NM_ATT_MDROP_NEXT; - attr[1] = e1_port1; - attr[2] = ts1; - - return abis_nm_sendmsg(bts, msg); -} - -/* Chapter 8.7.1 */ -int abis_nm_perform_test(struct gsm_bts *bts, uint8_t obj_class, - uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, - uint8_t test_nr, uint8_t auton_report, struct msgb *msg) -{ - struct abis_om_hdr *oh; - - DEBUGP(DNM, "PEFORM TEST %s\n", abis_nm_test_name(test_nr)); - - if (!msg) - msg = nm_msgb_alloc(); - - msgb_tv_push(msg, NM_ATT_AUTON_REPORT, auton_report); - msgb_tv_push(msg, NM_ATT_TEST_NO, test_nr); - oh = (struct abis_om_hdr *) msgb_push(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, msgb_l3len(msg), NM_MT_PERF_TEST, - obj_class, bts_nr, trx_nr, ts_nr); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_event_reports(struct gsm_bts *bts, int on) -{ - if (on == 0) - return __simple_cmd(bts, NM_MT_STOP_EVENT_REP); - else - return __simple_cmd(bts, NM_MT_REST_EVENT_REP); -} - -/* Siemens (or BS-11) specific commands */ - -int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect) -{ - if (reconnect == 0) - return __simple_cmd(bts, NM_MT_BS11_DISCONNECT); - else - return __simple_cmd(bts, NM_MT_BS11_RECONNECT); -} - -int abis_nm_bs11_restart(struct gsm_bts *bts) -{ - return __simple_cmd(bts, NM_MT_BS11_RESTART); -} - - -struct bs11_date_time { - uint16_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t min; - uint8_t sec; -} __attribute__((packed)); - - -void get_bs11_date_time(struct bs11_date_time *aet) -{ - time_t t; - struct tm *tm; - - t = time(NULL); - tm = localtime(&t); - aet->sec = tm->tm_sec; - aet->min = tm->tm_min; - aet->hour = tm->tm_hour; - aet->day = tm->tm_mday; - aet->month = tm->tm_mon; - aet->year = htons(1900 + tm->tm_year); -} - -int abis_nm_bs11_reset_resource(struct gsm_bts *bts) -{ - return __simple_cmd(bts, NM_MT_BS11_RESET_RESOURCE); -} - -int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin) -{ - if (begin) - return __simple_cmd(bts, NM_MT_BS11_BEGIN_DB_TX); - else - return __simple_cmd(bts, NM_MT_BS11_END_DB_TX); -} - -int abis_nm_bs11_create_object(struct gsm_bts *bts, - enum abis_bs11_objtype type, uint8_t idx, - uint8_t attr_len, const uint8_t *attr) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t *cur; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, attr_len, NM_MT_BS11_CREATE_OBJ, - NM_OC_BS11, type, 0, idx); - cur = msgb_put(msg, attr_len); - memcpy(cur, attr, attr_len); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_delete_object(struct gsm_bts *bts, - enum abis_bs11_objtype type, uint8_t idx) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, - NM_OC_BS11, type, 0, idx); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, uint8_t idx) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t zero = 0x00; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 3, NM_MT_BS11_CREATE_OBJ, - NM_OC_BS11_ENVABTSE, 0, idx, 0xff); - msgb_tlv_put(msg, 0x99, 1, &zero); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_create_bport(struct gsm_bts *bts, uint8_t idx) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 0, NM_MT_BS11_CREATE_OBJ, NM_OC_BS11_BPORT, - idx, 0xff, 0xff); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_delete_bport(struct gsm_bts *bts, uint8_t idx) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, NM_OC_BS11_BPORT, - idx, 0xff, 0xff); - - return abis_nm_sendmsg(bts, msg); -} - -static const uint8_t sm_attr[] = { NM_ATT_TEI, NM_ATT_ABIS_CHANNEL }; -int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 2+sizeof(sm_attr), NM_MT_GET_ATTR, NM_OC_SITE_MANAGER, - 0xff, 0xff, 0xff); - msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(sm_attr), sm_attr); - - return abis_nm_sendmsg(bts, msg); -} - -/* like abis_nm_conn_terr_traf + set_tei */ -int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, uint8_t e1_port, - uint8_t e1_timeslot, uint8_t e1_subslot, - uint8_t tei) -{ - struct abis_om_hdr *oh; - struct abis_nm_channel *ch; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, sizeof(*ch)+2, NM_MT_BS11_SET_ATTR, - NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff); - - ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); - fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); - msgb_tv_put(msg, NM_ATT_TEI, tei); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, uint8_t level) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, - NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr); - msgb_tlv_put(msg, NM_ATT_BS11_TXPWR, 1, &level); - - return abis_nm_sendmsg(trx->bts, msg); -} - -int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t attr = NM_ATT_BS11_TXPWR; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, - NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr); - msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr); - - return abis_nm_sendmsg(trx->bts, msg); -} - -int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t attr[] = { NM_ATT_BS11_PLL_MODE }; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, - NM_OC_BS11, BS11_OBJ_LI, 0x00, 0x00); - msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_get_cclk(struct gsm_bts *bts) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t attr[] = { NM_ATT_BS11_CCLK_ACCURACY, - NM_ATT_BS11_CCLK_TYPE }; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, - NM_OC_BS11, BS11_OBJ_CCLK, 0x00, 0x00); - msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr); - - return abis_nm_sendmsg(bts, msg); - -} - -//static const uint8_t bs11_logon_c7[] = { 0x07, 0xd9, 0x01, 0x11, 0x0d, 0x10, 0x20 }; - -int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on) -{ - return abis_nm_bs11_logon(bts, 0x02, "FACTORY", on); -} - -int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on) -{ - return abis_nm_bs11_logon(bts, 0x03, "FIELD ", on); -} - -int abis_nm_bs11_logon(struct gsm_bts *bts, uint8_t level, const char *name, int on) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - struct bs11_date_time bdt; - - get_bs11_date_time(&bdt); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - if (on) { - uint8_t len = 3*2 + sizeof(bdt) - + 1 + strlen(name); - fill_om_fom_hdr(oh, len, NM_MT_BS11_LMT_LOGON, - NM_OC_BS11_BTSE, 0xff, 0xff, 0xff); - msgb_tlv_put(msg, NM_ATT_BS11_LMT_LOGIN_TIME, - sizeof(bdt), (uint8_t *) &bdt); - msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_ACC_LEV, - 1, &level); - msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_NAME, - strlen(name), (uint8_t *)name); - } else { - fill_om_fom_hdr(oh, 0, NM_MT_BS11_LMT_LOGOFF, - NM_OC_BS11_BTSE, 0xff, 0xff, 0xff); - } - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password) -{ - struct abis_om_hdr *oh; - struct msgb *msg; - - if (strlen(password) != 10) - return -EINVAL; - - msg = nm_msgb_alloc(); - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 2+strlen(password), NM_MT_BS11_SET_ATTR, - NM_OC_BS11, BS11_OBJ_TRX1, 0x00, 0x00); - msgb_tlv_put(msg, NM_ATT_BS11_PASSWORD, 10, (const uint8_t *)password); - - return abis_nm_sendmsg(bts, msg); -} - -/* change the BS-11 PLL Mode to either locked (E1 derived) or standalone */ -int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked) -{ - struct abis_om_hdr *oh; - struct msgb *msg; - uint8_t tlv_value; - - msg = nm_msgb_alloc(); - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11, - BS11_OBJ_LI, 0x00, 0x00); - - if (locked) - tlv_value = BS11_LI_PLL_LOCKED; - else - tlv_value = BS11_LI_PLL_STANDALONE; - - msgb_tlv_put(msg, NM_ATT_BS11_PLL_MODE, 1, &tlv_value); - - return abis_nm_sendmsg(bts, msg); -} - -/* Set the calibration value of the PLL (work value/set value) - * It depends on the login which one is changed */ -int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value) -{ - struct abis_om_hdr *oh; - struct msgb *msg; - uint8_t tlv_value[2]; - - msg = nm_msgb_alloc(); - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11, - BS11_OBJ_TRX1, 0x00, 0x00); - - tlv_value[0] = value>>8; - tlv_value[1] = value&0xff; - - msgb_tlv_put(msg, NM_ATT_BS11_PLL, 2, tlv_value); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_get_state(struct gsm_bts *bts) -{ - return __simple_cmd(bts, NM_MT_BS11_GET_STATE); -} - -/* BS11 SWL */ - -void *tall_fle_ctx = NULL; - -struct abis_nm_bs11_sw { - struct gsm_bts *bts; - char swl_fname[PATH_MAX]; - uint8_t win_size; - int forced; - struct llist_head file_list; - gsm_cbfn *user_cb; /* specified by the user */ -}; -static struct abis_nm_bs11_sw _g_bs11_sw, *g_bs11_sw = &_g_bs11_sw; - -struct file_list_entry { - struct llist_head list; - char fname[PATH_MAX]; -}; - -struct file_list_entry *fl_dequeue(struct llist_head *queue) -{ - struct llist_head *lh; - - if (llist_empty(queue)) - return NULL; - - lh = queue->next; - llist_del(lh); - - return llist_entry(lh, struct file_list_entry, list); -} - -static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw) -{ - char linebuf[255]; - struct llist_head *lh, *lh2; - FILE *swl; - int rc = 0; - - swl = fopen(bs11_sw->swl_fname, "r"); - if (!swl) - 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 (fgets(linebuf, sizeof(linebuf), swl)) { - char file_id[12+1]; - char file_version[80+1]; - struct file_list_entry *fle; - static char dir[PATH_MAX]; - - if (strlen(linebuf) < 4) - continue; - - rc = sscanf(linebuf+4, "%12s:%80s\r\n", file_id, file_version); - if (rc < 0) { - perror("ERR parsing SWL file"); - rc = -EINVAL; - goto out; - } - if (rc < 2) - continue; - - fle = talloc_zero(tall_fle_ctx, struct file_list_entry); - if (!fle) { - rc = -ENOMEM; - goto out; - } - - /* construct new filename */ - osmo_strlcpy(dir, bs11_sw->swl_fname, sizeof(dir)); - strncat(fle->fname, dirname(dir), sizeof(fle->fname) - 1); - strcat(fle->fname, "/"); - strncat(fle->fname, file_id, sizeof(fle->fname) - 1 -strlen(fle->fname)); - - llist_add_tail(&fle->list, &bs11_sw->file_list); - } - -out: - fclose(swl); - return rc; -} - -/* bs11 swload specific callback, passed to abis_nm core swload */ -static int bs11_swload_cbfn(unsigned int hook, unsigned int event, - struct msgb *msg, void *data, void *param) -{ - struct abis_nm_bs11_sw *bs11_sw = data; - struct file_list_entry *fle; - int rc = 0; - - switch (event) { - case NM_MT_LOAD_END_ACK: - fle = fl_dequeue(&bs11_sw->file_list); - if (fle) { - /* start download the next file of our file list */ - rc = abis_nm_software_load(bs11_sw->bts, 0xff, fle->fname, - bs11_sw->win_size, - bs11_sw->forced, - &bs11_swload_cbfn, bs11_sw); - talloc_free(fle); - } else { - /* activate the SWL */ - rc = abis_nm_software_activate(bs11_sw->bts, - bs11_sw->swl_fname, - bs11_swload_cbfn, - bs11_sw); - } - break; - case NM_MT_LOAD_SEG_ACK: - case NM_MT_LOAD_END_NACK: - case NM_MT_LOAD_INIT_ACK: - case NM_MT_LOAD_INIT_NACK: - case NM_MT_ACTIVATE_SW_NACK: - case NM_MT_ACTIVATE_SW_ACK: - default: - /* fallthrough to the user callback */ - if (bs11_sw->user_cb) - rc = bs11_sw->user_cb(hook, event, msg, NULL, NULL); - break; - } - - return rc; -} - -/* Siemens provides a SWL file that is a mere listing of all the other - * files that are part of a software release. We need to upload first - * the list file, and then each file that is listed in the list file */ -int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname, - uint8_t win_size, int forced, gsm_cbfn *cbfn) -{ - struct abis_nm_bs11_sw *bs11_sw = g_bs11_sw; - struct file_list_entry *fle; - int rc = 0; - - INIT_LLIST_HEAD(&bs11_sw->file_list); - bs11_sw->bts = bts; - bs11_sw->win_size = win_size; - bs11_sw->user_cb = cbfn; - bs11_sw->forced = forced; - - osmo_strlcpy(bs11_sw->swl_fname, fname, sizeof(bs11_sw->swl_fname)); - rc = bs11_read_swl_file(bs11_sw); - if (rc < 0) - return rc; - - /* dequeue next item in file list */ - fle = fl_dequeue(&bs11_sw->file_list); - if (!fle) - return -EINVAL; - - /* start download the next file of our file list */ - rc = abis_nm_software_load(bts, 0xff, fle->fname, win_size, forced, - bs11_swload_cbfn, bs11_sw); - talloc_free(fle); - return rc; -} - -#if 0 -static uint8_t req_attr_btse[] = { - NM_ATT_ADM_STATE, NM_ATT_BS11_LMT_LOGON_SESSION, - NM_ATT_BS11_LMT_LOGIN_TIME, NM_ATT_BS11_LMT_USER_ACC_LEV, - NM_ATT_BS11_LMT_USER_NAME, - - 0xaf, NM_ATT_BS11_RX_OFFSET, NM_ATT_BS11_VENDOR_NAME, - - NM_ATT_BS11_SW_LOAD_INTENDED, NM_ATT_BS11_SW_LOAD_SAFETY, - - NM_ATT_BS11_SW_LOAD_STORED }; - -static uint8_t req_attr_btsm[] = { - NM_ATT_ABIS_CHANNEL, NM_ATT_TEI, NM_ATT_BS11_ABIS_EXT_TIME, - NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xce, NM_ATT_FILE_ID, - NM_ATT_FILE_VERSION, NM_ATT_OPER_STATE, 0xe8, NM_ATT_BS11_ALL_TEST_CATG, - NM_ATT_SW_DESCR, NM_ATT_GET_ARI }; -#endif - -static uint8_t req_attr[] = { - NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xa8, NM_ATT_OPER_STATE, - 0xd5, 0xa1, NM_ATT_BS11_ESN_FW_CODE_NO, NM_ATT_BS11_ESN_HW_CODE_NO, - 0x42, NM_ATT_BS11_ESN_PCB_SERIAL, NM_ATT_BS11_PLL }; - -int abis_nm_bs11_get_serno(struct gsm_bts *bts) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - /* SiemensHW CCTRL object */ - fill_om_fom_hdr(oh, 2+sizeof(req_attr), NM_MT_GET_ATTR, NM_OC_BS11, - 0x03, 0x00, 0x00); - msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(req_attr), req_attr); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_set_ext_time(struct gsm_bts *bts) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - struct bs11_date_time aet; - - get_bs11_date_time(&aet); - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - /* SiemensHW CCTRL object */ - fill_om_fom_hdr(oh, 2+sizeof(aet), NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, - 0xff, 0xff, 0xff); - msgb_tlv_put(msg, NM_ATT_BS11_ABIS_EXT_TIME, sizeof(aet), (uint8_t *) &aet); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, uint8_t bport) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - uint8_t attr = NM_ATT_BS11_LINE_CFG; - - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, - NM_OC_BS11_BPORT, bport, 0xff, 0x02); - msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr); - - return abis_nm_sendmsg(bts, msg); -} - -int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, uint8_t bport, enum abis_bs11_line_cfg line_cfg) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - struct bs11_date_time aet; - - get_bs11_date_time(&aet); - oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); - fill_om_fom_hdr(oh, 2, NM_MT_BS11_SET_ATTR, NM_OC_BS11_BPORT, - bport, 0xff, 0x02); - msgb_tv_put(msg, NM_ATT_BS11_LINE_CFG, line_cfg); - - return abis_nm_sendmsg(bts, msg); -} - -/* ip.access nanoBTS specific commands */ -static const char ipaccess_magic[] = "com.ipaccess"; - - -static int abis_nm_rx_ipacc(struct msgb *msg) -{ - struct in_addr addr; - struct abis_om_hdr *oh = msgb_l2(msg); - struct abis_om_fom_hdr *foh; - uint8_t idstrlen = oh->data[0]; - struct tlv_parsed tp; - struct ipacc_ack_signal_data signal; - struct e1inp_sign_link *sign_link = msg->dst; - - foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen); - - if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) { - LOGPFOH(DNM, LOGL_ERROR, foh, "id string is not com.ipaccess !?!\n"); - return -EINVAL; - } - - abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); - - DEBUGPFOH(DNM, foh, "IPACCESS(0x%02x): ", foh->msg_type); - - switch (foh->msg_type) { - case NM_MT_IPACC_RSL_CONNECT_ACK: - DEBUGPC(DNM, "RSL CONNECT ACK "); - if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP)) { - memcpy(&addr, - TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP), sizeof(addr)); - - DEBUGPC(DNM, "IP=%s ", inet_ntoa(addr)); - } - if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP_PORT)) - DEBUGPC(DNM, "PORT=%u ", - ntohs(*((uint16_t *) - TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP_PORT)))); - if (TLVP_PRESENT(&tp, NM_ATT_IPACC_STREAM_ID)) - DEBUGPC(DNM, "STREAM=0x%02x ", - *TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID)); - DEBUGPC(DNM, "\n"); - osmo_timer_del(&sign_link->trx->rsl_connect_timeout); - break; - case NM_MT_IPACC_RSL_CONNECT_NACK: - LOGPFOH(DNM, LOGL_ERROR, foh, "RSL CONNECT NACK "); - if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) - LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", - abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); - else - LOGPC(DNM, LOGL_ERROR, "\n"); - osmo_timer_del(&sign_link->trx->rsl_connect_timeout); - break; - case NM_MT_IPACC_SET_NVATTR_ACK: - DEBUGPFOH(DNM, foh, "SET NVATTR ACK\n"); - /* FIXME: decode and show the actual attributes */ - break; - case NM_MT_IPACC_SET_NVATTR_NACK: - LOGPFOH(DNM, LOGL_ERROR, foh, "SET NVATTR NACK "); - if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) - LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", - abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); - else - LOGPC(DNM, LOGL_ERROR, "\n"); - break; - case NM_MT_IPACC_GET_NVATTR_ACK: - DEBUGPFOH(DNM, foh, "GET NVATTR ACK\n"); - /* FIXME: decode and show the actual attributes */ - break; - case NM_MT_IPACC_GET_NVATTR_NACK: - LOGPFOH(DNM, LOGL_ERROR, foh, "GET NVATTR NACK "); - if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) - LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", - abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); - else - LOGPC(DNM, LOGL_ERROR, "\n"); - break; - case NM_MT_IPACC_SET_ATTR_ACK: - DEBUGPFOH(DNM, foh, "SET ATTR ACK\n"); - break; - case NM_MT_IPACC_SET_ATTR_NACK: - LOGPFOH(DNM, LOGL_ERROR, foh, "SET ATTR NACK "); - if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) - LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", - abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); - else - LOGPC(DNM, LOGL_ERROR, "\n"); - break; - default: - DEBUGPC(DNM, "unknown\n"); - break; - } - - /* signal handling */ - switch (foh->msg_type) { - case NM_MT_IPACC_RSL_CONNECT_NACK: - case NM_MT_IPACC_SET_NVATTR_NACK: - case NM_MT_IPACC_GET_NVATTR_NACK: - signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr); - signal.msg_type = foh->msg_type; - osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal); - break; - case NM_MT_IPACC_SET_NVATTR_ACK: - signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr); - signal.msg_type = foh->msg_type; - osmo_signal_dispatch(SS_NM, S_NM_IPACC_ACK, &signal); - break; - default: - break; - } - - return 0; -} - -/* send an ip-access manufacturer specific message */ -int abis_nm_ipaccess_msg(struct gsm_bts *bts, uint8_t msg_type, - uint8_t obj_class, uint8_t bts_nr, - uint8_t trx_nr, uint8_t ts_nr, - uint8_t *attr, int attr_len) -{ - struct msgb *msg = nm_msgb_alloc(); - struct abis_om_hdr *oh; - struct abis_om_fom_hdr *foh; - uint8_t *data; - - /* construct the 12.21 OM header, observe the erroneous length */ - oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh)); - fill_om_hdr(oh, sizeof(*foh) + attr_len); - oh->mdisc = ABIS_OM_MDISC_MANUF; - - /* add the ip.access magic */ - data = msgb_put(msg, sizeof(ipaccess_magic)+1); - *data++ = sizeof(ipaccess_magic); - memcpy(data, ipaccess_magic, sizeof(ipaccess_magic)); - - /* fill the 12.21 FOM header */ - foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh)); - foh->msg_type = msg_type; - foh->obj_class = obj_class; - foh->obj_inst.bts_nr = bts_nr; - foh->obj_inst.trx_nr = trx_nr; - foh->obj_inst.ts_nr = ts_nr; - - if (attr && attr_len) { - data = msgb_put(msg, attr_len); - memcpy(data, attr, attr_len); - } - - return abis_nm_sendmsg(bts, msg); -} - -/* set some attributes in NVRAM */ -int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, uint8_t *attr, - int attr_len) -{ - return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_SET_NVATTR, - NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, attr, - attr_len); -} - -static void rsl_connect_timeout(void *data) -{ - struct gsm_bts_trx *trx = data; - struct ipacc_ack_signal_data signal; - - LOGP(DRSL, LOGL_NOTICE, "(bts=%d,trx=%d) RSL connection request timed out\n", trx->bts->nr, trx->nr); - - /* Fake an RSL CONECT NACK message from the BTS. */ - signal.trx = trx; - signal.msg_type = NM_MT_IPACC_RSL_CONNECT_NACK; - 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 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)); - - /* if ip == 0, we use the default IP */ - if (ip == 0) - attr_len -= 5; - - LOGP(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); - if (error == 0) - osmo_timer_schedule(&trx->rsl_connect_timeout, 60, 0); - - return error; -} - -/* restart / reboot an ip.access nanoBTS */ -int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx) -{ - struct abis_om_hdr *oh; - struct msgb *msg = nm_msgb_alloc(); - - 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); - - return abis_nm_sendmsg_direct(trx->bts, msg); -} - -int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, uint8_t obj_class, - uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, - uint8_t *attr, uint8_t attr_len) -{ - return abis_nm_ipaccess_msg(bts, NM_MT_IPACC_SET_ATTR, - obj_class, bts_nr, trx_nr, ts_nr, - attr, attr_len); -} - -void abis_nm_ipaccess_cgi(uint8_t *buf, struct gsm_bts *bts) -{ - struct gsm48_ra_id *_buf = (struct gsm48_ra_id*)buf; - uint16_t ci = htons(bts->cell_identity); - /* we simply reuse the GSM48 function and write the Cell ID over the position where the RAC - * starts */ - gsm48_ra_id_by_bts(_buf, bts); - memcpy(&_buf->rac, &ci, sizeof(ci)); -} - -void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason) -{ - uint8_t new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; - - - if (!trx->bts || !trx->bts->oml_link) { - /* Set initial state which will be sent when BTS connects. */ - trx->mo.nm_state.administrative = new_state; - return; - } - - LOGP(DNM, LOGL_NOTICE, "(bts=%d,trx=%d) Requesting administrative state change %s -> %s [%s]\n", - trx->bts->nr, trx->nr, - get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative), - get_value_string(abis_nm_adm_state_names, new_state), reason); - - abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER, - trx->bts->bts_nr, trx->nr, 0xff, - new_state); -} - -static const struct value_string ipacc_testres_names[] = { - { NM_IPACC_TESTRES_SUCCESS, "SUCCESS" }, - { NM_IPACC_TESTRES_TIMEOUT, "TIMEOUT" }, - { NM_IPACC_TESTRES_NO_CHANS, "NO CHANNELS" }, - { NM_IPACC_TESTRES_PARTIAL, "PARTIAL" }, - { NM_IPACC_TESTRES_STOPPED, "STOPPED" }, - { 0, NULL } -}; - -const char *ipacc_testres_name(uint8_t res) -{ - return get_value_string(ipacc_testres_names, res); -} - -void ipac_parse_cgi(struct osmo_cell_global_id *cid, const uint8_t *buf) -{ - osmo_plmn_from_bcd(buf, &cid->lai.plmn); - cid->lai.lac = ntohs(*((uint16_t *)&buf[3])); - cid->cell_identity = ntohs(*((uint16_t *)&buf[5])); -} - -/* parse BCCH information IEI from wire format to struct ipac_bcch_info */ -int ipac_parse_bcch_info(struct ipac_bcch_info *binf, uint8_t *buf) -{ - uint8_t *cur = buf; - uint16_t len __attribute__((unused)); - - memset(binf, 0, sizeof(*binf)); - - if (cur[0] != NM_IPAC_EIE_BCCH_INFO) - return -EINVAL; - cur++; - - len = ntohs(*(uint16_t *)cur); - cur += 2; - - binf->info_type = ntohs(*(uint16_t *)cur); - cur += 2; - - if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL) - binf->freq_qual = *cur >> 2; - - binf->arfcn = (*cur++ & 3) << 8; - binf->arfcn |= *cur++; - - if (binf->info_type & IPAC_BINF_RXLEV) - binf->rx_lev = *cur & 0x3f; - cur++; - - if (binf->info_type & IPAC_BINF_RXQUAL) - binf->rx_qual = *cur & 0x7; - cur++; - - if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL) - binf->freq_err = ntohs(*(uint16_t *)cur); - cur += 2; - - if (binf->info_type & IPAC_BINF_FRAME_OFFSET) - binf->frame_offset = ntohs(*(uint16_t *)cur); - cur += 2; - - if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET) - binf->frame_nr_offset = ntohl(*(uint32_t *)cur); - cur += 4; - -#if 0 - /* Somehow this is not set correctly */ - if (binf->info_type & IPAC_BINF_BSIC) -#endif - binf->bsic = *cur & 0x3f; - cur++; - - ipac_parse_cgi(&binf->cgi, cur); - cur += 7; - - if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2) { - memcpy(binf->ba_list_si2, cur, sizeof(binf->ba_list_si2)); - cur += sizeof(binf->ba_list_si2); - } - - if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2bis) { - memcpy(binf->ba_list_si2bis, cur, - sizeof(binf->ba_list_si2bis)); - cur += sizeof(binf->ba_list_si2bis); - } - - if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2ter) { - memcpy(binf->ba_list_si2ter, cur, - sizeof(binf->ba_list_si2ter)); - cur += sizeof(binf->ba_list_si2ter); - } - - return 0; -} - -void abis_nm_clear_queue(struct gsm_bts *bts) -{ - struct msgb *msg; - - while (!llist_empty(&bts->abis_queue)) { - msg = msgb_dequeue(&bts->abis_queue); - msgb_free(msg); - } - - bts->abis_nm_pend = 0; -} diff --git a/src/libbsc/abis_nm_ipaccess.c b/src/libbsc/abis_nm_ipaccess.c deleted file mode 100644 index b8225383a..000000000 --- a/src/libbsc/abis_nm_ipaccess.c +++ /dev/null @@ -1,87 +0,0 @@ -/* GSM Network Management (OML) messages on the A-bis interface - * Extensions for the ip.access A-bis over IP protocol*/ - -/* (C) 2008-2009 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -/* A list of all the 'embedded' attributes of ip.access */ -enum ipa_embedded_att { - IPA_ATT_ARFCN_WHITELIST = 0x01, - IPA_ATT_ARFCN_BLACKLIST = 0x02, - IPA_ATT_FREQ_ERR_LIST = 0x03, - IPA_ATT_CHAN_USAGE_LIST = 0x04, - IPA_ATT_BCCH_INF_TYPE = 0x05, - IPA_ATT_BCCH_INF = 0x06, - IPA_ATT_CONFIG = 0x07, - IPA_ATT_RESULT_DETAILS = 0x08, - IPA_ATT_RXLEV_THRESH = 0x09, - IPA_ATT_FREQ_SYNC_OPT = 0x0a, - IPA_ATT_MAC_ADDR = 0x0b, - IPA_ATT_HW_SW_COMPAT_NR = 0x0c, - IPA_ATT_MANUF_SER_NR = 0x0d, - IPA_ATT_OEM_ID = 0x0e, - IPA_ATT_DATETIME_MANUF = 0x0f, - IPA_ATT_DATETIME_CALIB = 0x10, - IPA_ATT_BEACON_INF = 0x11, - IPA_ATT_FREQ_ERR = 0x12, - IPA_ATT_SNMP_COMM_STRING = 0x13, - IPA_ATT_SNMP_TRAP_ADDR = 0x14, - IPA_ATT_SNMP_TRAP_PORT = 0x15, - IPA_ATT_SNMP_MAN_ADDR = 0x16, - IPA_ATT_SNMP_SYS_CONTACT = 0x17, - IPA_ATT_FACTORY_ID = 0x18, - IPA_ATT_FACTORY_SERIAL = 0x19, - IPA_ATT_LOGGED_EVT_IND = 0x1a, - IPA_ATT_LOCAL_ADD_TEXT = 0x1b, - IPA_ATT_FREQ_BANDS = 0x1c, - IPA_ATT_MAX_TA = 0x1d, - IPA_ATT_CIPH_ALG = 0x1e, - IPA_ATT_CHAN_TYPES = 0x1f, - IPA_ATT_CHAN_MODES = 0x20, - IPA_ATT_GPRS_CODING_SCHEMES = 0x21, - IPA_ATT_RTP_FEATURES = 0x22, - IPA_ATT_RSL_FEATURES = 0x23, - IPA_ATT_BTS_HW_CLASS = 0x24, - IPA_ATT_BTS_ID = 0x25, - IPA_ATT_BCAST_L2_MSG = 0x26, -}; - -/* append an ip.access channel list to the given msgb */ -static int ipa_chan_list_append(struct msgb *msg, uint8_t ie, - uint16_t *arfcns, int arfcn_count) -{ - int i; - uint8_t *u8; - uint16_t *u16; - - /* tag */ - u8 = msgb_push(msg, 1); - *u8 = ie; - - /* length in octets */ - u16 = msgb_push(msg, 2); - *u16 = htons(arfcn_count * 2); - - for (i = 0; i < arfcn_count; i++) { - u16 = msgb_push(msg, 2); - *u16 = htons(arfcns[i]); - } - - return 0; -} diff --git a/src/libbsc/abis_nm_vty.c b/src/libbsc/abis_nm_vty.c deleted file mode 100644 index 3019eb828..000000000 --- a/src/libbsc/abis_nm_vty.c +++ /dev/null @@ -1,188 +0,0 @@ -/* VTY interface for A-bis OML (Netowrk Management) */ - -/* (C) 2009-2018 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -static struct cmd_node oml_node = { - OML_NODE, - "%s(oml)# ", - 1, -}; - -struct oml_node_state { - struct gsm_bts *bts; - uint8_t obj_class; - 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" \ - "BTS Object\n" \ - "Radio Carrier Object\n" \ - "Baseband Transceiver Object\n" \ - "Channel (Timeslot) Object\n" \ - "Adjacent Object (Siemens)\n" \ - "Handover Object (Siemens)\n" \ - "Power Control Object (Siemens)\n" \ - "BTSE Object (Siemens)\n" \ - "Rack Object (Siemens)\n" \ - "Test Object (Siemens)\n" \ - "ENVABTSE Object (Siemens)\n" \ - "BPORT Object (Siemens)\n" \ - "GPRS NSE Object (ip.access/osmo-bts)\n" \ - "GPRS Cell Object (ip.acecss/osmo-bts)\n" \ - "GPRS NSVC Object (ip.acecss/osmo-bts)\n" \ - "SIEMENSHW Object (Siemens)\n" - - -DEFUN(oml_class_inst, oml_class_inst_cmd, - "bts <0-255> oml class " NM_OBJCLASS_VTY - " instance <0-255> <0-255> <0-255>", - "BTS related commands\n" "BTS Number\n" - "Manipulate the OML managed objects\n" - "Object Class\n" NM_OBJCLASS_VTY_HELP - "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n") -{ - struct gsm_bts *bts; - struct oml_node_state *oms; - 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; - } - - oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); - if (!oms) - return CMD_WARNING; - - oms->bts = bts; - oms->obj_class = get_string_value(abis_nm_obj_class_names, argv[1]); - oms->obj_inst[0] = atoi(argv[2]); - oms->obj_inst[1] = atoi(argv[3]); - oms->obj_inst[2] = atoi(argv[4]); - - vty->index = oms; - vty->node = OML_NODE; - - return CMD_SUCCESS; - -} - -DEFUN(oml_classnum_inst, oml_classnum_inst_cmd, - "bts <0-255> oml class <0-255> instance <0-255> <0-255> <0-255>", - "BTS related commands\n" "BTS Number\n" - "Manipulate the OML managed objects\n" - "Object Class\n" "Object Class\n" - "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n") -{ - struct gsm_bts *bts; - struct oml_node_state *oms; - 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; - } - - oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); - if (!oms) - return CMD_WARNING; - - oms->bts = bts; - oms->obj_class = atoi(argv[1]); - oms->obj_inst[0] = atoi(argv[2]); - oms->obj_inst[1] = atoi(argv[3]); - oms->obj_inst[2] = atoi(argv[4]); - - vty->index = oms; - vty->node = OML_NODE; - - return CMD_SUCCESS; -} - -DEFUN(oml_chg_adm_state, oml_chg_adm_state_cmd, - "change-adm-state (locked|unlocked|shutdown|null)", - "Change the Administrative State\n" - "Locked\n" "Unlocked\n" "Shutdown\n" "NULL\n") -{ - struct oml_node_state *oms = vty->index; - enum abis_nm_adm_state state; - - state = get_string_value(abis_nm_adm_state_names, argv[0]); - - abis_nm_chg_adm_state(oms->bts, oms->obj_class, oms->obj_inst[0], - oms->obj_inst[1], oms->obj_inst[2], state); - - return CMD_SUCCESS; -} - -DEFUN(oml_opstart, oml_opstart_cmd, - "opstart", "Send an OPSTART message to the object") -{ - struct oml_node_state *oms = vty->index; - - abis_nm_opstart(oms->bts, oms->obj_class, oms->obj_inst[0], - oms->obj_inst[1], oms->obj_inst[2]); - - return CMD_SUCCESS; -} - -int abis_nm_vty_init(void) -{ - install_element(ENABLE_NODE, &oml_class_inst_cmd); - install_element(ENABLE_NODE, &oml_classnum_inst_cmd); - install_node(&oml_node, dummy_config_write); - - install_element(OML_NODE, &oml_chg_adm_state_cmd); - install_element(OML_NODE, &oml_opstart_cmd); - - return 0; -} diff --git a/src/libbsc/abis_om2000.c b/src/libbsc/abis_om2000.c deleted file mode 100644 index d533ea198..000000000 --- a/src/libbsc/abis_om2000.c +++ /dev/null @@ -1,2769 +0,0 @@ -/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface - * implemented based on protocol trace analysis, no formal documentation */ - -/* (C) 2010-2011,2016 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -/* FIXME: move to libosmocore */ -struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm, - struct osmo_fsm_inst *parent, - uint32_t parent_term_event, - const char *id) -{ - 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); - 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); - - return fi; -} - - -#define OM_ALLOC_SIZE 1024 -#define OM_HEADROOM_SIZE 128 - -#define OM2K_TIMEOUT 10 -#define TRX_FSM_TIMEOUT 60 -#define BTS_FSM_TIMEOUT 60 - -/* use following functions from abis_nm.c: - * om2k_msgb_alloc() - * abis_om2k_sendmsg() - */ - -struct abis_om2k_hdr { - struct abis_om_hdr om; - uint16_t msg_type; - struct abis_om2k_mo mo; - uint8_t data[0]; -} __attribute__ ((packed)); - -enum abis_om2k_msgtype { - OM2K_MSGT_ABORT_SP_CMD = 0x0000, - OM2K_MSGT_ABORT_SP_COMPL = 0x0002, - OM2K_MSGT_ALARM_REP_ACK = 0x0004, - OM2K_MSGT_ALARM_REP_NACK = 0x0005, - OM2K_MSGT_ALARM_REP = 0x0006, - OM2K_MSGT_ALARM_STATUS_REQ = 0x0008, - OM2K_MSGT_ALARM_STATUS_REQ_ACK = 0x000a, - OM2K_MSGT_ALARM_STATUS_REQ_REJ = 0x000b, - OM2K_MSGT_ALARM_STATUS_RES_ACK = 0x000c, - OM2K_MSGT_ALARM_STATUS_RES_NACK = 0x000d, - OM2K_MSGT_ALARM_STATUS_RES = 0x000e, - OM2K_MSGT_CAL_TIME_RESP = 0x0010, - OM2K_MSGT_CAL_TIME_REJ = 0x0011, - OM2K_MSGT_CAL_TIME_REQ = 0x0012, - - OM2K_MSGT_CON_CONF_REQ = 0x0014, - OM2K_MSGT_CON_CONF_REQ_ACK = 0x0016, - OM2K_MSGT_CON_CONF_REQ_REJ = 0x0017, - OM2K_MSGT_CON_CONF_RES_ACK = 0x0018, - OM2K_MSGT_CON_CONF_RES_NACK = 0x0019, - OM2K_MSGT_CON_CONF_RES = 0x001a, - - OM2K_MSGT_CONNECT_CMD = 0x001c, - OM2K_MSGT_CONNECT_COMPL = 0x001e, - OM2K_MSGT_CONNECT_REJ = 0x001f, - - OM2K_MSGT_DISABLE_REQ = 0x0028, - OM2K_MSGT_DISABLE_REQ_ACK = 0x002a, - OM2K_MSGT_DISABLE_REQ_REJ = 0x002b, - OM2K_MSGT_DISABLE_RES_ACK = 0x002c, - OM2K_MSGT_DISABLE_RES_NACK = 0x002d, - OM2K_MSGT_DISABLE_RES = 0x002e, - OM2K_MSGT_DISCONNECT_CMD = 0x0030, - OM2K_MSGT_DISCONNECT_COMPL = 0x0032, - OM2K_MSGT_DISCONNECT_REJ = 0x0033, - OM2K_MSGT_ENABLE_REQ = 0x0034, - OM2K_MSGT_ENABLE_REQ_ACK = 0x0036, - OM2K_MSGT_ENABLE_REQ_REJ = 0x0037, - OM2K_MSGT_ENABLE_RES_ACK = 0x0038, - OM2K_MSGT_ENABLE_RES_NACK = 0x0039, - OM2K_MSGT_ENABLE_RES = 0x003a, - - OM2K_MSGT_FAULT_REP_ACK = 0x0040, - OM2K_MSGT_FAULT_REP_NACK = 0x0041, - OM2K_MSGT_FAULT_REP = 0x0042, - - OM2K_MSGT_IS_CONF_REQ = 0x0060, - OM2K_MSGT_IS_CONF_REQ_ACK = 0x0062, - OM2K_MSGT_IS_CONF_REQ_REJ = 0x0063, - OM2K_MSGT_IS_CONF_RES_ACK = 0x0064, - OM2K_MSGT_IS_CONF_RES_NACK = 0x0065, - OM2K_MSGT_IS_CONF_RES = 0x0066, - - OM2K_MSGT_OP_INFO = 0x0074, - OM2K_MSGT_OP_INFO_ACK = 0x0076, - OM2K_MSGT_OP_INFO_REJ = 0x0077, - OM2K_MSGT_RESET_CMD = 0x0078, - OM2K_MSGT_RESET_COMPL = 0x007a, - OM2K_MSGT_RESET_REJ = 0x007b, - OM2K_MSGT_RX_CONF_REQ = 0x007c, - OM2K_MSGT_RX_CONF_REQ_ACK = 0x007e, - OM2K_MSGT_RX_CONF_REQ_REJ = 0x007f, - OM2K_MSGT_RX_CONF_RES_ACK = 0x0080, - OM2K_MSGT_RX_CONF_RES_NACK = 0x0081, - OM2K_MSGT_RX_CONF_RES = 0x0082, - OM2K_MSGT_START_REQ = 0x0084, - OM2K_MSGT_START_REQ_ACK = 0x0086, - OM2K_MSGT_START_REQ_REJ = 0x0087, - OM2K_MSGT_START_RES_ACK = 0x0088, - OM2K_MSGT_START_RES_NACK = 0x0089, - OM2K_MSGT_START_RES = 0x008a, - OM2K_MSGT_STATUS_REQ = 0x008c, - OM2K_MSGT_STATUS_RESP = 0x008e, - OM2K_MSGT_STATUS_REJ = 0x008f, - - OM2K_MSGT_TEST_REQ = 0x0094, - OM2K_MSGT_TEST_REQ_ACK = 0x0096, - OM2K_MSGT_TEST_REQ_REJ = 0x0097, - OM2K_MSGT_TEST_RES_ACK = 0x0098, - OM2K_MSGT_TEST_RES_NACK = 0x0099, - OM2K_MSGT_TEST_RES = 0x009a, - - OM2K_MSGT_TF_CONF_REQ = 0x00a0, - OM2K_MSGT_TF_CONF_REQ_ACK = 0x00a2, - OM2K_MSGT_TF_CONF_REQ_REJ = 0x00a3, - OM2K_MSGT_TF_CONF_RES_ACK = 0x00a4, - OM2K_MSGT_TF_CONF_RES_NACK = 0x00a5, - OM2K_MSGT_TF_CONF_RES = 0x00a6, - OM2K_MSGT_TS_CONF_REQ = 0x00a8, - OM2K_MSGT_TS_CONF_REQ_ACK = 0x00aa, - OM2K_MSGT_TS_CONF_REQ_REJ = 0x00ab, - OM2K_MSGT_TS_CONF_RES_ACK = 0x00ac, - OM2K_MSGT_TS_CONF_RES_NACK = 0x00ad, - OM2K_MSGT_TS_CONF_RES = 0x00ae, - OM2K_MSGT_TX_CONF_REQ = 0x00b0, - OM2K_MSGT_TX_CONF_REQ_ACK = 0x00b2, - OM2K_MSGT_TX_CONF_REQ_REJ = 0x00b3, - OM2K_MSGT_TX_CONF_RES_ACK = 0x00b4, - OM2K_MSGT_TX_CONF_RES_NACK = 0x00b5, - OM2K_MSGT_TX_CONF_RES = 0x00b6, - - OM2K_MSGT_CAPA_REQ = 0x00e8, - OM2K_MSGT_CAPA_REQ_ACK = 0x00ea, - OM2K_MSGT_CAPA_REQ_REJ = 0x00eb, - OM2K_MSGT_CAPA_RES = 0x00ee, - OM2K_MSGT_CAPA_RES_ACK = 0x00ec, - OM2K_MSGT_CAPA_RES_NACK = 0x00ed, - - OM2K_MSGT_NEGOT_REQ_ACK = 0x0104, - OM2K_MSGT_NEGOT_REQ_NACK = 0x0105, - OM2K_MSGT_NEGOT_REQ = 0x0106, -}; - -enum abis_om2k_dei { - OM2K_DEI_ACCORDANCE_IND = 0x00, - OM2K_DEI_BCC = 0x06, - OM2K_DEI_BS_AG_BKS_RES = 0x07, - OM2K_DEI_BSIC = 0x09, - OM2K_DEI_BA_PA_MFRMS = 0x0a, - OM2K_DEI_CBCH_INDICATOR = 0x0b, - OM2K_DEI_CCCH_OPTIONS = 0x0c, - OM2K_DEI_CAL_TIME = 0x0d, - OM2K_DEI_COMBINATION = 0x0f, - OM2K_DEI_CON_CONN_LIST = 0x10, - OM2K_DEI_DRX_DEV_MAX = 0x12, - OM2K_DEI_END_LIST_NR = 0x13, - OM2K_DEI_EXT_COND_MAP_1 = 0x14, - OM2K_DEI_EXT_COND_MAP_2 = 0x15, - OM2K_DEI_FILLING_MARKER = 0x1c, - OM2K_DEI_FN_OFFSET = 0x1d, - OM2K_DEI_FREQ_LIST = 0x1e, - OM2K_DEI_FREQ_SPEC_RX = 0x1f, - OM2K_DEI_FREQ_SPEC_TX = 0x20, - OM2K_DEI_HSN = 0x21, - OM2K_DEI_ICM_INDICATOR = 0x22, - OM2K_DEI_INT_FAULT_MAP_1A = 0x23, - OM2K_DEI_INT_FAULT_MAP_1B = 0x24, - OM2K_DEI_INT_FAULT_MAP_2A = 0x25, - OM2K_DEI_INT_FAULT_MAP_2A_EXT = 0x26, - OM2K_DEI_IS_CONN_LIST = 0x27, - OM2K_DEI_LIST_NR = 0x28, - OM2K_DEI_LOCAL_ACCESS = 0x2a, - OM2K_DEI_MAIO = 0x2b, - OM2K_DEI_MO_STATE = 0x2c, - OM2K_DEI_NY1 = 0x2d, - OM2K_DEI_OP_INFO = 0x2e, - OM2K_DEI_POWER = 0x2f, - OM2K_DEI_REASON_CODE = 0x32, - OM2K_DEI_RX_DIVERSITY = 0x33, - OM2K_DEI_REPL_UNIT_MAP = 0x34, - OM2K_DEI_RESULT_CODE = 0x35, - OM2K_DEI_T3105 = 0x38, - OM2K_DEI_TF_MODE = 0x3a, - OM2K_DEI_TS_NR = 0x3c, - OM2K_DEI_TSC = 0x3d, - OM2K_DEI_BTS_VERSION = 0x40, - OM2K_DEI_OML_IWD_VERSION = 0x41, - OM2K_DEI_RSL_IWD_VERSION = 0x42, - OM2K_DEI_OML_FUNC_MAP_1 = 0x43, - OM2K_DEI_OML_FUNC_MAP_2 = 0x44, - OM2K_DEI_RSL_FUNC_MAP_1 = 0x45, - OM2K_DEI_RSL_FUNC_MAP_2 = 0x46, - OM2K_DEI_EXT_RANGE = 0x47, - OM2K_DEI_REQ_IND = 0x48, - OM2K_DEI_REPL_UNIT_MAP_EXT = 0x50, - OM2K_DEI_ICM_BOUND_PARAMS = 0x74, - OM2K_DEI_LSC = 0x79, - OM2K_DEI_LSC_FILT_TIME = 0x7a, - OM2K_DEI_CALL_SUPV_TIME = 0x7b, - OM2K_DEI_ICM_CHAN_RATE = 0x7e, - OM2K_DEI_HW_INFO_SIG = 0x84, - OM2K_DEI_TF_SYNC_SRC = 0x86, - OM2K_DEI_TTA = 0x87, - OM2K_DEI_CAPA_SIG = 0x8a, - OM2K_DEI_NEGOT_REC1 = 0x90, - OM2K_DEI_NEGOT_REC2 = 0x91, - OM2K_DEI_ENCR_ALG = 0x92, - OM2K_DEI_INTERF_REJ_COMB = 0x94, - OM2K_DEI_FS_OFFSET = 0x98, - OM2K_DEI_EXT_COND_MAP_2_EXT = 0x9c, - OM2K_DEI_TSS_MO_STATE = 0x9d, -}; - -const struct tlv_definition om2k_att_tlvdef = { - .def = { - [OM2K_DEI_ACCORDANCE_IND] = { TLV_TYPE_TV }, - [OM2K_DEI_BCC] = { TLV_TYPE_TV }, - [OM2K_DEI_BS_AG_BKS_RES] = { TLV_TYPE_TV }, - [OM2K_DEI_BSIC] = { TLV_TYPE_TV }, - [OM2K_DEI_BA_PA_MFRMS] = { TLV_TYPE_TV }, - [OM2K_DEI_CBCH_INDICATOR] = { TLV_TYPE_TV }, - [OM2K_DEI_INT_FAULT_MAP_1A] = { TLV_TYPE_FIXED, 6 }, - [OM2K_DEI_INT_FAULT_MAP_1B] = { TLV_TYPE_FIXED, 6 }, - [OM2K_DEI_INT_FAULT_MAP_2A] = { TLV_TYPE_FIXED, 6 }, - [OM2K_DEI_INT_FAULT_MAP_2A_EXT]={ TLV_TYPE_FIXED, 6 }, - [OM2K_DEI_CCCH_OPTIONS] = { TLV_TYPE_TV }, - [OM2K_DEI_CAL_TIME] = { TLV_TYPE_FIXED, 6 }, - [OM2K_DEI_COMBINATION] = { TLV_TYPE_TV }, - [OM2K_DEI_CON_CONN_LIST] = { TLV_TYPE_TLV }, - [OM2K_DEI_DRX_DEV_MAX] = { TLV_TYPE_TV }, - [OM2K_DEI_END_LIST_NR] = { TLV_TYPE_TV }, - [OM2K_DEI_EXT_COND_MAP_1] = { TLV_TYPE_FIXED, 2 }, - [OM2K_DEI_EXT_COND_MAP_2] = { TLV_TYPE_FIXED, 2 }, - [OM2K_DEI_FILLING_MARKER] = { TLV_TYPE_TV }, - [OM2K_DEI_FN_OFFSET] = { TLV_TYPE_FIXED, 2 }, - [OM2K_DEI_FREQ_LIST] = { TLV_TYPE_TLV }, - [OM2K_DEI_FREQ_SPEC_RX] = { TLV_TYPE_FIXED, 2 }, - [OM2K_DEI_FREQ_SPEC_TX] = { TLV_TYPE_FIXED, 2 }, - [OM2K_DEI_HSN] = { TLV_TYPE_TV }, - [OM2K_DEI_ICM_INDICATOR] = { TLV_TYPE_TV }, - [OM2K_DEI_IS_CONN_LIST] = { TLV_TYPE_TLV }, - [OM2K_DEI_LIST_NR] = { TLV_TYPE_TV }, - [OM2K_DEI_LOCAL_ACCESS] = { TLV_TYPE_TV }, - [OM2K_DEI_MAIO] = { TLV_TYPE_TV }, - [OM2K_DEI_MO_STATE] = { TLV_TYPE_TV }, - [OM2K_DEI_NY1] = { TLV_TYPE_TV }, - [OM2K_DEI_OP_INFO] = { TLV_TYPE_TV }, - [OM2K_DEI_POWER] = { TLV_TYPE_TV }, - [OM2K_DEI_REASON_CODE] = { TLV_TYPE_TV }, - [OM2K_DEI_RX_DIVERSITY] = { TLV_TYPE_TV }, - [OM2K_DEI_RESULT_CODE] = { TLV_TYPE_TV }, - [OM2K_DEI_T3105] = { TLV_TYPE_TV }, - [OM2K_DEI_TF_MODE] = { TLV_TYPE_TV }, - [OM2K_DEI_TS_NR] = { TLV_TYPE_TV }, - [OM2K_DEI_TSC] = { TLV_TYPE_TV }, - [OM2K_DEI_BTS_VERSION] = { TLV_TYPE_FIXED, 12 }, - [OM2K_DEI_OML_IWD_VERSION] = { TLV_TYPE_FIXED, 6 }, - [OM2K_DEI_RSL_IWD_VERSION] = { TLV_TYPE_FIXED, 6 }, - [OM2K_DEI_OML_FUNC_MAP_1] = { TLV_TYPE_TLV }, - [OM2K_DEI_OML_FUNC_MAP_2] = { TLV_TYPE_TLV }, - [OM2K_DEI_RSL_FUNC_MAP_1] = { TLV_TYPE_TLV }, - [OM2K_DEI_RSL_FUNC_MAP_2] = { TLV_TYPE_TLV }, - [OM2K_DEI_EXT_RANGE] = { TLV_TYPE_TV }, - [OM2K_DEI_REQ_IND] = { TLV_TYPE_TV }, - [OM2K_DEI_REPL_UNIT_MAP] = { TLV_TYPE_FIXED, 6 }, - [OM2K_DEI_REPL_UNIT_MAP_EXT] = {TLV_TYPE_FIXED, 6}, - [OM2K_DEI_ICM_BOUND_PARAMS] = { TLV_TYPE_FIXED, 5 }, - [OM2K_DEI_LSC] = { TLV_TYPE_TV }, - [OM2K_DEI_LSC_FILT_TIME] = { TLV_TYPE_TV }, - [OM2K_DEI_CALL_SUPV_TIME] = { TLV_TYPE_TV }, - [OM2K_DEI_ICM_CHAN_RATE] = { TLV_TYPE_TV }, - [OM2K_DEI_HW_INFO_SIG] = { TLV_TYPE_FIXED, 2 }, - [OM2K_DEI_TF_SYNC_SRC] = { TLV_TYPE_TV }, - [OM2K_DEI_TTA] = { TLV_TYPE_TV }, - [OM2K_DEI_CAPA_SIG] = { TLV_TYPE_FIXED, 2 }, - [OM2K_DEI_NEGOT_REC1] = { TLV_TYPE_TLV }, - [OM2K_DEI_NEGOT_REC2] = { TLV_TYPE_TLV }, - [OM2K_DEI_ENCR_ALG] = { TLV_TYPE_TV }, - [OM2K_DEI_INTERF_REJ_COMB] = { TLV_TYPE_TV }, - [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 }, - }, -}; - -static const struct value_string om2k_msgcode_vals[] = { - { 0x0000, "Abort SP Command" }, - { 0x0002, "Abort SP Complete" }, - { 0x0004, "Alarm Report ACK" }, - { 0x0005, "Alarm Report NACK" }, - { 0x0006, "Alarm Report" }, - { 0x0008, "Alarm Status Request" }, - { 0x000a, "Alarm Status Request Accept" }, - { 0x000b, "Alarm Status Request Reject" }, - { 0x000c, "Alarm Status Result ACK" }, - { 0x000d, "Alarm Status Result NACK" }, - { 0x000e, "Alarm Status Result" }, - { 0x0010, "Calendar Time Response" }, - { 0x0011, "Calendar Time Reject" }, - { 0x0012, "Calendar Time Request" }, - { 0x0014, "CON Configuration Request" }, - { 0x0016, "CON Configuration Request Accept" }, - { 0x0017, "CON Configuration Request Reject" }, - { 0x0018, "CON Configuration Result ACK" }, - { 0x0019, "CON Configuration Result NACK" }, - { 0x001a, "CON Configuration Result" }, - { 0x001c, "Connect Command" }, - { 0x001e, "Connect Complete" }, - { 0x001f, "Connect Reject" }, - { 0x0028, "Disable Request" }, - { 0x002a, "Disable Request Accept" }, - { 0x002b, "Disable Request Reject" }, - { 0x002c, "Disable Result ACK" }, - { 0x002d, "Disable Result NACK" }, - { 0x002e, "Disable Result" }, - { 0x0030, "Disconnect Command" }, - { 0x0032, "Disconnect Complete" }, - { 0x0033, "Disconnect Reject" }, - { 0x0034, "Enable Request" }, - { 0x0036, "Enable Request Accept" }, - { 0x0037, "Enable Request Reject" }, - { 0x0038, "Enable Result ACK" }, - { 0x0039, "Enable Result NACK" }, - { 0x003a, "Enable Result" }, - { 0x003c, "Escape Downlink Normal" }, - { 0x003d, "Escape Downlink NACK" }, - { 0x003e, "Escape Uplink Normal" }, - { 0x003f, "Escape Uplink NACK" }, - { 0x0040, "Fault Report ACK" }, - { 0x0041, "Fault Report NACK" }, - { 0x0042, "Fault Report" }, - { 0x0044, "File Package End Command" }, - { 0x0046, "File Package End Result" }, - { 0x0047, "File Package End Reject" }, - { 0x0048, "File Relation Request" }, - { 0x004a, "File Relation Response" }, - { 0x004b, "File Relation Request Reject" }, - { 0x004c, "File Segment Transfer" }, - { 0x004e, "File Segment Transfer Complete" }, - { 0x004f, "File Segment Transfer Reject" }, - { 0x0050, "HW Information Request" }, - { 0x0052, "HW Information Request Accept" }, - { 0x0053, "HW Information Request Reject" }, - { 0x0054, "HW Information Result ACK" }, - { 0x0055, "HW Information Result NACK" }, - { 0x0056, "HW Information Result" }, - { 0x0060, "IS Configuration Request" }, - { 0x0062, "IS Configuration Request Accept" }, - { 0x0063, "IS Configuration Request Reject" }, - { 0x0064, "IS Configuration Result ACK" }, - { 0x0065, "IS Configuration Result NACK" }, - { 0x0066, "IS Configuration Result" }, - { 0x0068, "Load Data End" }, - { 0x006a, "Load Data End Result" }, - { 0x006b, "Load Data End Reject" }, - { 0x006c, "Load Data Init" }, - { 0x006e, "Load Data Init Accept" }, - { 0x006f, "Load Data Init Reject" }, - { 0x0070, "Loop Control Command" }, - { 0x0072, "Loop Control Complete" }, - { 0x0073, "Loop Control Reject" }, - { 0x0074, "Operational Information" }, - { 0x0076, "Operational Information Accept" }, - { 0x0077, "Operational Information Reject" }, - { 0x0078, "Reset Command" }, - { 0x007a, "Reset Complete" }, - { 0x007b, "Reset Reject" }, - { 0x007c, "RX Configuration Request" }, - { 0x007e, "RX Configuration Request Accept" }, - { 0x007f, "RX Configuration Request Reject" }, - { 0x0080, "RX Configuration Result ACK" }, - { 0x0081, "RX Configuration Result NACK" }, - { 0x0082, "RX Configuration Result" }, - { 0x0084, "Start Request" }, - { 0x0086, "Start Request Accept" }, - { 0x0087, "Start Request Reject" }, - { 0x0088, "Start Result ACK" }, - { 0x0089, "Start Result NACK" }, - { 0x008a, "Start Result" }, - { 0x008c, "Status Request" }, - { 0x008e, "Status Response" }, - { 0x008f, "Status Reject" }, - { 0x0094, "Test Request" }, - { 0x0096, "Test Request Accept" }, - { 0x0097, "Test Request Reject" }, - { 0x0098, "Test Result ACK" }, - { 0x0099, "Test Result NACK" }, - { 0x009a, "Test Result" }, - { 0x00a0, "TF Configuration Request" }, - { 0x00a2, "TF Configuration Request Accept" }, - { 0x00a3, "TF Configuration Request Reject" }, - { 0x00a4, "TF Configuration Result ACK" }, - { 0x00a5, "TF Configuration Result NACK" }, - { 0x00a6, "TF Configuration Result" }, - { 0x00a8, "TS Configuration Request" }, - { 0x00aa, "TS Configuration Request Accept" }, - { 0x00ab, "TS Configuration Request Reject" }, - { 0x00ac, "TS Configuration Result ACK" }, - { 0x00ad, "TS Configuration Result NACK" }, - { 0x00ae, "TS Configuration Result" }, - { 0x00b0, "TX Configuration Request" }, - { 0x00b2, "TX Configuration Request Accept" }, - { 0x00b3, "TX Configuration Request Reject" }, - { 0x00b4, "TX Configuration Result ACK" }, - { 0x00b5, "TX Configuration Result NACK" }, - { 0x00b6, "TX Configuration Result" }, - { 0x00bc, "DIP Alarm Report ACK" }, - { 0x00bd, "DIP Alarm Report NACK" }, - { 0x00be, "DIP Alarm Report" }, - { 0x00c0, "DIP Alarm Status Request" }, - { 0x00c2, "DIP Alarm Status Response" }, - { 0x00c3, "DIP Alarm Status Reject" }, - { 0x00c4, "DIP Quality Report I ACK" }, - { 0x00c5, "DIP Quality Report I NACK" }, - { 0x00c6, "DIP Quality Report I" }, - { 0x00c8, "DIP Quality Report II ACK" }, - { 0x00c9, "DIP Quality Report II NACK" }, - { 0x00ca, "DIP Quality Report II" }, - { 0x00dc, "DP Configuration Request" }, - { 0x00de, "DP Configuration Request Accept" }, - { 0x00df, "DP Configuration Request Reject" }, - { 0x00e0, "DP Configuration Result ACK" }, - { 0x00e1, "DP Configuration Result NACK" }, - { 0x00e2, "DP Configuration Result" }, - { 0x00e4, "Capabilities HW Info Report ACK" }, - { 0x00e5, "Capabilities HW Info Report NACK" }, - { 0x00e6, "Capabilities HW Info Report" }, - { 0x00e8, "Capabilities Request" }, - { 0x00ea, "Capabilities Request Accept" }, - { 0x00eb, "Capabilities Request Reject" }, - { 0x00ec, "Capabilities Result ACK" }, - { 0x00ed, "Capabilities Result NACK" }, - { 0x00ee, "Capabilities Result" }, - { 0x00f0, "FM Configuration Request" }, - { 0x00f2, "FM Configuration Request Accept" }, - { 0x00f3, "FM Configuration Request Reject" }, - { 0x00f4, "FM Configuration Result ACK" }, - { 0x00f5, "FM Configuration Result NACK" }, - { 0x00f6, "FM Configuration Result" }, - { 0x00f8, "FM Report Request" }, - { 0x00fa, "FM Report Response" }, - { 0x00fb, "FM Report Reject" }, - { 0x00fc, "FM Start Command" }, - { 0x00fe, "FM Start Complete" }, - { 0x00ff, "FM Start Reject" }, - { 0x0100, "FM Stop Command" }, - { 0x0102, "FM Stop Complete" }, - { 0x0103, "FM Stop Reject" }, - { 0x0104, "Negotiation Request ACK" }, - { 0x0105, "Negotiation Request NACK" }, - { 0x0106, "Negotiation Request" }, - { 0x0108, "BTS Initiated Request ACK" }, - { 0x0109, "BTS Initiated Request NACK" }, - { 0x010a, "BTS Initiated Request" }, - { 0x010c, "Radio Channels Release Command" }, - { 0x010e, "Radio Channels Release Complete" }, - { 0x010f, "Radio Channels Release Reject" }, - { 0x0118, "Feature Control Command" }, - { 0x011a, "Feature Control Complete" }, - { 0x011b, "Feature Control Reject" }, - - { 0, NULL } -}; - -/* TS 12.21 Section 9.4: Attributes */ -static const struct value_string om2k_attr_vals[] = { - { 0x00, "Accordance indication" }, - { 0x01, "Alarm Id" }, - { 0x02, "Alarm Data" }, - { 0x03, "Alarm Severity" }, - { 0x04, "Alarm Status" }, - { 0x05, "Alarm Status Type" }, - { 0x06, "BCC" }, - { 0x07, "BS_AG_BKS_RES" }, - { 0x09, "BSIC" }, - { 0x0a, "BA_PA_MFRMS" }, - { 0x0b, "CBCH Indicator" }, - { 0x0c, "CCCH Options" }, - { 0x0d, "Calendar Time" }, - { 0x0f, "Channel Combination" }, - { 0x10, "CON Connection List" }, - { 0x11, "Data End Indication" }, - { 0x12, "DRX_DEV_MAX" }, - { 0x13, "End List Number" }, - { 0x14, "External Condition Map Class 1" }, - { 0x15, "External Condition Map Class 2" }, - { 0x16, "File Relation Indication" }, - { 0x17, "File Revision" }, - { 0x18, "File Segment Data" }, - { 0x19, "File Segment Length" }, - { 0x1a, "File Segment Sequence Number" }, - { 0x1b, "File Size" }, - { 0x1c, "Filling Marker" }, - { 0x1d, "FN Offset" }, - { 0x1e, "Frequency List" }, - { 0x1f, "Frequency Specifier RX" }, - { 0x20, "Frequency Specifier TX" }, - { 0x21, "HSN" }, - { 0x22, "ICM Indicator" }, - { 0x23, "Internal Fault Map Class 1A" }, - { 0x24, "Internal Fault Map Class 1B" }, - { 0x25, "Internal Fault Map Class 2A" }, - { 0x26, "Internal Fault Map Class 2A Extension" }, - { 0x27, "IS Connection List" }, - { 0x28, "List Number" }, - { 0x29, "File Package State Indication" }, - { 0x2a, "Local Access State" }, - { 0x2b, "MAIO" }, - { 0x2c, "MO State" }, - { 0x2d, "Ny1" }, - { 0x2e, "Operational Information" }, - { 0x2f, "Power" }, - { 0x30, "RU Position Data" }, - { 0x31, "Protocol Error" }, - { 0x32, "Reason Code" }, - { 0x33, "Receiver Diversity" }, - { 0x34, "Replacement Unit Map" }, - { 0x35, "Result Code" }, - { 0x36, "RU Revision Data" }, - { 0x38, "T3105" }, - { 0x39, "Test Loop Setting" }, - { 0x3a, "TF Mode" }, - { 0x3b, "TF Compensation Value" }, - { 0x3c, "Time Slot Number" }, - { 0x3d, "TSC" }, - { 0x3e, "RU Logical Id" }, - { 0x3f, "RU Serial Number Data" }, - { 0x40, "BTS Version" }, - { 0x41, "OML IWD Version" }, - { 0x42, "RWL IWD Version" }, - { 0x43, "OML Function Map 1" }, - { 0x44, "OML Function Map 2" }, - { 0x45, "RSL Function Map 1" }, - { 0x46, "RSL Function Map 2" }, - { 0x47, "Extended Range Indicator" }, - { 0x48, "Request Indicators" }, - { 0x49, "DIP Alarm Condition Map" }, - { 0x4a, "ES Incoming" }, - { 0x4b, "ES Outgoing" }, - { 0x4e, "SES Incoming" }, - { 0x4f, "SES Outgoing" }, - { 0x50, "Replacement Unit Map Extension" }, - { 0x52, "UAS Incoming" }, - { 0x53, "UAS Outgoing" }, - { 0x58, "DF Incoming" }, - { 0x5a, "DF Outgoing" }, - { 0x5c, "SF" }, - { 0x60, "S Bits Setting" }, - { 0x61, "CRC-4 Use Option" }, - { 0x62, "T Parameter" }, - { 0x63, "N Parameter" }, - { 0x64, "N1 Parameter" }, - { 0x65, "N3 Parameter" }, - { 0x66, "N4 Parameter" }, - { 0x67, "P Parameter" }, - { 0x68, "Q Parameter" }, - { 0x69, "BI_Q1" }, - { 0x6a, "BI_Q2" }, - { 0x74, "ICM Boundary Parameters" }, - { 0x77, "AFT" }, - { 0x78, "AFT RAI" }, - { 0x79, "Link Supervision Control" }, - { 0x7a, "Link Supervision Filtering Time" }, - { 0x7b, "Call Supervision Time" }, - { 0x7c, "Interval Length UAS Incoming" }, - { 0x7d, "Interval Length UAS Outgoing" }, - { 0x7e, "ICM Channel Rate" }, - { 0x7f, "Attribute Identifier" }, - { 0x80, "FM Frequency List" }, - { 0x81, "FM Frequency Report" }, - { 0x82, "FM Percentile" }, - { 0x83, "FM Clear Indication" }, - { 0x84, "HW Info Signature" }, - { 0x85, "MO Record" }, - { 0x86, "TF Synchronisation Source" }, - { 0x87, "TTA" }, - { 0x88, "End Segment Number" }, - { 0x89, "Segment Number" }, - { 0x8a, "Capabilities Signature" }, - { 0x8c, "File Relation List" }, - { 0x90, "Negotiation Record I" }, - { 0x91, "Negotiation Record II" }, - { 0x92, "Encryption Algorithm" }, - { 0x94, "Interference Rejection Combining" }, - { 0x95, "Dedication Information" }, - { 0x97, "Feature Code" }, - { 0x98, "FS Offset" }, - { 0x99, "ESB Timeslot" }, - { 0x9a, "Master TG Instance" }, - { 0x9b, "Master TX Chain Delay" }, - { 0x9c, "External Condition Class 2 Extension" }, - { 0x9d, "TSs MO State" }, - { 0, NULL } -}; - -const struct value_string om2k_mo_class_short_vals[] = { - { 0x01, "TRXC" }, - { 0x03, "TS" }, - { 0x04, "TF" }, - { 0x05, "IS" }, - { 0x06, "CON" }, - { 0x07, "DP" }, - { 0x0a, "CF" }, - { 0x0b, "TX" }, - { 0x0c, "RX" }, - { 0, NULL } -}; - -const struct value_string om2k_result_strings[] = { - { 0x02, "Wrong state or out of sequence" }, - { 0x03, "File error" }, - { 0x04, "Fault, unspecified" }, - { 0x05, "Tuning fault" }, - { 0x06, "Protocol error" }, - { 0x07, "MO not connected" }, - { 0x08, "Parameter error" }, - { 0x09, "Optional function not supported" }, - { 0x0a, "Local access state LOCALLY DISCONNECTED" }, - { 0, NULL } -}; - -const struct value_string om2k_accordance_strings[] = { - { 0x00, "Data according to request" }, - { 0x01, "Data not according to request" }, - { 0x02, "Inconsistent MO data" }, - { 0x03, "Capability constraint violation" }, - { 0, NULL } -}; - -const struct value_string om2k_mostate_vals[] = { - { 0x00, "RESET" }, - { 0x01, "STARTED" }, - { 0x02, "ENABLED" }, - { 0x03, "DISABLED" }, - { 0, NULL } -}; - -/* entire decoded OM2K message (header + parsed TLV) */ -struct om2k_decoded_msg { - struct abis_om2k_hdr o2h; - uint16_t msg_type; - struct tlv_parsed tp; -}; - -/* resolve the OM2000 Managed Object by BTS + MO Address */ -static struct om2k_mo * -get_om2k_mo(struct gsm_bts *bts, const struct abis_om2k_mo *abis_mo) -{ - struct om2k_mo *mo = NULL; - struct gsm_bts_trx *trx; - - switch (abis_mo->class) { - case OM2K_MO_CLS_CF: - mo = &bts->rbs2000.cf.om2k_mo; - break; - case OM2K_MO_CLS_CON: - mo = &bts->rbs2000.con.om2k_mo; - break; - case OM2K_MO_CLS_IS: - mo = &bts->rbs2000.is.om2k_mo; - break; - case OM2K_MO_CLS_TF: - mo = &bts->rbs2000.tf.om2k_mo; - break; - - case OM2K_MO_CLS_TRXC: - trx = gsm_bts_trx_num(bts, abis_mo->inst); - if (!trx) - return NULL; - mo = &trx->rbs2000.trxc.om2k_mo; - break; - case OM2K_MO_CLS_TX: - trx = gsm_bts_trx_num(bts, abis_mo->inst); - if (!trx) - return NULL; - mo = &trx->rbs2000.tx.om2k_mo; - break; - case OM2K_MO_CLS_RX: - trx = gsm_bts_trx_num(bts, abis_mo->inst); - if (!trx) - return NULL; - mo = &trx->rbs2000.rx.om2k_mo; - break; - case OM2K_MO_CLS_TS: - trx = gsm_bts_trx_num(bts, abis_mo->assoc_so); - if (!trx) - return NULL; - if (abis_mo->inst >= ARRAY_SIZE(trx->ts)) - return NULL; - mo = &trx->ts[abis_mo->inst].rbs2000.om2k_mo; - break; - default: - return NULL; - }; - - return mo; -} - -static struct msgb *om2k_msgb_alloc(void) -{ - return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, - "OM2000"); -} - -static int abis_om2k_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len) -{ - return tlv_parse(tp, &om2k_att_tlvdef, buf, len, 0, 0); -} - -static int abis_om2k_msg_tlv_parse(struct tlv_parsed *tp, struct abis_om2k_hdr *oh) -{ - return abis_om2k_tlv_parse(tp, oh->data, oh->om.length - 6); -} - -/* decode/parse the message */ -static int om2k_decode_msg(struct om2k_decoded_msg *odm, struct msgb *msg) -{ - struct abis_om2k_hdr *o2h = msgb_l2(msg); - odm->msg_type = ntohs(o2h->msg_type); - odm->o2h = *o2h; - return abis_om2k_msg_tlv_parse(&odm->tp, o2h); -} - -static char *om2k_mo_name(const struct abis_om2k_mo *mo) -{ - static char mo_buf[64]; - - memset(mo_buf, 0, sizeof(mo_buf)); - snprintf(mo_buf, sizeof(mo_buf), "%s/%02x/%02x/%02x", - get_value_string(om2k_mo_class_short_vals, mo->class), - mo->bts, mo->assoc_so, mo->inst); - return mo_buf; -} - -/* 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) -{ - struct gsm_bts_trx *trx; - struct gsm_nm_state *nm_state = NULL; - - switch (mo->class) { - case OM2K_MO_CLS_TRXC: - trx = gsm_bts_trx_num(bts, mo->inst); - if (!trx) - return NULL; - nm_state = &trx->mo.nm_state; - break; - case OM2K_MO_CLS_TS: - trx = gsm_bts_trx_num(bts, mo->assoc_so); - if (!trx) - return NULL; - if (mo->inst >= ARRAY_SIZE(trx->ts)) - return NULL; - nm_state = &trx->ts[mo->inst].mo.nm_state; - break; - case OM2K_MO_CLS_TF: - nm_state = &bts->rbs2000.tf.mo.nm_state; - break; - case OM2K_MO_CLS_IS: - nm_state = &bts->rbs2000.is.mo.nm_state; - break; - case OM2K_MO_CLS_CON: - nm_state = &bts->rbs2000.con.mo.nm_state; - break; - case OM2K_MO_CLS_DP: - nm_state = &bts->rbs2000.con.mo.nm_state; - break; - case OM2K_MO_CLS_CF: - nm_state = &bts->mo.nm_state; - break; - case OM2K_MO_CLS_TX: - trx = gsm_bts_trx_num(bts, mo->inst); - if (!trx) - return NULL; - /* FIXME */ - break; - case OM2K_MO_CLS_RX: - trx = gsm_bts_trx_num(bts, mo->inst); - if (!trx) - return NULL; - /* FIXME */ - break; - } - - return nm_state; -} - -static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo) -{ - struct gsm_bts_trx *trx; - - switch (mo->class) { - case OM2K_MO_CLS_TX: - case OM2K_MO_CLS_RX: - case OM2K_MO_CLS_TRXC: - return gsm_bts_trx_num(bts, mo->inst); - case OM2K_MO_CLS_TS: - trx = gsm_bts_trx_num(bts, mo->assoc_so); - if (!trx) - return NULL; - if (mo->inst >= ARRAY_SIZE(trx->ts)) - return NULL; - return &trx->ts[mo->inst]; - case OM2K_MO_CLS_TF: - case OM2K_MO_CLS_IS: - case OM2K_MO_CLS_CON: - case OM2K_MO_CLS_DP: - case OM2K_MO_CLS_CF: - return bts; - } - - return NULL; -} - -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; - /* NOTICE: 12.21 Availability state values != OM2000 */ - new_state.availability = mo_state; - - memset(&nsd, 0, sizeof(nsd)); - - nsd.bts = bts; - nsd.obj = mo2obj(bts, mo); - nsd.old_state = nm_state; - nsd.new_state = &new_state; - nsd.om2k_mo = mo; - - osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd); - - nm_state->availability = new_state.availability; -} - -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 gsm_nm_state new_state; - - if (!nm_state) - return; - - new_state = *nm_state; - switch (op_state) { - case 1: - new_state.operational = NM_OPSTATE_ENABLED; - break; - case 0: - new_state.operational = NM_OPSTATE_DISABLED; - break; - default: - new_state.operational = NM_OPSTATE_NULL; - break; - } - - nm_state->operational = new_state.operational; -} - -static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg) -{ - struct abis_om2k_hdr *o2h; - struct gsm_bts_trx *trx; - - msg->l2h = msg->data; - o2h = (struct abis_om2k_hdr *) msg->l2h; - - /* Compute the length in the OML header */ - o2h->om.length = 6 + msgb_l2len(msg)-sizeof(*o2h); - - switch (o2h->mo.class) { - case OM2K_MO_CLS_TRXC: - case OM2K_MO_CLS_TX: - case OM2K_MO_CLS_RX: - /* Route through per-TRX OML Link to the appropriate TRX */ - trx = gsm_bts_trx_by_nr(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)); - return -ENODEV; - } - msg->dst = trx->oml_link; - break; - case OM2K_MO_CLS_TS: - /* Route through per-TRX OML Link to the appropriate TRX */ - trx = gsm_bts_trx_by_nr(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)); - return -ENODEV; - } - msg->dst = trx->oml_link; - break; - default: - /* Route through the IXU/DXU OML Link */ - msg->dst = bts->oml_link; - break; - } - - 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) -{ - o2h->om.mdisc = ABIS_OM_MDISC_FOM; - o2h->om.placement = ABIS_OM_PLACEMENT_ONLY; - o2h->om.sequence = 0; - /* We fill o2h->om.length later during om2k_sendmsg() */ - o2h->msg_type = htons(msg_type); - memcpy(&o2h->mo, mo, sizeof(o2h->mo)); -} - -static int abis_om2k_cal_time_resp(struct gsm_bts *bts) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - time_t tm_t; - 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); - - tm_t = time(NULL); - tm = localtime(&tm_t); - - msgb_put_u8(msg, OM2K_DEI_CAL_TIME); - msgb_put_u8(msg, tm->tm_year % 100); - msgb_put_u8(msg, tm->tm_mon + 1); - msgb_put_u8(msg, tm->tm_mday); - msgb_put_u8(msg, tm->tm_hour); - msgb_put_u8(msg, tm->tm_min); - msgb_put_u8(msg, tm->tm_sec); - - return abis_om2k_sendmsg(bts, msg); -} - -static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo, - uint8_t msg_type) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - - 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)); - - return abis_om2k_sendmsg(bts, msg); -} - -int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) -{ - return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_RESET_CMD); -} - -int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) -{ - return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_START_REQ); -} - -int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) -{ - return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_STATUS_REQ); -} - -int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) -{ - return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CONNECT_CMD); -} - -int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) -{ - return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISCONNECT_CMD); -} - -int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) -{ - return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_TEST_REQ); -} - -int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) -{ - return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_ENABLE_REQ); -} - -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) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - - o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, mo, OM2K_MSGT_OP_INFO); - - msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational); - - DEBUGP(DNM, "Tx MO=%s %s\n", 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 */ - update_op_state(bts, mo, operational); - - return abis_om2k_sendmsg(bts, msg); -} - -int abis_om2k_tx_cap_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) -{ - return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CAPA_REQ); -} - -static void om2k_fill_is_conn_grp(struct om2k_is_conn_grp *grp, uint16_t icp1, - uint16_t icp2, uint8_t cont_idx) -{ - grp->icp1 = htons(icp1); - grp->icp2 = htons(icp2); - grp->cont_idx = cont_idx; -} - -int abis_om2k_tx_is_conf_req(struct gsm_bts *bts) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - struct is_conn_group *grp; - unsigned int num_grps = 0, i = 0; - struct om2k_is_conn_grp *cg; - - /* count number of groups in linked list */ - llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list) - num_grps++; - - if (!num_grps) - return -EINVAL; - - /* allocate buffer for oml group array */ - cg = talloc_zero_array(bts, struct om2k_is_conn_grp, num_grps); - - /* fill array with data from linked list */ - llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list) - 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); - - 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); - - talloc_free(cg); - - DEBUGP(DNM, "Tx MO=%s %s\n", - 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); -} - -int abis_om2k_tx_con_conf_req(struct gsm_bts *bts) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - struct con_group *grp; - unsigned int num_grps = 0; - - /* count number of groups in linked list */ - llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list) - num_grps++; - - if (!num_grps) - return -EINVAL; - - /* first build the value part of the OM2K_DEI_CON_CONN_LIST DEI */ - msgb_put_u8(msg, num_grps); - llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list) { - struct con_path *cp; - unsigned int num_paths = 0; - llist_for_each_entry(cp, &grp->paths, list) - num_paths++; - msgb_put_u8(msg, num_paths); - llist_for_each_entry(cp, &grp->paths, list) { - struct om2k_con_path *om2k_cp; - om2k_cp = (struct om2k_con_path *) msgb_put(msg, sizeof(*om2k_cp)); - om2k_cp->ccp = htons(cp->ccp); - om2k_cp->ci = cp->ci; - om2k_cp->tag = cp->tag; - om2k_cp->tei = cp->tei; - } - } - msgb_push_u8(msg, msgb_length(msg)); - msgb_push_u8(msg, OM2K_DEI_CON_CONN_LIST); - - /* pre-pend the list number DEIs */ - msgb_tv_push(msg, OM2K_DEI_END_LIST_NR, 1); - msgb_tv_push(msg, OM2K_DEI_LIST_NR, 1); - - /* 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); - - DEBUGP(DNM, "Tx MO=%s %s\n", - 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); -} - -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; - mo->bts = 0; - mo->inst = trx->nr; - mo->assoc_so = 255; -} - -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; - mo->inst = ts->nr; - mo->assoc_so = ts->trx->nr; -} - -/* Configure a Receiver MO */ -int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - struct abis_om2k_mo mo; - - om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_RX); - - o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, &mo, OM2K_MSGT_RX_CONF_REQ); - - msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn); - msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */ - - return abis_om2k_sendmsg(trx->bts, msg); -} - -/* Configure a Transmitter MO */ -int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - struct abis_om2k_mo mo; - - om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_TX); - - o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TX_CONF_REQ); - - msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn); - msgb_tv_put(msg, OM2K_DEI_POWER, trx->nominal_power-trx->max_power_red); - msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, 0); /* Filling enabled */ - msgb_tv_put(msg, OM2K_DEI_BCC, trx->bts->bsic & 0x7); - /* Dedication Information is optional */ - - return abis_om2k_sendmsg(trx->bts, msg); -} - -enum abis_om2k_tf_mode { - OM2K_TF_MODE_MASTER = 0x00, - OM2K_TF_MODE_STANDALONE = 0x01, - OM2K_TF_MODE_SLAVE = 0x02, - OM2K_TF_MODE_UNDEFINED = 0xff, -}; - -static const uint8_t fs_offset_undef[5] = { 0xff, 0xff, 0xff, 0xff, 0xff }; - -int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts) -{ - struct msgb *msg = om2k_msgb_alloc(); - 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); - - 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); - - DEBUGP(DNM, "Tx MO=%s %s\n", - 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); -} - -static uint8_t pchan2comb(enum gsm_phys_chan_config pchan) -{ - switch (pchan) { - case GSM_PCHAN_CCCH: - return 4; - case GSM_PCHAN_CCCH_SDCCH4: - return 5; - case GSM_PCHAN_SDCCH8_SACCH8C: - return 3; - case GSM_PCHAN_TCH_F: - case GSM_PCHAN_TCH_H: - case GSM_PCHAN_PDCH: - case GSM_PCHAN_TCH_F_PDCH: - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - return 8; - default: - return 0; - } -} - -static uint8_t ts2comb(struct gsm_bts_trx_ts *ts) -{ - switch (ts->pchan) { - case 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)); - /* 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: */ - return 0; - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - return pchan2comb(GSM_PCHAN_TCH_F); - default: - return pchan2comb(ts->pchan); - } -} - -static int put_freq_list(uint8_t *buf, uint16_t arfcn) -{ - buf[0] = 0x00; /* TX/RX address */ - buf[1] = (arfcn >> 8); - buf[2] = (arfcn & 0xff); - - return 3; -} - -/* Compute a frequency list in OM2000 fomrmat */ -static int om2k_gen_freq_list(uint8_t *list, struct gsm_bts_trx_ts *ts) -{ - uint8_t *cur = list; - int len; - - if (ts->hopping.enabled) { - unsigned int i; - for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { - if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) - cur += put_freq_list(cur, i); - } - } else - cur += put_freq_list(cur, ts->trx->arfcn); - - len = cur - list; - - return len; -} - -const uint8_t icm_bound_params[] = { 0x02, 0x06, 0x0c, 0x16, 0x06 }; - -int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - struct abis_om2k_mo mo; - uint8_t freq_list[64*3]; /* BA max size: 64 ARFCN */ - int freq_list_len; - - om2k_ts_to_mo(&mo, ts); - - memset(freq_list, 0, sizeof(freq_list)); - freq_list_len = om2k_gen_freq_list(freq_list, ts); - if (freq_list_len < 0) - return freq_list_len; - - o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TS_CONF_REQ); - - msgb_tv_put(msg, OM2K_DEI_COMBINATION, ts2comb(ts)); - msgb_tv_put(msg, OM2K_DEI_TS_NR, ts->nr); - msgb_tlv_put(msg, OM2K_DEI_FREQ_LIST, freq_list_len, freq_list); - 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_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0); - msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */ - /* Optional: Interference Rejection Combining */ - msgb_tv_put(msg, OM2K_DEI_INTERF_REJ_COMB, 0x00); - switch (ts->pchan) { - case GSM_PCHAN_CCCH: - msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06); - msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01); - msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05); - /* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */ - msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01); - break; - case GSM_PCHAN_CCCH_SDCCH4: - msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); - msgb_tv_put(msg, OM2K_DEI_NY1, 35); - msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06); - msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0); - msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); - msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01); - msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); - msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05); - /* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */ - msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01); - msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, - sizeof(icm_bound_params), icm_bound_params); - break; - case GSM_PCHAN_SDCCH8_SACCH8C: - msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); - msgb_tv_put(msg, OM2K_DEI_NY1, 35); - msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0); - msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); - /* Disable RF RESOURCE INDICATION on idle channels */ - msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); - msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, - sizeof(icm_bound_params), icm_bound_params); - break; - default: - msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); - msgb_tv_put(msg, OM2K_DEI_NY1, 35); - msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); - /* Disable RF RESOURCE INDICATION on idle channels */ - msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); - msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, - sizeof(icm_bound_params), icm_bound_params); - msgb_tv_put(msg, OM2K_DEI_TTA, 10); /* Timer for Time Alignment */ - if (ts->pchan == GSM_PCHAN_TCH_H) - msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 1); /* TCH/H */ - else - msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 0); /* TCH/F */ - msgb_tv_put(msg, OM2K_DEI_LSC, 1); /* enabled */ - msgb_tv_put(msg, OM2K_DEI_LSC_FILT_TIME, 10); /* units of 100ms */ - msgb_tv_put(msg, OM2K_DEI_CALL_SUPV_TIME, 8); - msgb_tv_put(msg, OM2K_DEI_ENCR_ALG, 0x00); - /* Not sure what those below mean */ - msgb_tv_put(msg, 0x9e, 0x00); - msgb_tv_put(msg, 0x9f, 0x37); - msgb_tv_put(msg, 0xa0, 0x01); - break; - } - - DEBUGP(DNM, "Tx MO=%s %s\n", - om2k_mo_name(&mo), - get_value_string(om2k_msgcode_vals, OM2K_MSGT_TS_CONF_REQ)); - - return abis_om2k_sendmsg(ts->trx->bts, msg); -} - - -/*********************************************************************** - * OM2000 Managed Object (MO) FSM - ***********************************************************************/ - -#define S(x) (1 << (x)) - -enum om2k_event_name { - OM2K_MO_EVT_START, - OM2K_MO_EVT_RX_CONN_COMPL, - OM2K_MO_EVT_RX_RESET_COMPL, - OM2K_MO_EVT_RX_START_REQ_ACCEPT, - OM2K_MO_EVT_RX_START_RES, - OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, - OM2K_MO_EVT_RX_CFG_RES, - OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, - OM2K_MO_EVT_RX_ENA_RES, - OM2K_MO_EVT_RX_OPINFO_ACC, -}; - -static const struct value_string om2k_event_names[] = { - { OM2K_MO_EVT_START, "START" }, - { 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" }, - { OM2K_MO_EVT_RX_START_RES, "RX-START-RESULT" }, - { OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, "RX-CFG-REQ-ACCEPT" }, - { OM2K_MO_EVT_RX_CFG_RES, "RX-CFG-RESULT" }, - { OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, "RX-ENABLE-REQ-ACCEPT" }, - { OM2K_MO_EVT_RX_ENA_RES, "RX-ENABLE-RESULT" }, - { OM2K_MO_EVT_RX_OPINFO_ACC, "RX-OPINFO-ACCEPT" }, - { 0, NULL } -}; - -enum om2k_mo_fsm_state { - OM2K_ST_INIT, - OM2K_ST_WAIT_CONN_COMPL, - OM2K_ST_WAIT_RES_COMPL, - OM2K_ST_WAIT_START_ACCEPT, - OM2K_ST_WAIT_START_RES, - OM2K_ST_WAIT_CFG_ACCEPT, - OM2K_ST_WAIT_CFG_RES, - OM2K_ST_WAIT_ENABLE_ACCEPT, - OM2K_ST_WAIT_ENABLE_RES, - OM2K_ST_WAIT_OPINFO_ACCEPT, - OM2K_ST_DONE, - OM2K_ST_ERROR, -}; - -struct om2k_mo_fsm_priv { - struct gsm_bts_trx *trx; - struct om2k_mo *mo; - uint8_t ts_nr; -}; - -static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_mo_fsm_priv *omfp = fi->priv; - - OSMO_ASSERT(event == OM2K_MO_EVT_START); - - 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); - 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); - 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); - abis_om2k_tx_connect_cmd(omfp->trx->bts, &omfp->mo->addr); - break; - } -} - -static void om2k_mo_st_wait_conn_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_mo_fsm_priv *omfp = fi->priv; - - switch (omfp->mo->addr.class) { -#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); - 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); - abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr); - break; - } -} - -static void om2k_mo_st_wait_res_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_mo_fsm_priv *omfp = fi->priv; - - 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); -} - -static void om2k_mo_st_wait_start_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_decoded_msg *omd = data; - - switch (omd->msg_type) { - case OM2K_MSGT_START_REQ_ACK: - 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); - break; - } -} - -static void om2k_mo_st_wait_start_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_mo_fsm_priv *omfp = fi->priv; - struct gsm_bts_trx_ts *ts; - - switch (omfp->mo->addr.class) { - 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); - abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1); - return; - case OM2K_MO_CLS_DP: - /* Transition directoy to WAIT_ENABLE_ACCEPT */ - 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); - abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr); - return; -#endif - } - - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_ACCEPT, - OM2K_TIMEOUT, 0); - switch (omfp->mo->addr.class) { - case OM2K_MO_CLS_TF: - abis_om2k_tx_tf_conf_req(omfp->trx->bts); - break; - case OM2K_MO_CLS_IS: - abis_om2k_tx_is_conf_req(omfp->trx->bts); - break; - case OM2K_MO_CLS_CON: - abis_om2k_tx_con_conf_req(omfp->trx->bts); - break; - case OM2K_MO_CLS_TX: - abis_om2k_tx_tx_conf_req(omfp->trx); - break; - case OM2K_MO_CLS_RX: - abis_om2k_tx_rx_conf_req(omfp->trx); - break; - case OM2K_MO_CLS_TS: - ts = mo2obj(omfp->trx->bts, &omfp->mo->addr); - abis_om2k_tx_ts_conf_req(ts); - break; - } -} - -static void om2k_mo_st_wait_cfg_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_mo_fsm_priv *omfp = fi->priv; - uint32_t timeout = OM2K_TIMEOUT; - - if (omfp->mo->addr.class == OM2K_MO_CLS_TF) - timeout = 600; - - osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_RES, timeout, 0); -} - -static void om2k_mo_st_wait_cfg_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_mo_fsm_priv *omfp = fi->priv; - struct om2k_decoded_msg *omd = data; - uint8_t accordance; - - if (!TLVP_PRESENT(&omd->tp, OM2K_DEI_ACCORDANCE_IND)) { - osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); - return; - } - accordance = *TLVP_VAL(&omd->tp, OM2K_DEI_ACCORDANCE_IND); - - if (accordance != 0) { - /* accordance not OK */ - osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); - return; - } - - 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); -} - -static void om2k_mo_st_wait_enable_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_mo_fsm_priv *omfp = fi->priv; - struct om2k_decoded_msg *omd = data; - - switch (omd->msg_type) { - case OM2K_MSGT_ENABLE_REQ_REJ: - 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) - 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); - } -} - -static void om2k_mo_st_wait_enable_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_mo_fsm_priv *omfp = fi->priv; - //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); - abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1); -} - -static void om2k_mo_st_wait_opinfo_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - osmo_fsm_inst_state_chg(fi, OM2K_ST_DONE, 0, 0); -} - -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); -} - -static void om2k_mo_s_error_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_ERROR, NULL); -} - -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_ERROR) | - S(OM2K_ST_WAIT_CONN_COMPL) | - S(OM2K_ST_WAIT_START_ACCEPT) | - S(OM2K_ST_WAIT_RES_COMPL), - .action = om2k_mo_st_init, - }, - [OM2K_ST_WAIT_CONN_COMPL] = { - .name = "WAIT-CONN-COMPL", - .in_event_mask = S(OM2K_MO_EVT_RX_CONN_COMPL), - .out_state_mask = S(OM2K_ST_DONE) | - S(OM2K_ST_ERROR) | - S(OM2K_ST_WAIT_START_ACCEPT) | - S(OM2K_ST_WAIT_RES_COMPL), - .action = om2k_mo_st_wait_conn_compl, - }, - [OM2K_ST_WAIT_RES_COMPL] = { - .name = "WAIT-RES-COMPL", - .in_event_mask = S(OM2K_MO_EVT_RX_RESET_COMPL), - .out_state_mask = S(OM2K_ST_DONE) | - S(OM2K_ST_ERROR) | - S(OM2K_ST_WAIT_START_ACCEPT), - .action = om2k_mo_st_wait_res_compl, - }, - [OM2K_ST_WAIT_START_ACCEPT] = { - .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_ERROR) | - S(OM2K_ST_WAIT_START_RES), - .action =om2k_mo_st_wait_start_accept, - }, - [OM2K_ST_WAIT_START_RES] = { - .name = "WAIT-START-RES", - .in_event_mask = S(OM2K_MO_EVT_RX_START_RES), - .out_state_mask = S(OM2K_ST_DONE) | - S(OM2K_ST_ERROR) | - S(OM2K_ST_WAIT_CFG_ACCEPT) | - S(OM2K_ST_WAIT_OPINFO_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_ERROR) | - S(OM2K_ST_WAIT_CFG_RES), - .action = om2k_mo_st_wait_cfg_accept, - }, - [OM2K_ST_WAIT_CFG_RES] = { - .name = "WAIT-CFG-RES", - .in_event_mask = S(OM2K_MO_EVT_RX_CFG_RES), - .out_state_mask = S(OM2K_ST_DONE) | - S(OM2K_ST_ERROR) | - S(OM2K_ST_WAIT_ENABLE_ACCEPT), - .action = om2k_mo_st_wait_cfg_res, - }, - [OM2K_ST_WAIT_ENABLE_ACCEPT] = { - .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_ERROR) | - S(OM2K_ST_WAIT_ENABLE_RES), - .action = om2k_mo_st_wait_enable_accept, - }, - [OM2K_ST_WAIT_ENABLE_RES] = { - .name = "WAIT-ENABLE-RES", - .in_event_mask = S(OM2K_MO_EVT_RX_ENA_RES), - .out_state_mask = S(OM2K_ST_DONE) | - S(OM2K_ST_ERROR) | - S(OM2K_ST_WAIT_OPINFO_ACCEPT), - .action = om2k_mo_st_wait_enable_res, - }, - [OM2K_ST_WAIT_OPINFO_ACCEPT] = { - .name = "WAIT-OPINFO-ACCEPT", - .in_event_mask = S(OM2K_MO_EVT_RX_OPINFO_ACC), - .out_state_mask = S(OM2K_ST_DONE) | - S(OM2K_ST_ERROR), - .action = om2k_mo_st_wait_opinfo_accept, - }, - [OM2K_ST_DONE] = { - .name = "DONE", - .in_event_mask = 0, - .out_state_mask = 0, - .onenter = om2k_mo_s_done_onenter, - }, - [OM2K_ST_ERROR] = { - .name = "ERROR", - .in_event_mask = 0, - .out_state_mask = 0, - .onenter = om2k_mo_s_error_onenter, - }, - -}; - -static int om2k_mo_timer_cb(struct osmo_fsm_inst *fi) -{ - osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); - return 0; -} - -static struct osmo_fsm om2k_mo_fsm = { - .name = "OM2000-MO", - .states = om2k_is_states, - .num_states = ARRAY_SIZE(om2k_is_states), - .log_subsys = DNM, - .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) -{ - struct osmo_fsm_inst *fi; - struct om2k_mo_fsm_priv *omfp; - char idbuf[64]; - - snprintf(idbuf, sizeof(idbuf), "%s-%s", parent->id, - om2k_mo_name(&mo->addr)); - - fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent, - term_event, idbuf); - if (!fi) - return NULL; - - mo->fsm = fi; - omfp = talloc_zero(fi, struct om2k_mo_fsm_priv); - omfp->mo = mo; - omfp->trx = trx; - fi->priv = omfp; - - osmo_fsm_inst_dispatch(fi, OM2K_MO_EVT_START, NULL); - - return fi; -} - -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); - break; - - case OM2K_MSGT_RESET_COMPL: - case OM2K_MSGT_RESET_REJ: - 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); - break; - - case OM2K_MSGT_START_RES: - osmo_fsm_inst_dispatch(mo->fsm, - OM2K_MO_EVT_RX_START_RES, odm); - break; - - case OM2K_MSGT_CON_CONF_REQ_ACK: - case OM2K_MSGT_IS_CONF_REQ_ACK: - case OM2K_MSGT_RX_CONF_REQ_ACK: - 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); - break; - - case OM2K_MSGT_CON_CONF_RES: - case OM2K_MSGT_IS_CONF_RES: - case OM2K_MSGT_RX_CONF_RES: - 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); - 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); - break; - case OM2K_MSGT_ENABLE_RES: - 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); - break; - default: - return -1; - } - - return 0; -} - -/*********************************************************************** - * OM2000 TRX Finite State Machine, initializes TRXC and all siblings - ***********************************************************************/ - -enum om2k_trx_event { - OM2K_TRX_EVT_START, - OM2K_TRX_EVT_TRXC_DONE, - OM2K_TRX_EVT_TX_DONE, - OM2K_TRX_EVT_RX_DONE, - OM2K_TRX_EVT_TS_DONE, - OM2K_TRX_EVT_STOP, -}; - -static struct value_string om2k_trx_events[] = { - { OM2K_TRX_EVT_START, "START" }, - { OM2K_TRX_EVT_TRXC_DONE, "TRXC-DONE" }, - { OM2K_TRX_EVT_TX_DONE, "TX-DONE" }, - { OM2K_TRX_EVT_RX_DONE, "RX-DONE" }, - { OM2K_TRX_EVT_TS_DONE, "TS-DONE" }, - { OM2K_TRX_EVT_STOP, "STOP" }, - { 0, NULL } -}; - -enum om2k_trx_state { - OM2K_TRX_S_INIT, - OM2K_TRX_S_WAIT_TRXC, - OM2K_TRX_S_WAIT_TX, - OM2K_TRX_S_WAIT_RX, - OM2K_TRX_S_WAIT_TS, - OM2K_TRX_S_DONE, - OM2K_TRX_S_ERROR -}; - -struct om2k_trx_fsm_priv { - struct gsm_bts_trx *trx; - uint8_t next_ts_nr; -}; - -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); -} - -static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - 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); -} - -static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - 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); -} - -static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_trx_fsm_priv *otfp = fi->priv; - 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); - otfp->next_ts_nr = 0; - ts = &otfp->trx->ts[otfp->next_ts_nr++]; - om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx, - &ts->rbs2000.om2k_mo); -} - -static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_trx_fsm_priv *otfp = fi->priv; - struct gsm_bts_trx_ts *ts; - - if (otfp->next_ts_nr < 8) { - /* iterate to the next timeslot */ - ts = &otfp->trx->ts[otfp->next_ts_nr++]; - om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx, - &ts->rbs2000.om2k_mo); - } else { - /* only after all 8 TS */ - osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_DONE, 0, 0); - } -} - -static void om2k_trx_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct om2k_trx_fsm_priv *otfp = fi->priv; - gsm_bts_trx_set_system_infos(otfp->trx); - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); -} - -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), - .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), - .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), - .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), - .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_DONE), - .name = "WAIT-TS", - .action = om2k_trx_s_wait_ts, - }, - [OM2K_TRX_S_DONE] = { - .name = "DONE", - .onenter = om2k_trx_s_done_onenter, - }, - [OM2K_TRX_S_ERROR] = { - .name = "ERROR", - }, -}; - -static int om2k_trx_timer_cb(struct osmo_fsm_inst *fi) -{ - osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_ERROR, 0, 0); - return 0; -} - -static struct osmo_fsm om2k_trx_fsm = { - .name = "OM2000-TRX", - .states = om2k_trx_states, - .num_states = ARRAY_SIZE(om2k_trx_states), - .log_subsys = DNM, - .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) -{ - struct osmo_fsm_inst *fi; - struct om2k_trx_fsm_priv *otfp; - char idbuf[32]; - - 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); - if (!fi) - return NULL; - - otfp = talloc_zero(fi, struct om2k_trx_fsm_priv); - otfp->trx = trx; - fi->priv = otfp; - - osmo_fsm_inst_dispatch(fi, OM2K_TRX_EVT_START, NULL); - - return fi; -} - - -/*********************************************************************** - * OM2000 BTS Finite State Machine, initializes CF and all siblings - ***********************************************************************/ - -enum om2k_bts_event { - OM2K_BTS_EVT_START, - OM2K_BTS_EVT_CF_DONE, - OM2K_BTS_EVT_IS_DONE, - OM2K_BTS_EVT_CON_DONE, - OM2K_BTS_EVT_TF_DONE, - OM2K_BTS_EVT_TRX_DONE, - OM2K_BTS_EVT_STOP, -}; - -static const struct value_string om2k_bts_events[] = { - { OM2K_BTS_EVT_START, "START" }, - { OM2K_BTS_EVT_CF_DONE, "CF-DONE" }, - { OM2K_BTS_EVT_IS_DONE, "IS-DONE" }, - { OM2K_BTS_EVT_CON_DONE, "CON-DONE" }, - { OM2K_BTS_EVT_TF_DONE, "TF-DONE" }, - { OM2K_BTS_EVT_TRX_DONE, "TRX-DONE" }, - { OM2K_BTS_EVT_STOP, "STOP" }, - { 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_TRX, - OM2K_BTS_S_DONE, - OM2K_BTS_S_ERROR, -}; - -struct om2k_bts_fsm_priv { - struct gsm_bts *bts; - uint8_t next_trx_nr; -}; - -static void om2k_bts_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_bts_fsm_priv *obfp = fi->priv; - 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); -} - -static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_bts_fsm_priv *obfp = fi->priv; - struct gsm_bts *bts = obfp->bts; - - 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); -} - -static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_bts_fsm_priv *obfp = fi->priv; - struct gsm_bts *bts = obfp->bts; - - 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); -} - -static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_bts_fsm_priv *obfp = fi->priv; - struct gsm_bts *bts = obfp->bts; - - 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); -} - -static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_bts_fsm_priv *obfp = fi->priv; - struct gsm_bts_trx *trx; - - OSMO_ASSERT(event == OM2K_BTS_EVT_IS_DONE); - - 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); -} - -static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct om2k_bts_fsm_priv *obfp = fi->priv; - - OSMO_ASSERT(event == OM2K_BTS_EVT_TRX_DONE); - - 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); - } else { - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_DONE, 0, 0); - } -} - -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 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), - .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), - .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), - .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), - .name = "WAIT-CON", - .action = om2k_bts_s_wait_con, - }, - [OM2K_BTS_S_WAIT_IS] = { - .in_event_mask = S(OM2K_BTS_EVT_IS_DONE), - .out_state_mask = S(OM2K_BTS_S_ERROR) | - S(OM2K_BTS_S_WAIT_TRX), - .name = "WAIT-IS", - .action = om2k_bts_s_wait_is, - }, - [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), - .name = "WAIT-TRX", - .action = om2k_bts_s_wait_trx, - }, - [OM2K_BTS_S_DONE] = { - .name = "DONE", - .onenter = om2k_bts_s_done_onenter, - }, - [OM2K_BTS_S_ERROR] = { - .name = "ERROR", - }, -}; - -static int om2k_bts_timer_cb(struct osmo_fsm_inst *fi) -{ - osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_ERROR, 0, 0); - return 0; -} - -static struct osmo_fsm om2k_bts_fsm = { - .name = "OM2000-BTS", - .states = om2k_bts_states, - .num_states = ARRAY_SIZE(om2k_bts_states), - .log_subsys = DNM, - .event_names = om2k_bts_events, - .timer_cb = om2k_bts_timer_cb, -}; - -struct osmo_fsm_inst * -om2k_bts_fsm_start(struct gsm_bts *bts) -{ - struct osmo_fsm_inst *fi; - struct om2k_bts_fsm_priv *obfp; - char idbuf[16]; - - snprintf(idbuf, sizeof(idbuf), "%u", bts->nr); - - 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; -} - - -/*********************************************************************** - * OM2000 Negotiation - ***********************************************************************/ - -static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2k_mo *mo, - uint8_t *data, unsigned int len) -{ - struct msgb *msg = om2k_msgb_alloc(); - struct abis_om2k_hdr *o2k; - - o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); - fill_om2k_hdr(o2k, mo, OM2K_MSGT_NEGOT_REQ_ACK); - - msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data); - - DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), - get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK)); - - return abis_om2k_sendmsg(bts, msg); -} - -struct iwd_version { - uint8_t gen_char[3+1]; - uint8_t rev_char[3+1]; -}; - -struct iwd_type { - uint8_t num_vers; - struct iwd_version v[8]; -}; - -static int om2k_rx_negot_req(struct msgb *msg) -{ - struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst; - struct abis_om2k_hdr *o2h = msgb_l2(msg); - struct iwd_type iwd_types[16]; - uint8_t num_iwd_types = o2h->data[2]; - uint8_t *cur = o2h->data+3; - unsigned int i, v; - - uint8_t out_buf[1024]; - uint8_t *out_cur = out_buf+1; - uint8_t out_num_types = 0; - - memset(iwd_types, 0, sizeof(iwd_types)); - - /* Parse the RBS-supported IWD versions into iwd_types array */ - for (i = 0; i < num_iwd_types; i++) { - uint8_t num_versions = *cur++; - uint8_t iwd_type = *cur++; - - iwd_types[iwd_type].num_vers = num_versions; - - for (v = 0; v < num_versions; v++) { - struct iwd_version *iwd_v = &iwd_types[iwd_type].v[v]; - - memcpy(iwd_v->gen_char, cur, 3); - cur += 3; - memcpy(iwd_v->rev_char, cur, 3); - cur += 3; - - DEBUGP(DNM, "\tIWD Type %u Gen %s Rev %s\n", iwd_type, - iwd_v->gen_char, iwd_v->rev_char); - } - } - - /* Select the last version for each IWD type */ - for (i = 0; i < ARRAY_SIZE(iwd_types); i++) { - struct iwd_type *type = &iwd_types[i]; - struct iwd_version *last_v; - - if (type->num_vers == 0) - continue; - - out_num_types++; - - last_v = &type->v[type->num_vers-1]; - - *out_cur++ = i; - memcpy(out_cur, last_v->gen_char, 3); - out_cur += 3; - memcpy(out_cur, last_v->rev_char, 3); - out_cur += 3; - } - - out_buf[0] = out_num_types; - - return abis_om2k_tx_negot_req_ack(sign_link->trx->bts, &o2h->mo, out_buf, out_cur - out_buf); -} - - -/*********************************************************************** - * OM2000 Receive Message Handler - ***********************************************************************/ - -static int om2k_rx_nack(struct msgb *msg) -{ - struct abis_om2k_hdr *o2h = msgb_l2(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), - 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)); - - 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))); - LOGPC(DNM, LOGL_ERROR, "\n"); - - return 0; -} - -static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm) -{ - uint8_t mo_state; - - if (!TLVP_PRESENT(&odm->tp, OM2K_DEI_MO_STATE)) - 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), - get_value_string(om2k_msgcode_vals, odm->msg_type), - get_value_string(om2k_mostate_vals, mo_state)); - - /* Throw error message in case we see an enable rsponse that does - * 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)); - } - - update_mo_state(bts, &odm->o2h.mo, mo_state); - - return 0; -} - -/* Display fault report bits (helper function of display_fault_maps()) */ -static bool display_fault_bits(const uint8_t *vect, uint16_t len, - uint8_t dei, const struct abis_om2k_mo *mo) -{ - uint16_t i; - int k; - bool faults_present = false; - int first = 1; - char string[255]; - - /* Check if errors are present at all */ - for (i = 0; i < len; i++) - if (vect[i]) - faults_present = true; - if (!faults_present) - return false; - - sprintf(string, "Fault Report: %s (", - get_value_string(om2k_attr_vals, dei)); - - for (i = 0; i < len; i++) { - for (k = 0; k < 8; k++) { - if ((vect[i] >> k) & 1) { - if (!first) - sprintf(string + strlen(string), ","); - sprintf(string + strlen(string), "%d", k + i*8); - first = 0; - } - } - } - - sprintf(string + strlen(string), ")\n"); - DEBUGP(DNM, "Rx MO=%s %s", om2k_mo_name(mo), string); - - return true; -} - -/* Display fault report maps */ -static void display_fault_maps(const uint8_t *src, unsigned int src_len, - const struct abis_om2k_mo *mo) -{ - uint8_t tag; - uint16_t tag_len; - const uint8_t *val; - int src_pos = 0; - int rc; - int tlv_count = 0; - uint16_t msg_code; - bool faults_present = false; - - /* Chop off header */ - src+=4; - src_len-=4; - - /* Check message type */ - msg_code = (*src & 0xff) << 8; - src++; - src_len--; - msg_code |= (*src & 0xff); - 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)); - return; - } - - /* Chop off mo-interface */ - src += 4; - src_len -= 4; - - /* Iterate over each TLV element */ - while (1) { - - /* 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)); - return; - } - - /* Parse TLV field */ - 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)); - return; - } - - switch (tag) { - case OM2K_DEI_INT_FAULT_MAP_1A: - case OM2K_DEI_INT_FAULT_MAP_1B: - case OM2K_DEI_INT_FAULT_MAP_2A: - case OM2K_DEI_EXT_COND_MAP_1: - case OM2K_DEI_EXT_COND_MAP_2: - case OM2K_DEI_REPL_UNIT_MAP: - case OM2K_DEI_INT_FAULT_MAP_2A_EXT: - case OM2K_DEI_EXT_COND_MAP_2_EXT: - case OM2K_DEI_REPL_UNIT_MAP_EXT: - faults_present |= display_fault_bits(val, tag_len, - tag, mo); - break; - } - - /* Stop when no further TLV elements can be expected */ - if (src_len - src_pos < 2) - break; - - tlv_count++; - } - - if (!faults_present) { - DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n", - om2k_mo_name(mo)); - } -} - -int abis_om2k_rcvmsg(struct msgb *msg) -{ - struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst; - struct gsm_bts *bts = sign_link->trx->bts; - struct abis_om2k_hdr *o2h = msgb_l2(msg); - struct abis_om_hdr *oh = &o2h->om; - uint16_t msg_type = ntohs(o2h->msg_type); - struct om2k_decoded_msg odm; - struct om2k_mo *mo; - int rc = 0; - - /* Various consistency checks */ - if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { - 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); - 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); - 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))); - - om2k_decode_msg(&odm, msg); - - process_mo_state(bts, &odm); - - switch (msg_type) { - case OM2K_MSGT_CAL_TIME_REQ: - rc = abis_om2k_cal_time_resp(bts); - break; - 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; - case OM2K_MSGT_NEGOT_REQ: - rc = om2k_rx_negot_req(msg); - break; - case OM2K_MSGT_START_RES: - /* common processing here */ - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_RES_ACK); - /* below we dispatch into MO */ - break; - case OM2K_MSGT_IS_CONF_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_IS_CONF_RES_ACK); - break; - case OM2K_MSGT_CON_CONF_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CON_CONF_RES_ACK); - break; - case OM2K_MSGT_TX_CONF_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TX_CONF_RES_ACK); - break; - case OM2K_MSGT_RX_CONF_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RX_CONF_RES_ACK); - break; - case OM2K_MSGT_TS_CONF_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TS_CONF_RES_ACK); - break; - case OM2K_MSGT_TF_CONF_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TF_CONF_RES_ACK); - break; - case OM2K_MSGT_ENABLE_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_ENABLE_RES_ACK); - break; - case OM2K_MSGT_DISABLE_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_DISABLE_RES_ACK); - break; - case OM2K_MSGT_TEST_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TEST_RES_ACK); - break; - case OM2K_MSGT_CAPA_RES: - rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CAPA_RES_ACK); - break; - /* ERrors */ - case OM2K_MSGT_START_REQ_REJ: - case OM2K_MSGT_CONNECT_REJ: - case OM2K_MSGT_OP_INFO_REJ: - case OM2K_MSGT_DISCONNECT_REJ: - case OM2K_MSGT_TEST_REQ_REJ: - case OM2K_MSGT_CON_CONF_REQ_REJ: - case OM2K_MSGT_IS_CONF_REQ_REJ: - case OM2K_MSGT_TX_CONF_REQ_REJ: - case OM2K_MSGT_RX_CONF_REQ_REJ: - case OM2K_MSGT_TS_CONF_REQ_REJ: - case OM2K_MSGT_TF_CONF_REQ_REJ: - case OM2K_MSGT_ENABLE_REQ_REJ: - case OM2K_MSGT_ALARM_STATUS_REQ_REJ: - case OM2K_MSGT_DISABLE_REQ_REJ: - rc = om2k_rx_nack(msg); - break; - } - - /* Resolve the MO for this message */ - 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; - } - 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; - } - - /* Dispatch message to that MO */ - om2k_mo_fsm_recvmsg(bts, mo, &odm); - - 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) -{ - mo->addr.class = class; - mo->addr.bts = bts_nr; - mo->addr.assoc_so = assoc_so; - mo->addr.inst = inst; -} - -/* initialize the OM2K_MO members of gsm_bts_trx and its timeslots */ -void abis_om2k_trx_init(struct gsm_bts_trx *trx) -{ - struct gsm_bts *bts = trx->bts; - 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); - - 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); - gsm_ts_check_init(ts); - } -} - -/* initialize the OM2K_MO members of gsm_bts */ -void abis_om2k_bts_init(struct gsm_bts *bts) -{ - 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); -} - -static __attribute__((constructor)) void abis_om2k_init(void) -{ - osmo_fsm_register(&om2k_mo_fsm); - osmo_fsm_register(&om2k_bts_fsm); - osmo_fsm_register(&om2k_trx_fsm); -} diff --git a/src/libbsc/abis_om2000_vty.c b/src/libbsc/abis_om2000_vty.c deleted file mode 100644 index faf39c106..000000000 --- a/src/libbsc/abis_om2000_vty.c +++ /dev/null @@ -1,604 +0,0 @@ -/* VTY interface for A-bis OM2000 */ - -/* (C) 2010-2018 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -static struct cmd_node om2k_node = { - OM2K_NODE, - "%s(om2k)# ", - 1, -}; - -static struct cmd_node om2k_con_group_node = { - OM2K_CON_GROUP_NODE, - "%s(om2k-con-group)# ", - 1, -}; - -struct con_group; - -struct oml_node_state { - struct gsm_bts *bts; - struct abis_om2k_mo mo; - 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|ts|tf|is|con|dp|cf|tx|rx)" -#define OM2K_OBJCLASS_VTY_HELP "TRX Controller\n" \ - "Timeslot\n" \ - "Timing Function\n" \ - "Interface Switch\n" \ - "Abis Concentrator\n" \ - "Digital Path\n" \ - "Central Function\n" \ - "Transmitter\n" \ - "Receiver\n" - -DEFUN(om2k_class_inst, om2k_class_inst_cmd, - "bts <0-255> om2000 class " OM2K_OBJCLASS_VTY - " <0-255> <0-255> <0-255>", - "BTS related commands\n" "BTS Number\n" - "Manipulate the OM2000 managed objects\n" - "Object Class\n" OM2K_OBJCLASS_VTY_HELP - "BTS Number\n" "Associated SO Instance\n" "Instance Number\n") -{ - struct gsm_bts *bts; - struct oml_node_state *oms; - 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->type != GSM_BTS_TYPE_RBS2000) { - vty_out(vty, "%% BTS %d not an Ericsson RBS%s", - bts_nr, VTY_NEWLINE); - return CMD_WARNING; - } - - oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); - if (!oms) - return CMD_WARNING; - - oms->bts = bts; - oms->mo.class = get_string_value(om2k_mo_class_short_vals, argv[1]); - oms->mo.bts = atoi(argv[2]); - oms->mo.assoc_so = atoi(argv[3]); - oms->mo.inst = atoi(argv[4]); - - vty->index = oms; - vty->node = OM2K_NODE; - - return CMD_SUCCESS; - -} - -DEFUN(om2k_classnum_inst, om2k_classnum_inst_cmd, - "bts <0-255> om2000 class <0-255> <0-255> <0-255> <0-255>", - "BTS related commands\n" "BTS Number\n" - "Manipulate the OML managed objects\n" - "Object Class\n" "Object Class\n" - "BTS Number\n" "Associated SO Instance\n" "Instance Number\n") -{ - struct gsm_bts *bts; - struct oml_node_state *oms; - 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; - } - - oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); - if (!oms) - return CMD_WARNING; - - oms->bts = bts; - oms->mo.class = atoi(argv[1]); - oms->mo.bts = atoi(argv[2]); - oms->mo.assoc_so = atoi(argv[3]); - oms->mo.inst = atoi(argv[4]); - - vty->index = oms; - vty->node = OM2K_NODE; - - return CMD_SUCCESS; -} - -DEFUN(om2k_reset, om2k_reset_cmd, - "reset-command", - "Reset the MO\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_reset_cmd(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -DEFUN(om2k_start, om2k_start_cmd, - "start-request", - "Start the MO\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_start_req(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -DEFUN(om2k_status, om2k_status_cmd, - "status-request", - "Get the MO Status\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_status_req(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -DEFUN(om2k_connect, om2k_connect_cmd, - "connect-command", - "Connect the MO\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_connect_cmd(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -DEFUN(om2k_disconnect, om2k_disconnect_cmd, - "disconnect-command", - "Disconnect the MO\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_disconnect_cmd(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -DEFUN(om2k_enable, om2k_enable_cmd, - "enable-request", - "Enable the MO\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_enable_req(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -DEFUN(om2k_disable, om2k_disable_cmd, - "disable-request", - "Disable the MO\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_disable_req(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -DEFUN(om2k_op_info, om2k_op_info_cmd, - "operational-info <0-1>", - "Set operational information\n" - "Set operational info to 0 or 1\n") -{ - struct oml_node_state *oms = vty->index; - int oper = atoi(argv[0]); - - abis_om2k_tx_op_info(oms->bts, &oms->mo, oper); - return CMD_SUCCESS; -} - -DEFUN(om2k_test, om2k_test_cmd, - "test-request", - "Test the MO\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_test_req(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -DEFUN(om2k_cap_req, om2k_cap_req_cmd, - "capabilities-request", - "Request MO capabilities\n") -{ - struct oml_node_state *oms = vty->index; - - abis_om2k_tx_cap_req(oms->bts, &oms->mo); - return CMD_SUCCESS; -} - -static struct con_group *con_group_find_or_create(struct gsm_bts *bts, uint8_t cg) -{ - struct con_group *ent; - - llist_for_each_entry(ent, &bts->rbs2000.con.conn_groups, list) { - if (ent->cg == cg) - return ent; - } - - ent = talloc_zero(bts, struct con_group); - ent->bts = bts; - ent->cg = cg; - INIT_LLIST_HEAD(&ent->paths); - llist_add_tail(&ent->list, &bts->rbs2000.con.conn_groups); - - return ent; -} - -static int con_group_del(struct gsm_bts *bts, uint8_t cg_id) -{ - struct con_group *cg, *cg2; - - llist_for_each_entry_safe(cg, cg2, &bts->rbs2000.con.conn_groups, list) { - if (cg->cg == cg_id) { - llist_del(&cg->list); - talloc_free(cg); - return 0; - }; - } - return -ENOENT; -} - -static void con_group_add_path(struct con_group *cg, uint16_t ccp, - uint8_t ci, uint8_t tag, uint8_t tei) -{ - struct con_path *cp = talloc_zero(cg, struct con_path); - - cp->ccp = ccp; - cp->ci = ci; - cp->tag = tag; - cp->tei = tei; - llist_add(&cp->list, &cg->paths); -} - -static int con_group_del_path(struct con_group *cg, uint16_t ccp, - uint8_t ci, uint8_t tag, uint8_t tei) -{ - struct con_path *cp, *cp2; - llist_for_each_entry_safe(cp, cp2, &cg->paths, list) { - if (cp->ccp == ccp && cp->ci == ci && cp->tag == tag && - cp->tei == tei) { - llist_del(&cp->list); - talloc_free(cp); - return 0; - } - } - return -ENOENT; -} - -DEFUN(cfg_om2k_con_group, cfg_om2k_con_group_cmd, - "con-connection-group <1-31>", - "Configure a CON (Concentrator) Connection Group\n" - "CON Connection Group Number\n") -{ - struct gsm_bts *bts = vty->index; - struct con_group *cg; - uint8_t cgid = atoi(argv[0]); - - if (bts->type != GSM_BTS_TYPE_RBS2000) { - vty_out(vty, "%% CON MO only exists in RBS2000%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - cg = con_group_find_or_create(bts, cgid); - if (!cg) { - vty_out(vty, "%% Cannot create CON Group%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - vty->node = OM2K_CON_GROUP_NODE; - vty->index = cg; - - return CMD_SUCCESS; -} - -DEFUN(del_om2k_con_group, del_om2k_con_group_cmd, - "del-connection-group <1-31>", - "Delete a CON (Concentrator) Connection Group\n" - "CON Connection Group Number\n") -{ - struct gsm_bts *bts = vty->index; - int rc; - uint8_t cgid = atoi(argv[0]); - - if (bts->type != GSM_BTS_TYPE_RBS2000) { - vty_out(vty, "%% CON MO only exists in RBS2000%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - rc = con_group_del(bts, cgid); - if (rc != 0) { - vty_out(vty, "%% Cannot delete CON Group%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -#define CON_PATH_HELP "CON Path (In/Out)\n" \ - "Add CON Path to Concentration Group\n" \ - "Delete CON Path from Concentration Group\n" \ - "CON Conection Point\n" \ - "Contiguity Index\n" \ - -DEFUN(cfg_om2k_con_path_dec, cfg_om2k_con_path_dec_cmd, - "con-path (add|del) <0-2047> <0-255> deconcentrated <0-63>", - CON_PATH_HELP "De-concentrated in/outlet\n" "TEI Value\n") -{ - struct con_group *cg = vty->index; - uint16_t ccp = atoi(argv[1]); - uint8_t ci = atoi(argv[2]); - uint8_t tei = atoi(argv[3]); - - if (!strcmp(argv[0], "add")) - con_group_add_path(cg, ccp, ci, 0, tei); - else { - if (con_group_del_path(cg, ccp, ci, 0, tei) < 0) { - vty_out(vty, "%% No matching CON Path%s", - VTY_NEWLINE); - return CMD_WARNING; - } - } - - return CMD_SUCCESS; -} - -DEFUN(cfg_om2k_con_path_conc, cfg_om2k_con_path_conc_cmd, - "con-path (add|del) <0-2047> <0-255> concentrated <1-16>", - CON_PATH_HELP "Concentrated in/outlet\n" "Tag Number\n") -{ - struct con_group *cg = vty->index; - uint16_t ccp = atoi(argv[1]); - uint8_t ci = atoi(argv[2]); - uint8_t tag = atoi(argv[3]); - - if (!strcmp(argv[0], "add")) - con_group_add_path(cg, ccp, ci, tag, 0xff); - else { - if (con_group_del_path(cg, ccp, ci, tag, 0xff) < 0) { - vty_out(vty, "%% No matching CON list entry%s", - VTY_NEWLINE); - return CMD_WARNING; - } - } - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd, - "abis-lower-transport (single-timeslot|super-channel)", - "Configure thee Abis Lower Transport\n" - "Single Timeslot (classic Abis)\n" - "SuperChannel (Packet Abis)\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], "super-channel")) - bts->rbs2000.use_superchannel = 1; - else - bts->rbs2000.use_superchannel = 0; - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd, - "is-connection-list (add|del) <0-2047> <0-2047> <0-255>", - "Interface Switch Connection List\n" - "Add to IS list\n" "Delete from IS list\n" - "ICP1\n" "ICP2\n" "Contiguity Index\n") -{ - struct gsm_bts *bts = vty->index; - uint16_t icp1 = atoi(argv[1]); - uint16_t icp2 = atoi(argv[2]); - uint8_t ci = atoi(argv[3]); - struct is_conn_group *grp, *grp2; - - if (bts->type != GSM_BTS_TYPE_RBS2000) { - vty_out(vty, "%% IS MO only exists in RBS2000%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - if (!strcmp(argv[0], "add")) { - grp = talloc_zero(bts, struct is_conn_group); - grp->icp1 = icp1; - grp->icp2 = icp2; - grp->ci = ci; - llist_add_tail(&grp->list, &bts->rbs2000.is.conn_groups); - } else { - llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.is.conn_groups, list) { - if (grp->icp1 == icp1 && grp->icp2 == icp2 - && grp->ci == ci) { - llist_del(&grp->list); - talloc_free(grp); - return CMD_SUCCESS; - } - } - vty_out(vty, "%% No matching IS Conn Group found!%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - - -DEFUN(om2k_conf_req, om2k_conf_req_cmd, - "configuration-request", - "Send the configuration request for current MO\n") -{ - struct oml_node_state *oms = vty->index; - struct gsm_bts *bts = oms->bts; - struct gsm_bts_trx *trx = NULL; - struct gsm_bts_trx_ts *ts = NULL; - - switch (oms->mo.class) { - case OM2K_MO_CLS_IS: - abis_om2k_tx_is_conf_req(bts); - break; - case OM2K_MO_CLS_TS: - trx = gsm_bts_trx_by_nr(bts, oms->mo.assoc_so); - if (!trx) { - vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr, - oms->mo.assoc_so, VTY_NEWLINE); - return CMD_WARNING; - } - if (oms->mo.inst >= ARRAY_SIZE(trx->ts)) { - vty_out(vty, "%% Timeslot %u out of range%s", - oms->mo.inst, VTY_NEWLINE); - return CMD_WARNING; - } - ts = &trx->ts[oms->mo.inst]; - abis_om2k_tx_ts_conf_req(ts); - break; - case OM2K_MO_CLS_RX: - case OM2K_MO_CLS_TX: - case OM2K_MO_CLS_TRXC: - trx = gsm_bts_trx_by_nr(bts, oms->mo.inst); - if (!trx) { - vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr, - oms->mo.inst, VTY_NEWLINE); - return CMD_WARNING; - } - switch (oms->mo.class) { - case OM2K_MO_CLS_RX: - abis_om2k_tx_rx_conf_req(trx); - break; - case OM2K_MO_CLS_TX: - abis_om2k_tx_tx_conf_req(trx); - break; - default: - break; - } - break; - case OM2K_MO_CLS_TF: - abis_om2k_tx_tf_conf_req(bts); - break; - default: - vty_out(vty, "%% Don't know how to configure MO%s", - VTY_NEWLINE); - } - - return CMD_SUCCESS; -} - -static void dump_con_group(struct vty *vty, struct con_group *cg) -{ - struct con_path *cp; - - llist_for_each_entry(cp, &cg->paths, list) { - vty_out(vty, " con-path add %u %u ", cp->ccp, cp->ci); - if (cp->tei == 0xff) { - vty_out(vty, "concentrated %u%s", cp->tag, - VTY_NEWLINE); - } else { - vty_out(vty, "deconcentrated %u%s", cp->tei, - VTY_NEWLINE); - } - } -} - -void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts) -{ - struct is_conn_group *igrp; - struct con_group *cgrp; - - llist_for_each_entry(igrp, &bts->rbs2000.is.conn_groups, list) - vty_out(vty, " is-connection-list add %u %u %u%s", - igrp->icp1, igrp->icp2, igrp->ci, VTY_NEWLINE); - - llist_for_each_entry(cgrp, &bts->rbs2000.con.conn_groups, list) { - vty_out(vty, " con-connection-group %u%s", cgrp->cg, - VTY_NEWLINE); - dump_con_group(vty, cgrp); - } - if (bts->rbs2000.use_superchannel) - vty_out(vty, " abis-lower-transport super-channel%s", - VTY_NEWLINE); -} - -int abis_om2k_vty_init(void) -{ - install_element(ENABLE_NODE, &om2k_class_inst_cmd); - install_element(ENABLE_NODE, &om2k_classnum_inst_cmd); - install_node(&om2k_node, dummy_config_write); - - install_element(OM2K_NODE, &om2k_reset_cmd); - install_element(OM2K_NODE, &om2k_start_cmd); - install_element(OM2K_NODE, &om2k_status_cmd); - install_element(OM2K_NODE, &om2k_connect_cmd); - install_element(OM2K_NODE, &om2k_disconnect_cmd); - install_element(OM2K_NODE, &om2k_enable_cmd); - install_element(OM2K_NODE, &om2k_disable_cmd); - install_element(OM2K_NODE, &om2k_op_info_cmd); - install_element(OM2K_NODE, &om2k_test_cmd); - install_element(OM2K_NODE, &om2k_cap_req_cmd); - install_element(OM2K_NODE, &om2k_conf_req_cmd); - - install_node(&om2k_con_group_node, dummy_config_write); - install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_dec_cmd); - install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_conc_cmd); - - install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd); - install_element(BTS_NODE, &cfg_bts_alt_mode_cmd); - install_element(BTS_NODE, &cfg_om2k_con_group_cmd); - install_element(BTS_NODE, &del_om2k_con_group_cmd); - - return 0; -} diff --git a/src/libbsc/abis_rsl.c b/src/libbsc/abis_rsl.c deleted file mode 100644 index 48cc39f87..000000000 --- a/src/libbsc/abis_rsl.c +++ /dev/null @@ -1,3070 +0,0 @@ -/* GSM Radio Signalling Link messages on the A-bis interface - * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ - -/* (C) 2008-2010 by Harald Welte - * (C) 2012 by Holger Hans Peter Freyther - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define RSL_ALLOC_SIZE 1024 -#define RSL_ALLOC_HEADROOM 128 - -#define RTP_PT_GSM_FULL 3 -#define RTP_PT_GSM_HALF 96 -#define RTP_PT_GSM_EFR 97 -#define RTP_PT_AMR 98 - -enum sacch_deact { - SACCH_NONE, - SACCH_DEACTIVATE, -}; - -static int rsl_send_imm_assignment(struct gsm_lchan *lchan); -static void error_timeout_cb(void *data); -static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts); -static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc); -static void dyn_ts_switchover_complete(struct gsm_lchan *lchan); - -static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan, - struct gsm_meas_rep *resp) -{ - struct lchan_signal_data sig; - sig.lchan = lchan; - sig.mr = resp; - osmo_signal_dispatch(SS_LCHAN, sig_no, &sig); -} - -static void do_lchan_free(struct gsm_lchan *lchan) -{ - /* We start the error timer to make the channel available again */ - if (lchan->state == LCHAN_S_REL_ERR) { - osmo_timer_setup(&lchan->error_timer, error_timeout_cb, lchan); - osmo_timer_schedule(&lchan->error_timer, - lchan->ts->trx->bts->network->T3111 + 2, 0); - } else { - rsl_lchan_set_state(lchan, LCHAN_S_NONE); - } - lchan_free(lchan); -} - -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) { - case GSM48_CMODE_SPEECH_AMR: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_H]); - break; - case GSM48_CMODE_SPEECH_V1: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_HR]); - break; - default: - break; - } - } else if (lchan->type == GSM_LCHAN_TCH_F) { - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_AMR: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_F]); - break; - case GSM48_CMODE_SPEECH_V1: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_FR]); - break; - case GSM48_CMODE_SPEECH_EFR: - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_EFR]); - break; - default: - break; - } - } -} - -static uint8_t mdisc_by_msgtype(uint8_t msg_type) -{ - /* mask off the transparent bit ? */ - msg_type &= 0xfe; - - if ((msg_type & 0xf0) == 0x00) - return ABIS_RSL_MDISC_RLL; - if ((msg_type & 0xf0) == 0x10) { - if (msg_type >= 0x19 && msg_type <= 0x22) - return ABIS_RSL_MDISC_TRX; - else - return ABIS_RSL_MDISC_COM_CHAN; - } - if ((msg_type & 0xe0) == 0x20) - return ABIS_RSL_MDISC_DED_CHAN; - - return ABIS_RSL_MDISC_LOC; -} - -static inline void init_dchan_hdr(struct abis_rsl_dchan_hdr *dh, - uint8_t msg_type) -{ - dh->c.msg_discr = mdisc_by_msgtype(msg_type); - dh->c.msg_type = msg_type; - dh->ie_chan = RSL_IE_CHAN_NR; -} - -/* call rsl_lchan_lookup and set the log context */ -static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, - const char *log_name) -{ - int rc; - struct gsm_lchan *lchan = rsl_lchan_lookup(trx, chan_nr, &rc); - - if (!lchan) { - LOGP(DRSL, LOGL_ERROR, "%sunknown chan_nr=0x%02x\n", - log_name, chan_nr); - return NULL; - } - - if (rc < 0) - LOGP(DRSL, LOGL_ERROR, "%s %smismatching chan_nr=0x%02x\n", - gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr); - - return lchan; -} - -/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */ -uint64_t str_to_imsi(const char *imsi_str) -{ - uint64_t ret; - - ret = strtoull(imsi_str, NULL, 10); - - return ret; -} - -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); - - if (len < GSM_MACBLOCK_LEN) - memset(out+len, 0x2b, GSM_MACBLOCK_LEN - len); -} - -/* Chapter 9.3.7: Encryption Information */ -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; -} - -static void print_rsl_cause(int lvl, const uint8_t *cause_v, uint8_t cause_len) -{ - int i; - - LOGPC(DRSL, lvl, "CAUSE=0x%02x(%s) ", - cause_v[0], rsl_err_name(cause_v[0])); - for (i = 1; i < cause_len-1; i++) - LOGPC(DRSL, lvl, "%02x ", cause_v[i]); -} - -static void lchan_act_tmr_cb(void *data) -{ - struct gsm_lchan *lchan = data; - - rsl_lchan_mark_broken(lchan, "activation timeout"); - lchan_free(lchan); -} - -static void lchan_deact_tmr_cb(void *data) -{ - struct gsm_lchan *lchan = data; - - rsl_lchan_mark_broken(lchan, "de-activation timeout"); - lchan_free(lchan); -} - - -/* 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) -{ - struct abis_rsl_dchan_hdr *dh; - const struct gsm_bts *bts = trx->bts; - struct msgb *msg = rsl_msgb_alloc(); - uint8_t type = osmo_sitype2rsl(si_type); - - if (bts->c0 != trx) - LOGP(DRR, LOGL_ERROR, "Attempting to set BCCH SI%s on wrong BTS%u/TRX%u\n", - get_value_string(osmo_sitype_strs, si_type), bts->nr, trx->nr); - - dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof*dh); - init_dchan_hdr(dh, RSL_MT_BCCH_INFO); - dh->chan_nr = RSL_CHAN_BCCH; - - if (trx->bts->type == GSM_BTS_TYPE_RBS2000 - && type == RSL_SYSTEM_INFO_13) { - /* Ericsson proprietary encoding of SI13 */ - msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, RSL_ERIC_SYSTEM_INFO_13); - if (data) - msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data); - msgb_tv_put(msg, RSL_IE_ERIC_BCCH_MAPPING, 0x00); - } else { - /* Normal encoding */ - msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); - if (data) - msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data); - } - - msg->dst = trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, - const uint8_t *data, int len) -{ - struct abis_rsl_common_hdr *ch; - struct msgb *msg = rsl_msgb_alloc(); - - ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); - ch->msg_discr = ABIS_RSL_MDISC_TRX; - ch->msg_type = RSL_MT_SACCH_FILL; - - msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); - if (data) - msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); - - msg->dst = trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -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); - - dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); - init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY); - dh->chan_nr = chan_nr; - - msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); - if (data) - msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); - - msg->dst = lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -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); - - db = abs(db); - if (db > 30) - return -EINVAL; - - msg = rsl_msgb_alloc(); - - lchan->bs_power = db/2; - if (fpc) - lchan->bs_power |= 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); - - msg->dst = lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm) -{ - struct abis_rsl_dchan_hdr *dh; - struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); - int ctl_lvl; - - ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm); - if (ctl_lvl < 0) - return ctl_lvl; - - msg = rsl_msgb_alloc(); - - lchan->ms_power = ctl_lvl; - - if (fpc) - lchan->ms_power |= 0x20; - - dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); - init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL); - dh->chan_nr = chan_nr; - - msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); - - msg->dst = lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm, - struct gsm_lchan *lchan) -{ - 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; - if (lchan->ts->trx->bts->dtxd) - 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"); - - 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; - break; - case GSM_LCHAN_TCH_H: - 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)); - return -EINVAL; - } - - switch (lchan->tch_mode) { - case GSM48_CMODE_SIGN: - cm->chan_rate = 0; - break; - case GSM48_CMODE_SPEECH_V1: - cm->chan_rate = RSL_CMOD_SP_GSM1; - break; - case GSM48_CMODE_SPEECH_EFR: - cm->chan_rate = RSL_CMOD_SP_GSM2; - break; - case GSM48_CMODE_SPEECH_AMR: - cm->chan_rate = RSL_CMOD_SP_GSM3; - break; - 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; - } - break; - default: - LOGP(DRSL, LOGL_ERROR, - "unsupported lchan->tch_mode %u\n", - lchan->tch_mode); - return -EINVAL; - } - - return 0; -} - -static void mr_config_for_bts(struct gsm_lchan *lchan, struct msgb *msg) -{ - if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) - msgb_tlv_put(msg, RSL_IE_MR_CONFIG, lchan->mr_bts_lv[0], - lchan->mr_bts_lv + 1); -} - -static enum gsm_phys_chan_config pchan_for_lchant(enum gsm_chan_t type) -{ - switch (type) { - case GSM_LCHAN_TCH_F: - return GSM_PCHAN_TCH_F; - case GSM_LCHAN_TCH_H: - return GSM_PCHAN_TCH_H; - case GSM_LCHAN_NONE: - case GSM_LCHAN_PDTCH: - /* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only - * used in osmo-bts. Maybe set PDTCH and drop the NONE case - * here. */ - return GSM_PCHAN_PDCH; - default: - return GSM_PCHAN_UNKNOWN; - } -} - -/*! Tx simplified channel activation message for non-standard PDCH type. */ -static int rsl_chan_activate_lchan_as_pdch(struct gsm_lchan *lchan) -{ - struct msgb *msg; - struct abis_rsl_dchan_hdr *dh; - - /* This might be called after release of the second lchan of a TCH/H, - * but PDCH activation must always happen on the first lchan. Make sure - * the calling code passes the correct lchan. */ - OSMO_ASSERT(lchan == lchan->ts->lchan); - - rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); - - 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_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_PDCH); - - msgb_tv_put(msg, RSL_IE_ACT_TYPE, RSL_ACT_OSMO_PDCH); - - if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_RBS2000 && - lchan->ts->trx->bts->rbs2000.use_superchannel) { - const uint8_t eric_pgsl_tmr[] = { 30, 1 }; - msgb_tv_fixed_put(msg, RSL_IE_ERIC_PGSL_TIMERS, - sizeof(eric_pgsl_tmr), eric_pgsl_tmr); - } - - msg->dst = lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -/* Chapter 8.4.1 */ -int rsl_chan_activate_lchan(struct gsm_lchan *lchan, uint8_t act_type, - uint8_t ho_ref) -{ - struct abis_rsl_dchan_hdr *dh; - struct msgb *msg; - int rc; - uint8_t *len; - uint8_t ta; - - struct rsl_ie_chan_mode cm; - struct gsm48_chan_desc cd; - - /* If a TCH_F/PDCH TS is in PDCH mode, deactivate PDCH first. */ - if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH - && (lchan->ts->flags & TS_F_PDCH_ACTIVE)) { - /* store activation type and handover reference */ - lchan->dyn.act_type = act_type; - lchan->dyn.ho_ref = ho_ref; - return rsl_ipacc_pdch_activate(lchan->ts, 0); - } - - /* - * If necessary, release PDCH on dynamic TS. Note that sending a - * release here is only necessary when in PDCH mode; for TCH types, an - * RSL RF Chan Release is initiated by the BTS when a voice call ends, - * so when we reach this, it will already be released. If a dyn TS is - * in PDCH mode, it is still active and we need to initiate a release - * from the BSC side here. - * - * If pchan_is != pchan_want, the PDCH has already been taken down and - * the switchover now needs to enable the TCH lchan. - * - * To switch a dyn TS between TCH/H and TCH/F, it is sufficient to send - * a chan activ with the new lchan type, because it will already be - * released. - */ - if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && lchan->ts->dyn.pchan_is == lchan->ts->dyn.pchan_want) { - enum gsm_phys_chan_config pchan_want; - pchan_want = pchan_for_lchant(lchan->type); - if (lchan->ts->dyn.pchan_is != pchan_want) { - /* - * Make sure to record on lchan[0] so that we'll find - * it after the PDCH release. - */ - struct gsm_lchan *lchan0 = lchan->ts->lchan; - lchan0->dyn.act_type = act_type, - lchan0->dyn.ho_ref = ho_ref; - lchan0->dyn.rqd_ref = lchan->rqd_ref; - lchan0->dyn.rqd_ta = lchan->rqd_ta; - lchan->rqd_ref = NULL; - lchan->rqd_ta = 0; - DEBUGP(DRSL, "%s saved rqd_ref=%p ta=%u\n", - gsm_lchan_name(lchan0), lchan0->rqd_ref, - lchan0->rqd_ta); - return dyn_ts_switchover_start(lchan->ts, pchan_want); - } - } - - DEBUGP(DRSL, "%s Tx RSL Channel Activate with act_type=%s\n", - gsm_ts_and_pchan_name(lchan->ts), - rsl_act_type_name(act_type)); - - if (act_type == RSL_ACT_OSMO_PDCH) { - if (lchan->ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) { - LOGP(DRSL, LOGL_ERROR, - "%s PDCH channel activation only allowed on %s\n", - gsm_ts_and_pchan_name(lchan->ts), - gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH)); - return -EINVAL; - } - return rsl_chan_activate_lchan_as_pdch(lchan); - } - - if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && lchan->ts->dyn.pchan_want == GSM_PCHAN_PDCH) { - LOGP(DRSL, LOGL_ERROR, - "%s Expected PDCH activation kind\n", - gsm_ts_and_pchan_name(lchan->ts)); - return -EINVAL; - } - - rc = channel_mode_from_lchan(&cm, lchan); - if (rc < 0) { - LOGP(DRSL, LOGL_ERROR, - "%s Cannot find channel mode from lchan type\n", - gsm_ts_and_pchan_name(lchan->ts)); - return rc; - } - - rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); - - ta = lchan->rqd_ta; - - /* BS11 requires TA shifted by 2 bits */ - if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11) - ta <<= 2; - - memset(&cd, 0, sizeof(cd)); - gsm48_lchan2chan_desc(&cd, lchan); - - msg = rsl_msgb_alloc(); - dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); - init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV); - - if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) - dh->chan_nr = gsm_lchan_as_pchan2chan_nr( - lchan, lchan->ts->dyn.pchan_want); - else - dh->chan_nr = gsm_lchan2chan_nr(lchan); - - msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type); - msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), - (uint8_t *) &cm); - - /* - * The Channel Identification is needed for Phase1 phones - * and it contains the GSM48 Channel Description and the - * Mobile Allocation. The GSM 08.58 asks for the Mobile - * Allocation to have a length of zero. We are using the - * msgb_l3len to calculate the length of both messages. - */ - msgb_v_put(msg, RSL_IE_CHAN_IDENT); - len = msgb_put(msg, 1); - msgb_tv_fixed_put(msg, GSM48_IE_CHANDESC_2, sizeof(cd), (const uint8_t *) &cd); - - if (lchan->ts->hopping.enabled) - msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len, - lchan->ts->hopping.ma_data); - else - msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL); - - /* update the calculated size */ - msg->l3h = len + 1; - *len = msgb_l3len(msg); - - if (lchan->encr.alg_id > RSL_ENC_ALG_A5(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); - } - - switch (act_type) { - case RSL_ACT_INTER_ASYNC: - case RSL_ACT_INTER_SYNC: - msgb_tv_put(msg, RSL_IE_HANDO_REF, ho_ref); - break; - default: - 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); - mr_config_for_bts(lchan, msg); - - msg->dst = lchan->ts->trx->rsl_link; - - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_TOTAL]); - - rc = abis_rsl_sendmsg(msg); - if (!rc) - rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); - return rc; -} - -/* Chapter 8.4.9: Modify channel mode on BTS side */ -int rsl_chan_mode_modify_req(struct gsm_lchan *lchan) -{ - struct abis_rsl_dchan_hdr *dh; - struct msgb *msg; - int rc; - - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); - struct rsl_ie_chan_mode cm; - - rc = channel_mode_from_lchan(&cm, lchan); - if (rc < 0) - return rc; - - msg = rsl_msgb_alloc(); - dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); - init_dchan_hdr(dh, RSL_MT_MODE_MODIFY_REQ); - dh->chan_nr = chan_nr; - - msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), - (uint8_t *) &cm); - - if (lchan->encr.alg_id > RSL_ENC_ALG_A5(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); - } - - mr_config_for_bts(lchan, msg); - - msg->dst = lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -/* Chapter 8.4.6: Send the encryption command with given L3 info */ -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; - - /* First push the L3 IE tag and length */ - msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); - - /* then the link identifier (SAPI0, main sign link) */ - msgb_tv_push(msg, RSL_IE_LINK_IDENT, 0); - - /* then encryption information */ - rc = build_encr_info(encr_info, lchan); - if (rc <= 0) - return rc; - msgb_tlv_push(msg, RSL_IE_ENCR_INFO, rc, encr_info); - - /* and finally the DCHAN header */ - dh = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dh)); - init_dchan_hdr(dh, RSL_MT_ENCR_CMD); - dh->chan_nr = chan_nr; - - msg->dst = lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -/* Chapter 8.4.5 / 4.6: Deactivate the SACCH after 04.08 RR CHAN RELEASE */ -int rsl_deact_sacch(struct gsm_lchan *lchan) -{ - struct abis_rsl_dchan_hdr *dh; - struct msgb *msg = rsl_msgb_alloc(); - - 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); - - msg->lchan = lchan; - msg->dst = lchan->ts->trx->rsl_link; - - DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan)); - - return abis_rsl_sendmsg(msg); -} - -static bool dyn_ts_should_switch_to_pdch(struct gsm_bts_trx_ts *ts) -{ - int ss; - - if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) - return false; - - if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) - return false; - - /* Already in PDCH mode? */ - if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) - return false; - - /* See if all lchans are released. */ - for (ss = 0; ss < ts_subslots(ts); ss++) { - struct gsm_lchan *lc = &ts->lchan[ss]; - if (lc->state != LCHAN_S_NONE) { - DEBUGP(DRSL, "%s lchan %u still in use" - " (type=%s,state=%s)\n", - gsm_ts_and_pchan_name(ts), lc->nr, - gsm_lchant_name(lc->type), - gsm_lchans_name(lc->state)); - /* An lchan is still used. */ - return false; - } - } - - /* All channels are released, go to PDCH mode. */ - DEBUGP(DRSL, "%s back to PDCH\n", - gsm_ts_and_pchan_name(ts)); - return true; -} - -static void error_timeout_cb(void *data) -{ - struct gsm_lchan *lchan = data; - if (lchan->state != LCHAN_S_REL_ERR) { - LOGP(DRSL, LOGL_ERROR, "%s error timeout but not in error state: %d\n", - gsm_lchan_name(lchan), lchan->state); - return; - } - - /* go back to the none state */ - LOGP(DRSL, LOGL_INFO, "%s is back in operation.\n", gsm_lchan_name(lchan)); - rsl_lchan_set_state(lchan, LCHAN_S_NONE); - - /* Put PDCH channel back into PDCH mode, if GPRS is enabled */ - if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH - && lchan->ts->trx->bts->gprs.mode != BTS_GPRS_NONE) - rsl_ipacc_pdch_activate(lchan->ts, 1); - - if (dyn_ts_should_switch_to_pdch(lchan->ts)) - dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH); -} - -static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan); - -/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */ -static int rsl_rf_chan_release(struct gsm_lchan *lchan, int error, - enum sacch_deact deact_sacch) -{ - struct abis_rsl_dchan_hdr *dh; - struct msgb *msg; - int rc; - - /* Stop timers that should lead to a channel release */ - osmo_timer_del(&lchan->T3109); - - if (lchan->state == LCHAN_S_REL_ERR) { - LOGP(DRSL, LOGL_NOTICE, "%s is in error state, not sending release.\n", - gsm_lchan_name(lchan)); - return -1; - } - - 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); - - msg->lchan = lchan; - msg->dst = lchan->ts->trx->rsl_link; - - if (error) - DEBUGP(DRSL, "%s RF Channel Release due to error: %d\n", - gsm_lchan_name(lchan), error); - else - DEBUGP(DRSL, "%s RF Channel Release\n", gsm_lchan_name(lchan)); - - if (error) { - /* - * FIXME: GSM 04.08 gives us two options for the abnormal - * chanel release. This can be either like in the non-existent - * sub-lcuase 3.5.1 or for the main signalling link deactivate - * the SACCH, start timer T3109 and consider the channel as - * released. - * - * This code is doing the later for all raido links and not - * only the main link. Right now all SAPIs are released on the - * local end, the SACCH will be de-activated and right now the - * T3111 will be started. First T3109 should be started and then - * the T3111. - * - * TODO: Move this out of the function. - */ - - /* - * sacch de-activate and "local end release" - */ - if (deact_sacch == SACCH_DEACTIVATE) - rsl_deact_sacch(lchan); - rsl_release_sapis_from(lchan, 0, RSL_REL_LOCAL_END); - - /* - * TODO: start T3109 now. - */ - rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR); - } - - /* Start another timer or assume the BTS sends a ACK/NACK? */ - osmo_timer_setup(&lchan->act_timer, lchan_deact_tmr_cb, lchan); - osmo_timer_schedule(&lchan->act_timer, 4, 0); - - rc = abis_rsl_sendmsg(msg); - - /* BTS will respond by RF CHAN REL ACK */ - return rc; -} - -/* - * Special handling for channel releases in the error case. - */ -static int rsl_rf_chan_release_err(struct gsm_lchan *lchan) -{ - enum sacch_deact sacch_deact; - if (lchan->state != LCHAN_S_ACTIVE) - return 0; - switch (ts_pchan(lchan->ts)) { - case GSM_PCHAN_TCH_F: - case GSM_PCHAN_TCH_H: - case GSM_PCHAN_CCCH_SDCCH4: - case GSM_PCHAN_CCCH_SDCCH4_CBCH: - case GSM_PCHAN_SDCCH8_SACCH8C: - case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: - sacch_deact = SACCH_DEACTIVATE; - break; - default: - sacch_deact = SACCH_NONE; - break; - } - return rsl_rf_chan_release(lchan, 1, sacch_deact); -} - -static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan) -{ - struct gsm_bts_trx_ts *ts = lchan->ts; - - DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", gsm_lchan_name(lchan)); - - /* Stop all pending timers */ - osmo_timer_del(&lchan->act_timer); - osmo_timer_del(&lchan->T3111); - - /* - * The BTS didn't respond within the timeout to our channel - * release request and we have marked the channel as broken. - * Now we do receive an ACK and let's be conservative. If it - * is a sysmoBTS we know that only one RF Channel Release ACK - * will be sent. So let's "repair" the channel. - */ - if (lchan->state == LCHAN_S_BROKEN) { - 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); - if (dyn_ts_should_switch_to_pdch(lchan->ts)) - dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH); - return 0; - } - - if (lchan->state != LCHAN_S_REL_REQ && lchan->state != LCHAN_S_REL_ERR) - LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n", - gsm_lchan_name(lchan), - gsm_lchans_name(lchan->state)); - - do_lchan_free(lchan); - - /* - * Check Osmocom RSL CHAN ACT style dynamic TCH/F_TCH/H_PDCH TS for pending - * transitions in these cases: - * - * a) after PDCH was released due to switchover request, activate TCH. - * BSC initiated this switchover, so dyn.pchan_is != pchan_want and - * lchan->type has been set to the desired GSM_LCHAN_*. - * - * b) Voice call ended and a TCH is released. If the TS is now unused, - * switch to PDCH. Here still dyn.pchan_is == dyn.pchan_want because - * we're only just notified and may decide to switch to PDCH now. - */ - if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { - DEBUGP(DRSL, "%s Rx RSL Channel Release ack for lchan %u\n", - gsm_ts_and_pchan_name(ts), lchan->nr); - - /* (a) */ - if (ts->dyn.pchan_is != ts->dyn.pchan_want) - return dyn_ts_switchover_continue(ts); - - /* (b) */ - if (dyn_ts_should_switch_to_pdch(ts)) - return dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH); - } - - /* - * Put a dynamic TCH/F_PDCH channel back to PDCH mode iff it was - * released successfully. If in error, the PDCH ACT will follow after - * T3111 in error_timeout_cb(). - * - * Any state other than LCHAN_S_REL_ERR became LCHAN_S_NONE after above - * do_lchan_free(). Assert this, because that's what ensures a PDCH ACT - * on a TCH/F_PDCH TS in all cases. - * - * If GPRS is disabled, always skip the PDCH ACT. - */ - OSMO_ASSERT(lchan->state == LCHAN_S_NONE - || lchan->state == LCHAN_S_REL_ERR); - if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) - return 0; - if (ts->pchan == GSM_PCHAN_TCH_F_PDCH - && lchan->state == LCHAN_S_NONE) - return rsl_ipacc_pdch_activate(ts, 1); - return 0; -} - -int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, uint8_t len, - uint8_t *ms_ident, uint8_t chan_needed, bool is_gprs) -{ - struct abis_rsl_dchan_hdr *dh; - struct msgb *msg = rsl_msgb_alloc(); - - 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; - - msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group); - msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2); - msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed); - - /* Ericsson wants to have this IE in case a paging message - * relates to packet paging */ - 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; - - return abis_rsl_sendmsg(msg); -} - -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 *msg = rsl_msgb_alloc(); - struct abis_rsl_dchan_hdr *dh; - uint8_t buf[GSM_MACBLOCK_LEN]; - - 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; - - switch (bts->type) { - case GSM_BTS_TYPE_BS11: - msgb_tlv_put(msg, RSL_IE_IMM_ASS_INFO, len, val); - break; - default: - /* If phase 2, construct a FULL_IMM_ASS_INFO */ - pad_macblock(buf, val, len); - msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, GSM_MACBLOCK_LEN, - buf); - break; - } - - msg->dst = bts->c0->rsl_link; - return msg; -} - -/* Chapter 8.5.6 */ -int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val) -{ - struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val); - if (!msg) - return 1; - 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) -{ - struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val); - if (!msg) - return 1; - - /* 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); - - return abis_rsl_sendmsg(msg); -} - -/* 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 abis_rsl_dchan_hdr *dh; - - 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); - 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; - - return abis_rsl_sendmsg(msg); -} - - -/* Send "DATA REQUEST" message with given L3 Info payload */ -/* Chapter 8.3.1 */ -int rsl_data_request(struct msgb *msg, uint8_t link_id) -{ - if (msg->lchan == NULL) { - LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n"); - return -EINVAL; - } - - rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, gsm_lchan2chan_nr(msg->lchan), - link_id, 1); - - msg->dst = msg->lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -/* Send "ESTABLISH REQUEST" message with given L3 Info payload */ -/* Chapter 8.3.1 */ -int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id) -{ - struct msgb *msg; - - msg = rsl_rll_simple(RSL_MT_EST_REQ, gsm_lchan2chan_nr(lchan), - link_id, 0); - msg->dst = lchan->ts->trx->rsl_link; - - DEBUGP(DRLL, "%s RSL RLL ESTABLISH REQ (link_id=0x%02x)\n", - gsm_lchan_name(lchan), link_id); - - return abis_rsl_sendmsg(msg); -} - -static void rsl_handle_release(struct gsm_lchan *lchan); - -/* Special work handler to handle missing RSL_MT_REL_CONF message from - * Nokia InSite BTS */ -static void lchan_rel_work_cb(void *data) -{ - struct gsm_lchan *lchan = data; - int sapi; - - for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { - if (lchan->sapis[sapi] == LCHAN_SAPI_REL) - lchan->sapis[sapi] = LCHAN_SAPI_UNUSED; - } - rsl_handle_release(lchan); -} - -/* Chapter 8.3.7 Request the release of multiframe mode of RLL connection. - This is what higher layers should call. The BTS then responds with - RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE, - which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls - lchan_free() */ -int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id, - enum rsl_rel_mode release_mode) -{ - - struct msgb *msg; - - msg = rsl_rll_simple(RSL_MT_REL_REQ, gsm_lchan2chan_nr(lchan), - link_id, 0); - /* 0 is normal release, 1 is local end */ - msgb_tv_put(msg, RSL_IE_RELEASE_MODE, release_mode); - - /* FIXME: start some timer in case we don't receive a REL ACK ? */ - - msg->dst = lchan->ts->trx->rsl_link; - - DEBUGP(DRLL, "%s RSL RLL RELEASE REQ (link_id=0x%02x, reason=%u)\n", - gsm_lchan_name(lchan), link_id, release_mode); - - abis_rsl_sendmsg(msg); - - /* Do not wait for Nokia BTS to send the confirm. */ - if (is_nokia_bts(lchan->ts->trx->bts) - && lchan->ts->trx->bts->nokia.no_loc_rel_cnf - && release_mode == RSL_REL_LOCAL_END) { - DEBUGP(DRLL, "Scheduling release, becasuse Nokia InSite BTS does not send a RELease CONFirm.\n"); - lchan->sapis[link_id & 0x7] = LCHAN_SAPI_REL; - osmo_timer_setup(&lchan->rel_work, lchan_rel_work_cb, lchan); - osmo_timer_schedule(&lchan->rel_work, 0, 0); - } - - return 0; -} - -int rsl_lchan_mark_broken(struct gsm_lchan *lchan, const char *reason) -{ - LOGP(DRSL, LOGL_ERROR, "%s %s lchan broken: %s\n", - gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), reason); - rsl_lchan_set_state(lchan, LCHAN_S_BROKEN); - lchan->broken_reason = reason; - return 0; -} - -int rsl_lchan_set_state_with_log(struct gsm_lchan *lchan, enum gsm_lchan_state state, const char *file, unsigned line) -{ - if (lchan->state != state) - LOGPSRC(DRSL, LOGL_DEBUG, file, line, "%s state %s -> %s\n", - gsm_lchan_name(lchan), gsm_lchans_name(lchan->state), gsm_lchans_name(state)); - - lchan->state = state; - return 0; -} - -/* Chapter 8.4.2: Channel Activate Acknowledge */ -static int rsl_rx_chan_act_ack(struct msgb *msg) -{ - struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); - struct gsm_lchan *lchan = msg->lchan; - struct gsm_bts_trx_ts *ts = lchan->ts; - - /* BTS has confirmed channel activation, we now need - * to assign the activated channel to the MS */ - if (rslh->ie_chan != RSL_IE_CHAN_NR) - return -EINVAL; - - osmo_timer_del(&lchan->act_timer); - - if (lchan->state == LCHAN_S_BROKEN) { - int do_release = is_sysmobts_v2(ts->trx->bts); - LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK for broken channel. %s\n", - gsm_lchan_name(lchan), - do_release ? "Releasing it" : "Keeping it broken"); - if (do_release) { - talloc_free(lchan->rqd_ref); - lchan->rqd_ref = NULL; - lchan->rqd_ta = 0; - rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE); - if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { - /* - * lchan_act_tmr_cb() already called - * lchan_free() and cleared the lchan->type, so - * calling dyn_ts_switchover_complete() here - * would not have the desired effect of - * mimicking an activated lchan that we can - * release. Instead hack the dyn ts state to - * make sure that rsl_rx_rf_chan_rel_ack() will - * switch back to PDCH, i.e. have pchan_is == - * pchan_want, both != GSM_PCHAN_PDCH: - */ - ts->dyn.pchan_is = GSM_PCHAN_NONE; - ts->dyn.pchan_want = GSM_PCHAN_NONE; - } - rsl_rf_chan_release(msg->lchan, 0, SACCH_NONE); - } - return 0; - } - - if (lchan->state != LCHAN_S_ACT_REQ) - LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n", - gsm_lchan_name(lchan), - gsm_lchans_name(lchan->state)); - rsl_lchan_set_state(lchan, LCHAN_S_ACTIVE); - - if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) - dyn_ts_switchover_complete(lchan); - - if (lchan->rqd_ref) { - rsl_send_imm_assignment(lchan); - talloc_free(lchan->rqd_ref); - lchan->rqd_ref = NULL; - lchan->rqd_ta = 0; - } - - send_lchan_signal(S_LCHAN_ACTIVATE_ACK, lchan, NULL); - - /* Update bts attributes inside the PCU */ - if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH || - ts->pchan == GSM_PCHAN_TCH_F_PDCH || - ts->pchan == GSM_PCHAN_PDCH) - pcu_info_update(ts->trx->bts); - - return 0; -} - -/* Chapter 8.4.3: Channel Activate NACK */ -static int rsl_rx_chan_act_nack(struct msgb *msg) -{ - struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); - struct tlv_parsed tp; - - osmo_timer_del(&msg->lchan->act_timer); - - rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]); - - if (msg->lchan->state == LCHAN_S_BROKEN) { - LOGP(DRSL, LOGL_ERROR, - "%s CHANNEL ACTIVATE NACK for broken channel.\n", - gsm_lchan_name(msg->lchan)); - return -1; - } - - LOGP(DRSL, LOGL_ERROR, "%s CHANNEL ACTIVATE NACK ", - gsm_lchan_name(msg->lchan)); - - /* BTS has rejected channel activation ?!? */ - if (dh->ie_chan != RSL_IE_CHAN_NR) - return -EINVAL; - - rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); - if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) { - const uint8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE); - print_rsl_cause(LOGL_ERROR, cause, - TLVP_LEN(&tp, RSL_IE_CAUSE)); - msg->lchan->error_cause = *cause; - if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC) { - rsl_lchan_mark_broken(msg->lchan, "NACK on activation"); - } else - rsl_rf_chan_release(msg->lchan, 1, SACCH_DEACTIVATE); - - } else { - rsl_lchan_mark_broken(msg->lchan, "NACK on activation no IE"); - } - - LOGPC(DRSL, LOGL_ERROR, "\n"); - - send_lchan_signal(S_LCHAN_ACTIVATE_NACK, msg->lchan, NULL); - return 0; -} - -/* Chapter 8.4.4: Connection Failure Indication */ -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 tlv_parsed tp; - uint8_t cause = 0; - - LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL in state %s ", - gsm_lchan_name(msg->lchan), - gsm_lchans_name(msg->lchan->state)); - - rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); - - if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) { - print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE), - TLVP_LEN(&tp, RSL_IE_CAUSE)); - cause = *TLVP_VAL(&tp, RSL_IE_CAUSE); - } - - LOGPC(DRSL, LOGL_NOTICE, "\n"); - rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]); - - osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_RSL_CONN_FAIL, &cause); - - return 0; -} - -static void print_meas_rep_uni(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); -} - -static void print_meas_rep(struct gsm_lchan *lchan, struct gsm_meas_rep *mr) -{ - int i; - const char *name = ""; - - if (lchan && lchan->conn) { - if (lchan->conn->bsub) - name = bsc_subscr_name(lchan->conn->bsub); - else - name = lchan->name; - } - - DEBUGP(DMEAS, "[%s] MEASUREMENT RESULT NR=%d ", name, mr->nr); - - if (mr->flags & MEAS_REP_F_DL_DTX) - DEBUGPC(DMEAS, "DTXd "); - - print_meas_rep_uni(&mr->ul, "ul"); - DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power); - - if (mr->flags & MEAS_REP_F_MS_TO) - DEBUGPC(DMEAS, "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); - } - - if (mr->flags & MEAS_REP_F_UL_DTX) - DEBUGPC(DMEAS, "DTXu "); - if (mr->flags & MEAS_REP_F_BA1) - DEBUGPC(DMEAS, "BA1 "); - if (!(mr->flags & MEAS_REP_F_DL_VALID)) - DEBUGPC(DMEAS, "NOT VALID "); - else - print_meas_rep_uni(&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)); - } -} - -static struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan) -{ - struct gsm_meas_rep *meas_rep; - - meas_rep = &lchan->meas_rep[lchan->meas_rep_idx]; - memset(meas_rep, 0, sizeof(*meas_rep)); - meas_rep->lchan = lchan; - lchan->meas_rep_idx = (lchan->meas_rep_idx + 1) - % ARRAY_SIZE(lchan->meas_rep); - - return meas_rep; -} - -static int rsl_rx_meas_res(struct msgb *msg) -{ - struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); - struct tlv_parsed tp; - struct gsm_meas_rep *mr = lchan_next_meas_rep(msg->lchan); - uint8_t len; - const uint8_t *val; - int rc; - - /* check if this channel is actually active */ - /* FIXME: maybe this check should be way more generic/centralized */ - if (msg->lchan->state != LCHAN_S_ACTIVE) { - LOGP(DRSL, LOGL_DEBUG, "%s: MEAS RES for inactive channel\n", - gsm_lchan_name(msg->lchan)); - return 0; - } - - memset(mr, 0, sizeof(*mr)); - mr->lchan = msg->lchan; - - rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); - - if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) || - !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) || - !TLVP_PRESENT(&tp, RSL_IE_BS_POWER)) { - LOGP(DRSL, LOGL_ERROR, "%s Measurement Report lacks mandatory IEs\n", - gsm_lchan_name(mr->lchan)); - return -EIO; - } - - /* Mandatory Parts */ - mr->nr = *TLVP_VAL(&tp, RSL_IE_MEAS_RES_NR); - - len = TLVP_LEN(&tp, RSL_IE_UPLINK_MEAS); - val = TLVP_VAL(&tp, RSL_IE_UPLINK_MEAS); - if (len >= 3) { - if (val[0] & 0x40) - mr->flags |= MEAS_REP_F_DL_DTX; - mr->ul.full.rx_lev = val[0] & 0x3f; - mr->ul.sub.rx_lev = val[1] & 0x3f; - mr->ul.full.rx_qual = val[2]>>3 & 0x7; - mr->ul.sub.rx_qual = val[2] & 0x7; - } - - mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); - - /* Optional Parts */ - if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET)) { - /* According to 3GPP TS 48.058 § MS Timing Offset = Timing Offset field - 63 */ - mr->ms_timing_offset = *TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET) - 63; - mr->flags |= MEAS_REP_F_MS_TO; - } - - if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) { - struct e1inp_sign_link *sign_link = msg->dst; - - val = TLVP_VAL(&tp, RSL_IE_L1_INFO); - mr->flags |= MEAS_REP_F_MS_L1; - mr->ms_l1.pwr = ms_pwr_dbm(sign_link->trx->bts->band, val[0] >> 3); - 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 */ - if (msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11 - || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) - mr->ms_l1.ta >>= 2; - /* store TA for next assignment/handover */ - mr->lchan->rqd_ta = mr->ms_l1.ta; - } - if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { - msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO); - rc = gsm48_parse_meas_rep(mr, msg); - if (rc < 0) - return rc; - } - - mr->lchan->meas_rep_count++; - mr->lchan->meas_rep_last_seen_nr = mr->nr; - LOGP(DRSL, LOGL_DEBUG, "%s: meas_rep_count++=%d meas_rep_last_seen_nr=%u\n", - gsm_lchan_name(mr->lchan), mr->lchan->meas_rep_count, mr->lchan->meas_rep_last_seen_nr); - - print_meas_rep(msg->lchan, mr); - - send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr); - - return 0; -} - -/* Chapter 8.4.7 */ -static int rsl_rx_hando_det(struct msgb *msg) -{ - struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); - struct tlv_parsed tp; - - DEBUGP(DRSL, "%s HANDOVER DETECT ", gsm_lchan_name(msg->lchan)); - - rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); - - if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY)) - DEBUGPC(DRSL, "access delay = %u\n", - *TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY)); - else - DEBUGPC(DRSL, "\n"); - - send_lchan_signal(S_LCHAN_HANDOVER_DETECT, msg->lchan, NULL); - - return 0; -} - -static bool lchan_may_change_pdch(struct gsm_lchan *lchan, bool pdch_act) -{ - struct gsm_bts_trx_ts *ts; - - OSMO_ASSERT(lchan); - - ts = lchan->ts; - OSMO_ASSERT(ts); - OSMO_ASSERT(ts->trx); - OSMO_ASSERT(ts->trx->bts); - - if (lchan->ts->pchan != GSM_PCHAN_TCH_F_PDCH) { - LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK" - " for channel that is no TCH/F_PDCH\n", - gsm_lchan_name(lchan), - gsm_pchan_name(ts->pchan), - pdch_act? "ACT" : "DEACT"); - return false; - } - - if (lchan->state != LCHAN_S_NONE) { - LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK" - " in unexpected state: %s\n", - gsm_lchan_name(lchan), - gsm_pchan_name(ts->pchan), - pdch_act? "ACT" : "DEACT", - gsm_lchans_name(lchan->state)); - return false; - } - return true; -} - -static int rsl_rx_pdch_act_ack(struct msgb *msg) -{ - if (!lchan_may_change_pdch(msg->lchan, true)) - return -EINVAL; - - msg->lchan->ts->flags |= TS_F_PDCH_ACTIVE; - msg->lchan->ts->flags &= ~TS_F_PDCH_ACT_PENDING; - - return 0; -} - -static int rsl_rx_pdch_deact_ack(struct msgb *msg) -{ - if (!lchan_may_change_pdch(msg->lchan, false)) - return -EINVAL; - - msg->lchan->ts->flags &= ~TS_F_PDCH_ACTIVE; - msg->lchan->ts->flags &= ~TS_F_PDCH_DEACT_PENDING; - - rsl_chan_activate_lchan(msg->lchan, msg->lchan->dyn.act_type, - msg->lchan->dyn.ho_ref); - - return 0; -} - -static int abis_rsl_rx_dchan(struct msgb *msg) -{ - struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); - int rc = 0; - char *ts_name; - struct e1inp_sign_link *sign_link = msg->dst; - - msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr, - "Abis RSL rx DCHAN: "); - if (!msg->lchan) - return -1; - ts_name = gsm_lchan_name(msg->lchan); - - switch (rslh->c.msg_type) { - case RSL_MT_CHAN_ACTIV_ACK: - DEBUGP(DRSL, "%s CHANNEL ACTIVATE ACK\n", ts_name); - rc = rsl_rx_chan_act_ack(msg); - count_codecs(sign_link->trx->bts, msg->lchan); - break; - case RSL_MT_CHAN_ACTIV_NACK: - rc = rsl_rx_chan_act_nack(msg); - break; - case RSL_MT_CONN_FAIL: - rc = rsl_rx_conn_fail(msg); - break; - case RSL_MT_MEAS_RES: - rc = rsl_rx_meas_res(msg); - break; - case RSL_MT_HANDO_DET: - rc = rsl_rx_hando_det(msg); - break; - case RSL_MT_RF_CHAN_REL_ACK: - rc = rsl_rx_rf_chan_rel_ack(msg->lchan); - break; - case RSL_MT_MODE_MODIFY_ACK: - count_codecs(sign_link->trx->bts, msg->lchan); - DEBUGP(DRSL, "%s CHANNEL MODE MODIFY ACK\n", ts_name); - break; - case RSL_MT_MODE_MODIFY_NACK: - LOGP(DRSL, LOGL_ERROR, "%s CHANNEL MODE MODIFY NACK\n", ts_name); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_MODE_MODIFY_NACK]); - break; - case RSL_MT_IPAC_PDCH_ACT_ACK: - DEBUGP(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name); - rc = rsl_rx_pdch_act_ack(msg); - break; - case RSL_MT_IPAC_PDCH_ACT_NACK: - LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH ACT NACK\n", ts_name); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); - break; - case RSL_MT_IPAC_PDCH_DEACT_ACK: - DEBUGP(DRSL, "%s IPAC PDCH DEACT ACK\n", ts_name); - rc = rsl_rx_pdch_deact_ack(msg); - break; - case RSL_MT_IPAC_PDCH_DEACT_NACK: - LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH DEACT NACK\n", ts_name); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); - 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: - LOGP(DRSL, LOGL_NOTICE, "%s Unimplemented Abis RSL DChan " - "msg 0x%02x\n", ts_name, rslh->c.msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); - break; - default: - LOGP(DRSL, LOGL_NOTICE, "%s unknown Abis RSL DChan msg 0x%02x\n", - ts_name, rslh->c.msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); - return -EINVAL; - } - - return rc; -} - -static int rsl_rx_error_rep(struct msgb *msg) -{ - struct abis_rsl_common_hdr *rslh = msgb_l2(msg); - struct tlv_parsed tp; - struct e1inp_sign_link *sign_link = msg->dst; - - LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(sign_link->trx)); - - rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh)); - - if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) - print_rsl_cause(LOGL_ERROR, TLVP_VAL(&tp, RSL_IE_CAUSE), - TLVP_LEN(&tp, RSL_IE_CAUSE)); - - LOGPC(DRSL, LOGL_ERROR, "\n"); - - return 0; -} - -static int abis_rsl_rx_trx(struct msgb *msg) -{ - struct abis_rsl_common_hdr *rslh = msgb_l2(msg); - struct e1inp_sign_link *sign_link = msg->dst; - int rc = 0; - - switch (rslh->msg_type) { - case RSL_MT_ERROR_REPORT: - rc = rsl_rx_error_rep(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)); - break; - case RSL_MT_OVERLOAD: - /* indicate CCCH / ACCH / processor overload */ - LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n", - gsm_trx_name(sign_link->trx)); - break; - case 0x42: /* Nokia specific: SI End ACK */ - LOGP(DRSL, LOGL_INFO, "Nokia SI End ACK\n"); - break; - case 0x43: /* Nokia specific: SI End NACK */ - LOGP(DRSL, LOGL_INFO, "Nokia SI End NACK\n"); - break; - 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]); - return -EINVAL; - } - return rc; -} - -/* If T3101 expires, we never received a response to IMMEDIATE ASSIGN */ -static void t3101_expired(void *data) -{ - struct gsm_lchan *lchan = data; - LOGP(DRSL, LOGL_NOTICE, - "%s T3101 expired: no response to IMMEDIATE ASSIGN\n", - gsm_lchan_name(lchan)); - rsl_rf_chan_release(lchan, 1, SACCH_DEACTIVATE); -} - -/* If T3111 expires, we will send the RF Channel Request */ -static void t3111_expired(void *data) -{ - struct gsm_lchan *lchan = data; - LOGP(DRSL, LOGL_NOTICE, - "%s T3111 expired: releasing RF Channel\n", - gsm_lchan_name(lchan)); - rsl_rf_chan_release(lchan, 0, SACCH_NONE); -} - -/* If T3109 expires the MS has not send a UA/UM do the error release */ -static void t3109_expired(void *data) -{ - struct gsm_lchan *lchan = data; - - LOGP(DRSL, LOGL_ERROR, - "%s SACCH deactivation timeout.\n", gsm_lchan_name(lchan)); - rsl_rf_chan_release(lchan, 1, SACCH_NONE); -} - -/* Format an IMM ASS REJ according to 04.08 Chapter 9.1.20 */ -static int rsl_send_imm_ass_rej(struct gsm_bts *bts, - struct gsm48_req_ref *rqd_ref, - uint8_t wait_ind) -{ - uint8_t buf[GSM_MACBLOCK_LEN]; - struct gsm48_imm_ass_rej *iar = (struct gsm48_imm_ass_rej *)buf; - - /* create IMMEDIATE ASSIGN REJECT 04.08 message */ - memset(iar, 0, sizeof(*iar)); - iar->proto_discr = GSM48_PDISC_RR; - iar->msg_type = GSM48_MT_RR_IMM_ASS_REJ; - iar->page_mode = GSM48_PM_SAME; - - /* - * Set all request references and wait indications to the same value. - * 3GPP TS 44.018 v4.5.0 release 4 (section 9.1.20.2) requires that - * we duplicate reference and wait indication to fill the message. - * The BTS will aggregate up to 4 of our ASS REJ messages if possible. - */ - memcpy(&iar->req_ref1, rqd_ref, sizeof(iar->req_ref1)); - iar->wait_ind1 = wait_ind; - memcpy(&iar->req_ref2, rqd_ref, sizeof(iar->req_ref2)); - iar->wait_ind2 = wait_ind; - memcpy(&iar->req_ref3, rqd_ref, sizeof(iar->req_ref3)); - iar->wait_ind3 = wait_ind; - memcpy(&iar->req_ref4, rqd_ref, sizeof(iar->req_ref4)); - iar->wait_ind4 = wait_ind; - - /* we need to subtract 1 byte from sizeof(*iar) since ia includes the l2_plen field */ - iar->l2_plen = GSM48_LEN2PLEN((sizeof(*iar)-1)); - - return rsl_imm_assign_cmd(bts, sizeof(*iar), (uint8_t *) iar); -} - -/* Handle packet channel rach requests */ -static int rsl_rx_pchan_rqd(struct msgb *msg, struct gsm_bts *bts) -{ - struct gsm48_req_ref *rqd_ref; - struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg); - rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1]; - uint8_t ra = rqd_ref->ra; - uint8_t t1, t2, t3; - uint32_t fn; - uint8_t rqd_ta; - uint8_t is_11bit; - - /* Process rach request and forward contained information to PCU */ - if (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",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_hdr->data[sizeof(struct gsm48_req_ref)+2]; - } - - return pcu_tx_rach_ind(bts, rqd_ta, ra, fn, is_11bit, - GSM_L1_BURST_TYPE_ACCESS_0); -} - -/* MS has requested a channel on the RACH */ -static int rsl_rx_chan_rqd(struct msgb *msg) -{ - struct e1inp_sign_link *sign_link = msg->dst; - struct gsm_bts *bts = sign_link->trx->bts; - struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg); - struct gsm48_req_ref *rqd_ref; - enum gsm_chan_t lctype; - enum gsm_chreq_reason_t chreq_reason; - struct gsm_lchan *lchan; - uint8_t rqd_ta; - - uint16_t arfcn; - uint8_t subch; - - /* parse request reference to be used in immediate assign */ - if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE) - return -EINVAL; - - rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1]; - - /* parse access delay and use as TA */ - if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY) - return -EINVAL; - rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2]; - - /* Determine channel request cause code */ - chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci); - LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: reason: %s (ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n", - msg->lchan->ts->trx->bts->nr, - get_value_string(gsm_chreq_descs, chreq_reason), - rqd_ref->ra, bts->network->neci, chreq_reason); - - /* Handle PDCH related rach requests (in case of BSC-co-located-PCU */ - if (chreq_reason == GSM_CHREQ_REASON_PDCH) - return rsl_rx_pchan_rqd(msg, bts); - - /* determine channel type (SDCCH/TCH_F/TCH_H) based on - * request reference RA */ - lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra); - - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL]); - - /* check availability / allocate channel - * - * - First try to allocate SDCCH. - * - If SDCCH is not available, try whatever MS requested, if not SDCCH. - * - If there is still no channel available, reject channel request. - * - * lchan_alloc() possibly tries to allocate larger lchans. - * - * Note: If the MS requests not TCH/H, we don't know if the phone - * supports TCH/H, so we must assign TCH/F or SDCCH. - */ - lchan = lchan_alloc(bts, GSM_LCHAN_SDCCH, 0); - if (!lchan && lctype != GSM_LCHAN_SDCCH) { - LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s " - "0x%x, retrying with %s\n", - msg->lchan->ts->trx->bts->nr, - gsm_lchant_name(GSM_LCHAN_SDCCH), rqd_ref->ra, - gsm_lchant_name(lctype)); - lchan = lchan_alloc(bts, lctype, 0); - } - if (!lchan) { - uint8_t wait_ind; - LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s 0x%x\n", - msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra); - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL]); - if (bts->T3122) - wait_ind = bts->T3122; - else if (bts->network->T3122) - wait_ind = bts->network->T3122 & 0xff; - else - wait_ind = GSM_T3122_DEFAULT; - /* The BTS will gather multiple CHAN RQD and reject up to 4 MS at the same time. */ - rsl_send_imm_ass_rej(bts, rqd_ref, wait_ind); - return 0; - } - - /* - * Expecting lchan state to be NONE, except for dyn TS in PDCH mode. - * Those are expected to be ACTIVE: the PDCH release will be sent from - * rsl_chan_activate_lchan() below. - */ - if (lchan->state != LCHAN_S_NONE - && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH - && lchan->state == LCHAN_S_ACTIVE)) - LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel " - "in state %s\n", gsm_lchan_name(lchan), - gsm_lchans_name(lchan->state)); - - /* save the RACH data as we need it after the CHAN ACT ACK */ - lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref); - if (!lchan->rqd_ref) { - LOGP(DRSL, LOGL_ERROR, "Failed to allocate gsm48_req_ref.\n"); - lchan_free(lchan); - return -ENOMEM; - } - - memcpy(lchan->rqd_ref, rqd_ref, sizeof(*rqd_ref)); - lchan->rqd_ta = rqd_ta; - - arfcn = lchan->ts->trx->arfcn; - subch = lchan->nr; - - lchan->encr.alg_id = RSL_ENC_ALG_A5(0); /* no encryption */ - lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power); - lchan->bs_power = 0; /* 0dB reduction, output power = Pn */ - lchan->rsl_cmode = RSL_CMOD_SPD_SIGN; - lchan->tch_mode = GSM48_CMODE_SIGN; - - /* Start another timer or assume the BTS sends a ACK/NACK? */ - osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan); - osmo_timer_schedule(&lchan->act_timer, 4, 0); - - DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s " - "r=%s ra=0x%02x ta=%d\n", gsm_lchan_name(lchan), arfcn, subch, - gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason), - rqd_ref->ra, rqd_ta); - - rsl_chan_activate_lchan(lchan, RSL_ACT_INTRA_IMM_ASS, 0); - - return 0; -} - -static int rsl_send_imm_assignment(struct gsm_lchan *lchan) -{ - struct gsm_bts *bts = lchan->ts->trx->bts; - uint8_t buf[GSM_MACBLOCK_LEN]; - struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf; - - /* create IMMEDIATE ASSIGN 04.08 messge */ - memset(ia, 0, sizeof(*ia)); - /* we set ia->l2_plen once we know the length of the MA below */ - 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); - - /* use request reference extracted from CHAN_RQD */ - memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref)); - ia->timing_advance = lchan->rqd_ta; - if (!lchan->ts->hopping.enabled) { - ia->mob_alloc_len = 0; - } else { - ia->mob_alloc_len = lchan->ts->hopping.ma_len; - memcpy(ia->mob_alloc, lchan->ts->hopping.ma_data, ia->mob_alloc_len); - } - /* we need to subtract 1 byte from sizeof(*ia) since ia includes the l2_plen field */ - ia->l2_plen = GSM48_LEN2PLEN((sizeof(*ia)-1) + ia->mob_alloc_len); - - /* Start timer T3101 to wait for GSM48_MT_RR_PAG_RESP */ - osmo_timer_setup(&lchan->T3101, t3101_expired, lchan); - osmo_timer_schedule(&lchan->T3101, bts->network->T3101, 0); - - /* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */ - return rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia); -} - -/* current load on the CCCH */ -static int rsl_rx_ccch_load(struct msgb *msg) -{ - struct e1inp_sign_link *sign_link = msg->dst; - struct abis_rsl_dchan_hdr *rslh = msgb_l2(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; - - 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; - } - 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) { - sd.rach_slot_count = rslh->data[2] << 8 | rslh->data[3]; - sd.rach_busy_count = rslh->data[4] << 8 | rslh->data[5]; - sd.rach_access_count = rslh->data[6] << 8 | rslh->data[7]; - osmo_signal_dispatch(SS_CCCH, S_CCCH_RACH_LOAD, &sd); - } - break; - default: - break; - } - - return 0; -} - -/* Ericsson specific: Immediate Assign Sent */ -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; - - LOGP(DRSL, LOGL_INFO, "IMM.ass sent\n"); - msgb_pull(msg, sizeof(*dh)); - - /* FIXME: Move to TLV once we support defining TV types with V having len != 1 byte */ - if(msg->len < 5) - LOGP(DRSL, LOGL_ERROR, "short IMM.ass sent message!\n"); - else if(msg->data[0] != RSL_IE_ERIC_MOBILE_ID) - 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); - } - return 0; -} - -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); - int rc = 0; - - msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr, - "Abis RSL rx CCHAN: "); - - switch (rslh->c.msg_type) { - case RSL_MT_CHAN_RQD: - /* MS has requested a channel on the RACH */ - rc = rsl_rx_chan_rqd(msg); - break; - case RSL_MT_CCCH_LOAD_IND: - /* current load on the CCCH */ - rc = rsl_rx_ccch_load(msg); - break; - case RSL_MT_DELETE_IND: - /* CCCH overloaded, IMM_ASSIGN was dropped */ - case RSL_MT_CBCH_LOAD_IND: - /* current load on the CBCH */ - LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message " - "type %s\n", rsl_msg_name(rslh->c.msg_type)); - break; - case RSL_MT_ERICSSON_IMM_ASS_SENT: - rc = rsl_rx_ericsson_imm_assign_sent(msg); - break; - 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]); - return -EINVAL; - } - - return rc; -} - -static int rsl_rx_rll_err_ind(struct msgb *msg) -{ - struct tlv_parsed tp; - 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 (!TLVP_PRESENT(&tp, RSL_IE_RLM_CAUSE)) { - LOGP(DRLL, LOGL_ERROR, - "%s ERROR INDICATION without mandantory cause.\n", - gsm_lchan_name(msg->lchan)); - return -1; - } - - rlm_cause = *TLVP_VAL(&tp, RSL_IE_RLM_CAUSE); - LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s in state=%s\n", - gsm_lchan_name(msg->lchan), - rsl_rlm_cause_name(rlm_cause), - gsm_lchans_name(msg->lchan->state)); - - rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND); - - if (rlm_cause == RLL_CAUSE_T200_EXPIRED) { - rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR]); - return rsl_rf_chan_release_err(msg->lchan); - } - - return 0; -} - -static void rsl_handle_release(struct gsm_lchan *lchan) -{ - int sapi; - struct gsm_bts *bts; - - /* - * Maybe only one link/SAPI was releasd or the error handling - * was activated. Just return now and let the other code handle - * it. - */ - if (lchan->state != LCHAN_S_REL_REQ) - return; - - for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { - if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) - continue; - LOGP(DRSL, LOGL_DEBUG, "%s waiting for SAPI=%d to be released.\n", - gsm_lchan_name(lchan), sapi); - return; - } - - - /* Stop T3109 and wait for T3111 before re-using the channel */ - osmo_timer_del(&lchan->T3109); - osmo_timer_setup(&lchan->T3111, t3111_expired, lchan); - bts = lchan->ts->trx->bts; - osmo_timer_schedule(&lchan->T3111, bts->network->T3111, 0); -} - -/* ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST - 0x02, 0x06, - 0x01, 0x20, - 0x02, 0x00, - 0x0b, 0x00, 0x0f, 0x05, 0x08, ... */ - -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; - char *ts_name; - uint8_t sapi = rllh->link_id & 7; - - msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, - "Abis RSL rx RLL: "); - ts_name = gsm_lchan_name(msg->lchan); - DEBUGP(DRLL, "%s SAPI=%u ", ts_name, sapi); - - switch (rllh->c.msg_type) { - case RSL_MT_DATA_IND: - DEBUGPC(DRLL, "DATA INDICATION\n"); - if (msgb_l2len(msg) > - sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && - rllh->data[0] == RSL_IE_L3_INFO) { - msg->l3h = &rllh->data[3]; - return gsm0408_rcvmsg(msg, rllh->link_id); - } - break; - case RSL_MT_EST_IND: - DEBUGPC(DRLL, "ESTABLISH INDICATION\n"); - /* lchan is established, stop T3101 */ - - /* Note: By definition the first Establish Indication must - * happen first on SAPI 0, once the connection on SAPI 0 is - * made, parallel connections on other SAPIs are permitted */ - if (sapi != 0 && msg->lchan->sapis[0] != LCHAN_SAPI_MS) { - LOGP(DRLL, LOGL_NOTICE, "MS attempted to establish DCCH on SAPI=%d (expected SAPI=0)\n", - rllh->link_id & 0x7); - - /* Note: We do not need to close the channel, - * since we might still get a proper Establish Ind. - * If not, T3101 will close the channel on timeout. */ - break; - } - - /* Note: Check for MF SACCH on SAPI=0 (not permitted). By - * definition we establish a link in multiframe (MF) mode. - * (see also 3GPP TS 48.058, chapter 3.1. However, on SAPI=0 - * SACCH is only allowed in UL mode, not in MF mode. - * (see also 3GPP TS 44.005, figure 5) So we have to drop such - * Establish Indications */ - if (sapi == 0 && (rllh->link_id >> 6 & 0x03) == 1) { - LOGP(DRLL, LOGL_NOTICE, "MS attempted to establish an SACCH in MF mode on SAPI=0 (not permitted)\n"); - - /* Note: We do not need to close the channel, - * since we might still get a proper Establish Ind. - * If not, T3101 will close the channel on timeout. */ - break; - } - - msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_MS; - osmo_timer_del(&msg->lchan->T3101); - if (msgb_l2len(msg) > - sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && - rllh->data[0] == RSL_IE_L3_INFO) { - msg->l3h = &rllh->data[3]; - return gsm0408_rcvmsg(msg, rllh->link_id); - } - break; - case RSL_MT_EST_CONF: - DEBUGPC(DRLL, "ESTABLISH CONFIRM\n"); - msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_NET; - rll_indication(msg->lchan, rllh->link_id, - BSC_RLLR_IND_EST_CONF); - break; - case RSL_MT_REL_IND: - /* BTS informs us of having received DISC from MS */ - DEBUGPC(DRLL, "RELEASE INDICATION\n"); - msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; - rll_indication(msg->lchan, rllh->link_id, - BSC_RLLR_IND_REL_IND); - rsl_handle_release(msg->lchan); - /* if it was the main signalling link, let the subscr_conn_fsm know */ - if (msg->lchan->conn && sapi == 0 && (rllh->link_id >> 6) == 0) - osmo_fsm_inst_dispatch(msg->lchan->conn->fi, GSCON_EV_RLL_REL_IND, msg); - break; - case RSL_MT_REL_CONF: - /* BTS informs us of having received UA from MS, - * in response to DISC that we've sent earlier */ - DEBUGPC(DRLL, "RELEASE CONFIRMATION\n"); - msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; - rsl_handle_release(msg->lchan); - break; - case RSL_MT_ERROR_IND: - DEBUGPC(DRLL, "ERROR INDICATION\n"); - rc = rsl_rx_rll_err_ind(msg); - break; - case RSL_MT_UNIT_DATA_IND: - DEBUGPC(DRLL, "UNIT DATA INDICATION\n"); - LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message " - "type 0x%02x\n", rllh->c.msg_type); - break; - default: - DEBUGPC(DRLL, "UNKNOWN\n"); - LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message " - "type 0x%02x\n", rllh->c.msg_type); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); - } - return rc; -} - -static uint8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan) -{ - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_V1: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return 0x00; - case GSM_LCHAN_TCH_H: - return 0x03; - default: - break; - } - break; - case GSM48_CMODE_SPEECH_EFR: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return 0x01; - /* there's no half-rate EFR */ - default: - break; - } - break; - case GSM48_CMODE_SPEECH_AMR: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return 0x02; - case GSM_LCHAN_TCH_H: - return 0x05; - default: - break; - } - break; - default: - break; - } - LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for " - "tch_mode == 0x%02x\n", lchan->tch_mode); - return 0; -} - -static uint8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan) -{ - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_V1: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return RTP_PT_GSM_FULL; - case GSM_LCHAN_TCH_H: - return RTP_PT_GSM_HALF; - default: - break; - } - break; - case GSM48_CMODE_SPEECH_EFR: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return RTP_PT_GSM_EFR; - /* there's no half-rate EFR */ - default: - break; - } - break; - case GSM48_CMODE_SPEECH_AMR: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - case GSM_LCHAN_TCH_H: - return RTP_PT_AMR; - default: - break; - } - break; - default: - break; - } - LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for " - "tch_mode == 0x%02x\n & lchan_type == %d", - lchan->tch_mode, lchan->type); - return 0; -} - -/* ip.access specific RSL extensions */ -static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv) -{ - struct in_addr ip; - uint16_t port, conn_id; - - if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) { - ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_LOCAL_IP); - DEBUGPC(DRSL, "LOCAL_IP=%s ", inet_ntoa(ip)); - lchan->abis_ip.bound_ip = ntohl(ip.s_addr); - } - - if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_PORT)) { - port = tlvp_val16_unal(tv, RSL_IE_IPAC_LOCAL_PORT); - port = ntohs(port); - DEBUGPC(DRSL, "LOCAL_PORT=%u ", port); - lchan->abis_ip.bound_port = port; - } - - if (TLVP_PRESENT(tv, RSL_IE_IPAC_CONN_ID)) { - conn_id = tlvp_val16_unal(tv, RSL_IE_IPAC_CONN_ID); - conn_id = ntohs(conn_id); - DEBUGPC(DRSL, "CON_ID=%u ", conn_id); - lchan->abis_ip.conn_id = conn_id; - } - - if (TLVP_PRESENT(tv, RSL_IE_IPAC_RTP_PAYLOAD2)) { - lchan->abis_ip.rtp_payload2 = - *TLVP_VAL(tv, RSL_IE_IPAC_RTP_PAYLOAD2); - DEBUGPC(DRSL, "RTP_PAYLOAD2=0x%02x ", - lchan->abis_ip.rtp_payload2); - } - - if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) { - lchan->abis_ip.speech_mode = - *TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE); - DEBUGPC(DRSL, "speech_mode=0x%02x ", - lchan->abis_ip.speech_mode); - } - - if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) { - ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_REMOTE_IP); - DEBUGPC(DRSL, "REMOTE_IP=%s ", inet_ntoa(ip)); - lchan->abis_ip.connect_ip = ntohl(ip.s_addr); - } - - if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_PORT)) { - port = tlvp_val16_unal(tv, RSL_IE_IPAC_REMOTE_PORT); - port = ntohs(port); - DEBUGPC(DRSL, "REMOTE_PORT=%u ", port); - lchan->abis_ip.connect_port = port; - } - - DEBUGPC(DRSL, "\n"); -} - -/*! \brief Issue IPA RSL CRCX to configure RTP on BTS side - * \param[in] lchan Logical Channel for which we issue CRCX - */ -int rsl_ipacc_crcx(struct gsm_lchan *lchan) -{ - struct msgb *msg = rsl_msgb_alloc(); - struct abis_rsl_dchan_hdr *dh; - - 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); - - /* 0x1- == receive-only, 0x-1 == EFR codec */ - lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan); - lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan); - 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); - - DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n", - gsm_lchan_name(lchan), lchan->abis_ip.speech_mode, - lchan->abis_ip.rtp_payload); - - msg->dst = lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -/*! \brief Issue IPA RSL MDCX to configure MGW-side of RTP - * \param[in] lchan Logical Channel for which we issue MDCX - * \param[in] ip Remote (MGW) IP address for RTP - * \param[in] port Remote (MGW) UDP port number for RTP - * \param[in] rtp_payload2 Contents of RTP PAYLOAD 2 IE - */ -int rsl_ipacc_mdcx(struct gsm_lchan *lchan, uint32_t ip, uint16_t port, - uint8_t rtp_payload2) -{ - struct msgb *msg = rsl_msgb_alloc(); - struct abis_rsl_dchan_hdr *dh; - uint32_t *att_ip; - struct in_addr ia; - - 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); - - /* we need to store these now as MDCX_ACK does not return them :( */ - lchan->abis_ip.rtp_payload2 = rtp_payload2; - lchan->abis_ip.connect_port = port; - lchan->abis_ip.connect_ip = ip; - - /* 0x0- == both directions, 0x-1 == EFR codec */ - lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan); - lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan); - - ia.s_addr = htonl(ip); - DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d " - "CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan), - inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2, - lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode); - - 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(ip)); - *att_ip = ia.s_addr; - msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port); - 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 (rtp_payload2) - msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2); - - msg->dst = lchan->ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -static bool check_gprs_enabled(struct gsm_bts_trx_ts *ts) -{ - if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) { - LOGP(DRSL, LOGL_NOTICE, "%s: GPRS mode is 'none': not activating PDCH.\n", - gsm_ts_and_pchan_name(ts)); - return false; - } - return true; -} - -int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act) -{ - struct msgb *msg = rsl_msgb_alloc(); - struct abis_rsl_dchan_hdr *dh; - uint8_t msg_type; - - if (ts->flags & TS_F_PDCH_PENDING_MASK) { - LOGP(DRSL, LOGL_ERROR, - "%s PDCH %s requested, but a PDCH%s%s is still pending\n", - gsm_ts_name(ts), - act ? "ACT" : "DEACT", - ts->flags & TS_F_PDCH_ACT_PENDING? " ACT" : "", - ts->flags & TS_F_PDCH_DEACT_PENDING? " DEACT" : ""); - return -EINVAL; - } - - if (act){ - if (!check_gprs_enabled(ts)) - return -ENOTSUP; - - msg_type = RSL_MT_IPAC_PDCH_ACT; - ts->flags |= TS_F_PDCH_ACT_PENDING; - } else { - msg_type = RSL_MT_IPAC_PDCH_DEACT; - ts->flags |= TS_F_PDCH_DEACT_PENDING; - } - /* TODO add timeout to cancel PDCH DE/ACT */ - - dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); - init_dchan_hdr(dh, msg_type); - dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; - dh->chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0); - - DEBUGP(DRSL, "%s IPAC PDCH %sACT\n", gsm_ts_name(ts), - act ? "" : "DE"); - - msg->dst = ts->trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg) -{ - struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); - struct tlv_parsed tv; - struct gsm_lchan *lchan = msg->lchan; - - /* the BTS has acknowledged a local bind, it now tells us the IP - * 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 (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) || - !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) || - !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) { - LOGP(DRSL, LOGL_NOTICE, "mandatory IE missing"); - return -EINVAL; - } - - ipac_parse_rtp(lchan, &tv); - - osmo_signal_dispatch(SS_ABISIP, S_ABISIP_CRCX_ACK, msg->lchan); - - return 0; -} - -static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg) -{ - struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); - struct tlv_parsed tv; - struct gsm_lchan *lchan = msg->lchan; - - /* the BTS has acknowledged a remote connect request and - * 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)); - ipac_parse_rtp(lchan, &tv); - osmo_signal_dispatch(SS_ABISIP, S_ABISIP_MDCX_ACK, msg->lchan); - - return 0; -} - -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 (TLVP_PRESENT(&tv, RSL_IE_CAUSE)) - print_rsl_cause(LOGL_DEBUG, TLVP_VAL(&tv, RSL_IE_CAUSE), - TLVP_LEN(&tv, RSL_IE_CAUSE)); - - osmo_signal_dispatch(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan); - - return 0; -} - -static int abis_rsl_rx_ipacc(struct msgb *msg) -{ - struct e1inp_sign_link *sign_link = msg->dst; - struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); - char *ts_name; - int rc = 0; - - msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, - "Abis RSL rx IPACC: "); - ts_name = gsm_lchan_name(msg->lchan); - - switch (rllh->c.msg_type) { - case RSL_MT_IPAC_CRCX_ACK: - DEBUGP(DRSL, "%s IPAC_CRCX_ACK ", ts_name); - rc = abis_rsl_rx_ipacc_crcx_ack(msg); - break; - case RSL_MT_IPAC_CRCX_NACK: - /* somehow the BTS was unable to bind the lchan to its local - * port?!? */ - LOGP(DRSL, LOGL_ERROR, "%s IPAC_CRCX_NACK\n", ts_name); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); - break; - case RSL_MT_IPAC_MDCX_ACK: - /* the BTS tells us that a connect operation was successful */ - DEBUGP(DRSL, "%s IPAC_MDCX_ACK ", ts_name); - rc = abis_rsl_rx_ipacc_mdcx_ack(msg); - break; - case RSL_MT_IPAC_MDCX_NACK: - /* somehow the BTS was unable to connect the lchan to a remote - * port */ - LOGP(DRSL, LOGL_ERROR, "%s IPAC_MDCX_NACK\n", ts_name); - rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); - break; - case RSL_MT_IPAC_DLCX_IND: - DEBUGP(DRSL, "%s IPAC_DLCX_IND ", ts_name); - rc = abis_rsl_rx_ipacc_dlcx_ind(msg); - break; - default: - LOGP(DRSL, 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]); - break; - } - - return rc; -} - -int dyn_ts_switchover_start(struct gsm_bts_trx_ts *ts, - enum gsm_phys_chan_config to_pchan) -{ - int ss; - int rc = -EIO; - - OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); - - if (ts->dyn.pchan_is != ts->dyn.pchan_want) { - LOGP(DRSL, LOGL_ERROR, - "%s: Attempt to switch dynamic channel to %s," - " but is already in switchover.\n", - gsm_ts_and_pchan_name(ts), - gsm_pchan_name(to_pchan)); - return ts->dyn.pchan_want == to_pchan? 0 : -EAGAIN; - } - - if (ts->dyn.pchan_is == to_pchan) { - LOGP(DRSL, LOGL_INFO, - "%s %s Already is in %s mode, cannot switchover.\n", - gsm_ts_name(ts), gsm_pchan_name(ts->pchan), - gsm_pchan_name(to_pchan)); - return -EINVAL; - } - - /* Paranoia: let's make sure all is indeed released. */ - for (ss = 0; ss < ts_subslots(ts); ss++) { - struct gsm_lchan *lc = &ts->lchan[ss]; - if (lc->state != LCHAN_S_NONE) { - LOGP(DRSL, LOGL_ERROR, - "%s Attempt to switch dynamic channel to %s," - " but is not fully released.\n", - gsm_ts_and_pchan_name(ts), - gsm_pchan_name(to_pchan)); - return -EAGAIN; - } - } - - if (to_pchan == GSM_PCHAN_PDCH && !check_gprs_enabled(ts)) - return -ENOTSUP; - - DEBUGP(DRSL, "%s starting switchover to %s\n", - gsm_ts_and_pchan_name(ts), gsm_pchan_name(to_pchan)); - - /* Record that we're busy switching. */ - ts->dyn.pchan_want = to_pchan; - - /* - * To switch from PDCH, we need to initiate the release from the BSC - * side. dyn_ts_switchover_continue() will be called from - * rsl_rx_rf_chan_rel_ack(). PDCH is always on lchan[0]. - */ - if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) { - rsl_lchan_set_state(ts->lchan, LCHAN_S_REL_REQ); - rc = rsl_rf_chan_release(ts->lchan, 0, SACCH_NONE); - if (rc) { - LOGP(DRSL, LOGL_ERROR, - "%s RSL RF Chan Release failed\n", - gsm_ts_and_pchan_name(ts)); - return dyn_ts_switchover_failed(ts, rc); - } - return 0; - } - - /* - * To switch from TCH/F and TCH/H pchans, this has been called from - * rsl_rx_rf_chan_rel_ack(), i.e. release is complete. Go ahead and - * activate as new type. This will always be PDCH. - */ - return dyn_ts_switchover_continue(ts); -} - -static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts) -{ - int rc; - uint8_t act_type; - uint8_t ho_ref; - int ss; - struct gsm_lchan *lchan; - - OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); - DEBUGP(DRSL, "%s switchover: release complete," - " activating new pchan type\n", - gsm_ts_and_pchan_name(ts)); - - if (ts->dyn.pchan_is == ts->dyn.pchan_want) { - LOGP(DRSL, LOGL_ERROR, - "%s Requested to switchover dynamic channel to the" - " same type it is already in.\n", - gsm_ts_and_pchan_name(ts)); - return 0; - } - - for (ss = 0; ss < ts_subslots(ts); ss++) { - lchan = &ts->lchan[ss]; - if (lchan->rqd_ref) { - LOGP(DRSL, LOGL_ERROR, - "%s During dyn TS switchover, expecting no" - " Request Reference to be pending. Discarding!\n", - gsm_lchan_name(lchan)); - talloc_free(lchan->rqd_ref); - lchan->rqd_ref = NULL; - } - } - - /* - * When switching pchan modes, all lchans are unused. So always - * activate whatever wants to be activated on the first lchan. (We - * wouldn't remember to use lchan[1] across e.g. a PDCH deact anyway) - */ - lchan = ts->lchan; - - /* - * For TCH/x, the lchan->type has been set in lchan_alloc(), but it may - * have been lost during channel release due to dynamic switchover. - * - * For PDCH, the lchan->type will actually remain NONE. - * TODO: set GSM_LCHAN_PDTCH? - */ - switch (ts->dyn.pchan_want) { - case GSM_PCHAN_TCH_F: - lchan->type = GSM_LCHAN_TCH_F; - break; - case GSM_PCHAN_TCH_H: - lchan->type = GSM_LCHAN_TCH_H; - break; - case GSM_PCHAN_PDCH: - lchan->type = GSM_LCHAN_NONE; - break; - default: - LOGP(DRSL, LOGL_ERROR, - "%s Invalid target pchan for dynamic TS\n", - gsm_ts_and_pchan_name(ts)); - } - - act_type = (ts->dyn.pchan_want == GSM_PCHAN_PDCH) - ? RSL_ACT_OSMO_PDCH - : lchan->dyn.act_type; - ho_ref = (ts->dyn.pchan_want == GSM_PCHAN_PDCH) - ? 0 - : lchan->dyn.ho_ref; - - /* Fetch the rqd_ref back from before switchover started. */ - lchan->rqd_ref = lchan->dyn.rqd_ref; - lchan->rqd_ta = lchan->dyn.rqd_ta; - lchan->dyn.rqd_ref = NULL; - lchan->dyn.rqd_ta = 0; - - /* During switchover, we have received a release ack, which means that - * the act_timer has been stopped. Start the timer again so we mark - * this channel broken if the activation ack comes too late. */ - osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan); - osmo_timer_schedule(&lchan->act_timer, 4, 0); - - rc = rsl_chan_activate_lchan(lchan, act_type, ho_ref); - if (rc) { - LOGP(DRSL, LOGL_ERROR, - "%s RSL Chan Activate failed\n", - gsm_ts_and_pchan_name(ts)); - return dyn_ts_switchover_failed(ts, rc); - } - return 0; -} - -static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc) -{ - ts->dyn.pchan_want = ts->dyn.pchan_is; - LOGP(DRSL, LOGL_ERROR, "%s Error %d during dynamic channel switchover." - " Going back to previous pchan.\n", gsm_ts_and_pchan_name(ts), - rc); - return rc; -} - -static void dyn_ts_switchover_complete(struct gsm_lchan *lchan) -{ - enum gsm_phys_chan_config pchan_act; - enum gsm_phys_chan_config pchan_was; - struct gsm_bts_trx_ts *ts = lchan->ts; - - OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); - - pchan_act = pchan_for_lchant(lchan->type); - /* - * Paranoia: do the types match? - * In case of errors: we've received an act ack already, so what to do - * about it? Logging the error should suffice for now. - */ - if (pchan_act != ts->dyn.pchan_want) - LOGP(DRSL, LOGL_ERROR, - "%s Requested transition does not match lchan type %s\n", - gsm_ts_and_pchan_name(ts), - gsm_lchant_name(lchan->type)); - - pchan_was = ts->dyn.pchan_is; - ts->dyn.pchan_is = ts->dyn.pchan_want = pchan_act; - - if (pchan_was != ts->dyn.pchan_is) - LOGP(DRSL, LOGL_INFO, "%s switchover from %s complete.\n", - gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan_was)); -} - -/* Entry-point where L2 RSL from BTS enters */ -int abis_rsl_rcvmsg(struct msgb *msg) -{ - struct e1inp_sign_link *sign_link; - struct abis_rsl_common_hdr *rslh; - int rc = 0; - - if (!msg) { - DEBUGP(DRSL, "Empty RSL msg?..\n"); - return -1; - } - - if (msgb_l2len(msg) < sizeof(*rslh)) { - DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg)); - msgb_free(msg); - return -1; - } - - sign_link = msg->dst; - rslh = msgb_l2(msg); - - switch (rslh->msg_discr & 0xfe) { - case ABIS_RSL_MDISC_RLL: - rc = abis_rsl_rx_rll(msg); - break; - case ABIS_RSL_MDISC_DED_CHAN: - rc = abis_rsl_rx_dchan(msg); - break; - case ABIS_RSL_MDISC_COM_CHAN: - rc = abis_rsl_rx_cchan(msg); - break; - case ABIS_RSL_MDISC_TRX: - rc = abis_rsl_rx_trx(msg); - break; - case ABIS_RSL_MDISC_LOC: - LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL msg disc 0x%02x\n", - rslh->msg_discr); - break; - case ABIS_RSL_MDISC_IPACCESS: - rc = abis_rsl_rx_ipacc(msg); - break; - 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]); - rc = -EINVAL; - } - msgb_free(msg); - return rc; -} - -int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number, - struct rsl_ie_cb_cmd_type cb_command, - const uint8_t *data, int len) -{ - struct abis_rsl_dchan_hdr *dh; - struct msgb *cb_cmd; - - cb_cmd = rsl_msgb_alloc(); - if (!cb_cmd) - return -1; - - dh = (struct abis_rsl_dchan_hdr *) msgb_put(cb_cmd, sizeof(*dh)); - init_dchan_hdr(dh, RSL_MT_SMS_BC_CMD); - dh->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN; - dh->chan_nr = chan_number; /* TODO: check the chan config */ - - msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, *(uint8_t*)&cb_command); - msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data); - - cb_cmd->dst = bts->c0->rsl_link; - - return abis_rsl_sendmsg(cb_cmd); -} - -int rsl_nokia_si_begin(struct gsm_bts_trx *trx) -{ - struct abis_rsl_common_hdr *ch; - struct msgb *msg = rsl_msgb_alloc(); - - ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); - ch->msg_discr = ABIS_RSL_MDISC_TRX; - ch->msg_type = 0x40; /* Nokia SI Begin */ - - msg->dst = trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -int rsl_nokia_si_end(struct gsm_bts_trx *trx) -{ - struct abis_rsl_common_hdr *ch; - struct msgb *msg = rsl_msgb_alloc(); - - ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); - ch->msg_discr = ABIS_RSL_MDISC_TRX; - ch->msg_type = 0x41; /* Nokia SI End */ - - msgb_tv_put(msg, 0xFD, 0x00); /* Nokia Pagemode Info, No paging reorganisation required */ - - msg->dst = trx->rsl_link; - - return abis_rsl_sendmsg(msg); -} - -int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduction) -{ - struct abis_rsl_common_hdr *ch; - struct msgb *msg = rsl_msgb_alloc(); - - ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); - ch->msg_discr = ABIS_RSL_MDISC_DED_CHAN; - ch->msg_type = RSL_MT_BS_POWER_CONTROL; - - 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; - - return abis_rsl_sendmsg(msg); -} - -/** - * Release all allocated SAPIs starting from @param start and - * release them with the given release mode. Once the release - * confirmation arrives it will be attempted to release the - * the RF channel. - */ -int rsl_release_sapis_from(struct gsm_lchan *lchan, int start, - enum rsl_rel_mode release_mode) -{ - int no_sapi = 1; - int sapi; - - for (sapi = start; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { - uint8_t link_id; - if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) - continue; - - link_id = sapi; - if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H) - link_id |= 0x40; - rsl_release_request(lchan, link_id, release_mode); - no_sapi = 0; - } - - return no_sapi; -} - -int rsl_start_t3109(struct gsm_lchan *lchan) -{ - struct gsm_bts *bts = lchan->ts->trx->bts; - - osmo_timer_setup(&lchan->T3109, t3109_expired, lchan); - osmo_timer_schedule(&lchan->T3109, bts->network->T3109, 0); - return 0; -} - -/** - * \brief directly RF Channel Release the lchan - * - * When no SAPI was allocated, directly release the logical channel. This - * should only be called from chan_alloc.c on channel release handling. In - * case no SAPI was established the RF Channel can be directly released, - */ -int rsl_direct_rf_release(struct gsm_lchan *lchan) -{ - int i; - for (i = 0; i < ARRAY_SIZE(lchan->sapis); ++i) { - if (lchan->sapis[i] != LCHAN_SAPI_UNUSED) { - LOGP(DRSL, LOGL_ERROR, "%s SAPI(%d) still allocated.\n", - gsm_lchan_name(lchan), i); - return -1; - } - } - - /* Now release it */ - return rsl_rf_chan_release(lchan, 0, SACCH_NONE); -} - -/* Initial timeslot actions when a timeslot first comes into operation. */ -static bool gsm_ts_init(struct gsm_bts_trx_ts *ts) -{ - dyn_ts_init(ts); - return true; -} - -/* Trigger initial timeslot actions iff both OML and RSL are setup. */ -void gsm_ts_check_init(struct gsm_bts_trx_ts *ts) -{ - struct gsm_bts *bts = ts->trx->bts; - if (bts->model->oml_is_ts_ready - && !bts->model->oml_is_ts_ready(ts)) - return; - if (!ts->trx->rsl_link) - return; - if (ts->initialized) - return; - ts->initialized = gsm_ts_init(ts); -} - -void gsm_trx_mark_all_ts_uninitialized(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]; - ts->initialized = false; - } -} - -void gsm_bts_mark_all_ts_uninitialized(struct gsm_bts *bts) -{ - struct gsm_bts_trx *trx; - llist_for_each_entry(trx, &bts->trx_list, list) - gsm_trx_mark_all_ts_uninitialized(trx); -} diff --git a/src/libbsc/acc_ramp.c b/src/libbsc/acc_ramp.c deleted file mode 100644 index ac9f02da1..000000000 --- a/src/libbsc/acc_ramp.c +++ /dev/null @@ -1,363 +0,0 @@ -/* (C) 2018 by sysmocom s.f.m.c. GmbH - * - * Author: Stefan Sperling - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -/* - * Check if an ACC has been permanently barred for a BTS, - * e.g. with the 'rach access-control-class' VTY command. - */ -static bool acc_is_permanently_barred(struct gsm_bts *bts, unsigned int acc) -{ - OSMO_ASSERT(acc >= 0 && acc <= 9); - if (acc == 8 || acc == 9) - return (bts->si_common.rach_control.t2 & (1 << (acc - 8))); - return (bts->si_common.rach_control.t3 & (1 << (acc))); -} - -static void allow_one_acc(struct acc_ramp *acc_ramp, unsigned int acc) -{ - OSMO_ASSERT(acc >= 0 && acc <= 9); - if (acc_ramp->barred_accs & (1 << acc)) - LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: allowing Access Control Class %u\n", acc_ramp->bts->nr, acc); - acc_ramp->barred_accs &= ~(1 << acc); -} - -static void barr_one_acc(struct acc_ramp *acc_ramp, unsigned int acc) -{ - OSMO_ASSERT(acc >= 0 && acc <= 9); - if ((acc_ramp->barred_accs & (1 << acc)) == 0) - LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: barring Access Control Class %u\n", acc_ramp->bts->nr, acc); - acc_ramp->barred_accs |= (1 << acc); -} - -static void barr_all_accs(struct acc_ramp *acc_ramp) -{ - unsigned int acc; - for (acc = 0; acc < 10; acc++) { - if (!acc_is_permanently_barred(acc_ramp->bts, acc)) - barr_one_acc(acc_ramp, acc); - } -} - -static void allow_all_accs(struct acc_ramp *acc_ramp) -{ - unsigned int acc; - for (acc = 0; acc < 10; acc++) { - if (!acc_is_permanently_barred(acc_ramp->bts, acc)) - allow_one_acc(acc_ramp, acc); - } -} - -static unsigned int get_next_step_interval(struct acc_ramp *acc_ramp) -{ - struct gsm_bts *bts = acc_ramp->bts; - uint64_t load; - - if (acc_ramp->step_interval_is_fixed) - return acc_ramp->step_interval_sec; - - /* Scale the step interval to current channel load average. */ - load = (bts->chan_load_avg << 8); /* convert to fixed-point */ - acc_ramp->step_interval_sec = ((load * ACC_RAMP_STEP_INTERVAL_MAX) / 100) >> 8; - if (acc_ramp->step_interval_sec < ACC_RAMP_STEP_SIZE_MIN) - acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN; - else if (acc_ramp->step_interval_sec > ACC_RAMP_STEP_INTERVAL_MAX) - acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MAX; - - LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: step interval set to %u seconds based on %u%% channel load average\n", - bts->nr, acc_ramp->step_interval_sec, bts->chan_load_avg); - return acc_ramp->step_interval_sec; -} - -static void do_acc_ramping_step(void *data) -{ - struct acc_ramp *acc_ramp = data; - int i; - - /* Shortcut in case we only do one ramping step. */ - if (acc_ramp->step_size == ACC_RAMP_STEP_SIZE_MAX) { - allow_all_accs(acc_ramp); - gsm_bts_set_system_infos(acc_ramp->bts); - return; - } - - /* Allow 'step_size' ACCs, starting from ACC0. ACC9 will be allowed last. */ - for (i = 0; i < acc_ramp->step_size; i++) { - int idx = ffs(acc_ramp_get_barred_t3(acc_ramp)); - if (idx > 0) { - /* One of ACC0-ACC7 is still bared. */ - unsigned int acc = idx - 1; - if (!acc_is_permanently_barred(acc_ramp->bts, acc)) - allow_one_acc(acc_ramp, acc); - } else { - idx = ffs(acc_ramp_get_barred_t2(acc_ramp)); - if (idx == 1 || idx == 2) { - /* ACC8 or ACC9 is still barred. */ - unsigned int acc = idx - 1 + 8; - if (!acc_is_permanently_barred(acc_ramp->bts, acc)) - allow_one_acc(acc_ramp, acc); - } else { - /* All ACCs are now allowed. */ - break; - } - } - } - - gsm_bts_set_system_infos(acc_ramp->bts); - - /* If we have not allowed all ACCs yet, schedule another ramping step. */ - if (acc_ramp_get_barred_t2(acc_ramp) != 0x00 || - acc_ramp_get_barred_t3(acc_ramp) != 0x00) - osmo_timer_schedule(&acc_ramp->step_timer, get_next_step_interval(acc_ramp), 0); -} - -/* 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) -{ - 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) - return 0; - - if (nsd->obj_class != NM_OC_RADIO_CARRIER) - return 0; - - trx = nsd->obj; - - LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: administrative state %s -> %s\n", - acc_ramp->bts->nr, trx->nr, - get_value_string(abis_nm_adm_state_names, nsd->old_state->administrative), - get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative)); - LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: operational state %s -> %s\n", - acc_ramp->bts->nr, trx->nr, - 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) - return 0; - - /* RSL must already be up. We cannot send RACH system information to the BTS otherwise. */ - if (trx->rsl_link == NULL) { - LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change because RSL link is down\n", - acc_ramp->bts->nr, trx->nr); - 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 - LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " - "because TRX is transitioning into operational state '%s'\n", - acc_ramp->bts->nr, trx->nr, - 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 - LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " - "because TRX is not usable\n", acc_ramp->bts->nr, trx->nr); - } - break; - case NM_STATE_LOCKED: - case NM_STATE_SHUTDOWN: - abort_ramping = true; - break; - case NM_STATE_NULL: - default: - LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized administrative state '0x%x' " - "reported for TRX 0\n", acc_ramp->bts->nr, 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 - LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " - "because TRX is transitioning into administrative state '%s'\n", - acc_ramp->bts->nr, trx->nr, - 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 - LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " - "because TRX is in administrative state '%s'\n", - acc_ramp->bts->nr, trx->nr, - 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: - LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized operational state '0x%x' " - "reported for TRX 0\n", acc_ramp->bts->nr, nsd->new_state->administrative); - break; - } - } - - if (trigger_ramping) - acc_ramp_trigger(acc_ramp); - else if (abort_ramping) - acc_ramp_abort(acc_ramp); - - return 0; -} - -/*! - * Initialize an acc_ramp data structure. - * Storage for this structure must be provided by the caller. - * - * By default, ACC ramping is disabled and all ACCs are allowed. - * - * \param[in] acc_ramp Pointer to acc_ramp structure to be initialized. - * \param[in] bts BTS which uses this ACC ramp data structure. - */ -void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts) -{ - acc_ramp->bts = bts; - acc_ramp_set_enabled(acc_ramp, false); - acc_ramp->step_size = ACC_RAMP_STEP_SIZE_DEFAULT; - acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN; - acc_ramp->step_interval_is_fixed = false; - allow_all_accs(acc_ramp); - 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); -} - -/*! - * Change the ramping step size which controls how many ACCs will be allowed per ramping step. - * Returns negative on error (step_size out of range), else zero. - * \param[in] acc_ramp Pointer to acc_ramp structure. - * \param[in] step_size The new step size value. - */ -int acc_ramp_set_step_size(struct acc_ramp *acc_ramp, unsigned int step_size) -{ - if (step_size < ACC_RAMP_STEP_SIZE_MIN || step_size > ACC_RAMP_STEP_SIZE_MAX) - return -ERANGE; - - acc_ramp->step_size = step_size; - LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step size set to %u\n", acc_ramp->bts->nr, step_size); - return 0; -} - -/*! - * Change the ramping step interval to a fixed value. Unless this function is called, - * the interval is automatically scaled to the BTS channel load average. - * \param[in] acc_ramp Pointer to acc_ramp structure. - * \param[in] step_interval The new fixed step interval in seconds. - */ -int acc_ramp_set_step_interval(struct acc_ramp *acc_ramp, unsigned int step_interval) -{ - if (step_interval < ACC_RAMP_STEP_INTERVAL_MIN || step_interval > ACC_RAMP_STEP_INTERVAL_MAX) - return -ERANGE; - - acc_ramp->step_interval_sec = step_interval; - acc_ramp->step_interval_is_fixed = true; - LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to %u seconds\n", - acc_ramp->bts->nr, step_interval); - return 0; -} - -/*! - * Clear a previously set fixed ramping step interval, so that the interval - * is again automatically scaled to the BTS channel load average. - * \param[in] acc_ramp Pointer to acc_ramp structure. - */ -void acc_ramp_set_step_interval_dynamic(struct acc_ramp *acc_ramp) -{ - acc_ramp->step_interval_is_fixed = false; - LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to 'dynamic'\n", - acc_ramp->bts->nr); -} - -/*! - * Determine if ACC ramping should be started according to configuration, and - * begin the ramping process if the necessary conditions are present. - * Perform at least one ramping step to allow 'step_size' ACCs. - * If 'step_size' is ACC_RAMP_STEP_SIZE_MAX, or if ACC ramping is disabled, - * all ACCs will be allowed immediately. - * \param[in] acc_ramp Pointer to acc_ramp structure. - */ -void acc_ramp_trigger(struct acc_ramp *acc_ramp) -{ - /* Abort any previously running ramping process and allow all available ACCs. */ - acc_ramp_abort(acc_ramp); - - if (acc_ramp_is_enabled(acc_ramp)) { - /* Set all available ACCs to barred and start ramping up. */ - barr_all_accs(acc_ramp); - do_acc_ramping_step(acc_ramp); - } -} - -/*! - * Abort the ramping process and allow all available ACCs immediately. - * \param[in] acc_ramp Pointer to acc_ramp structure. - */ -void acc_ramp_abort(struct acc_ramp *acc_ramp) -{ - if (osmo_timer_pending(&acc_ramp->step_timer)) - osmo_timer_del(&acc_ramp->step_timer); - - allow_all_accs(acc_ramp); -} diff --git a/src/libbsc/arfcn_range_encode.c b/src/libbsc/arfcn_range_encode.c deleted file mode 100644 index 84f9f635f..000000000 --- a/src/libbsc/arfcn_range_encode.c +++ /dev/null @@ -1,336 +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 . - */ - -#include -#include - -#include - -#include - -#include - -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; - - /* - * 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/libbsc/bsc_api.c b/src/libbsc/bsc_api.c deleted file mode 100644 index 8ae781e96..000000000 --- a/src/libbsc/bsc_api.c +++ /dev/null @@ -1,841 +0,0 @@ -/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */ - -/* (C) 2010-2011 by Holger Hans Peter Freyther - * (C) 2010-2011 by On-Waves - * (C) 2009,2017 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#define GSM0808_T10_VALUE 6, 0 - -#define HO_DTAP_CACHE_MSGB_CB_LINK_ID 0 -#define HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH 1 - -static void rll_ind_cb(struct gsm_lchan *, uint8_t, void *, enum bsc_rllr_ind); -static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id); -static void handle_release(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); -static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); -static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); - -/*! \brief Determine and apply AMR multi-rate configuration to lchan - * Determine which AMR multi-rate configuration to use and apply it to - * the lchan (so it can be communicated to BTS and MS during channel - * activation. - * \param[in] conn subscriber connection (used to resolve bsc_api) - * \param[out] lchan logical channel to which to apply mr config - * \param[in] full_rate whether to use full-rate (1) or half-rate (0) config - */ -static void handle_mr_config(struct gsm_subscriber_connection *conn, - struct gsm_lchan *lchan, int full_rate) -{ - struct bsc_api *api; - api = conn->network->bsc_api; - struct amr_multirate_conf *mr; - struct gsm48_multi_rate_conf *mr_conf; - - /* BSC api override for this method, used in OsmoBSC mode with - * bsc_mr_config() to use MSC-specific/specified configuration */ - if (api->mr_config) - return api->mr_config(conn, lchan, full_rate); - - /* NITB case: use the BTS-specic multi-rate configuration from - * the vty/configuration file */ - if (full_rate) - mr = &lchan->ts->trx->bts->mr_full; - else - mr = &lchan->ts->trx->bts->mr_half; - - mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie; - mr_conf->ver = 1; - - /* default, if no AMR codec defined */ - if (!mr->gsm48_ie[1]) { - mr_conf->icmi = 1; - mr_conf->m5_90 = 1; - } - /* store encoded MR config IE lchan for both MS (uplink) and BTS - * (downlink) directions */ - gsm48_multirate_config(lchan->mr_ms_lv, mr, mr->ms_mode); - gsm48_multirate_config(lchan->mr_bts_lv, mr, mr->bts_mode); -} - -/* - * Start a new assignment and make sure that it is completed within T10 either - * positively, negatively or by the timeout. - * - * 1.) allocate a new lchan - * 2.) copy the encryption key and other data from the - * old to the new channel. - * 3.) RSL Channel Activate this channel and wait - * - * -> Signal handler for the LCHAN - * 4.) Send GSM 04.08 assignment command to the MS - * - * -> Assignment Complete/Assignment Failure - * 5.) Release the SDCCH, continue signalling on the new link - */ -static int handle_new_assignment(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate) -{ - struct gsm_lchan *new_lchan; - enum gsm_chan_t chan_type; - - chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H; - - new_lchan = lchan_alloc(conn_get_bts(conn), chan_type, 0); - - if (!new_lchan) { - LOGP(DMSC, LOGL_NOTICE, "%s No free channel for %s\n", - bsc_subscr_name(conn->bsub), gsm_lchant_name(chan_type)); - return -1; - } - - /* check if we are on TCH/F and requested TCH/H, but got TCH/F */ - if (conn->lchan->type == new_lchan->type - && chan_type != new_lchan->type) { - LOGPLCHAN(conn->lchan, DHO, LOGL_NOTICE, - "-> %s Will not re-assign to identical channel type, %s was requested\n", - gsm_lchan_name(new_lchan), gsm_lchant_name(chan_type)); - lchan_free(new_lchan); - return -1; - } - - /* copy old data to the new channel */ - memcpy(&new_lchan->encr, &conn->lchan->encr, sizeof(new_lchan->encr)); - new_lchan->ms_power = conn->lchan->ms_power; - new_lchan->bs_power = conn->lchan->bs_power; - new_lchan->rqd_ta = conn->lchan->rqd_ta; - - /* copy new data to it */ - new_lchan->tch_mode = chan_mode; - new_lchan->rsl_cmode = (chan_mode == GSM48_CMODE_SIGN) ? - RSL_CMOD_SPD_SIGN : RSL_CMOD_SPD_SPEECH; - - /* handle AMR correctly */ - if (chan_mode == GSM48_CMODE_SPEECH_AMR) - handle_mr_config(conn, new_lchan, full_rate); - - if (rsl_chan_activate_lchan(new_lchan, 0x1, 0) < 0) { - LOGPLCHAN(new_lchan, DHO, LOGL_ERROR, "could not activate channel\n"); - lchan_free(new_lchan); - return -1; - } - - /* remember that we have the channel */ - conn->secondary_lchan = new_lchan; - new_lchan->conn = conn; - return 0; -} - -static void ho_dtap_cache_add(struct gsm_subscriber_connection *conn, struct msgb *msg, - int link_id, bool allow_sacch) -{ - if (conn->ho_dtap_cache_len >= 23) { - LOGP(DHO, LOGL_ERROR, "%s: Cannot cache more DTAP messages," - " already reached sane maximum of %u cached messages\n", - bsc_subscr_name(conn->bsub), conn->ho_dtap_cache_len); - msgb_free(msg); - return; - } - conn->ho_dtap_cache_len ++; - LOGP(DHO, LOGL_DEBUG, "%s: Caching DTAP message during ho/ass (%u)\n", - bsc_subscr_name(conn->bsub), conn->ho_dtap_cache_len); - msg->cb[HO_DTAP_CACHE_MSGB_CB_LINK_ID] = (unsigned long)link_id; - msg->cb[HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH] = allow_sacch ? 1 : 0; - msgb_enqueue(&conn->ho_dtap_cache, msg); -} - -void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send) -{ - struct msgb *msg; - unsigned int flushed_count = 0; - - if (conn->secondary_lchan || conn->ho) { - LOGP(DHO, LOGL_ERROR, "%s: Cannot send cached DTAP messages, handover/assignment is still ongoing\n", - bsc_subscr_name(conn->bsub)); - send = 0; - } - - while ((msg = msgb_dequeue(&conn->ho_dtap_cache))) { - conn->ho_dtap_cache_len --; - flushed_count ++; - if (send) { - int link_id = (int)msg->cb[HO_DTAP_CACHE_MSGB_CB_LINK_ID]; - bool allow_sacch = !!msg->cb[HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH]; - LOGP(DHO, LOGL_DEBUG, "%s: Sending cached DTAP message after handover/assignment (%u/%u)\n", - bsc_subscr_name(conn->bsub), flushed_count, conn->ho_dtap_cache_len); - gsm0808_submit_dtap(conn, msg, link_id, allow_sacch); - } else - msgb_free(msg); - } -} - -int bsc_api_init(struct gsm_network *network, struct bsc_api *api) -{ - network->bsc_api = api; - return 0; -} - -/*! \brief process incoming 08.08 DTAP from MSC (send via BTS to MS) */ -int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, - struct msgb *msg, int link_id, int allow_sacch) -{ - uint8_t sapi; - - - if (!conn->lchan) { - LOGP(DMSC, LOGL_ERROR, - "%s Called submit dtap without an lchan.\n", - bsc_subscr_name(conn->bsub)); - msgb_free(msg); - return -1; - } - - /* buffer message during assignment / handover */ - if (conn->secondary_lchan || conn->ho) { - ho_dtap_cache_add(conn, msg, link_id, !! allow_sacch); - return 0; - } - - 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) { - if (conn->lchan->type == GSM_LCHAN_TCH_F || conn->lchan->type == GSM_LCHAN_TCH_H) - link_id |= 0x40; - } - - msg->l3h = msg->data; - /* is requested SAPI already up? */ - if (conn->lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) { - /* Establish L2 for additional SAPI */ - OBSC_LINKID_CB(msg) = link_id; - if (rll_establish(msg->lchan, sapi, rll_ind_cb, msg) != 0) { - msgb_free(msg); - send_sapi_reject(conn, link_id); - return -1; - } - return 0; - } else { - /* Directly forward via RLL/RSL to BTS */ - return rsl_data_request(msg, link_id); - } -} - -/* - * \brief Check if the given channel is compatible with the mode/fullrate - */ -static int chan_compat_with_mode(struct gsm_lchan *lchan, int chan_mode, int full_rate) -{ - switch (chan_mode) { - case GSM48_CMODE_SIGN: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - case GSM_LCHAN_TCH_H: - case GSM_LCHAN_SDCCH: - return 1; - default: - return 0; - } - case GSM48_CMODE_SPEECH_V1: - case GSM48_CMODE_SPEECH_AMR: - case GSM48_CMODE_DATA_3k6: - case GSM48_CMODE_DATA_6k0: - /* these services can all run on TCH/H, but we may have - * an explicit override by the 'full_rate' argument */ - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return full_rate ? 1 : 0; - case GSM_LCHAN_TCH_H: - return full_rate ? 0 : 1; - default: - return 0; - } - case GSM48_CMODE_DATA_12k0: - case GSM48_CMODE_DATA_14k5: - case GSM48_CMODE_SPEECH_EFR: - /* these services all explicitly require a TCH/F */ - return (lchan->type == GSM_LCHAN_TCH_F) ? 1 : 0; - default: - return 0; - } -} - -/*! Send a GSM08.08 Assignment Request. Right now this does not contain the - * audio codec type or the allowed rates for the config. In case the current - * channel does not allow the selected mode a new one will be allocated. - * \param[out] conn related subscriber connection - * \param[in] chan_mode mode of the channel (see enum gsm48_chan_mode) - * \param[in] full_rate select full rate or half rate channel - * \returns 0 on success, 1 when no operation is neccessary, -1 on failure */ -int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate) -{ - /* TODO: Add multirate configuration, make it work for more than audio. */ - - struct bsc_api *api; - api = conn->network->bsc_api; - - if (!chan_compat_with_mode(conn->lchan, chan_mode, full_rate)) { - if (handle_new_assignment(conn, chan_mode, full_rate) != 0) - goto error; - } else { - /* Check if the channel is already in the requested mode, if - * yes, we skip unnecessary channel mode modify operations. */ - if (conn->lchan->tch_mode == chan_mode) - return 1; - - if (chan_mode == GSM48_CMODE_SPEECH_AMR) - handle_mr_config(conn, conn->lchan, full_rate); - - LOGPLCHAN(conn->lchan, DMSC, LOGL_NOTICE, - "Sending ChanModify for speech: %s\n", - get_value_string(gsm48_chan_mode_names, chan_mode)); - gsm48_lchan_modify(conn->lchan, chan_mode); - } - - /* we expect the caller will manage T10 */ - return 0; - -error: - api->assign_fail(conn, 0, NULL); - return -1; -} - -int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, unsigned int mi_len, - uint8_t *mi, int chan_type) -{ - return rsl_paging_cmd(bts, page_group, mi_len, mi, chan_type, false); -} - -static void handle_ass_compl(struct gsm_subscriber_connection *conn, - struct msgb *msg) -{ - struct gsm48_hdr *gh = msgb_l3(msg); - struct bsc_api *api = conn->network->bsc_api; - enum gsm48_rr_cause cause; - - /* Expecting gsm48_hdr + cause value */ - if (msgb_l3len(msg) != sizeof(*gh) + 1) { - LOGPLCHAN(msg->lchan, DRR, LOGL_ERROR, - "RR Assignment Complete: length invalid: %u, expected %zu\n", - msgb_l3len(msg), sizeof(*gh) + 1); - return; - } - - cause = gh->data[0]; - - LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "ASSIGNMENT COMPLETE cause = %s\n", - rr_cause_name(cause)); - - if (conn->ho) { - struct lchan_signal_data sig = { - .lchan = msg->lchan, - }; - osmo_signal_dispatch(SS_LCHAN, S_LCHAN_ASSIGNMENT_COMPL, &sig); - /* FIXME: release old channel */ - - /* send pending messages, if any */ - ho_dtap_cache_flush(conn, 1); - - return; - } - - if (conn->secondary_lchan != msg->lchan) { - LOGPLCHAN(msg->lchan, DRR, LOGL_ERROR, - "RR Assignment Complete does not match conn's secondary lchan.\n"); - return; - } - - /* swap channels */ - osmo_timer_del(&conn->T10); - - lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END); - conn->lchan = conn->secondary_lchan; - conn->secondary_lchan = NULL; - - /* send pending messages, if any */ - ho_dtap_cache_flush(conn, 1); - - if (is_ipaccess_bts(conn_get_bts(conn)) && conn->lchan->tch_mode != GSM48_CMODE_SIGN) - rsl_ipacc_crcx(conn->lchan); - - api->assign_compl(conn, cause); -} - -static void handle_ass_fail(struct gsm_subscriber_connection *conn, - struct msgb *msg) -{ - struct bsc_api *api = conn->network->bsc_api; - uint8_t *rr_failure; - struct gsm48_hdr *gh; - - if (conn->ho) { - struct lchan_signal_data sig; - struct gsm48_hdr *gh = msgb_l3(msg); - - LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "ASSIGNMENT FAILED cause = %s\n", - rr_cause_name(gh->data[0])); - - sig.lchan = msg->lchan; - sig.mr = NULL; - osmo_signal_dispatch(SS_LCHAN, S_LCHAN_ASSIGNMENT_FAIL, &sig); - /* FIXME: release allocated new channel */ - - /* send pending messages, if any */ - ho_dtap_cache_flush(conn, 1); - - return; - } - - if (conn->lchan != msg->lchan) { - LOGPLCHAN(msg->lchan, DMSC, LOGL_ERROR, - "Assignment failure should occur on primary lchan.\n"); - return; - } - - /* stop the timer and release it */ - osmo_timer_del(&conn->T10); - if (conn->secondary_lchan) { - lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); - conn->secondary_lchan = NULL; - } - - /* send pending messages, if any */ - ho_dtap_cache_flush(conn, 1); - - gh = msgb_l3(msg); - if (msgb_l3len(msg) - sizeof(*gh) != 1) { - LOGPLCHAN(conn->lchan, DMSC, LOGL_ERROR, "assignment failure unhandled: %zu\n", - msgb_l3len(msg) - sizeof(*gh)); - rr_failure = NULL; - } else { - rr_failure = &gh->data[0]; - } - - api->assign_fail(conn, - GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE, - rr_failure); -} - -static void handle_classmark_chg(struct gsm_subscriber_connection *conn, - struct msgb *msg) -{ - struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; - struct gsm48_hdr *gh = msgb_l3(msg); - unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); - uint8_t cm2_len, cm3_len = 0; - uint8_t *cm2, *cm3 = NULL; - - LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "CLASSMARK CHANGE "); - - /* classmark 2 */ - cm2_len = gh->data[0]; - cm2 = &gh->data[1]; - DEBUGPC(DRR, "CM2(len=%u) ", cm2_len); - - if (payload_len > cm2_len + 1) { - /* we must have a classmark3 */ - if (gh->data[cm2_len+1] != 0x20) { - DEBUGPC(DRR, "ERR CM3 TAG\n"); - return; - } - if (cm2_len > 3) { - DEBUGPC(DRR, "CM2 too long!\n"); - return; - } - - cm3_len = gh->data[cm2_len+2]; - cm3 = &gh->data[cm2_len+3]; - if (cm3_len > 14) { - DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len); - return; - } - DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len); - } - api->classmark_chg(conn, cm2, cm2_len, cm3, cm3_len); -} - -/* Chapter 9.1.16 Handover complete */ -static void handle_rr_ho_compl(struct msgb *msg) -{ - struct lchan_signal_data sig; - struct gsm48_hdr *gh = msgb_l3(msg); - - LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, - "HANDOVER COMPLETE cause = %s\n", rr_cause_name(gh->data[0])); - - sig.lchan = msg->lchan; - sig.mr = NULL; - osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig); - /* FIXME: release old channel */ - - /* send pending messages, if any */ - ho_dtap_cache_flush(msg->lchan->conn, 1); -} - -/* Chapter 9.1.17 Handover Failure */ -static void handle_rr_ho_fail(struct msgb *msg) -{ - struct lchan_signal_data sig; - struct gsm48_hdr *gh = msgb_l3(msg); - - /* Log on both RR and HO categories: it is an RR message, but is still quite important when - * filtering on HO. */ - LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, - "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0])); - LOGPLCHAN(msg->lchan, DHO, LOGL_DEBUG, - "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0])); - - sig.lchan = msg->lchan; - sig.mr = NULL; - osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig); - /* FIXME: release allocated new channel */ - - /* send pending messages, if any */ - ho_dtap_cache_flush(msg->lchan->conn, 1); -} - - -static void dispatch_dtap(struct gsm_subscriber_connection *conn, - uint8_t link_id, struct msgb *msg) -{ - struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; - struct gsm48_hdr *gh; - uint8_t pdisc; - uint8_t msg_type; - int rc; - - if (msgb_l3len(msg) < sizeof(*gh)) { - LOGP(DMSC, LOGL_ERROR, "(%s) Message too short for a GSM48 header.\n", - bsc_subscr_name(conn->bsub)); - return; - } - - gh = msgb_l3(msg); - pdisc = gsm48_hdr_pdisc(gh); - msg_type = gsm48_hdr_msg_type(gh); - - /* the idea is to handle all RR messages here, and only hand - * MM/CC/SMS-CP/LCS up to the MSC. Some messages like PAGING - * RESPONSE or CM SERVICE REQUEST will not be covered here, as - * they are only possible in the first L3 message of each L2 - * channel, i.e. 'conn' will not exist and gsm0408_rcvmsg() - * will call api->compl_l3() for it */ - switch (pdisc) { - case GSM48_PDISC_RR: - switch (msg_type) { - case GSM48_MT_RR_GPRS_SUSP_REQ: - LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, - "%s\n", gsm48_rr_msg_name(GSM48_MT_RR_GPRS_SUSP_REQ)); - break; - case GSM48_MT_RR_STATUS: - LOGPLCHAN(msg->lchan, DRR, LOGL_NOTICE, - "%s (cause: %s)\n", gsm48_rr_msg_name(GSM48_MT_RR_STATUS), - rr_cause_name(gh->data[0])); - break; - case GSM48_MT_RR_MEAS_REP: - /* This shouldn't actually end up here, as RSL treats - * L3 Info of 08.58 MEASUREMENT REPORT different by calling - * directly into gsm48_parse_meas_rep */ - LOGPLCHAN(msg->lchan, DMEAS, LOGL_ERROR, - "DIRECT GSM48 MEASUREMENT REPORT ?!?\n"); - gsm48_tx_rr_status(conn, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); - break; - case GSM48_MT_RR_HANDO_COMPL: - handle_rr_ho_compl(msg); - break; - case GSM48_MT_RR_HANDO_FAIL: - handle_rr_ho_fail(msg); - break; - case GSM48_MT_RR_CIPH_M_COMPL: - if (api->cipher_mode_compl) - api->cipher_mode_compl(conn, msg, - conn->lchan->encr.alg_id); - break; - case GSM48_MT_RR_ASS_COMPL: - handle_ass_compl(conn, msg); - break; - case GSM48_MT_RR_ASS_FAIL: - handle_ass_fail(conn, msg); - break; - case GSM48_MT_RR_CHAN_MODE_MODIF_ACK: - osmo_timer_del(&conn->T10); - rc = gsm48_rx_rr_modif_ack(msg); - if (rc < 0) { - api->assign_fail(conn, - GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, - NULL); - } else if (rc >= 0) { - api->assign_compl(conn, 0); - } - break; - case GSM48_MT_RR_CLSM_CHG: - handle_classmark_chg(conn, msg); - break; - case GSM48_MT_RR_APP_INFO: - /* Passing RR APP INFO to MSC, not quite - * according to spec */ - if (api->dtap) - api->dtap(conn, link_id, msg); - break; - default: - /* Drop unknown RR message */ - LOGPLCHAN(msg->lchan, DRR, LOGL_NOTICE, - "Dropping %s 04.08 RR message\n", gsm48_rr_msg_name(msg_type)); - gsm48_tx_rr_status(conn, GSM48_RR_CAUSE_MSG_TYPE_N); - break; - } - break; - default: - if (api->dtap) - api->dtap(conn, link_id, msg); - break; - } -} - -/*! \brief RSL has received a DATA INDICATION with L3 from MS */ -int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id) -{ - int rc; - struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; - struct gsm_lchan *lchan; - - lchan = msg->lchan; - if (lchan->state != LCHAN_S_ACTIVE) { - LOGPLCHAN(msg->lchan, DRSL, LOGL_INFO, "Got data in non active state, discarding.\n"); - return -1; - } - - - if (lchan->conn) { - /* if we already have a connection, forward via DTAP to - * MSC */ - dispatch_dtap(lchan->conn, link_id, msg); - } else { - /* allocate a new connection */ - rc = BSC_API_CONN_POL_REJECT; - lchan->conn = bsc_subscr_con_allocate(msg->lchan->ts->trx->bts->network); - if (!lchan->conn) { - lchan_release(lchan, 1, RSL_REL_NORMAL); - return -1; - } - lchan->conn->lchan = lchan; - - /* fwd via bsc_api to send COMPLETE L3 INFO to MSC */ - rc = api->compl_l3(lchan->conn, msg, 0); - - if (rc != BSC_API_CONN_POL_ACCEPT) { - //osmo_fsm_inst_dispatch(lchan->conn->fi, FIXME, NULL); - } - } - - return 0; -} - -/*! \brief We received a GSM 08.08 CIPHER MODE from the MSC */ -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); -} - -/* - * Release all occupied RF Channels but stay around for more. - */ -int gsm0808_clear(struct gsm_subscriber_connection *conn) -{ - if (conn->ho) - bsc_clear_handover(conn, 1); - - if (conn->secondary_lchan) - lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); - - if (conn->lchan) - lchan_release(conn->lchan, 1, RSL_REL_NORMAL); - - conn->lchan = NULL; - conn->secondary_lchan = NULL; - - osmo_timer_del(&conn->T10); - - return 0; -} - -static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id) -{ - struct bsc_api *api; - - if (!conn) - return; - - api = conn->network->bsc_api; - if (!api || !api->sapi_n_reject) - return; - - api->sapi_n_reject(conn, link_id); -} - -static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, enum bsc_rllr_ind rllr_ind) -{ - struct msgb *msg = _data; - - /* - * There seems to be a small window that the RLL timer can - * fire after a lchan_release call and before the S_CHALLOC_FREED - * is called. Check if a conn is set before proceeding. - */ - if (!lchan->conn) - return; - - switch (rllr_ind) { - case BSC_RLLR_IND_EST_CONF: - rsl_data_request(msg, OBSC_LINKID_CB(msg)); - break; - case BSC_RLLR_IND_REL_IND: - case BSC_RLLR_IND_ERR_IND: - case BSC_RLLR_IND_TIMEOUT: - send_sapi_reject(lchan->conn, OBSC_LINKID_CB(msg)); - msgb_free(msg); - break; - } -} - -static int bsc_handle_lchan_signal(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct bsc_api *bsc; - struct gsm_lchan *lchan; - struct lchan_signal_data *lchan_data; - - if (subsys != SS_LCHAN) - return 0; - - - lchan_data = signal_data; - if (!lchan_data->lchan || !lchan_data->lchan->conn) - return 0; - - lchan = lchan_data->lchan; - bsc = lchan->ts->trx->bts->network->bsc_api; - if (!bsc) - return 0; - - switch (signal) { - case S_LCHAN_UNEXPECTED_RELEASE: - handle_release(lchan->conn, bsc, lchan); - break; - case S_LCHAN_ACTIVATE_ACK: - handle_chan_ack(lchan->conn, bsc, lchan); - break; - case S_LCHAN_ACTIVATE_NACK: - handle_chan_nack(lchan->conn, bsc, lchan); - break; - } - - return 0; -} - -static void handle_release(struct gsm_subscriber_connection *conn, - struct bsc_api *bsc, struct gsm_lchan *lchan) -{ - if (conn->secondary_lchan == lchan) { - osmo_timer_del(&conn->T10); - conn->secondary_lchan = NULL; - - bsc->assign_fail(conn, - GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, - NULL); - } - - /* clear the connection now */ - if (bsc->clear_request) - bsc->clear_request(conn, 0); - - /* now give up all channels */ - if (conn->lchan == lchan) - conn->lchan = NULL; - if (conn->ho && conn->ho->new_lchan == lchan) - bsc_clear_handover(conn, 0); - lchan->conn = NULL; -} - -static void handle_chan_ack(struct gsm_subscriber_connection *conn, - struct bsc_api *api, struct gsm_lchan *lchan) -{ - if (conn->secondary_lchan != lchan) - return; - - LOGPLCHAN(lchan, DMSC, LOGL_NOTICE, "Sending RR Assignment\n"); - gsm48_send_rr_ass_cmd(conn->lchan, lchan, lchan->ms_power); -} - -static void handle_chan_nack(struct gsm_subscriber_connection *conn, - struct bsc_api *api, struct gsm_lchan *lchan) -{ - if (conn->secondary_lchan != lchan) - return; - - LOGPLCHAN(lchan, DMSC, LOGL_ERROR, "Channel activation failed. Waiting for timeout now\n"); - conn->secondary_lchan->conn = NULL; - conn->secondary_lchan = NULL; -} - -static __attribute__((constructor)) void on_dso_load_bsc(void) -{ - osmo_signal_register_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL); -} diff --git a/src/libbsc/bsc_ctrl_commands.c b/src/libbsc/bsc_ctrl_commands.c deleted file mode 100644 index 171feaff0..000000000 --- a/src/libbsc/bsc_ctrl_commands.c +++ /dev/null @@ -1,500 +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 . - * - */ -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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); - } - - 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); - 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->bsc_data->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->bsc_data->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/libbsc/bsc_ctrl_lookup.c b/src/libbsc/bsc_ctrl_lookup.c deleted file mode 100644 index 38d1ba4ea..000000000 --- a/src/libbsc/bsc_ctrl_lookup.c +++ /dev/null @@ -1,123 +0,0 @@ -/* SNMP-like status interface. Look-up of BTS/TRX/MSC - * - * (C) 2010-2011 by Daniel Willmann - * (C) 2010-2011 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 General Public License as published by - * the Free Software Foundation; either version 2 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. - * - */ - -#include - -#include -#include -#include -#include -#include -#include - -extern vector ctrl_node_vec; - -/*! \brief control interface lookup function for bsc/bts/msc gsm_data - * \param[in] data Private data passed to controlif_setup() - * \param[in] vline Vector of the line holding the command string - * \param[out] node_type type (CTRL_NODE_) that was determined - * \param[out] node_data private dta of node that was determined - * \param i Current index into vline, up to which it is parsed - */ -static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type, - void **node_data, int *i) -{ - struct gsm_network *net = data; - struct gsm_bts *bts = NULL; - struct gsm_bts_trx *trx = NULL; - struct gsm_bts_trx_ts *ts = NULL; - struct bsc_msc_data *msc = NULL; - char *token = vector_slot(vline, *i); - long num; - - /* TODO: We need to make sure that the following chars are digits - * and/or use strtol to check if number conversion was successful - * Right now something like net.bts_stats will not work */ - if (!strcmp(token, "bts")) { - if (*node_type != CTRL_NODE_ROOT || !net) - goto err_missing; - (*i)++; - if (!ctrl_parse_get_num(vline, *i, &num)) - goto err_index; - - bts = gsm_bts_num(net, num); - if (!bts) - goto err_missing; - *node_data = bts; - *node_type = CTRL_NODE_BTS; - } else if (!strcmp(token, "trx")) { - if (*node_type != CTRL_NODE_BTS || !*node_data) - goto err_missing; - bts = *node_data; - (*i)++; - if (!ctrl_parse_get_num(vline, *i, &num)) - goto err_index; - - trx = gsm_bts_trx_num(bts, num); - if (!trx) - goto err_missing; - *node_data = trx; - *node_type = CTRL_NODE_TRX; - } else if (!strcmp(token, "ts")) { - if (*node_type != CTRL_NODE_TRX || !*node_data) - goto err_missing; - trx = *node_data; - (*i)++; - if (!ctrl_parse_get_num(vline, *i, &num)) - goto err_index; - - if ((num >= 0) && (num < TRX_NR_TS)) - ts = &trx->ts[num]; - if (!ts) - goto err_missing; - *node_data = ts; - *node_type = CTRL_NODE_TS; - } else if (!strcmp(token, "msc")) { - if (*node_type != CTRL_NODE_ROOT || !net) - goto err_missing; - (*i)++; - if (!ctrl_parse_get_num(vline, *i, &num)) - goto err_index; - - msc = osmo_msc_data_find(net, num); - if (!msc) - goto err_missing; - *node_data = msc; - *node_type = CTRL_NODE_MSC; - } else - return 0; - - return 1; -err_missing: - return -ENODEV; -err_index: - return -ERANGE; -} - -struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net, - const char *bind_addr, uint16_t port) -{ - return ctrl_interface_setup_dynip2(net, bind_addr, port, - bsc_ctrl_node_lookup, - _LAST_CTRL_NODE_BSC); -} diff --git a/src/libbsc/bsc_dyn_ts.c b/src/libbsc/bsc_dyn_ts.c deleted file mode 100644 index ed7caed7f..000000000 --- a/src/libbsc/bsc_dyn_ts.c +++ /dev/null @@ -1,60 +0,0 @@ -/* Dynamic PDCH initialisation implementation shared across NM and RSL */ - -/* (C) 2016 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 . - * - */ - -#include -#include -#include -#include - -static void tchf_pdch_ts_init(struct gsm_bts_trx_ts *ts) -{ - int rc; - - rc = rsl_ipacc_pdch_activate(ts, 1); - if (rc != 0 && rc != -ENOTSUP) - LOGP(DRSL, LOGL_ERROR, "%s %s: PDCH ACT failed\n", - gsm_ts_name(ts), gsm_pchan_name(ts->pchan)); -} - -static void tchf_tchh_pdch_ts_init(struct gsm_bts_trx_ts *ts) -{ - dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH); -} - -void dyn_ts_init(struct gsm_bts_trx_ts *ts) -{ - /* Clear all TCH/F_PDCH flags */ - ts->flags &= ~(TS_F_PDCH_PENDING_MASK | TS_F_PDCH_ACTIVE); - - /* Clear TCH/F_TCH/H_PDCH state */ - ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; - ts->dyn.pending_chan_activ = NULL; - - switch (ts->pchan) { - case GSM_PCHAN_TCH_F_PDCH: - tchf_pdch_ts_init(ts); - break; - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - tchf_tchh_pdch_ts_init(ts); - break; - default: - break; - } -} diff --git a/src/libbsc/bsc_init.c b/src/libbsc/bsc_init.c deleted file mode 100644 index 508a7f410..000000000 --- a/src/libbsc/bsc_init.c +++ /dev/null @@ -1,591 +0,0 @@ -/* A hackish minimal BSC (+MSC +HLR) implementation */ - -/* (C) 2008-2018 by Harald Welte - * (C) 2009 by Holger Hans Peter Freyther - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -/* Callback function for NACK on the OML NM */ -static int oml_msg_nack(struct nm_nack_signal_data *nack) -{ - if (nack->mt == NM_MT_GET_ATTR_NACK) { - LOGP(DNM, LOGL_ERROR, "BTS%u does not support Get Attributes " - "OML message.\n", nack->bts->nr); - return 0; - } - - if (nack->mt == NM_MT_SET_BTS_ATTR_NACK) - LOGP(DNM, LOGL_ERROR, "Failed to set BTS attributes. That is fatal. " - "Was the bts type and frequency properly specified?\n"); - else - LOGP(DNM, LOGL_ERROR, "Got %s NACK going to drop the OML links.\n", - abis_nm_nack_name(nack->mt)); - - if (!nack->bts) { - LOGP(DNM, LOGL_ERROR, "Unknown bts. Can not drop it.\n"); - return 0; - } - - if (is_ipaccess_bts(nack->bts)) - ipaccess_drop_oml(nack->bts); - - return 0; -} - -/* 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_nack_signal_data *nack; - - switch (signal) { - case S_NM_NACK: - nack = signal_data; - return oml_msg_nack(nack); - default: - break; - } - return 0; -} - -int bsc_shutdown_net(struct gsm_network *net) -{ - struct gsm_bts *bts; - - llist_for_each_entry(bts, &net->bts_list, list) { - LOGP(DNM, LOGL_NOTICE, "shutting down OML for BTS %u\n", bts->nr); - osmo_signal_dispatch(SS_L_GLOBAL, S_GLOBAL_BTS_CLOSE_OM, bts); - } - - return 0; -} - -unsigned long long bts_uptime(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); - return 0; - } - - if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { - LOGP(DNM, LOGL_ERROR, "BTS %u uptime 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); -} - -static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len) -{ - struct gsm_bts *bts = trx->bts; - int rc, j; - - if (si_len) { - DEBUGP(DRR, "SI%s: %s\n", get_value_string(osmo_sitype_strs, i), - osmo_hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN)); - } else - DEBUGP(DRR, "SI%s: OFF\n", get_value_string(osmo_sitype_strs, i)); - - switch (i) { - case SYSINFO_TYPE_5: - case SYSINFO_TYPE_5bis: - case SYSINFO_TYPE_5ter: - case SYSINFO_TYPE_6: - rc = rsl_sacch_filling(trx, osmo_sitype2rsl(i), - si_len ? GSM_BTS_SI(bts, i) : NULL, si_len); - break; - case SYSINFO_TYPE_2quater: - if (si_len == 0) { - rc = rsl_bcch_info(trx, i, NULL, 0); - break; - } - rc = 0; - for (j = 0; j <= bts->si2q_count; j++) - rc = rsl_bcch_info(trx, i, (const uint8_t *)GSM_BTS_SI2Q(bts, j), GSM_MACBLOCK_LEN); - break; - default: - rc = rsl_bcch_info(trx, i, si_len ? GSM_BTS_SI(bts, i) : NULL, si_len); - break; - } - - return rc; -} - -/* set all system information types for a TRX */ -int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx) -{ - int i, rc; - struct gsm_bts *bts = trx->bts; - uint8_t gen_si[_MAX_SYSINFO_TYPE], n_si = 0, n; - int si_len[_MAX_SYSINFO_TYPE]; - - bts->si_common.cell_sel_par.ms_txpwr_max_ccch = - ms_pwr_ctl_lvl(bts->band, bts->ms_max_power); - bts->si_common.cell_sel_par.neci = bts->network->neci; - - /* Zero/forget the state of the dynamically computed SIs, leeping the static ones */ - bts->si_valid = bts->si_mode_static; - - /* First, we determine which of the SI messages we actually need */ - - if (trx == bts->c0) { - /* 1...4 are always present on a C0 TRX */ - gen_si[n_si++] = SYSINFO_TYPE_1; - gen_si[n_si++] = SYSINFO_TYPE_2; - gen_si[n_si++] = SYSINFO_TYPE_2bis; - gen_si[n_si++] = SYSINFO_TYPE_2ter; - gen_si[n_si++] = SYSINFO_TYPE_2quater; - gen_si[n_si++] = SYSINFO_TYPE_3; - gen_si[n_si++] = SYSINFO_TYPE_4; - - /* 13 is always present on a C0 TRX of a GPRS BTS */ - if (bts->gprs.mode != BTS_GPRS_NONE) - gen_si[n_si++] = SYSINFO_TYPE_13; - } - - /* 5 and 6 are always present on every TRX */ - gen_si[n_si++] = SYSINFO_TYPE_5; - gen_si[n_si++] = SYSINFO_TYPE_5bis; - gen_si[n_si++] = SYSINFO_TYPE_5ter; - gen_si[n_si++] = SYSINFO_TYPE_6; - - /* Second, we generate the selected SI via RSL */ - - for (n = 0; n < n_si; n++) { - i = gen_si[n]; - /* Only generate SI if this SI is not in "static" (user-defined) mode */ - if (!(bts->si_mode_static & (1 << i))) { - /* 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); - if (rc < 0) - goto err_out; - si_len[i] = 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; - } - } - - /* Third, we send the selected SI via RSL */ - - for (n = 0; n < n_si; n++) { - i = gen_si[n]; - /* 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)) - rc = rsl_si(trx, i, 0); - else - rc = rsl_si(trx, i, si_len[i]); - if (rc < 0) - return rc; - } - - /* Make sure the PCU is aware (in case anything GPRS related has - * changed in SI */ - pcu_info_update(bts); - - return 0; -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)); - return rc; -} - -/* set all system information types for a BTS */ -int gsm_bts_set_system_infos(struct gsm_bts *bts) -{ - struct gsm_bts_trx *trx; - - /* Generate a new ID */ - bts->bcch_change_mark += 1; - bts->bcch_change_mark %= 0x7; - - llist_for_each_entry(trx, &bts->trx_list, list) { - int rc; - - rc = gsm_bts_trx_set_system_infos(trx); - if (rc != 0) - return rc; - } - - return 0; -} - -/* Produce a MA as specified in 10.5.2.21 */ -static int 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 */ - struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc; - struct bitvec *ts_arfcn = &ts->hopping.arfcns; - struct bitvec *ma = &ts->hopping.ma; - unsigned int num_cell_arfcns, bitnum, n_chan; - int i; - - /* re-set the MA to all-zero */ - ma->cur_bit = 0; - ts->hopping.ma_len = 0; - 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++; - } - - /* pad it to octet-aligned number of bits */ - ts->hopping.ma_len = num_cell_arfcns / 8; - if (num_cell_arfcns % 8) - ts->hopping.ma_len++; - - n_chan = 0; - for (i = 0; i < 1024; i++) { - if (!bitvec_get_bit_pos(cell_chan, i)) - continue; - /* set the corresponding bit in the MA */ - bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan; - if (bitvec_get_bit_pos(ts_arfcn, i)) - bitvec_set_bit_pos(ma, bitnum, 1); - else - bitvec_set_bit_pos(ma, bitnum, 0); - n_chan++; - } - - /* ARFCN 0 is special: It is coded last in the bitmask */ - if (bitvec_get_bit_pos(cell_chan, 0)) { - n_chan++; - /* set the corresponding bit in the MA */ - bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan; - if (bitvec_get_bit_pos(ts_arfcn, 0)) - bitvec_set_bit_pos(ma, bitnum, 1); - else - bitvec_set_bit_pos(ma, bitnum, 0); - } - - return 0; -} - -static void bootstrap_rsl(struct gsm_bts_trx *trx) -{ - unsigned int i; - - LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) " - "on ARFCN %u using MCC-MNC %s LAC=%u CID=%u BSIC=%u\n", - trx->bts->nr, trx->nr, trx->arfcn, - osmo_plmn_name(&bsc_gsmnet->plmn), - trx->bts->location_area_code, - trx->bts->cell_identity, trx->bts->bsic); - - if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) { - rsl_nokia_si_begin(trx); - } - - /* - * Trigger ACC ramping before sending system information to BTS. - * This ensures that RACH control in system information is configured correctly. - * TRX 0 should be usable and unlocked, otherwise starting ACC ramping is pointless. - */ - if (trx_is_usable(trx) && trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) - acc_ramp_trigger(&trx->bts->acc_ramp); - - gsm_bts_trx_set_system_infos(trx); - - if (trx->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); - } - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - generate_ma_for_ts(ts); - gsm_ts_check_init(ts); - } -} - -/* 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 ts_no, lchan_no; - /* 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; - /* was static in system_information.c */ - extern int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts); - 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); - break; - case S_L_INP_TEI_DN: - LOGP(DLMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx); - - if (isd->link_type == E1INP_SIGN_OML) - rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_OML_FAIL]); - 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); - } - - /* - * free all allocated channels. change the nm_state so the - * trx and trx_ts becomes unusable and chan_alloc.c can not - * allocate from it. - */ - for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) { - struct gsm_bts_trx_ts *ts = &trx->ts[ts_no]; - - for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) { - if (ts->lchan[lchan_no].state != LCHAN_S_NONE) - lchan_free(&ts->lchan[lchan_no]); - lchan_reset(&ts->lchan[lchan_no]); - } - } - - gsm_bts_mo_reset(trx->bts); - - abis_nm_clear_queue(trx->bts); - break; - default: - break; - } - - return 0; -} - -static int bootstrap_bts(struct gsm_bts *bts) -{ - int i, n; - - 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; - } - - /* Control Channel Description is set from vty/config */ - - /* T3212 is set from vty/config */ - - /* Set ccch config by looking at ts config */ - for (n=0, i=0; i<8; i++) - n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0; - - /* Indicate R99 MSC in SI3 */ - bts->si_common.chan_desc.mscr = 1; - - switch (n) { - case 0: - bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C; - /* Limit reserved block to 2 on combined channel according to - 3GPP TS 44.018 Table 10.5.2.11.1 */ - if (bts->si_common.chan_desc.bs_ag_blks_res > 2) { - LOGP(DNM, LOGL_NOTICE, "CCCH is combined with SDCCHs, " - "reducing BS-AG-BLKS-RES value %d -> 2\n", - bts->si_common.chan_desc.bs_ag_blks_res); - bts->si_common.chan_desc.bs_ag_blks_res = 2; - } - break; - case 1: - bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC; - break; - case 2: - bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC; - break; - case 3: - bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC; - break; - case 4: - bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC; - break; - default: - LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n"); - return -EINVAL; - } - - bts->si_common.cell_options.pwrc = 0; /* PWRC not set */ - - bts->si_common.cell_sel_par.acs = 0; - - bts->si_common.ncc_permitted = 0xff; - - bts->chan_load_samples_idx = 0; - - /* ACC ramping is initialized from vty/config */ - - /* Initialize the BTS state */ - gsm_bts_mo_reset(bts); - - return 0; -} - -int bsc_network_alloc(void) -{ - /* initialize our data structures */ - bsc_gsmnet = bsc_network_init(tall_bsc_ctx); - if (!bsc_gsmnet) - return -ENOMEM; - - return 0; -} - -int bsc_network_configure(const char *config_file) -{ - struct gsm_bts *bts; - int rc; - - 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); - 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); - if (rc < 0) - return rc; - - osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL); - 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); - if (rc < 0) { - LOGP(DNM, LOGL_FATAL, "Error bootstrapping BTS\n"); - return rc; - } - rc = e1_reconfig_bts(bts); - if (rc < 0) { - LOGP(DNM, LOGL_FATAL, "Error enabling E1 input driver\n"); - return rc; - } - } - - return 0; -} diff --git a/src/libbsc/bsc_rf_ctrl.c b/src/libbsc/bsc_rf_ctrl.c deleted file mode 100644 index f4a21b53a..000000000 --- a/src/libbsc/bsc_rf_ctrl.c +++ /dev/null @@ -1,534 +0,0 @@ -/* RF Ctl handling socket */ - -/* (C) 2010 by Harald Welte - * (C) 2010-2014 by Holger Hans Peter Freyther - * (C) 2010-2014 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 . - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -#define RF_CMD_QUERY '?' -#define RF_CMD_OFF '0' -#define RF_CMD_ON '1' -#define RF_CMD_D_OFF 'd' -#define RF_CMD_ON_G 'g' - -static const struct value_string opstate_names[] = { - { OSMO_BSC_RF_OPSTATE_INOPERATIONAL, "inoperational" }, - { OSMO_BSC_RF_OPSTATE_OPERATIONAL, "operational" }, - { 0, NULL } -}; - -static const struct value_string adminstate_names[] = { - { OSMO_BSC_RF_ADMINSTATE_UNLOCKED, "unlocked" }, - { OSMO_BSC_RF_ADMINSTATE_LOCKED, "locked" }, - { 0, NULL } -}; - -static const struct value_string policy_names[] = { - { OSMO_BSC_RF_POLICY_OFF, "off" }, - { OSMO_BSC_RF_POLICY_ON, "on" }, - { OSMO_BSC_RF_POLICY_GRACE, "grace" }, - { OSMO_BSC_RF_POLICY_UNKNOWN, "unknown" }, - { 0, NULL } -}; - -const char *osmo_bsc_rf_get_opstate_name(enum osmo_bsc_rf_opstate opstate) -{ - return get_value_string(opstate_names, opstate); -} - -const char *osmo_bsc_rf_get_adminstate_name(enum osmo_bsc_rf_adminstate adminstate) -{ - return get_value_string(adminstate_names, adminstate); -} - -const char *osmo_bsc_rf_get_policy_name(enum osmo_bsc_rf_policy policy) -{ - return get_value_string(policy_names, policy); -} - -enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_bts(struct gsm_bts *bts) -{ - struct gsm_bts_trx *trx; - - llist_for_each_entry(trx, &bts->trx_list, list) { - if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED) - return OSMO_BSC_RF_OPSTATE_OPERATIONAL; - } - - /* No trx were active, so this bts is disabled */ - return OSMO_BSC_RF_OPSTATE_INOPERATIONAL; -} - -enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bts) -{ - struct gsm_bts_trx *trx; - - llist_for_each_entry(trx, &bts->trx_list, list) { - if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) - return OSMO_BSC_RF_ADMINSTATE_UNLOCKED; - } - - /* All trx administrative states were locked */ - return OSMO_BSC_RF_ADMINSTATE_LOCKED; -} - -enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts) -{ - struct osmo_bsc_data *bsc_data = bts->network->bsc_data; - - if (!bsc_data) - return OSMO_BSC_RF_POLICY_UNKNOWN; - - switch (bsc_data->rf_ctrl->policy) { - case S_RF_ON: - return OSMO_BSC_RF_POLICY_ON; - case S_RF_OFF: - return OSMO_BSC_RF_POLICY_OFF; - case S_RF_GRACE: - return OSMO_BSC_RF_POLICY_GRACE; - default: - return OSMO_BSC_RF_POLICY_UNKNOWN; - } -} - -static int lock_each_trx(struct gsm_network *net, bool lock) -{ - struct gsm_bts *bts; - - 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) { - LOGP(DLINP, LOGL_DEBUG, - "Excluding BTS(%d) from trx lock.\n", bts->nr); - continue; - } - - llist_for_each_entry(trx, &bts->trx_list, list) { - gsm_trx_lock_rf(trx, lock, "ctrl"); - } - } - - return 0; -} - -static void send_resp(struct osmo_bsc_rf_conn *conn, char send) -{ - struct msgb *msg; - - msg = msgb_alloc(10, "RF Query"); - if (!msg) { - LOGP(DLINP, LOGL_ERROR, "Failed to allocate response msg.\n"); - return; - } - - msg->l2h = msgb_put(msg, 1); - msg->l2h[0] = send; - - if (osmo_wqueue_enqueue(&conn->queue, msg) != 0) { - LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the answer.\n"); - msgb_free(msg); - return; - } - - return; -} - - -/* - * Send a - * 'g' when we are in grace mode - * '1' when one TRX is online, - * '0' otherwise - */ -static void handle_query(struct osmo_bsc_rf_conn *conn) -{ - struct gsm_bts *bts; - char send = RF_CMD_OFF; - - if (conn->rf->policy == S_RF_GRACE) - return send_resp(conn, RF_CMD_ON_G); - - llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) { - struct gsm_bts_trx *trx; - - /* Exclude the BTS from the global lock */ - if (bts->excl_from_rf_lock) { - LOGP(DLINP, LOGL_DEBUG, - "Excluding BTS(%d) from query.\n", bts->nr); - 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) { - send = RF_CMD_ON; - break; - } - } - } - - send_resp(conn, send); -} - -static void rf_check_cb(void *_data) -{ - struct gsm_bts *bts; - struct osmo_bsc_rf *rf = _data; - - llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) { - struct gsm_bts_trx *trx; - - /* don't bother to check a booting or missing BTS */ - if (!bts->oml_link || !is_ipaccess_bts(bts)) - continue; - - /* Exclude the BTS from the global lock */ - if (bts->excl_from_rf_lock) { - LOGP(DLINP, LOGL_DEBUG, - "Excluding BTS(%d) from query.\n", bts->nr); - 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_ENABLED || - trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) { - LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n"); - ipaccess_drop_oml(bts); - break; - } - } - } -} - -static void send_signal(struct osmo_bsc_rf *rf, int val) -{ - struct rf_signal_data sig; - sig.net = rf->gsm_network; - - rf->policy = val; - osmo_signal_dispatch(SS_RF, val, &sig); -} - -static int switch_rf_off(struct osmo_bsc_rf *rf) -{ - lock_each_trx(rf->gsm_network, true); - send_signal(rf, S_RF_OFF); - - return 0; -} - -static void grace_timeout(void *_data) -{ - struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data; - - LOGP(DLINP, LOGL_NOTICE, "Grace timeout. Going to disable all BTS/TRX.\n"); - switch_rf_off(rf); -} - -static int enter_grace(struct osmo_bsc_rf *rf) -{ - if (osmo_timer_pending(&rf->grace_timeout)) { - LOGP(DLINP, LOGL_NOTICE, "RF Grace timer is pending. Not restarting.\n"); - return 0; - } - - osmo_timer_setup(&rf->grace_timeout, grace_timeout, rf); - osmo_timer_schedule(&rf->grace_timeout, rf->gsm_network->bsc_data->mid_call_timeout, 0); - LOGP(DLINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n", - rf->gsm_network->bsc_data->mid_call_timeout); - - send_signal(rf, S_RF_GRACE); - return 0; -} - -static void rf_delay_cmd_cb(void *data) -{ - struct osmo_bsc_rf *rf = data; - - switch (rf->last_request) { - case RF_CMD_D_OFF: - rf->last_state_command = "RF Direct Off"; - osmo_timer_del(&rf->rf_check); - osmo_timer_del(&rf->grace_timeout); - switch_rf_off(rf); - break; - case RF_CMD_ON: - rf->last_state_command = "RF Direct On"; - osmo_timer_del(&rf->grace_timeout); - lock_each_trx(rf->gsm_network, false); - send_signal(rf, S_RF_ON); - osmo_timer_schedule(&rf->rf_check, 3, 0); - break; - case RF_CMD_OFF: - rf->last_state_command = "RF Scheduled Off"; - osmo_timer_del(&rf->rf_check); - enter_grace(rf); - break; - } -} - -static int rf_read_cmd(struct osmo_fd *fd) -{ - struct osmo_bsc_rf_conn *conn = fd->data; - char buf[1]; - int rc; - - rc = read(fd->fd, buf, sizeof(buf)); - if (rc != sizeof(buf)) { - LOGP(DLINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno)); - osmo_fd_unregister(fd); - close(fd->fd); - osmo_wqueue_clear(&conn->queue); - talloc_free(conn); - return -1; - } - - switch (buf[0]) { - case RF_CMD_QUERY: - handle_query(conn); - break; - case RF_CMD_D_OFF: - case RF_CMD_ON: - case RF_CMD_OFF: - osmo_bsc_rf_schedule_lock(conn->rf, buf[0]); - break; - default: - conn->rf->last_state_command = "Unknown command"; - LOGP(DLINP, LOGL_ERROR, "Unknown command %d\n", buf[0]); - break; - } - - return 0; -} - -static int rf_write_cmd(struct osmo_fd *fd, struct msgb *msg) -{ - int rc; - - rc = write(fd->fd, msg->data, msg->len); - if (rc != msg->len) { - LOGP(DLINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno)); - return -1; - } - - return 0; -} - -static int rf_ctrl_accept(struct osmo_fd *bfd, unsigned int what) -{ - struct osmo_bsc_rf_conn *conn; - struct osmo_bsc_rf *rf = bfd->data; - struct sockaddr_un addr; - socklen_t len = sizeof(addr); - int fd; - - fd = accept(bfd->fd, (struct sockaddr *) &addr, &len); - if (fd < 0) { - LOGP(DLINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n", - errno, strerror(errno)); - return -1; - } - - conn = talloc_zero(rf, struct osmo_bsc_rf_conn); - if (!conn) { - LOGP(DLINP, LOGL_ERROR, "Failed to allocate mem.\n"); - close(fd); - return -1; - } - - osmo_wqueue_init(&conn->queue, 10); - conn->queue.bfd.data = conn; - conn->queue.bfd.fd = fd; - conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE; - conn->queue.read_cb = rf_read_cmd; - conn->queue.write_cb = rf_write_cmd; - conn->rf = rf; - - if (osmo_fd_register(&conn->queue.bfd) != 0) { - close(fd); - talloc_free(conn); - return -1; - } - - return 0; -} - -static void rf_auto_off_cb(void *_timer) -{ - struct osmo_bsc_rf *rf = _timer; - - LOGP(DLINP, LOGL_NOTICE, - "Going to switch off RF due lack of a MSC connection.\n"); - osmo_bsc_rf_schedule_lock(rf, RF_CMD_D_OFF); -} - -static int msc_signal_handler(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct gsm_network *net; - struct msc_signal_data *msc; - struct osmo_bsc_rf *rf; - - /* check if we want to handle this signal */ - if (subsys != SS_MSC) - return 0; - - net = handler_data; - msc = signal_data; - - /* check if we have the needed information */ - if (!net->bsc_data) - return 0; - if (msc->data->type != MSC_CON_TYPE_NORMAL) - return 0; - - rf = net->bsc_data->rf_ctrl; - switch (signal) { - case S_MSC_LOST: - if (net->bsc_data->auto_off_timeout < 0) - return 0; - if (osmo_timer_pending(&rf->auto_off_timer)) - return 0; - osmo_timer_schedule(&rf->auto_off_timer, - net->bsc_data->auto_off_timeout, 0); - break; - case S_MSC_CONNECTED: - osmo_timer_del(&rf->auto_off_timer); - break; - } - - return 0; -} - -static int rf_create_socket(struct osmo_bsc_rf *rf, const char *path) -{ - unsigned int namelen; - struct sockaddr_un local; - struct osmo_fd *bfd; - int rc; - - bfd = &rf->listen; - bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (bfd->fd < 0) { - LOGP(DLINP, LOGL_ERROR, "Can not create socket. %d/%s\n", - errno, strerror(errno)); - return -1; - } - - local.sun_family = AF_UNIX; - osmo_strlcpy(local.sun_path, path, sizeof(local.sun_path)); - unlink(local.sun_path); - - /* we use the same magic that X11 uses in Xtranssock.c for - * calculating the proper length of the sockaddr */ -#if defined(BSD44SOCKETS) || defined(__UNIXWARE__) - local.sun_len = strlen(local.sun_path); -#endif -#if defined(BSD44SOCKETS) || defined(SUN_LEN) - namelen = SUN_LEN(&local); -#else - namelen = strlen(local.sun_path) + - offsetof(struct sockaddr_un, sun_path); -#endif - - rc = bind(bfd->fd, (struct sockaddr *) &local, namelen); - if (rc != 0) { - LOGP(DLINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n", - local.sun_path, errno, strerror(errno)); - close(bfd->fd); - return -1; - } - - if (listen(bfd->fd, 0) != 0) { - LOGP(DLINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno)); - close(bfd->fd); - return -1; - } - - bfd->when = BSC_FD_READ; - bfd->cb = rf_ctrl_accept; - bfd->data = rf; - - if (osmo_fd_register(bfd) != 0) { - LOGP(DLINP, LOGL_ERROR, "Failed to register bfd.\n"); - close(bfd->fd); - return -1; - } - - return 0; -} - -struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net) -{ - struct osmo_bsc_rf *rf; - - rf = talloc_zero(NULL, struct osmo_bsc_rf); - if (!rf) { - LOGP(DLINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n"); - return NULL; - } - - if (path && rf_create_socket(rf, path) != 0) { - talloc_free(rf); - return NULL; - } - - rf->gsm_network = net; - rf->policy = S_RF_ON; - rf->last_state_command = ""; - rf->last_rf_lock_ctrl_command = talloc_strdup(rf, ""); - - /* check the rf state */ - osmo_timer_setup(&rf->rf_check, rf_check_cb, rf); - - /* delay cmd handling */ - osmo_timer_setup(&rf->delay_cmd, rf_delay_cmd_cb, rf); - - osmo_timer_setup(&rf->auto_off_timer, rf_auto_off_cb, rf); - - /* listen to RF signals */ - osmo_signal_register_handler(SS_MSC, msc_signal_handler, net); - - return rf; -} - -void osmo_bsc_rf_schedule_lock(struct osmo_bsc_rf *rf, char cmd) -{ - rf->last_request = cmd; - if (!osmo_timer_pending(&rf->delay_cmd)) - osmo_timer_schedule(&rf->delay_cmd, 1, 0); -} diff --git a/src/libbsc/bsc_rll.c b/src/libbsc/bsc_rll.c deleted file mode 100644 index ebf9b8856..000000000 --- a/src/libbsc/bsc_rll.c +++ /dev/null @@ -1,139 +0,0 @@ -/* GSM BSC Radio Link Layer API - * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ - -/* (C) 2009 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct bsc_rll_req { - struct llist_head list; - struct osmo_timer_list timer; - - struct gsm_lchan *lchan; - uint8_t link_id; - - void (*cb)(struct gsm_lchan *lchan, uint8_t link_id, - void *data, enum bsc_rllr_ind); - void *data; -}; - -/* we only compare C1, C2 and SAPI */ -#define LINKID_MASK 0xC7 - -static LLIST_HEAD(bsc_rll_reqs); - -static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type) -{ - llist_del(&rllr->list); - rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type); - talloc_free(rllr); -} - -static void timer_cb(void *_rllr) -{ - struct bsc_rll_req *rllr = _rllr; - - complete_rllr(rllr, BSC_RLLR_IND_TIMEOUT); -} - -/* establish a RLL connection with given SAPI / priority */ -int rll_establish(struct gsm_lchan *lchan, uint8_t sapi, - void (*cb)(struct gsm_lchan *, uint8_t, void *, - enum bsc_rllr_ind), - void *data) -{ - struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req); - uint8_t link_id; - if (!rllr) - return -ENOMEM; - - link_id = sapi; - - /* If we are a TCH and not in signalling mode, we need to - * indicate that the new RLL connection is to be made on the SACCH */ - if ((lchan->type == GSM_LCHAN_TCH_F || - lchan->type == GSM_LCHAN_TCH_H) && sapi != 0) - link_id |= 0x40; - - rllr->lchan = lchan; - rllr->link_id = link_id; - rllr->cb = cb; - rllr->data = data; - - llist_add(&rllr->list, &bsc_rll_reqs); - - osmo_timer_setup(&rllr->timer, timer_cb, rllr); - osmo_timer_schedule(&rllr->timer, 7, 0); - - /* send the RSL RLL ESTablish REQuest */ - return rsl_establish_request(rllr->lchan, rllr->link_id); -} - -/* Called from RSL code in case we have received an indication regarding - * any RLL link */ -void rll_indication(struct gsm_lchan *lchan, uint8_t link_id, uint8_t type) -{ - struct bsc_rll_req *rllr, *rllr2; - - llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) { - if (rllr->lchan == lchan && - (rllr->link_id & LINKID_MASK) == (link_id & LINKID_MASK)) { - osmo_timer_del(&rllr->timer); - complete_rllr(rllr, type); - return; - } - } -} - -static int rll_lchan_signal(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct challoc_signal_data *challoc; - struct bsc_rll_req *rllr, *rllr2; - - if (subsys != SS_CHALLOC || signal != S_CHALLOC_FREED) - return 0; - - challoc = (struct challoc_signal_data *) signal_data; - - llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) { - if (rllr->lchan == challoc->lchan) { - osmo_timer_del(&rllr->timer); - complete_rllr(rllr, BSC_RLLR_IND_ERR_IND); - } - } - - return 0; -} - -static __attribute__((constructor)) void on_dso_load_rll(void) -{ - osmo_signal_register_handler(SS_CHALLOC, rll_lchan_signal, NULL); -} diff --git a/src/libbsc/bsc_subscr_conn_fsm.c b/src/libbsc/bsc_subscr_conn_fsm.c deleted file mode 100644 index bafe14589..000000000 --- a/src/libbsc/bsc_subscr_conn_fsm.c +++ /dev/null @@ -1,1204 +0,0 @@ -/* (C) 2017 by Harald Welte - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define S(x) (1 << (x)) - -#define MGCP_MGW_TIMEOUT 4 /* in seconds */ -#define MGCP_MGW_TIMEOUT_TIMER_NR 1 - -#define MGCP_MGW_HO_TIMEOUT 4 /* in seconds */ -#define MGCP_MGW_HO_TIMEOUT_TIMER_NR 2 - -#define GSM0808_T10_TIMER_NR 10 -#define GSM0808_T10_VALUE 6 - -#define ENDPOINT_ID "rtpbridge/*@mgw" - -enum gscon_fsm_states { - ST_INIT, - /* waiting for CC from MSC */ - ST_WAIT_CC, - /* active connection */ - ST_ACTIVE, - /* during assignment; waiting for ASS_CMPL */ - ST_WAIT_ASS_CMPL, - /* BSSMAP CLEAR has been received */ - ST_CLEARING, - -/* MGW handling */ - /* during assignment; waiting for MGW response to CRCX for BTS */ - ST_WAIT_CRCX_BTS, - /* during assignment; waiting for MGW response to MDCX for BTS */ - ST_WAIT_MDCX_BTS, - /* during assignment; waiting for MGW response to CRCX for MSC */ - ST_WAIT_CRCX_MSC, - -/* MT (inbound) handover */ - /* Wait for Handover Access from MS/BTS */ - ST_WAIT_MT_HO_ACC, - /* Wait for RR Handover Complete from MS/BTS */ - ST_WAIT_MT_HO_COMPL, - -/* MO (outbound) handover */ - /* Wait for Handover Command / Handover Required Reject from MSC */ - ST_WAIT_MO_HO_CMD, - /* Wait for Clear Command from MSC */ - ST_MO_HO_PROCEEDING, - -/* Internal HO handling */ - /* Wait for the handover logic to complete the handover */ - ST_WAIT_HO_COMPL, - /* during handover; waiting for MGW response to MDCX for BTS */ - ST_WAIT_MDCX_BTS_HO, -}; - -static const struct value_string gscon_fsm_event_names[] = { - {GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"}, - {GSCON_EV_A_CONN_REQ, "MO-CONNECT.req"}, - {GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"}, - {GSCON_EV_A_ASSIGNMENT_CMD, "ASSIGNMENT_CMD"}, - {GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"}, - {GSCON_EV_A_DISC_IND, "DISCONNET.ind"}, - {GSCON_EV_A_HO_REQ, "HANDOVER_REQUEST"}, - - {GSCON_EV_RR_ASS_COMPL, "RR_ASSIGN_COMPL"}, - {GSCON_EV_RR_ASS_FAIL, "RR_ASSIGN_FAIL"}, - {GSCON_EV_RLL_REL_IND, "RLL_RELEASE.ind"}, - {GSCON_EV_RSL_CONN_FAIL, "RSL_CONN_FAIL.ind"}, - {GSCON_EV_RSL_CLEAR_COMPL, "RSL_CLEAR_COMPLETE"}, - - {GSCON_EV_MO_DTAP, "MO-DTAP"}, - {GSCON_EV_MT_DTAP, "MT-DTAP"}, - {GSCON_EV_TX_SCCP, "TX_SCCP"}, - - {GSCON_EV_MGW_FAIL_BTS, "MGW_FAILURE_BTS"}, - {GSCON_EV_MGW_FAIL_MSC, "MGW_FAILURE_MSC"}, - {GSCON_EV_MGW_CRCX_RESP_BTS, "MGW_CRCX_RESPONSE_BTS"}, - {GSCON_EV_MGW_MDCX_RESP_BTS, "MGW_MDCX_RESPONSE_BTS"}, - {GSCON_EV_MGW_CRCX_RESP_MSC, "MGW_CRCX_RESPONSE_MSC"}, - {GSCON_EV_MGW_MDCX_RESP_MSC, "MGW_MDCX_RESPONSE_MSC"}, - - {GSCON_EV_HO_START, "HO_START"}, - {GSCON_EV_HO_TIMEOUT, "HO_TIMEOUT"}, - {GSCON_EV_HO_FAIL, "HO_FAIL"}, - {GSCON_EV_HO_COMPL, "HO_COMPL"}, - - {0, NULL} -}; - -/* Send data SCCP message through SCCP connection. All sigtran messages - * that are send from this FSM must use this function. Never use - * osmo_bsc_sigtran_send() directly since this would defeat the checks - * provided by this function. */ -static void sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) -{ - int rc; - - /* Make sure that we only attempt to send SCCP messages if we have - * a life SCCP connection. Otherwise drop the message. */ - if (fi->state == ST_INIT || fi->state == ST_WAIT_CC) { - LOGPFSML(fi, LOGL_ERROR, "No active SCCP connection, dropping message!\n"); - msgb_free(msg); - return; - } - - rc = osmo_bsc_sigtran_send(conn, msg); - if (rc < 0) - LOGPFSML(fi, LOGL_ERROR, "Unable to deliver SCCP message!\n"); -} - - -/* See TS 48.008 3.2.2.11 Channel Type Octet 5 */ -static int bssap_speech_from_lchan(const struct gsm_lchan *lchan) -{ - switch (lchan->type) { - case GSM_LCHAN_TCH_H: - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_V1: - return 0x05; - case GSM48_CMODE_SPEECH_AMR: - return 0x25; - default: - return -1; - } - break; - case GSM_LCHAN_TCH_F: - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_V1: - return 0x01; - case GSM48_CMODE_SPEECH_EFR: - return 0x11; - case GSM48_CMODE_SPEECH_AMR: - return 0x21; - default: - return -1; - } - break; - default: - return -1; - } -} - -/* GSM 08.08 3.2.2.33 */ -static uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan) -{ - uint8_t channel_mode = 0, channel = 0; - - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_V1: - case GSM48_CMODE_SPEECH_EFR: - case GSM48_CMODE_SPEECH_AMR: - channel_mode = 0x9; - break; - case GSM48_CMODE_SIGN: - channel_mode = 0x8; - break; - case GSM48_CMODE_DATA_14k5: - channel_mode = 0xe; - break; - case GSM48_CMODE_DATA_12k0: - channel_mode = 0xb; - break; - case GSM48_CMODE_DATA_6k0: - channel_mode = 0xc; - break; - case GSM48_CMODE_DATA_3k6: - channel_mode = 0xd; - break; - } - - switch (lchan->type) { - case GSM_LCHAN_NONE: - channel = 0x0; - break; - case GSM_LCHAN_SDCCH: - channel = 0x1; - break; - case GSM_LCHAN_TCH_F: - channel = 0x8; - break; - case GSM_LCHAN_TCH_H: - channel = 0x9; - break; - case GSM_LCHAN_UNKNOWN: - default: - LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan); - break; - } - - return channel_mode << 4 | channel; -} - -/* Add the LCLS BSS Status IE to a BSSMAP message. We assume this is - * called on a msgb that was returned by gsm0808_create_ass_compl() */ -static void bssmap_add_lcls_status(struct msgb *msg, enum gsm0808_lcls_status status) -{ - OSMO_ASSERT(msg->l3h[0] == BSSAP_MSG_BSS_MANAGEMENT); - OSMO_ASSERT(msg->l3h[2] == BSS_MAP_MSG_ASSIGMENT_COMPLETE || - msg->l3h[2] == BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE || - msg->l3h[2] == BSS_MAP_MSG_HANDOVER_COMPLETE || - msg->l3h[2] == BSS_MAP_MSG_HANDOVER_PERFORMED); - OSMO_ASSERT(msgb_tailroom(msg) >= 2); - - /* append IE to end of message */ - msgb_tv_put(msg, GSM0808_IE_LCLS_BSS_STATUS, status); - /* increment the "length" byte in the BSSAP header */ - msg->l3h[1] += 2; -} - -/* Add (append) the LCLS BSS Status IE to a BSSMAP message, if there is any LCLS - * active on the given \a conn */ -static void bssmap_add_lcls_status_if_needed(struct gsm_subscriber_connection *conn, - struct msgb *msg) -{ - enum gsm0808_lcls_status status = lcls_get_status(conn); - if (status != 0xff) { - LOGPFSM(conn->fi, "Adding LCLS BSS-Status (%s) to %s\n", - gsm0808_lcls_status_name(status), - gsm0808_bssmap_name(msg->l3h[2])); - bssmap_add_lcls_status(msg, status); - } -} - -/* Generate and send assignment complete message */ -static void send_ass_compl(struct gsm_lchan *lchan, struct osmo_fsm_inst *fi, bool voice) -{ - struct msgb *resp; - struct gsm0808_speech_codec sc; - struct gsm0808_speech_codec *sc_ptr = NULL; - struct gsm_subscriber_connection *conn; - struct sockaddr_storage *addr_local = NULL; - int perm_spch = 0; - - conn = lchan->conn; - OSMO_ASSERT(conn); - - /* apply LCLS configuration (if any) */ - lcls_apply_config(conn); - - LOGPFSML(fi, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp.conn_id); - - /* Generate voice related fields */ - if (voice) { - perm_spch = bssap_speech_from_lchan(lchan); - switch (conn->sccp.msc->a.asp_proto) { - case OSMO_SS7_ASP_PROT_IPA: - /* don't add any AoIP specific fields. CIC allocated by MSC */ - break; - default: - OSMO_ASSERT(lchan->abis_ip.ass_compl.valid); - addr_local = &conn->user_plane.aoip_rtp_addr_local; - - /* Extrapolate speech codec from speech mode */ - gsm0808_speech_codec_from_chan_type(&sc, perm_spch); - sc_ptr = ≻ - break; - } - /* FIXME: AMR codec configuration must be derived from lchan1! */ - } - - /* Generate message */ - resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause, - lchan_to_chosen_channel(lchan), - lchan->encr.alg_id, perm_spch, - addr_local, sc_ptr, NULL); - - if (!resp) { - LOGPFSML(fi, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n", - conn->sccp.conn_id); - } - - /* Add LCLS BSS-Status IE in case there is any LCLS status for this connection */ - bssmap_add_lcls_status_if_needed(conn, resp); - - sigtran_send(conn, resp, fi); -} - -/* forward MT DTAP from BSSAP side to RSL side */ -static void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) -{ - int rc; - struct msgb *resp = NULL; - - OSMO_ASSERT(fi); - OSMO_ASSERT(msg); - OSMO_ASSERT(conn); - - rc = gsm0808_submit_dtap(conn, msg, OBSC_LINKID_CB(msg), 1); - if (rc != 0) { - LOGPFSML(fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n"); - resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - return; - } -} - -/* forward MO DTAP from RSL side to BSSAP side */ -static void forward_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) -{ - struct msgb *resp = NULL; - - OSMO_ASSERT(msg); - OSMO_ASSERT(conn); - - resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg)); - sigtran_send(conn, resp, fi); -} - -/* In case there are open MGCP connections, toss - * those connections */ -static void toss_mgcp_conn(struct gsm_subscriber_connection *conn, struct osmo_fsm_inst *fi) -{ - LOGPFSML(fi, LOGL_ERROR, "tossing all MGCP connections...\n"); - - if (conn->user_plane.fi_bts) { - mgcp_conn_delete(conn->user_plane.fi_bts); - conn->user_plane.fi_bts = NULL; - } - - if (conn->user_plane.fi_msc) { - mgcp_conn_delete(conn->user_plane.fi_msc); - conn->user_plane.fi_msc = NULL; - } - - if (conn->user_plane.mgw_endpoint) { - talloc_free(conn->user_plane.mgw_endpoint); - conn->user_plane.mgw_endpoint = NULL; - } -} - -static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct osmo_scu_prim *scu_prim = NULL; - struct msgb *msg = NULL; - int rc; - - switch (event) { - case GSCON_EV_A_CONN_REQ: - /* RLL ESTABLISH IND with initial L3 Message */ - msg = data; - /* FIXME: Extract Mobile ID and update FSM using osmo_fsm_inst_set_id() - * i.e. we will probably extract the mobile identity earlier, where the - * imsi filter code is. Then we could just use it here. - * related: OS#2969 */ - - rc = osmo_bsc_sigtran_open_conn(conn, msg); - if (rc < 0) { - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); - } else { - /* SCCP T(conn est) is 1-2 minutes, way too long. The MS will timeout - * using T3210 (20s), T3220 (5s) or T3230 (10s) */ - osmo_fsm_inst_state_chg(fi, ST_WAIT_CC, 20, 993210); - } - break; - case GSCON_EV_A_CONN_IND: - scu_prim = data; - if (!conn->sccp.msc) { - LOGPFSML(fi, LOGL_NOTICE, "N-CONNECT.ind from unknown MSC %s\n", - osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr)); - 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); - } - /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() - * related: OS2969 (same as above) */ - - LOGPFSML(fi, LOGL_NOTICE, "No support for MSC-originated SCCP Connections yet\n"); - 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); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -/* We've sent the CONNECTION.req to the SCCP provider and are waiting for CC from MSC */ -static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch (event) { - case GSCON_EV_A_CONN_CFM: - /* MSC has confirmed the connection, we now change into the - * active state and wait there for further operations */ - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - /* if there's user payload, forward it just like EV_MT_DTAP */ - /* FIXME: Question: if there's user payload attached to the CC, forward it like EV_MT_DTAP? */ - break; - default: - OSMO_ASSERT(false); - break; - } -} - -static const char *get_mgw_ep_name(struct gsm_subscriber_connection *conn) -{ - static char ep_name[256]; - struct bsc_msc_data *msc = conn->sccp.msc; - - switch (conn->sccp.msc->a.asp_proto) { - case OSMO_SS7_ASP_PROT_IPA: - /* derive endpoint name from CIC on A interface side */ - snprintf(ep_name, sizeof(ep_name), "%x@mgw", - mgcp_port_to_cic(conn->user_plane.rtp_port, msc->rtp_base)); - break; - default: - /* use dynamic RTPBRIDGE endpoint allocation in MGW */ - osmo_strlcpy(ep_name, ENDPOINT_ID, sizeof(ep_name)); - break; - } - return ep_name; -} - -/* We're on an active subscriber connection, passing DTAP back and forth */ -static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct msgb *resp = NULL; - struct mgcp_conn_peer conn_peer; - int rc; - - switch (event) { - case GSCON_EV_A_ASSIGNMENT_CMD: - /* MSC requests us to perform assignment, this code section is - * triggered via signal GSCON_EV_A_ASSIGNMENT_CMD from - * bssmap_handle_assignm_req() in osmo_bsc_bssap.c, which does - * the parsing of incoming assignment requests. */ - - LOGPFSML(fi, LOGL_NOTICE, "Channel assignment: chan_mode=%s, full_rate=%i\n", - get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), - conn->user_plane.full_rate); - - /* FIXME: We need to check if current channel is sufficient. If - * yes, do MODIFY. If not, do assignment (see commented lines below) */ - - switch (conn->user_plane.chan_mode) { - case GSM48_CMODE_SPEECH_V1: - case GSM48_CMODE_SPEECH_EFR: - case GSM48_CMODE_SPEECH_AMR: - /* A voice channel is requested, so we run down the - * mgcp-ass-mgcp state-chain (see FIXME above) */ - memset(&conn_peer, 0, sizeof(conn_peer)); - conn_peer.call_id = conn->sccp.conn_id; - osmo_strlcpy(conn_peer.endpoint, get_mgw_ep_name(conn), sizeof(conn_peer.endpoint)); - - /* (Pre)Change state and create the connection */ - osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); - conn->user_plane.fi_bts = - mgcp_conn_create(conn->network->mgw.client, fi, GSCON_EV_MGW_FAIL_BTS, - GSCON_EV_MGW_CRCX_RESP_BTS, &conn_peer); - if (!conn->user_plane.fi_bts) { - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); - sigtran_send(conn, resp, fi); - return; - } - break; - case GSM48_CMODE_SIGN: - /* A signalling channel is requested, so we perform the - * channel assignment directly without performing any - * MGCP actions. ST_WAIT_ASS_CMPL will see by the - * conn->user_plane.chan_mode parameter that this - * assignment is for a signalling channel and will then - * change back to ST_ACTIVE (here) immediately. */ - rc = gsm0808_assign_req(conn, conn->user_plane.chan_mode, - conn->user_plane.full_rate); - - if (rc == 1) { - send_ass_compl(conn->lchan, fi, false); - return; - } else if (rc != 0) { - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); - sigtran_send(conn, resp, fi); - return; - } - - osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR); - break; - default: - /* An unsupported channel is requested, so we have to - * reject this request by sending an assignment failure - * message immediately */ - LOGPFSML(fi, LOGL_ERROR, "Requested channel mode is not supported! chan_mode=%s full_rate=%d\n", - get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), - conn->user_plane.full_rate); - - /* The requested channel mode is not supported */ - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, NULL); - sigtran_send(conn, resp, fi); - break; - } - break; - case GSCON_EV_HO_START: - rc = bsc_handover_start_gscon(conn); - if (rc) { - resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); - return; - } - - /* Note: No timeout is set here, T3103 in handover_logic.c - * will generate a GSCON_EV_HO_TIMEOUT event should the - * handover time out, so we do not need another timeout - * here (maybe its worth to think about giving GSCON - * more power over the actual handover process). */ - osmo_fsm_inst_state_chg(fi, ST_WAIT_HO_COMPL, 0, 0); - break; - case GSCON_EV_A_HO_REQ: - /* FIXME: reject any handover requests with HO FAIL until implemented */ - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -/* Before we may start the channel assignment we need to get an IP/Port for the - * RTP connection from the MGW */ -static void gscon_fsm_wait_crcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct mgcp_conn_peer *conn_peer = NULL; - struct msgb *resp = NULL; - int rc; - - switch (event) { - case GSCON_EV_MGW_CRCX_RESP_BTS: - conn_peer = data; - - /* Check if the MGW has assigned an enpoint to us, otherwise we - * can not proceed. */ - if (strlen(conn_peer->endpoint) <= 0) { - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - return; - } - - /* Memorize the endpoint name we got assigned from the MGW. - * When the BTS sided connection is done, we need to create - * a second connection on that same endpoint, so we need - * to know its ID */ - if (!conn->user_plane.mgw_endpoint) - conn->user_plane.mgw_endpoint = talloc_zero_size(conn, MGCP_ENDPOINT_MAXLEN); - OSMO_ASSERT(conn->user_plane.mgw_endpoint); - osmo_strlcpy(conn->user_plane.mgw_endpoint, conn_peer->endpoint, MGCP_ENDPOINT_MAXLEN); - - /* Store the IP-Address and the port the MGW assigned to us, - * then start the channel assignment. */ - conn->user_plane.rtp_port = conn_peer->port; - conn->user_plane.rtp_ip = osmo_ntohl(inet_addr(conn_peer->addr)); - rc = gsm0808_assign_req(conn, conn->user_plane.chan_mode, conn->user_plane.full_rate); - if (rc != 0) { - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_SPEECH_VERSION_UNAVAILABLE, NULL); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - return; - } - - osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR); - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -/* We're waiting for an ASSIGNMENT COMPLETE from MS */ -static void gscon_fsm_wait_ass_cmpl(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct gsm_lchan *lchan = conn->lchan; - struct mgcp_conn_peer conn_peer; - struct in_addr addr; - struct msgb *resp = NULL; - int rc; - - switch (event) { - case GSCON_EV_RR_ASS_COMPL: - switch (conn->user_plane.chan_mode) { - case GSM48_CMODE_SPEECH_V1: - case GSM48_CMODE_SPEECH_EFR: - case GSM48_CMODE_SPEECH_AMR: - /* FIXME: What if we are using SCCP-Lite? */ - - /* We are dealing with a voice channel, so we can not - * confirm the assignment directly. We must first do - * some final steps on the MGCP side. */ - - /* Prepare parameters with the information we got during the assignment */ - memset(&conn_peer, 0, sizeof(conn_peer)); - addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); - osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr)); - conn_peer.port = lchan->abis_ip.bound_port; - - /* (Pre)Change state and modify the connection */ - osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); - rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); - if (rc != 0) { - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - return; - } - break; - case GSM48_CMODE_SIGN: - /* Confirm the successful assignment on BSSMAP and - * change back into active state */ - send_ass_compl(lchan, fi, false); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - break; - default: - /* Unsupported modes should have been already filtered - * by gscon_fsm_active(). If we reach the default - * section here anyway than some unsupported mode must - * have made it into the FSM, this would be a bug, so - * we fire an assertion here */ - OSMO_ASSERT(false); - break; - } - - break; - case GSCON_EV_RR_ASS_FAIL: - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE, NULL); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -/* We are waiting for the MGW response to the MDCX */ -static void gscon_fsm_wait_mdcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct mgcp_conn_peer conn_peer; - struct sockaddr_in *sin = NULL; - struct msgb *resp = NULL; - - switch (event) { - case GSCON_EV_MGW_MDCX_RESP_BTS: - - /* Prepare parameters with the connection information we got - * with the assignment command */ - memset(&conn_peer, 0, sizeof(conn_peer)); - conn_peer.call_id = conn->sccp.conn_id; - sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; - conn_peer.port = osmo_ntohs(sin->sin_port); - osmo_strlcpy(conn_peer.addr, inet_ntoa(sin->sin_addr), sizeof(conn_peer.addr)); - - /* Make sure we use the same endpoint where we created the - * BTS connection. */ - osmo_strlcpy(conn_peer.endpoint, conn->user_plane.mgw_endpoint, sizeof(conn_peer.endpoint)); - - switch (conn->sccp.msc->a.asp_proto) { - case OSMO_SS7_ASP_PROT_IPA: - /* Send assignment complete message to the MSC */ - send_ass_compl(conn->lchan, fi, true); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - break; - default: - /* (Pre)Change state and create the connection */ - osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_MSC, MGCP_MGW_TIMEOUT, - MGCP_MGW_TIMEOUT_TIMER_NR); - conn->user_plane.fi_msc = mgcp_conn_create(conn->network->mgw.client, fi, - GSCON_EV_MGW_FAIL_MSC, - GSCON_EV_MGW_CRCX_RESP_MSC, &conn_peer); - if (!conn->user_plane.fi_msc) { - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - return; - } - break; - } - - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -static void gscon_fsm_wait_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct mgcp_conn_peer *conn_peer = NULL; - struct gsm_lchan *lchan = conn->lchan; - struct sockaddr_in *sin = NULL; - - switch (event) { - case GSCON_EV_MGW_CRCX_RESP_MSC: - conn_peer = data; - - /* Store address information we got in response from the CRCX command. */ - sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_local; - sin->sin_family = AF_INET; - sin->sin_addr.s_addr = inet_addr(conn_peer->addr); - sin->sin_port = osmo_ntohs(conn_peer->port); - - /* Send assignment complete message to the MSC */ - send_ass_compl(lchan, fi, true); - - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -static void gscon_fsm_clearing(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct msgb *resp; - - switch (event) { - case GSCON_EV_RSL_CLEAR_COMPL: - resp = gsm0808_create_clear_complete(); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -/* Wait for the handover logic to tell us whether the handover completed, - * failed or has timed out */ -static void gscon_fsm_wait_ho_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct mgcp_conn_peer conn_peer; - struct gsm_lchan *lchan = conn->lchan; - struct in_addr addr; - struct msgb *resp; - int rc; - - switch (event) { - case GSCON_EV_HO_COMPL: - /* The handover logic informs us that the handover has been - * completet. Now we have to tell the MGW the IP/Port on the - * new BTS so that the uplink RTP traffic can be redirected - * there. */ - - /* Prepare parameters with the information we got during the - * handover procedure (via IPACC) */ - memset(&conn_peer, 0, sizeof(conn_peer)); - addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); - osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr)); - conn_peer.port = lchan->abis_ip.bound_port; - - /* (Pre)Change state and modify the connection */ - osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_HO_TIMEOUT_TIMER_NR); - rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); - if (rc != 0) { - resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); - return; - } - break; - case GSCON_EV_HO_TIMEOUT: - case GSCON_EV_HO_FAIL: - /* The handover logic informs us that the handover failed for - * some reason. This means the phone stays on the TS/BTS on - * which it currently is. We will change back to the active - * state again as there are no further operations needed */ - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -/* Wait for the MGW to confirm handover related modification of the connection - * parameters */ -static void gscon_fsm_wait_mdcx_bts_ho(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - switch (event) { - case GSCON_EV_MGW_MDCX_RESP_BTS: - /* The MGW has confirmed the handover MDCX, and the handover - * is now also done on the RTP side. We may now change back - * to the active state. */ - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); - break; - case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); - break; - default: - OSMO_ASSERT(false); - break; - } -} - -#define EV_TRANSPARENT_SCCP S(GSCON_EV_TX_SCCP) | S(GSCON_EV_MO_DTAP) | S(GSCON_EV_MT_DTAP) - -static const struct osmo_fsm_state gscon_fsm_states[] = { - [ST_INIT] = { - .name = OSMO_STRINGIFY(INIT), - .in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND), - .out_state_mask = S(ST_WAIT_CC), - .action = gscon_fsm_init, - }, - [ST_WAIT_CC] = { - .name = OSMO_STRINGIFY(WAIT_CC), - .in_event_mask = S(GSCON_EV_A_CONN_CFM), - .out_state_mask = S(ST_ACTIVE), - .action = gscon_fsm_wait_cc, - }, - [ST_ACTIVE] = { - .name = OSMO_STRINGIFY(ACTIVE), - .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_A_ASSIGNMENT_CMD) | - S(GSCON_EV_A_HO_REQ) | S(GSCON_EV_HO_START), - .out_state_mask = S(ST_CLEARING) | S(ST_WAIT_CRCX_BTS) | S(ST_WAIT_ASS_CMPL) | - S(ST_WAIT_MO_HO_CMD) | S(ST_WAIT_HO_COMPL), - .action = gscon_fsm_active, - }, - [ST_WAIT_CRCX_BTS] = { - .name = OSMO_STRINGIFY(WAIT_CRCX_BTS), - .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_BTS), - .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_ASS_CMPL), - .action = gscon_fsm_wait_crcx_bts, - }, - [ST_WAIT_ASS_CMPL] = { - .name = OSMO_STRINGIFY(WAIT_ASS_CMPL), - .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_RR_ASS_COMPL) | S(GSCON_EV_RR_ASS_FAIL), - .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS), - .action = gscon_fsm_wait_ass_cmpl, - }, - [ST_WAIT_MDCX_BTS] = { - .name = OSMO_STRINGIFY(WAIT_MDCX_BTS), - .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS), - .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CRCX_MSC), - .action = gscon_fsm_wait_mdcx_bts, - }, - [ST_WAIT_CRCX_MSC] = { - .name = OSMO_STRINGIFY(WAIT_CRCX_MSC), - .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_MSC), - .out_state_mask = S(ST_ACTIVE), - .action = gscon_fsm_wait_crcx_msc, - }, - [ST_CLEARING] = { - .name = OSMO_STRINGIFY(CLEARING), - .in_event_mask = S(GSCON_EV_RSL_CLEAR_COMPL), - .action = gscon_fsm_clearing, - }, - - /* TODO: external handover, probably it makes sense to break up the - * program flow in handover_logic.c a bit and handle some of the logic - * here? */ - [ST_WAIT_MT_HO_ACC] = { - .name = OSMO_STRINGIFY(WAIT_MT_HO_ACC), - }, - [ST_WAIT_MT_HO_COMPL] = { - .name = OSMO_STRINGIFY(WAIT_MT_HO_COMPL), - }, - [ST_WAIT_MO_HO_CMD] = { - .name = OSMO_STRINGIFY(WAIT_MO_HO_CMD), - }, - [ST_MO_HO_PROCEEDING] = { - .name = OSMO_STRINGIFY(MO_HO_PROCEEDING), - }, - - /* Internal handover */ - [ST_WAIT_HO_COMPL] = { - .name = OSMO_STRINGIFY(WAIT_HO_COMPL), - .in_event_mask = S(GSCON_EV_HO_COMPL) | S(GSCON_EV_HO_FAIL) | S(GSCON_EV_HO_TIMEOUT), - .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS_HO) | S(ST_CLEARING), - .action = gscon_fsm_wait_ho_compl, - }, - [ST_WAIT_MDCX_BTS_HO] = { - .name = OSMO_STRINGIFY(WAIT_MDCX_BTS_HO), - .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS), - .action = gscon_fsm_wait_mdcx_bts_ho, - .out_state_mask = S(ST_ACTIVE), - }, -}; - -static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct msgb *resp = NULL; - - /* When a connection on the MGW fails, make sure that the reference - * in our book-keeping is erased. */ - switch (event) { - case GSCON_EV_MGW_FAIL_BTS: - conn->user_plane.fi_bts = NULL; - break; - case GSCON_EV_MGW_FAIL_MSC: - conn->user_plane.fi_msc = NULL; - break; - } - - /* Regular allstate event processing */ - switch (event) { - case GSCON_EV_MGW_FAIL_BTS: - case GSCON_EV_MGW_FAIL_MSC: - /* Note: An MGW connection die per definition at any time. - * However, if it dies during the assignment we must return - * with an assignment failure */ - OSMO_ASSERT(fi->state != ST_INIT && fi->state != ST_WAIT_CC); - if (fi->state == ST_WAIT_CRCX_BTS || fi->state == ST_WAIT_ASS_CMPL || fi->state == ST_WAIT_MDCX_BTS - || fi->state == ST_WAIT_CRCX_MSC) { - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - } - break; - case GSCON_EV_A_CLEAR_CMD: - /* MSC tells us to cleanly shut down */ - osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); - gsm0808_clear(conn); - /* 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 */ - toss_mgcp_conn(conn, fi); - - /* FIXME: Question: Is this a hack to force a clear complete from internel? - * nobody seems to send the event from outside? */ - osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RSL_CLEAR_COMPL, NULL); - break; - case GSCON_EV_A_DISC_IND: - /* MSC or SIGTRAN network has hard-released SCCP connection, - * terminate the FSM now. */ - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); - break; - case GSCON_EV_RLL_REL_IND: - /* BTS reports that one of the LAPDm data links was released */ - /* send proper clear request to MSC */ - LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n"); - resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE); - sigtran_send(conn, resp, fi); - break; - case GSCON_EV_RSL_CONN_FAIL: - LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n"); - resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); - sigtran_send(conn, resp, fi); - break; - case GSCON_EV_MGW_MDCX_RESP_MSC: - LOGPFSML(fi, LOGL_DEBUG, "Rx MDCX of MSC side (LCLS?)\n"); - break; - case GSCON_EV_LCLS_FAIL: - break; - default: - OSMO_ASSERT(false); - break; - } -} - -void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send); - -static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - if (conn->ho) { - LOGPFSML(fi, LOGL_DEBUG, "Releasing handover state\n"); - bsc_clear_handover(conn, 1); - conn->ho = NULL; - } - - if (conn->secondary_lchan) { - LOGPFSML(fi, LOGL_DEBUG, "Releasing secondary_lchan\n"); - lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); - conn->secondary_lchan = NULL; - } - if (conn->lchan) { - LOGPFSML(fi, LOGL_DEBUG, "Releasing lchan\n"); - lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END); - conn->lchan = NULL; - } - - if (conn->bsub) { - LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n"); - bsc_subscr_put(conn->bsub); - conn->bsub = NULL; - } - - if (conn->sccp.state != SUBSCR_SCCP_ST_NONE) { - 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); - conn->sccp.state = SUBSCR_SCCP_ST_NONE; - } - - /* drop pending messages */ - ho_dtap_cache_flush(conn, 0); - - penalty_timers_free(&conn->hodec2.penalty_timers); - - llist_del(&conn->entry); - talloc_free(conn); - fi->priv = NULL; -} - -static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - /* Make sure all possibly still open MGCP connections get closed */ - toss_mgcp_conn(conn, fi); - - if (conn->lcls.fi) { - /* request termination of LCLS FSM */ - osmo_fsm_inst_term(conn->lcls.fi, cause, NULL); - conn->lcls.fi = NULL; - } -} - -static int gscon_timer_cb(struct osmo_fsm_inst *fi) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct msgb *resp = NULL; - - switch (fi->T) { - case 993210: - /* MSC has not responded/confirmed connection with CC, this - * could indicate a bad SCCP connection. We now inform the the - * FSM that controls the BSSMAP reset about the event. Maybe - * a BSSMAP reset is necessary. */ - a_reset_conn_fail(conn->sccp.msc->a.reset_fsm); - - /* Since we could not reach the MSC, we give up and terminate - * the FSM instance now (N-DISCONNET.req is sent in - * gscon_cleanup() above) */ - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); - break; - case GSM0808_T10_TIMER_NR: /* Assignment Failed */ - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, NULL); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - break; - case MGCP_MGW_TIMEOUT_TIMER_NR: /* Assignment failed (no response from MGW) */ - resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); - sigtran_send(conn, resp, fi); - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - break; - case MGCP_MGW_HO_TIMEOUT_TIMER_NR: /* Handover failed (no response from MGW) */ - osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); - break; - default: - OSMO_ASSERT(false); - } - return 0; -} - -static struct osmo_fsm gscon_fsm = { - .name = "SUBSCR_CONN", - .states = gscon_fsm_states, - .num_states = ARRAY_SIZE(gscon_fsm_states), - .allstate_event_mask = S(GSCON_EV_A_DISC_IND) | S(GSCON_EV_A_CLEAR_CMD) | S(GSCON_EV_RSL_CONN_FAIL) | - S(GSCON_EV_RLL_REL_IND) | S(GSCON_EV_MGW_FAIL_BTS) | S(GSCON_EV_MGW_FAIL_MSC) | - S(GSCON_EV_MGW_MDCX_RESP_MSC) | S(GSCON_EV_LCLS_FAIL), - .allstate_action = gscon_fsm_allstate, - .cleanup = gscon_cleanup, - .pre_term = gscon_pre_term, - .timer_cb = gscon_timer_cb, - .log_subsys = DMSC, - .event_names = gscon_fsm_event_names, -}; - -/* Allocate a subscriber connection and its associated FSM */ -struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) -{ - struct gsm_subscriber_connection *conn; - static bool g_initialized = false; - - if (!g_initialized) { - osmo_fsm_register(&gscon_fsm); - osmo_fsm_register(&lcls_fsm); - g_initialized = true; - } - - conn = talloc_zero(net, struct gsm_subscriber_connection); - if (!conn) - return NULL; - - conn->network = net; - INIT_LLIST_HEAD(&conn->ho_dtap_cache); - /* BTW, penalty timers will be initialized on-demand. */ - conn->sccp.conn_id = -1; - - /* 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 */ - conn->fi = osmo_fsm_inst_alloc(&gscon_fsm, net, conn, LOGL_NOTICE, NULL); - if (!conn->fi) { - talloc_free(conn); - return NULL; - } - - /* initialize to some magic values that indicate "IE not [yet] received" */ - conn->lcls.config = 0xff; - conn->lcls.control = 0xff; - conn->lcls.fi = osmo_fsm_inst_alloc_child(&lcls_fsm, conn->fi, GSCON_EV_LCLS_FAIL); - if (!conn->lcls.fi) { - osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_ERROR, NULL); - return NULL; - } - conn->lcls.fi->priv = conn; - - llist_add_tail(&conn->entry, &net->subscr_conns); - return conn; -} diff --git a/src/libbsc/bsc_subscriber.c b/src/libbsc/bsc_subscriber.c deleted file mode 100644 index d9d90baa9..000000000 --- a/src/libbsc/bsc_subscriber.c +++ /dev/null @@ -1,168 +0,0 @@ -/* GSM subscriber details for use in BSC land */ - -/* - * (C) 2016 by sysmocom s.f.m.c. GmbH - * - * Author: Neels Hofmeyr - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include - -#include -#include - -#include -#include - -static struct bsc_subscr *bsc_subscr_alloc(struct llist_head *list) -{ - struct bsc_subscr *bsub; - - bsub = talloc_zero(list, struct bsc_subscr); - if (!bsub) - return NULL; - - llist_add_tail(&bsub->entry, list); - bsub->use_count = 1; - - return bsub; -} - -struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list, - const char *imsi) -{ - struct bsc_subscr *bsub; - - if (!imsi || !*imsi) - return NULL; - - llist_for_each_entry(bsub, list, entry) { - if (!strcmp(bsub->imsi, imsi)) - return bsc_subscr_get(bsub); - } - return NULL; -} - -struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list, - uint32_t tmsi) -{ - struct bsc_subscr *bsub; - - if (tmsi == GSM_RESERVED_TMSI) - return NULL; - - llist_for_each_entry(bsub, list, entry) { - if (bsub->tmsi == tmsi) - return bsc_subscr_get(bsub); - } - return NULL; -} - -void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi) -{ - if (!bsub) - return; - osmo_strlcpy(bsub->imsi, imsi, sizeof(bsub->imsi)); -} - -struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list, - const char *imsi) -{ - struct bsc_subscr *bsub; - bsub = bsc_subscr_find_by_imsi(list, imsi); - if (bsub) - return bsub; - bsub = bsc_subscr_alloc(list); - bsc_subscr_set_imsi(bsub, imsi); - return bsub; -} - -struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list, - uint32_t tmsi) -{ - struct bsc_subscr *bsub; - bsub = bsc_subscr_find_by_tmsi(list, tmsi); - if (bsub) - return bsub; - bsub = bsc_subscr_alloc(list); - bsub->tmsi = tmsi; - return bsub; -} - -const char *bsc_subscr_name(struct bsc_subscr *bsub) -{ - static char buf[32]; - if (!bsub) - return "unknown"; - if (bsub->imsi[0]) - snprintf(buf, sizeof(buf), "IMSI:%s", bsub->imsi); - else - snprintf(buf, sizeof(buf), "TMSI:0x%08x", bsub->tmsi); - return buf; -} - -static void bsc_subscr_free(struct bsc_subscr *bsub) -{ - llist_del(&bsub->entry); - talloc_free(bsub); -} - -struct bsc_subscr *_bsc_subscr_get(struct bsc_subscr *bsub, - const char *file, int line) -{ - OSMO_ASSERT(bsub->use_count < INT_MAX); - bsub->use_count++; - LOGPSRC(DREF, LOGL_DEBUG, file, line, - "BSC subscr %s usage increases to: %d\n", - bsc_subscr_name(bsub), bsub->use_count); - return bsub; -} - -struct bsc_subscr *_bsc_subscr_put(struct bsc_subscr *bsub, - const char *file, int line) -{ - bsub->use_count--; - LOGPSRC(DREF, bsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR, - file, line, - "BSC subscr %s usage decreases to: %d\n", - bsc_subscr_name(bsub), bsub->use_count); - if (bsub->use_count <= 0) - bsc_subscr_free(bsub); - return NULL; -} - -void log_set_filter_bsc_subscr(struct log_target *target, - struct bsc_subscr *bsc_subscr) -{ - struct bsc_subscr **fsub = (void*)&target->filter_data[LOG_FLT_BSC_SUBSCR]; - - /* free the old data */ - if (*fsub) { - bsc_subscr_put(*fsub); - *fsub = NULL; - } - - if (bsc_subscr) { - target->filter_map |= (1 << LOG_FLT_BSC_SUBSCR); - *fsub = bsc_subscr_get(bsc_subscr); - } else - target->filter_map &= ~(1 << LOG_FLT_BSC_SUBSCR); -} diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c deleted file mode 100644 index 757a8a1cf..000000000 --- a/src/libbsc/bsc_vty.c +++ /dev/null @@ -1,5003 +0,0 @@ -/* OpenBSC interface to quagga VTY */ -/* (C) 2009-2017 by Harald Welte - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "../../bscconfig.h" - -#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" }, - { BTS_LOC_FIX_3D, "fix3d" }, - { 0, NULL } -}; - -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) -{ - /* It can't hurt to force callers to continue to pass the vty instance - * to this function, in case we'd like to retrieve the global - * gsm_network instance from the vty at some point in the future. But - * until then, just return the global pointer, which should have been - * initialized by common_cs_vty_init(). - */ - OSMO_ASSERT(vty_global_gsm_network); - return vty_global_gsm_network; -} - -static int dummy_config_write(struct vty *v) -{ - return CMD_SUCCESS; -} - -static 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), - get_value_string(abis_nm_adm_state_names, nms->administrative), - abis_nm_avail_name(nms->availability), VTY_NEWLINE); -} - -static void dump_pchan_load_vty(struct vty *vty, char *prefix, - const struct pchan_load *pl) -{ - int i; - int dumped = 0; - - for (i = 0; i < ARRAY_SIZE(pl->pchan); i++) { - const struct load_counter *lc = &pl->pchan[i]; - unsigned int percent; - - if (lc->total == 0) - continue; - - percent = (lc->used * 100) / lc->total; - - vty_out(vty, "%s%20s: %3u%% (%u/%u)%s", prefix, - gsm_pchan_name(i), percent, lc->used, lc->total, - VTY_NEWLINE); - dumped ++; - } - if (!dumped) - vty_out(vty, "%s(none)%s", prefix, VTY_NEWLINE); -} - -static void net_dump_vty(struct vty *vty, struct gsm_network *net) -{ - struct pchan_load pl; - int i; - - vty_out(vty, "BSC is on MCC-MNC %s and has %u BTS%s", - osmo_plmn_name(&net->plmn), net->num_bts, VTY_NEWLINE); - vty_out(vty, "%s", VTY_NEWLINE); - vty_out(vty, " Encryption:"); - for (i = 0; i < 8; i++) { - if (net->a5_encryption_mask & (1 << i)) - vty_out(vty, " A5/%u", i); - } - vty_out(vty, "%s", VTY_NEWLINE); - vty_out(vty, " NECI (TCH/H): %u%s", net->neci, - VTY_NEWLINE); - vty_out(vty, " Use TCH for Paging any: %d%s", net->pag_any_tch, - VTY_NEWLINE); - - { - struct gsm_bts *bts; - unsigned int ho_active_count = 0; - unsigned int ho_inactive_count = 0; - - llist_for_each_entry(bts, &net->bts_list, list) { - if (ho_get_ho_active(bts->ho)) - ho_active_count ++; - else - ho_inactive_count ++; - } - - if (ho_active_count && ho_inactive_count) - vty_out(vty, " Handover: On at %u BTS, Off at %u BTS%s", - ho_active_count, ho_inactive_count, VTY_NEWLINE); - else - vty_out(vty, " Handover: %s%s", ho_active_count ? "On" : "Off", - VTY_NEWLINE); - } - - network_chan_load(&pl, net); - vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); - dump_pchan_load_vty(vty, " ", &pl); - - /* show rf */ - if (net->bsc_data) - vty_out(vty, " Last RF Command: %s%s", - net->bsc_data->rf_ctrl->last_state_command, - VTY_NEWLINE); - if (net->bsc_data) - vty_out(vty, " Last RF Lock Command: %s%s", - net->bsc_data->rf_ctrl->last_rf_lock_ctrl_command, - VTY_NEWLINE); -} - -DEFUN(bsc_show_net, bsc_show_net_cmd, "show network", - SHOW_STR "Display information about a GSM NETWORK\n") -{ - struct gsm_network *net = gsmnet_from_vty(vty); - net_dump_vty(vty, net); - - 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); -} - -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_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 ramping: %senabled%s", - acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "not ", VTY_NEWLINE); - if (acc_ramp_is_enabled(&bts->acc_ramp)) { - if (!acc_ramp_step_interval_is_dynamic(&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); - else - vty_out(vty, " Access Control Class ramping step interval: dynamic%s", 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); - 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); - 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 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); - - 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); - - 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") -{ - struct gsm_network *net = gsmnet_from_vty(vty); - int bts_nr; - - if (argc != 0) { - /* use the BTS number that the user has specified */ - bts_nr = atoi(argv[0]); - if (bts_nr >= net->num_bts) { - vty_out(vty, "%% can't find BTS '%s'%s", argv[0], - VTY_NEWLINE); - return CMD_WARNING; - } - bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); - return CMD_SUCCESS; - } - /* print all BTS's */ - for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) - bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); - - 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 != GSM_PCHAN_NONE) - vty_out(vty, " phys_chan_config %s%s", - gsm_pchan_name(ts->pchan), 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.nm_state.administrative == NM_STATE_LOCKED ? 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 11bit_rach_support_for_egprs %u%s", - bts->gprs.supports_egprs_11bit_rach, VTY_NEWLINE); - - 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 in_addr ia; - - ia.s_addr = htonl(nsvc->remote_ip); - 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); - vty_out(vty, " gprs nsvc %u remote udp port %u%s", i, - nsvc->remote_port, VTY_NEWLINE); - vty_out(vty, " gprs nsvc %u remote ip %s%s", i, - inet_ntoa(ia), 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-descrption attach %u%s", - bts->si_common.chan_desc.att, VTY_NEWLINE); - vty_out(vty, " channel-descrption bs-pa-mfrms %u%s", - bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE); - vty_out(vty, " channel-descrption bs-ag-blks-res %u%s", - bts->si_common.chan_desc.bs_ag_blks_res, 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); - vty_out(vty, " %saccess-control-class-ramping%s", acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "no ", VTY_NEWLINE); - if (!acc_ramp_step_interval_is_dynamic(&bts->acc_ramp)) { - vty_out(vty, " access-control-class-ramping-step-interval %u%s", - acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE); - } else { - vty_out(vty, " access-control-class-ramping-step-interval dynamic%s", VTY_NEWLINE); - } - vty_out(vty, " access-control-class-ramping-step-size %u%s", acc_ramp_get_step_size(&bts->acc_ramp), - 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, " ip.access 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, " ip.access rsl-ip %s%s", inet_ntoa(ia), - VTY_NEWLINE); - } - vty_out(vty, " oml ip.access 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); - } - - 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); - - 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<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; -} - -/* small helper macro for conditional dumping of timer */ -#define VTY_OUT_TIMER(number) \ - if (gsmnet->T##number != GSM_T##number##_DEFAULT) \ - vty_out(vty, " timer t"#number" %u%s", gsmnet->T##number, VTY_NEWLINE) - -static int config_write_net(struct vty *vty) -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - int i; - - vty_out(vty, "network%s", VTY_NEWLINE); - vty_out(vty, " network country code %s%s", osmo_mcc_name(gsmnet->plmn.mcc), VTY_NEWLINE); - vty_out(vty, " mobile network code %s%s", - osmo_mnc_name(gsmnet->plmn.mnc, gsmnet->plmn.mnc_3_digits), VTY_NEWLINE); - vty_out(vty, " encryption a5"); - for (i = 0; i < 8; i++) { - if (gsmnet->a5_encryption_mask & (1 << i)) - vty_out(vty, " %u", i); - } - vty_out(vty, "%s", VTY_NEWLINE); - vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE); - vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE); - - ho_vty_write_net(vty, gsmnet); - - VTY_OUT_TIMER(3101); - VTY_OUT_TIMER(3103); - VTY_OUT_TIMER(3105); - VTY_OUT_TIMER(3107); - VTY_OUT_TIMER(3109); - VTY_OUT_TIMER(3111); - VTY_OUT_TIMER(3113); - VTY_OUT_TIMER(3115); - VTY_OUT_TIMER(3117); - VTY_OUT_TIMER(3119); - VTY_OUT_TIMER(3122); - VTY_OUT_TIMER(3141); - if (!gsmnet->dyn_ts_allow_tch_f) - vty_out(vty, " dyn_ts_allow_tch_f 0%s", VTY_NEWLINE); - if (gsmnet->tz.override != 0) { - if (gsmnet->tz.dst) - vty_out(vty, " timezone %d %d %d%s", - gsmnet->tz.hr, gsmnet->tz.mn, gsmnet->tz.dst, - VTY_NEWLINE); - else - vty_out(vty, " timezone %d %d%s", - gsmnet->tz.hr, gsmnet->tz.mn, VTY_NEWLINE); - } - if (gsmnet->t3212 == 0) - vty_out(vty, " no periodic location update%s", VTY_NEWLINE); - else - vty_out(vty, " periodic location update %u%s", - gsmnet->t3212 * 6, VTY_NEWLINE); - - { - uint16_t meas_port; - char *meas_host; - const char *meas_scenario; - - meas_feed_cfg_get(&meas_host, &meas_port); - meas_scenario = meas_feed_scenario_get(); - - if (meas_port) - vty_out(vty, " meas-feed destination %s %u%s", - meas_host, meas_port, VTY_NEWLINE); - if (strlen(meas_scenario) > 0) - vty_out(vty, " meas-feed scenario %s%s", - meas_scenario, VTY_NEWLINE); - } - - return CMD_SUCCESS; -} - -static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx) -{ - 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, " NM State: "); - net_dump_nmstate(vty, &trx->mo.nm_state); - 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%s", - trx->rsl_tei, VTY_NEWLINE); - } else { - vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); - e1isl_dump_vty(vty, trx->rsl_link); - } -} - -static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts) -{ - uint8_t trx_nr; - for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) - trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); -} - -DEFUN(show_trx, - show_trx_cmd, - "show trx [<0-255>] [<0-255>]", - SHOW_STR "Display information about a TRX\n" - BTS_TRX_STR) -{ - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_bts *bts = NULL; - int bts_nr, trx_nr; - - if (argc >= 1) { - /* use the BTS number that the user has specified */ - bts_nr = atoi(argv[0]); - 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) { - trx_nr = atoi(argv[1]); - if (trx_nr >= bts->num_trx) { - vty_out(vty, "%% can't find TRX '%s'%s", argv[1], - VTY_NEWLINE); - return CMD_WARNING; - } - trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); - return CMD_SUCCESS; - } - if (bts) { - /* print all TRX in this BTS */ - print_all_trx(vty, bts); - return CMD_SUCCESS; - } - - for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) - print_all_trx(vty, gsm_bts_num(net, bts_nr)); - - return CMD_SUCCESS; -} - - -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, TSC %u", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts)); - if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { - vty_out(vty, " (%s mode)", - ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F"); - } else if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { - vty_out(vty, " (%s mode)", gsm_pchan_name(ts->dyn.pchan_is)); - } - vty_out(vty, "%s", VTY_NEWLINE); - vty_out(vty, " NM State: "); - 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>]", - SHOW_STR "Display information about a TS\n" - BTS_TRX_TS_STR) -{ - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_bts *bts = NULL; - struct gsm_bts_trx *trx = NULL; - struct gsm_bts_trx_ts *ts = NULL; - int bts_nr, trx_nr, ts_nr; - - if (argc >= 1) { - /* use the BTS number that the user has specified */ - bts_nr = atoi(argv[0]); - 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) { - trx_nr = atoi(argv[1]); - if (trx_nr >= bts->num_trx) { - vty_out(vty, "%% can't find TRX '%s'%s", argv[1], - VTY_NEWLINE); - return CMD_WARNING; - } - trx = gsm_bts_trx_num(bts, trx_nr); - } - if (argc >= 3) { - ts_nr = atoi(argv[2]); - if (ts_nr >= TRX_NR_TS) { - vty_out(vty, "%% can't find TS '%s'%s", argv[2], - VTY_NEWLINE); - return CMD_WARNING; - } - /* Fully Specified: print and exit */ - ts = &trx->ts[ts_nr]; - ts_dump_vty(vty, ts); - return CMD_SUCCESS; - } - - if (bts && trx) { - /* Iterate over all TS in this TRX */ - for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { - ts = &trx->ts[ts_nr]; - ts_dump_vty(vty, ts); - } - } else if (bts) { - /* Iterate over all TRX in this BTS, TS in each TRX */ - for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { - 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_dump_vty(vty, ts); - } - } - } else { - /* Iterate over all BTS, TRX in each BTS, TS in each TRX */ - for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { - bts = gsm_bts_num(net, bts_nr); - for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { - 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_dump_vty(vty, ts); - } - } - } - } - - return CMD_SUCCESS; -} - -static 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); - if (bsub->tmsi != GSM_RESERVED_TMSI) - vty_out(vty, " TMSI: 0x%08x%s", bsub->tmsi, - VTY_NEWLINE); - vty_out(vty, " Use count: %d%s", 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"); -} - -/* FIXME: move this to libosmogsm */ -static const struct value_string gsm48_cmode_names[] = { - { GSM48_CMODE_SIGN, "signalling" }, - { GSM48_CMODE_SPEECH_V1, "FR or HR" }, - { GSM48_CMODE_SPEECH_EFR, "EFR" }, - { GSM48_CMODE_SPEECH_AMR, "AMR" }, - { GSM48_CMODE_DATA_14k5, "CSD(14k5)" }, - { GSM48_CMODE_DATA_12k0, "CSD(12k0)" }, - { GSM48_CMODE_DATA_6k0, "CSD(6k0)" }, - { GSM48_CMODE_DATA_3k6, "CSD(3k6)" }, - { 0, NULL } -}; - -/* 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) -{ - switch (ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - if (ts->dyn.pchan_is == ts->dyn.pchan_want) - vty_out(vty, " as %s", - gsm_pchan_name(ts->dyn.pchan_is)); - else - vty_out(vty, " switching %s -> %s", - gsm_pchan_name(ts->dyn.pchan_is), - gsm_pchan_name(ts->dyn.pchan_want)); - break; - case GSM_PCHAN_TCH_F_PDCH: - if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) - vty_out(vty, " as %s", - (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" - : "TCH/F"); - else - vty_out(vty, " switching %s -> %s", - (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" - : "TCH/F", - (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" - : "TCH/F"); - break; - default: - /* no dyn ts */ - break; - } -} - -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); - /* show dyn TS details, if applicable */ - switch (lchan->ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - vty_out(vty, " Osmocom Dyn TS:"); - vty_out_dyn_ts_status(vty, lchan->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, lchan->ts); - vty_out(vty, VTY_NEWLINE); - break; - default: - /* no dyn ts */ - break; - } - vty_out(vty, " Connection: %u, State: %s%s%s%s", - lchan->conn ? 1: 0, - gsm_lchans_name(lchan->state), - lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "", - lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "", - 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", - get_value_string(gsm48_cmode_names, 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)); - 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), gsm_lchans_name(lchan->state), - 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 *)) -{ - int lchan_nr; - for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) { - struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; - if ((lchan->type == GSM_LCHAN_NONE) && (lchan->state == LCHAN_S_NONE)) - continue; - dump_cb(vty, lchan); - } - - return CMD_SUCCESS; -} - -static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty, - void (*dump_cb)(struct vty *, struct gsm_lchan *)) -{ - int ts_nr; - - for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { - struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; - dump_lchan_trx_ts(ts, vty, dump_cb); - } - - return CMD_SUCCESS; -} - -static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty, - void (*dump_cb)(struct vty *, struct gsm_lchan *)) -{ - int trx_nr; - - for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { - struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr); - dump_lchan_trx(trx, vty, dump_cb); - } - - return CMD_SUCCESS; -} - -static int lchan_summary(struct vty *vty, int argc, const char **argv, - void (*dump_cb)(struct vty *, struct gsm_lchan *)) -{ - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_bts *bts = NULL; - struct gsm_bts_trx *trx = NULL; - struct gsm_bts_trx_ts *ts = NULL; - struct gsm_lchan *lchan; - int bts_nr, trx_nr, ts_nr, lchan_nr; - - if (argc >= 1) { - /* use the BTS number that the user has specified */ - bts_nr = atoi(argv[0]); - 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 == 1) - return dump_lchan_bts(bts, vty, dump_cb); - } - if (argc >= 2) { - trx_nr = atoi(argv[1]); - if (trx_nr >= bts->num_trx) { - vty_out(vty, "%% can't find TRX %s%s", argv[1], - VTY_NEWLINE); - return CMD_WARNING; - } - trx = gsm_bts_trx_num(bts, trx_nr); - - if (argc == 2) - return dump_lchan_trx(trx, vty, dump_cb); - } - if (argc >= 3) { - ts_nr = atoi(argv[2]); - if (ts_nr >= TRX_NR_TS) { - vty_out(vty, "%% can't find TS %s%s", argv[2], - VTY_NEWLINE); - return CMD_WARNING; - } - ts = &trx->ts[ts_nr]; - - if (argc == 3) - return dump_lchan_trx_ts(ts, vty, dump_cb); - } - if (argc >= 4) { - lchan_nr = atoi(argv[3]); - if (lchan_nr >= TS_MAX_LCHAN) { - vty_out(vty, "%% can't find LCHAN %s%s", argv[3], - VTY_NEWLINE); - return CMD_WARNING; - } - lchan = &ts->lchan[lchan_nr]; - dump_cb(vty, lchan); - return CMD_SUCCESS; - } - - - for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { - bts = gsm_bts_num(net, bts_nr); - dump_lchan_bts(bts, vty, dump_cb); - } - - return CMD_SUCCESS; -} - - -DEFUN(show_lchan, - show_lchan_cmd, - "show lchan [<0-255>] [<0-255>] [<0-7>] [<0-7>]", - SHOW_STR "Display information about a logical channel\n" - BTS_TRX_TS_LCHAN_STR) -{ - return lchan_summary(vty, argc, argv, lchan_dump_full_vty); -} - -DEFUN(show_lchan_summary, - show_lchan_summary_cmd, - "show lchan summary [<0-255>] [<0-255>] [<0-7>] [<0-7>]", - SHOW_STR "Display information about a logical channel\n" - "Short summary\n" - BTS_TRX_TS_LCHAN_STR) -{ - return lchan_summary(vty, argc, argv, lchan_dump_short_vty); -} - -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, mode=%s, mgw_ep=%s%s", - conn->sccp.conn_id, conn->sccp.msc->nr, conn->hodec2.failures, - get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), - conn->user_plane.mgw_endpoint, VTY_NEWLINE); - if (conn->lcls.global_call_ref_len) { - vty_out(vty, " LCLS GCR: %s%s", - osmo_hexdump_nospc(conn->lcls.global_call_ref, conn->lcls.global_call_ref_len), - VTY_NEWLINE); - vty_out(vty, " LCLS Config: 0x%02x, LCLS Control: 0x%02x, LCLS BSS Status: %s%s", - conn->lcls.config, conn->lcls.control, osmo_fsm_inst_state_name(conn->lcls.fi), - VTY_NEWLINE); - } - if (conn->lchan) - lchan_dump_full_vty(vty, conn->lchan); - if (conn->secondary_lchan) - lchan_dump_full_vty(vty, conn->secondary_lchan); -} - -DEFUN(show_subscr_conn, - show_subscr_conn_cmd, - "show conns", - SHOW_STR "Display currently active subscriber connections\n") -{ - 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) - vty_out(vty, "None%s", VTY_NEWLINE); - - return CMD_SUCCESS; -} - -static int trigger_ho_or_as(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_bts *to_bts) -{ - int rc; - - 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); - rc = bsc_handover_start(HODEC_NONE, from_lchan, to_bts, from_lchan->type); - if (rc) { - vty_out(vty, "bsc_handover_start() returned %d=%s%s", rc, - strerror(-rc), VTY_NEWLINE); - return CMD_WARNING; - } - return CMD_SUCCESS; -} - -static int ho_or_as(struct vty *vty, const char *argv[], int argc) -{ - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_subscriber_connection *conn; - struct gsm_bts *bts; - struct gsm_bts *new_bts = NULL; - unsigned int bts_nr = atoi(argv[0]); - 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]); - - /* Lookup the BTS where we want to handover to */ - llist_for_each_entry(bts, &net->bts_list, list) { - if (bts->nr == bts_nr_new) { - new_bts = bts; - break; - } - } - - if (!new_bts) { - vty_out(vty, "Unable to trigger handover, specified bts #%u does not exist %s", - bts_nr_new, VTY_NEWLINE); - return CMD_WARNING; - } - } - - action = new_bts ? "handover" : "assignment"; - - /* Find the connection/lchan that we want to handover */ - llist_for_each_entry(conn, &net->subscr_conns, entry) { - if (conn_get_bts(conn)->nr == bts_nr && - conn->lchan->ts->trx->nr == trx_nr && - 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); - } - } - - vty_out(vty, "Unable to trigger %s, specified connection (bts=%u,trx=%u,ts=%u,ss=%u) does not exist%s", - action, bts_nr, trx_nr, ts_nr, ss_nr, VTY_NEWLINE); - - return CMD_WARNING; -} - -#define MANUAL_HANDOVER_STR "Manually trigger handover (for debugging)\n" -#define MANUAL_ASSIGNMENT_STR "Manually trigger assignment (for debugging)\n" - -DEFUN(handover_subscr_conn, - handover_subscr_conn_cmd, - "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> handover <0-255>", - BTS_NR_TRX_TS_SS_STR2 - MANUAL_HANDOVER_STR - "New " BTS_NR_STR) -{ - return ho_or_as(vty, argv, argc); -} - -DEFUN(assignment_subscr_conn, - assignment_subscr_conn_cmd, - "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> assignment", - BTS_NR_TRX_TS_SS_STR2 - MANUAL_ASSIGNMENT_STR) -{ - return ho_or_as(vty, argv, argc); -} - -static struct gsm_lchan *find_used_voice_lchan(struct vty *vty) -{ - struct gsm_bts *bts; - struct gsm_network *network = gsmnet_from_vty(vty); - - llist_for_each_entry(bts, &network->bts_list, list) { - struct gsm_bts_trx *trx; - - llist_for_each_entry(trx, &bts->trx_list, list) { - int i; - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - int j; - int subslots; - - /* skip administratively deactivated timeslots */ - if (!nm_is_running(&ts->mo.nm_state)) - continue; - - subslots = ts_subslots(ts); - for (j = 0; j < subslots; j++) { - struct gsm_lchan *lchan = &ts->lchan[j]; - - if (lchan->state == LCHAN_S_ACTIVE - && (lchan->type == GSM_LCHAN_TCH_F - || lchan->type == GSM_LCHAN_TCH_H)) { - - vty_out(vty, "Found voice call: %s%s", - gsm_lchan_name(lchan), VTY_NEWLINE); - lchan_dump_full_vty(vty, lchan); - return lchan; - } - } - } - } - } - - vty_out(vty, "Cannot find any ongoing voice calls%s", VTY_NEWLINE); - return NULL; -} - -static struct gsm_bts *find_other_bts_with_free_slots(struct vty *vty, struct gsm_bts *not_this_bts, - enum gsm_phys_chan_config free_type) -{ - struct gsm_bts *bts; - struct gsm_network *network = gsmnet_from_vty(vty); - - llist_for_each_entry(bts, &network->bts_list, list) { - struct gsm_bts_trx *trx; - - if (bts == not_this_bts) - continue; - - llist_for_each_entry(trx, &bts->trx_list, list) { - int i; - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - int j; - int subslots; - - /* skip administratively deactivated timeslots */ - if (!nm_is_running(&ts->mo.nm_state)) - continue; - - if (ts->pchan != free_type) - continue; - - subslots = ts_subslots(ts); - for (j = 0; j < subslots; j++) { - struct gsm_lchan *lchan = &ts->lchan[j]; - - if (lchan->state == LCHAN_S_NONE) { - vty_out(vty, "Found unused %s slot: %s%s", - gsm_pchan_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); - return NULL; -} - -DEFUN(handover_any, handover_any_cmd, - "handover any", - MANUAL_HANDOVER_STR - "Pick any actively used TCH/F or TCH/H lchan and handover to any other BTS." - " This is likely to fail if not all BTS are guaranteed to be reachable by the MS.\n") -{ - struct gsm_lchan *from_lchan; - struct gsm_bts *to_bts; - - from_lchan = find_used_voice_lchan(vty); - if (!from_lchan) - return CMD_WARNING; - - to_bts = find_other_bts_with_free_slots(vty, from_lchan->ts->trx->bts, - ts_pchan(from_lchan->ts)); - if (!to_bts) - return CMD_WARNING; - - return trigger_ho_or_as(vty, from_lchan, to_bts); -} - -DEFUN(assignment_any, assignment_any_cmd, - "assignment any", - MANUAL_ASSIGNMENT_STR - "Pick any actively used TCH/F or TCH/H lchan and re-assign within the same BTS." - " This will fail if no lchans of the same type are available besides the used one.\n") -{ - struct gsm_lchan *from_lchan; - - from_lchan = find_used_voice_lchan(vty); - if (!from_lchan) - return CMD_WARNING; - - return trigger_ho_or_as(vty, from_lchan, NULL); -} - -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); - bsc_subscr_dump_vty(vty, pag->bsub); -} - -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) - paging_dump_vty(vty, pag); -} - -DEFUN(show_paging, - show_paging_cmd, - "show paging [<0-255>]", - SHOW_STR "Display information about paging reuqests of a BTS\n" - BTS_NR_STR) -{ - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_bts *bts; - int bts_nr; - - if (argc >= 1) { - /* use the BTS number that the user has specified */ - bts_nr = atoi(argv[0]); - 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); - bts_paging_dump_vty(vty, bts); - - return CMD_SUCCESS; - } - for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { - bts = gsm_bts_num(net, bts_nr); - bts_paging_dump_vty(vty, bts); - } - - return CMD_SUCCESS; -} - -DEFUN(show_paging_group, - show_paging_group_cmd, - "show paging-group <0-255> IMSI", - SHOW_STR "Display the paging group\n" - BTS_NR_STR "IMSI\n") -{ - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_bts *bts; - unsigned int page_group; - int bts_nr = atoi(argv[0]); - - 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 (!bts) { - vty_out(vty, "%% can't find BTS %s%s", argv[0], VTY_NEWLINE); - return CMD_WARNING; - } - - page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc, - str_to_imsi(argv[1])); - vty_out(vty, "%%Paging group for IMSI %" PRIu64 " on BTS #%d is %u%s", - str_to_imsi(argv[1]), bts->nr, - page_group, VTY_NEWLINE); - return CMD_SUCCESS; -} - -DEFUN(cfg_net_neci, - cfg_net_neci_cmd, - "neci (0|1)", - "New Establish Cause Indication\n" - "Don't set the NECI bit\n" "Set the NECI bit\n") -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - - gsmnet->neci = atoi(argv[0]); - gsm_net_update_ctype(gsmnet); - return CMD_SUCCESS; -} - -DEFUN(cfg_net_pag_any_tch, - cfg_net_pag_any_tch_cmd, - "paging any use tch (0|1)", - "Assign a TCH when receiving a Paging Any request\n" - "Any Channel\n" "Use\n" "TCH\n" - "Do not use TCH for Paging Request Any\n" - "Do use TCH for Paging Request Any\n") -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - gsmnet->pag_any_tch = atoi(argv[0]); - gsm_net_update_ctype(gsmnet); - return CMD_SUCCESS; -} - -#define DEFAULT_TIMER(number) GSM_T##number##_DEFAULT -/* Add another expansion so that DEFAULT_TIMER() becomes its value */ -#define EXPAND_AND_STRINGIFY(x) OSMO_STRINGIFY(x) - -#define DECLARE_TIMER(number, doc) \ - DEFUN(cfg_net_T##number, \ - cfg_net_T##number##_cmd, \ - "timer t" #number " (default|<1-65535>)", \ - "Configure GSM Timers\n" \ - doc " (default: " EXPAND_AND_STRINGIFY(DEFAULT_TIMER(number)) " seconds)\n" \ - "Set to default timer value" \ - " (" EXPAND_AND_STRINGIFY(DEFAULT_TIMER(number)) " seconds)\n" \ - "Timer Value in seconds\n") \ -{ \ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); \ - int value; \ - if (strcmp(argv[0], "default") == 0) \ - value = DEFAULT_TIMER(number); \ - else \ - value = atoi(argv[0]); \ - \ - gsmnet->T##number = value; \ - return CMD_SUCCESS; \ -} - -DECLARE_TIMER(3101, "Set the timeout value for IMMEDIATE ASSIGNMENT") -DECLARE_TIMER(3103, "Set the timeout value for HANDOVER") -DECLARE_TIMER(3105, "Set the timer for repetition of PHYSICAL INFORMATION") -DECLARE_TIMER(3107, "Currently not used") -DECLARE_TIMER(3109, "Set the RSL SACCH deactivation timeout") -DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel") -DECLARE_TIMER(3113, "Set the time to try paging a subscriber") -DECLARE_TIMER(3115, "Currently not used") -DECLARE_TIMER(3117, "Currently not used") -DECLARE_TIMER(3119, "Currently not used") -DECLARE_TIMER(3122, "Default waiting time (seconds) after IMM ASS REJECT") -DECLARE_TIMER(3141, "Currently not used") - -DEFUN_DEPRECATED(cfg_net_dtx, - cfg_net_dtx_cmd, - "dtx-used (0|1)", - ".HIDDEN\n""Obsolete\n""Obsolete\n") -{ - vty_out(vty, "%% 'dtx-used' is now deprecated: use dtx * " - "configuration options of BTS instead%s", VTY_NEWLINE); - return CMD_SUCCESS; -} - -/* per-BTS configuration */ -DEFUN(cfg_bts, - cfg_bts_cmd, - "bts <0-255>", - "Select a BTS to configure\n" - BTS_NR_STR) -{ - 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 = gsm_bts_alloc_register(gsmnet, GSM_BTS_TYPE_UNKNOWN, - HARDCODED_BSIC); - /* - * Initalize bts->acc_ramp here. Else we could segfault while - * processing a configuration file with ACC ramping settings. - */ - acc_ramp_init(&bts->acc_ramp, bts); - } 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(cfg_bts_type, - cfg_bts_type_cmd, - "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(cfg_bts_band, - cfg_bts_band_cmd, - "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(cfg_bts_dtxu, cfg_bts_dtxu_cmd, "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(cfg_bts_no_dtxu, cfg_bts_no_dtxu_cmd, "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(cfg_bts_dtxd, cfg_bts_dtxd_cmd, "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(cfg_bts_no_dtxd, cfg_bts_no_dtxd_cmd, "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(cfg_bts_ci, - cfg_bts_ci_cmd, - "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(cfg_bts_lac, - cfg_bts_lac_cmd, - "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(cfg_bts_bsic, - cfg_bts_bsic_cmd, - "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(cfg_bts_unit_id, - cfg_bts_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") -{ - 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(cfg_bts_rsl_ip, - cfg_bts_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") -{ - 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; -} - -#define NOKIA_STR "Nokia *Site related commands\n" - -DEFUN(cfg_bts_nokia_site_skip_reset, - cfg_bts_nokia_site_skip_reset_cmd, - "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(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") -{ - 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(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") -{ - 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(cfg_bts_stream_id, - cfg_bts_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") -{ - 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; -} - -#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n" - -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(cfg_bts_oml_e1_tei, - cfg_bts_oml_e1_tei_cmd, - "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(cfg_bts_challoc, cfg_bts_challoc_cmd, - "channel allocator (ascending|descending)", - "Channnel Allocator\n" "Channel Allocator\n" - "Allocate Timeslots and Transceivers in ascending order\n" - "Allocate Timeslots and Transceivers in descending order\n") -{ - 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(cfg_bts_rach_tx_integer, - cfg_bts_rach_tx_integer_cmd, - "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(cfg_bts_rach_max_trans, - cfg_bts_rach_max_trans_cmd, - "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(cfg_bts_chan_desc_att, - cfg_bts_chan_desc_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") -{ - struct gsm_bts *bts = vty->index; - bts->si_common.chan_desc.att = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_chan_desc_bs_pa_mfrms, - cfg_bts_chan_desc_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") -{ - 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; -} - -DEFUN(cfg_bts_chan_desc_bs_ag_blks_res, - cfg_bts_chan_desc_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") -{ - 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; -} - -#define NM_STR "Network Management\n" - -DEFUN(cfg_bts_rach_nm_b_thresh, - cfg_bts_rach_nm_b_thresh_cmd, - "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") -{ - struct gsm_bts *bts = vty->index; - bts->rach_b_thresh = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_rach_nm_ldavg, - cfg_bts_rach_nm_ldavg_cmd, - "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(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd, - "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(cfg_bts_rach_ec_allowed, cfg_bts_rach_ec_allowed_cmd, - "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(cfg_bts_rach_ac_class, cfg_bts_rach_ac_class_cmd, - "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)); - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd, - "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") -{ - struct gsm_bts *bts = vty->index; - - bts->ms_max_power = atoi(argv[0]); - - return CMD_SUCCESS; -} - -#define CELL_STR "Cell Parameters\n" - -DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd, - "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") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2; - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd, - "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)") -{ - struct gsm_bts *bts = vty->index; - - bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_cell_bar_qualify, cfg_bts_cell_bar_qualify_cmd, - "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(cfg_bts_cell_resel_ofs, cfg_bts_cell_resel_ofs_cmd, - "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(cfg_bts_temp_ofs, cfg_bts_temp_ofs_cmd, - "temporary offset <0-60>", - "Cell selection temporary negative offset\n" - "Cell selection temporary negative offset\n" - "Cell selection temporary negative offset in dB") -{ - 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(cfg_bts_temp_ofs_inf, cfg_bts_temp_ofs_inf_cmd, - "temporary offset infinite", - "Cell selection temporary negative offset\n" - "Cell selection temporary negative offset\n" - "Sets cell selection temporary negative offset to infinity") -{ - 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(cfg_bts_penalty_time, cfg_bts_penalty_time_cmd, - "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(cfg_bts_penalty_time_rsvd, cfg_bts_penalty_time_rsvd_cmd, - "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)") -{ - 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(cfg_bts_radio_link_timeout, cfg_bts_radio_link_timeout_cmd, - "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(cfg_bts_radio_link_timeout_inf, cfg_bts_radio_link_timeout_inf_cmd, - "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" - -DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd, - "gprs cell bvci <2-65535>", - GPRS_TEXT - "GPRS Cell Settings\n" - "GPRS BSSGP VC Identifier\n" - "GPRS BSSGP VC Identifier") -{ - /* ETSI TS 101 343: values 0 and 1 are reserved for signalling and PTM */ - struct gsm_bts *bts = vty->index; - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.cell.bvci = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd, - "gprs nsei <0-65535>", - GPRS_TEXT - "GPRS NS Entity Identifier\n" - "GPRS NS Entity Identifier") -{ - struct gsm_bts *bts = vty->index; - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - 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(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd, - "gprs nsvc <0-1> nsvci <0-65535>", - GPRS_TEXT NSVC_TEXT - "NS Virtual Connection Identifier\n" - "GPRS NS VC Identifier") -{ - struct gsm_bts *bts = vty->index; - int idx = atoi(argv[0]); - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.nsvc[idx].nsvci = atoi(argv[1]); - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd, - "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]); - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.nsvc[idx].local_port = atoi(argv[1]); - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd, - "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]); - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.nsvc[idx].remote_port = atoi(argv[1]); - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd, - "gprs nsvc <0-1> remote ip A.B.C.D", - GPRS_TEXT NSVC_TEXT - "GPRS NS Remote IP Address\n" - "GPRS NS Remote IP Address\n" - "GPRS NS Remote IP Address\n") -{ - struct gsm_bts *bts = vty->index; - int idx = atoi(argv[0]); - struct in_addr ia; - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - inet_aton(argv[1], &ia); - bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr); - - return CMD_SUCCESS; -} - -DEFUN(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") -{ - struct gsm_bts *bts = vty->index; - - bts->paging.free_chans_need = atoi(argv[0]); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd, - "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]); - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - 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(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd, - "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]); - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer)) - return CMD_WARNING; - - bts->gprs.cell.timer[idx] = val; - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd, - "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; - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.rac = atoi(argv[0]); - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_ctrl_ack, cfg_bts_gprs_ctrl_ack_cmd, - "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; - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.ctrl_ack_type_use_block = false; - - return CMD_SUCCESS; -} - -DEFUN(cfg_no_bts_gprs_ctrl_ack, cfg_no_bts_gprs_ctrl_ack_cmd, - "no gprs control-ack-type-rach", NO_STR 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; - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.ctrl_ack_type_use_block = true; - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_net_ctrl_ord, cfg_bts_gprs_net_ctrl_ord_cmd, - "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; - - if (bts->gprs.mode == BTS_GPRS_NONE) { - vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); - return CMD_WARNING; - } - - bts->gprs.net_ctrl_ord = atoi(argv[0] + 2); - - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd, - "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(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 "11 bit RACH options\n" - "Disable 11 bit RACH for EGPRS\n" - "Enable 11 bit RACH for EGPRS") -{ - struct gsm_bts *bts = vty->index; - - bts->gprs.supports_egprs_11bit_rach = atoi(argv[0]); - - if (bts->gprs.supports_egprs_11bit_rach > 1) { - vty_out(vty, "Error in RACH type%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if ((bts->gprs.mode == BTS_GPRS_NONE) && - (bts->gprs.supports_egprs_11bit_rach == 1)) { - vty_out(vty, "Error:gprs mode is none and 11bit rach is" - " enabled%s", VTY_NEWLINE); - return CMD_WARNING; - } - - 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(cfg_bts_si_mode, cfg_bts_si_mode_cmd, - "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(cfg_bts_si_static, cfg_bts_si_static_cmd, - "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(cfg_bts_early_cm, cfg_bts_early_cm_cmd, - "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(cfg_bts_early_cm_3g, cfg_bts_early_cm_3g_cmd, - "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(cfg_bts_neigh_mode, cfg_bts_neigh_mode_cmd, - "neighbor-list mode (automatic|manual|manual-si5)", - "Neighbor List\n" "Mode of Neighbor List generation\n" - "Automatically from all BTS in this OpenBSC\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(cfg_bts_neigh, cfg_bts_neigh_cmd, - "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]); - - if (!bts->neigh_list_manual_mode) { - vty_out(vty, "%% Cannot configure neighbor list in " - "automatic mode%s", 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(cfg_bts_si2quater_neigh_add, cfg_bts_si2quater_neigh_add_cmd, - "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(cfg_bts_si2quater_neigh_del, cfg_bts_si2quater_neigh_del_cmd, - "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(cfg_bts_si2quater_uarfcn_add, cfg_bts_si2quater_uarfcn_add_cmd, - "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(cfg_bts_si2quater_uarfcn_del, cfg_bts_si2quater_uarfcn_del_cmd, - "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(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd, - "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") -{ - 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 (!strcmp(argv[0], "add")) - bitvec_set_bit_pos(bv, arfcn, 1); - else - bitvec_set_bit_pos(bv, arfcn, 0); - - return CMD_SUCCESS; -} - -DEFUN(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") -{ - 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(cfg_bts_acc_ramping, - cfg_bts_acc_ramping_cmd, - "access-control-class-ramping", - "Enable Access Control Class ramping\n") -{ - struct gsm_bts *bts = vty->index; - - if (!acc_ramp_is_enabled(&bts->acc_ramp)) - acc_ramp_set_enabled(&bts->acc_ramp, true); - - /* - * ACC ramping takes effect either when the BTS reconnects RSL, - * or when RF administrative state changes to 'unlocked'. - */ - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_no_acc_ramping, cfg_bts_no_acc_ramping_cmd, - "no access-control-class-ramping", - NO_STR - "Disable Access Control Class ramping\n") -{ - 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); - gsm_bts_set_system_infos(bts); - } - - return CMD_SUCCESS; -} - -DEFUN(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\n") -{ - struct gsm_bts *bts = vty->index; - bool dynamic = (strcmp(argv[0], "dynamic") == 0); - int error; - - if (dynamic) { - acc_ramp_set_step_interval_dynamic(&bts->acc_ramp); - 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(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") -{ - 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; -} - -#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n" - -DEFUN(cfg_bts_excl_rf_lock, - cfg_bts_excl_rf_lock_cmd, - "rf-lock-exclude", - EXCL_RFLOCK_STR) -{ - struct gsm_bts *bts = vty->index; - bts->excl_from_rf_lock = 1; - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_no_excl_rf_lock, - cfg_bts_no_excl_rf_lock_cmd, - "no rf-lock-exclude", - NO_STR EXCL_RFLOCK_STR) -{ - 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(cfg_bts_force_comb_si, - cfg_bts_force_comb_si_cmd, - "force-combined-si", - FORCE_COMB_SI_STR) -{ - struct gsm_bts *bts = vty->index; - bts->force_combined_si = 1; - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_no_force_comb_si, - cfg_bts_no_force_comb_si_cmd, - "no force-combined-si", - NO_STR FORCE_COMB_SI_STR) -{ - struct gsm_bts *bts = vty->index; - bts->force_combined_si = 0; - 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(cfg_bts_codec0, cfg_bts_codec0_cmd, - "codec-support fr", - "Codec Support settings\nFullrate\n") -{ - _get_codec_from_arg(vty, 0, argv); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_codec1, cfg_bts_codec1_cmd, - "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(cfg_bts_codec2, cfg_bts_codec2_cmd, - "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(cfg_bts_codec3, cfg_bts_codec3_cmd, - "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(cfg_bts_codec4, cfg_bts_codec4_cmd, - "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(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) -{ - 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(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd, - "depeneds-on-bts <0-255>", - NO_STR "This BTS can only be started if another one is up\n" - BTS_NR_STR) -{ - 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 void 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; - - mr->gsm48_ie[1] = 0; - for (i = 0; i < argc; i++) - mr->gsm48_ie[1] |= 1 << atoi(argv[i]); - mr_conf->icmi = 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; - } -} - -#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(cfg_bts_amr_fr_modes1, cfg_bts_amr_fr_modes1_cmd, - "amr tch-f modes" AMR_TCHF_PAR_STR, - AMR_TEXT "Full Rate\n" AMR_MODE_TEXT - AMR_TCHF_HELP_STR) -{ - get_amr_from_arg(vty, 1, argv, 1); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_modes2, cfg_bts_amr_fr_modes2_cmd, - "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) -{ - get_amr_from_arg(vty, 2, argv, 1); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_modes3, cfg_bts_amr_fr_modes3_cmd, - "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) -{ - get_amr_from_arg(vty, 3, argv, 1); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_modes4, cfg_bts_amr_fr_modes4_cmd, - "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) -{ - get_amr_from_arg(vty, 4, argv, 1); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_start_mode, cfg_bts_amr_fr_start_mode_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_thres1, cfg_bts_amr_fr_thres1_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_thres2, cfg_bts_amr_fr_thres2_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_thres3, cfg_bts_amr_fr_thres3_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_hyst1, cfg_bts_amr_fr_hyst1_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_fr_hyst2, cfg_bts_amr_fr_hyst2_cmd, - "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(cfg_bts_amr_fr_hyst3, cfg_bts_amr_fr_hyst3_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_modes1, cfg_bts_amr_hr_modes1_cmd, - "amr tch-h modes" AMR_TCHH_PAR_STR, - AMR_TEXT "Half Rate\n" AMR_MODE_TEXT - AMR_TCHH_HELP_STR) -{ - get_amr_from_arg(vty, 1, argv, 0); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_modes2, cfg_bts_amr_hr_modes2_cmd, - "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) -{ - get_amr_from_arg(vty, 2, argv, 0); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_modes3, cfg_bts_amr_hr_modes3_cmd, - "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) -{ - get_amr_from_arg(vty, 3, argv, 0); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_modes4, cfg_bts_amr_hr_modes4_cmd, - "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) -{ - get_amr_from_arg(vty, 4, argv, 0); - return CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_start_mode, cfg_bts_amr_hr_start_mode_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_thres1, cfg_bts_amr_hr_thres1_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_thres2, cfg_bts_amr_hr_thres2_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_thres3, cfg_bts_amr_hr_thres3_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_hyst1, cfg_bts_amr_hr_hyst1_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_hyst2, cfg_bts_amr_hr_hyst2_cmd, - "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 CMD_SUCCESS; -} - -DEFUN(cfg_bts_amr_hr_hyst3, cfg_bts_amr_hr_hyst3_cmd, - "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 CMD_SUCCESS; -} - -#define TRX_TEXT "Radio Transceiver\n" - -/* per TRX configuration */ -DEFUN(cfg_trx, - cfg_trx_cmd, - "trx <0-255>", - TRX_TEXT - "Select a TRX to configure") -{ - 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(cfg_trx_arfcn, - cfg_trx_arfcn_cmd, - "arfcn <0-1023>", - "Set the ARFCN for this TRX\n" - "Absolute Radio Frequency Channel Number\n") -{ - int arfcn = atoi(argv[0]); - struct gsm_bts_trx *trx = vty->index; - - /* 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(cfg_trx_nominal_power, - cfg_trx_nominal_power_cmd, - "nominal power <0-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(cfg_trx_max_power_red, - cfg_trx_max_power_red_cmd, - "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; -} - -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(cfg_trx_rsl_e1_tei, - cfg_trx_rsl_e1_tei_cmd, - "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(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") -{ - 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(cfg_ts, - cfg_ts_cmd, - "timeslot <0-7>", - "Select a Timeslot to configure\n" - "Timeslot number\n") -{ - 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(cfg_ts_pchan, - cfg_ts_pchan_cmd, - "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 = 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) - return CMD_WARNING; - - ts->pchan = pchanc; - - return CMD_SUCCESS; -} - - - -DEFUN(cfg_ts_tsc, - cfg_ts_tsc_cmd, - "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(cfg_ts_hopping, - cfg_ts_hopping_cmd, - "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 support hopping%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - ts->hopping.enabled = enabled; - - return CMD_SUCCESS; -} - -DEFUN(cfg_ts_hsn, - cfg_ts_hsn_cmd, - "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(cfg_ts_maio, - cfg_ts_maio_cmd, - "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(cfg_ts_arfcn_add, - cfg_ts_arfcn_add_cmd, - "hopping arfcn add <0-1023>", - HOPPING_STR "Configure hopping ARFCN list\n" - "Add an entry to the hopping ARFCN list\n" "ARFCN\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - int arfcn = atoi(argv[0]); - - bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1); - - return CMD_SUCCESS; -} - -DEFUN(cfg_ts_arfcn_del, - cfg_ts_arfcn_del_cmd, - "hopping arfcn del <0-1023>", - HOPPING_STR "Configure hopping ARFCN list\n" - "Delete an entry to the hopping ARFCN list\n" "ARFCN\n") -{ - struct gsm_bts_trx_ts *ts = vty->index; - int arfcn = atoi(argv[0]); - - bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0); - - return CMD_SUCCESS; -} - -DEFUN(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") -{ - 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; - vty_out(vty, "%25s: %10"PRIu64" %s%s", desc->name, ctr->current, desc->description, VTY_NEWLINE); - return 0; -} - -void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net) -{ - rate_ctr_for_each_counter(net->bsc_ctrs, print_counter, vty); -} - -DEFUN(drop_bts, - drop_bts_cmd, - "drop bts connection <0-65535> (oml|rsl)", - "Debug/Simulation command to drop Abis/IP BTS\n" - "Debug/Simulation command to drop Abis/IP BTS\n" - "Debug/Simulation command to drop Abis/IP BTS\n" - "BTS NR\n" "Drop OML Connection\n" "Drop RSL Connection\n") -{ - struct gsm_network *gsmnet; - struct gsm_bts_trx *trx; - 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 (!is_ipaccess_bts(bts)) { - vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE); - return CMD_WARNING; - } - - - /* close all connections */ - if (strcmp(argv[1], "oml") == 0) { - ipaccess_drop_oml(bts); - } else if (strcmp(argv[1], "rsl") == 0) { - /* close all rsl connections */ - llist_for_each_entry(trx, &bts->trx_list, list) { - ipaccess_drop_rsl(trx); - } - } else { - vty_out(vty, "Argument must be 'oml# or 'rsl'.%s", VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN(restart_bts, restart_bts_cmd, - "restart-bts <0-65535>", - "Restart ip.access nanoBTS through OML\n" - BTS_NR_STR) -{ - struct gsm_network *gsmnet; - struct gsm_bts_trx *trx; - 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 (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) { - vty_out(vty, "This command only works for ipaccess nanoBTS.%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - /* go from last TRX to c0 */ - llist_for_each_entry_reverse(trx, &bts->trx_list, list) - abis_nm_ipaccess_restart(trx); - - return CMD_SUCCESS; -} - -DEFUN(bts_resend, bts_resend_cmd, - "bts <0-255> resend-system-information", - "BTS Specific Commands\n" BTS_NR_STR - "Re-generate + re-send BCCH SYSTEM INFORMATION\n") -{ - struct gsm_network *gsmnet; - struct gsm_bts_trx *trx; - 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; - } - - llist_for_each_entry_reverse(trx, &bts->trx_list, list) - gsm_bts_trx_set_system_infos(trx); - - return CMD_SUCCESS; -} - - -DEFUN(smscb_cmd, smscb_cmd_cmd, - "bts <0-255> smscb-command <1-4> HEXSTRING", - "BTS related commands\n" BTS_NR_STR - "SMS Cell Broadcast\n" "Last Valid Block\n" - "Hex Encoded SMSCB message (up to 88 octets)\n") -{ - struct gsm_bts *bts; - int bts_nr = atoi(argv[0]); - int last_block = atoi(argv[1]); - struct rsl_ie_cb_cmd_type cb_cmd; - uint8_t buf[88]; - 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; - } - rc = osmo_hexparse(argv[2], buf, sizeof(buf)); - if (rc < 0 || rc > sizeof(buf)) { - vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE); - return CMD_WARNING; - } - - cb_cmd.spare = 0; - cb_cmd.def_bcast = 0; - cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL; - - switch (last_block) { - case 1: - cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1; - break; - case 2: - cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2; - break; - case 3: - cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3; - break; - case 4: - cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4; - break; - default: - vty_out(vty, "Error parsing LASTBLOCK%s", VTY_NEWLINE); - return CMD_WARNING; - } - - rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, 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 - "Packet Data Channel\n" - "Activate Dynamic PDCH/TCH (-> PDCH mode)\n" - "Deactivate Dynamic PDCH/TCH (-> TCH mode)\n") -{ - struct gsm_bts_trx_ts *ts; - int activate; - - ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); - if (!ts) - return CMD_WARNING; - - if (!is_ipaccess_bts(ts->trx->bts)) { - vty_out(vty, "%% This command only works for ipaccess BTS%s", - VTY_NEWLINE); - return CMD_WARNING; - } - - if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) { - vty_out(vty, "%% Timeslot %u is not in dynamic TCH_F/PDCH " - "mode%s", ts->nr, VTY_NEWLINE); - return CMD_WARNING; - } - - if (!strcmp(argv[3], "activate")) - activate = 1; - else - activate = 0; - - rsl_ipacc_pdch_activate(ts, activate); - - return CMD_SUCCESS; - -} - -/* determine the logical channel type based on the physical channel type */ -static int lchan_type_by_pchan(enum gsm_phys_chan_config pchan) -{ - switch (pchan) { - case GSM_PCHAN_TCH_F: - return GSM_LCHAN_TCH_F; - case GSM_PCHAN_TCH_H: - return GSM_LCHAN_TCH_H; - case GSM_PCHAN_SDCCH8_SACCH8C: - case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: - case GSM_PCHAN_CCCH_SDCCH4: - case GSM_PCHAN_CCCH_SDCCH4_CBCH: - return GSM_LCHAN_SDCCH; - default: - return -1; - } -} - -/* configure the lchan for a single AMR mode (as specified) */ -static int lchan_set_single_amr_mode(struct gsm_lchan *lchan, uint8_t amr_mode) -{ - struct amr_multirate_conf mr; - struct gsm48_multi_rate_conf *mr_conf; - mr_conf = (struct gsm48_multi_rate_conf *) &mr.gsm48_ie; - - if (amr_mode > 7) - return -1; - - memset(&mr, 0, sizeof(mr)); - mr_conf->ver = 1; - /* bit-mask of supported modes, only one bit is set. Reflects - * Figure 10.5.2.47a where there are no thershold and only a - * single mode */ - mr.gsm48_ie[1] = 1 << amr_mode; - - mr.ms_mode[0].mode = amr_mode; - mr.bts_mode[0].mode = amr_mode; - - /* encode this configuration into the lchan for both uplink and - * downlink direction */ - gsm48_multirate_config(lchan->mr_ms_lv, &mr, mr.ms_mode); - gsm48_multirate_config(lchan->mr_bts_lv, &mr, mr.bts_mode); - - return 0; -} - -/* 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) [<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" "AMR Mode\n") -{ - struct gsm_bts_trx_ts *ts; - struct gsm_lchan *lchan; - int ss_nr = atoi(argv[3]); - const char *act_str = argv[4]; - const char *codec_str = argv[5]; - int activate; - - ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); - if (!ts) - return CMD_WARNING; - - lchan = &ts->lchan[ss_nr]; - - if (!strcmp(act_str, "activate")) - activate = 1; - else - activate = 0; - - if (ss_nr >= ts_subslots(ts)) { - vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s", - ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE); - return CMD_WARNING; - } - - if (activate) { - int lchan_t; - if (lchan->state != LCHAN_S_NONE) { - vty_out(vty, "%% Cannot activate: Channel busy!%s", VTY_NEWLINE); - return CMD_WARNING; - } - lchan_t = lchan_type_by_pchan(ts->pchan); - if (lchan_t < 0) - return CMD_WARNING; - /* configure the lchan */ - lchan->type = lchan_t; - lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; - if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr")) - lchan->tch_mode = GSM48_CMODE_SPEECH_V1; - else if (!strcmp(codec_str, "efr")) - lchan->tch_mode = GSM48_CMODE_SPEECH_EFR; - else if (!strcmp(codec_str, "amr")) { - int amr_mode; - if (argc < 7) { - vty_out(vty, "%% AMR requires specification of AMR mode%s", VTY_NEWLINE); - return CMD_WARNING; - } - amr_mode = atoi(argv[6]); - lchan->tch_mode = GSM48_CMODE_SPEECH_AMR; - lchan_set_single_amr_mode(lchan, amr_mode); - } - vty_out(vty, "%% activating lchan %s%s", gsm_lchan_name(lchan), VTY_NEWLINE); - rsl_chan_activate_lchan(lchan, RSL_ACT_TYPE_INITIAL, 0); - rsl_ipacc_crcx(lchan); - } else { - rsl_direct_rf_release(lchan); - } - - return CMD_SUCCESS; -} - -DEFUN(lchan_mdcx, lchan_mdcx_cmd, - "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> mdcx A.B.C.D <0-65535>", - BTS_NR_TRX_TS_SS_STR2 - "Modify RTP Connection\n" "MGW IP Address\n" "MGW UDP Port\n") -{ - struct gsm_bts_trx_ts *ts; - struct gsm_lchan *lchan; - int ss_nr = atoi(argv[3]); - int port = atoi(argv[5]); - struct in_addr ia; - inet_aton(argv[4], &ia); - - ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); - if (!ts) - return CMD_WARNING; - - lchan = &ts->lchan[ss_nr]; - - if (ss_nr >= ts_subslots(ts)) { - vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s", - ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE); - return CMD_WARNING; - } - - vty_out(vty, "%% connecting RTP of %s to %s:%u%s", gsm_lchan_name(lchan), - inet_ntoa(ia), port, VTY_NEWLINE); - rsl_ipacc_mdcx(lchan, ntohl(ia.s_addr), port, 0); - return CMD_SUCCESS; -} - -DEFUN(ctrl_trap, ctrl_trap_cmd, - "ctrl-interface generate-trap TRAP VALUE", - "Commands related to the CTRL Interface\n" - "Generate a TRAP for test purpose\n" - "Identity/Name of the TRAP variable\n" - "Value of the TRAP variable\n") -{ - struct gsm_network *net = gsmnet_from_vty(vty); - - ctrl_cmd_send_trap(net->ctrl, argv[0], (char *) argv[1]); - return CMD_SUCCESS; -} - -#define NETWORK_STR "Configure the GSM network\n" -#define CODE_CMD_STR "Code commands\n" -#define NAME_CMD_STR "Name Commands\n" -#define NAME_STR "Name to use\n" - -DEFUN(cfg_net, - cfg_net_cmd, - "network", NETWORK_STR) -{ - vty->index = gsmnet_from_vty(vty); - vty->node = GSMNET_NODE; - - return CMD_SUCCESS; -} - -DEFUN(cfg_net_ncc, - cfg_net_ncc_cmd, - "network country code <1-999>", - "Set the GSM network country code\n" - "Country commands\n" - CODE_CMD_STR - "Network Country Code to use\n") -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - uint16_t mcc; - - if (osmo_mcc_from_str(argv[0], &mcc)) { - vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE); - return CMD_WARNING; - } - - gsmnet->plmn.mcc = mcc; - - return CMD_SUCCESS; -} - -DEFUN(cfg_net_mnc, - cfg_net_mnc_cmd, - "mobile network code <0-999>", - "Set the GSM mobile network code\n" - "Network Commands\n" - CODE_CMD_STR - "Mobile Network Code to use\n") -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - uint16_t mnc; - bool mnc_3_digits; - - if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) { - vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE); - return CMD_WARNING; - } - - gsmnet->plmn.mnc = mnc; - gsmnet->plmn.mnc_3_digits = mnc_3_digits; - - return CMD_SUCCESS; -} - -DEFUN(cfg_net_encryption, - cfg_net_encryption_cmd, - "encryption a5 <0-3> [<0-3>] [<0-3>] [<0-3>]", - "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") -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - unsigned int i; - - gsmnet->a5_encryption_mask = 0; - for (i = 0; i < argc; i++) - gsmnet->a5_encryption_mask |= (1 << atoi(argv[i])); - - return CMD_SUCCESS; -} - -DEFUN_DEPRECATED(cfg_net_dyn_ts_allow_tch_f, - cfg_net_dyn_ts_allow_tch_f_cmd, - "dyn_ts_allow_tch_f (0|1)", - "Allow or disallow allocating TCH/F on TCH_F_TCH_H_PDCH timeslots\n" - "Disallow TCH/F on TCH_F_TCH_H_PDCH (default)\n" - "Allow TCH/F on TCH_F_TCH_H_PDCH\n") -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - gsmnet->dyn_ts_allow_tch_f = atoi(argv[0]) ? true : false; - vty_out(vty, "%% dyn_ts_allow_tch_f is deprecated, rather use msc/codec-list to pick codecs%s", - VTY_NEWLINE); - return CMD_SUCCESS; -} - -DEFUN(cfg_net_timezone, - cfg_net_timezone_cmd, - "timezone <-19-19> (0|15|30|45)", - "Set the Timezone Offset of the network\n" - "Timezone offset (hours)\n" - "Timezone offset (00 minutes)\n" - "Timezone offset (15 minutes)\n" - "Timezone offset (30 minutes)\n" - "Timezone offset (45 minutes)\n" - ) -{ - struct gsm_network *net = vty->index; - int tzhr = atoi(argv[0]); - int tzmn = atoi(argv[1]); - - net->tz.hr = tzhr; - net->tz.mn = tzmn; - net->tz.dst = 0; - net->tz.override = 1; - - return CMD_SUCCESS; -} - -DEFUN(cfg_net_timezone_dst, - cfg_net_timezone_dst_cmd, - "timezone <-19-19> (0|15|30|45) <0-2>", - "Set the Timezone Offset of the network\n" - "Timezone offset (hours)\n" - "Timezone offset (00 minutes)\n" - "Timezone offset (15 minutes)\n" - "Timezone offset (30 minutes)\n" - "Timezone offset (45 minutes)\n" - "DST offset (hours)\n" - ) -{ - struct gsm_network *net = vty->index; - int tzhr = atoi(argv[0]); - int tzmn = atoi(argv[1]); - int tzdst = atoi(argv[2]); - - net->tz.hr = tzhr; - net->tz.mn = tzmn; - net->tz.dst = tzdst; - net->tz.override = 1; - - return CMD_SUCCESS; -} - -DEFUN(cfg_net_no_timezone, - cfg_net_no_timezone_cmd, - "no timezone", - NO_STR - "Disable network timezone override, use system tz\n") -{ - struct gsm_network *net = vty->index; - - net->tz.override = 0; - - return CMD_SUCCESS; -} - -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") -{ - struct gsm_network *net = vty->index; - - net->t3212 = atoi(argv[0]) / 6; - - return CMD_SUCCESS; -} - -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") -{ - struct gsm_network *net = vty->index; - - net->t3212 = 0; - - return CMD_SUCCESS; -} - -#define MEAS_FEED_STR "Measurement Report export\n" - -DEFUN(cfg_net_meas_feed_dest, cfg_net_meas_feed_dest_cmd, - "meas-feed destination ADDR <0-65535>", - MEAS_FEED_STR "Where to forward Measurement Report feeds\n" "address or hostname\n" "port number\n") -{ - int rc; - const char *host = argv[0]; - uint16_t port = atoi(argv[1]); - - rc = meas_feed_cfg_set(host, port); - if (rc < 0) - return CMD_WARNING; - - return CMD_SUCCESS; -} - -DEFUN(cfg_net_meas_feed_scenario, cfg_net_meas_feed_scenario_cmd, - "meas-feed scenario NAME", - MEAS_FEED_STR "Set a name to include in the Measurement Report feeds\n" "Name string, up to 31 characters\n") -{ - meas_feed_scenario_set(argv[0]); - - return CMD_SUCCESS; -} - -extern int bsc_vty_init_extra(void); - -int bsc_vty_init(struct gsm_network *network) -{ - 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); - - OSMO_ASSERT(vty_global_gsm_network == NULL); - vty_global_gsm_network = network; - - osmo_stats_vty_add_cmds(); - - install_element(CONFIG_NODE, &cfg_net_cmd); - install_node(&net_node, config_write_net); - install_element(GSMNET_NODE, &cfg_net_ncc_cmd); - install_element(GSMNET_NODE, &cfg_net_mnc_cmd); - install_element(GSMNET_NODE, &cfg_net_encryption_cmd); - install_element(GSMNET_NODE, &cfg_net_timezone_cmd); - install_element(GSMNET_NODE, &cfg_net_timezone_dst_cmd); - install_element(GSMNET_NODE, &cfg_net_no_timezone_cmd); - install_element(GSMNET_NODE, &cfg_net_per_loc_upd_cmd); - install_element(GSMNET_NODE, &cfg_net_no_per_loc_upd_cmd); - 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_ve(&bsc_show_net_cmd); - install_element_ve(&show_bts_cmd); - install_element_ve(&show_trx_cmd); - install_element_ve(&show_ts_cmd); - install_element_ve(&show_lchan_cmd); - install_element_ve(&show_lchan_summary_cmd); - - install_element_ve(&show_subscr_conn_cmd); - install_element_ve(&handover_any_cmd); - install_element_ve(&assignment_any_cmd); - - install_element_ve(&show_paging_cmd); - install_element_ve(&show_paging_group_cmd); - - logging_vty_add_cmds(NULL); - osmo_talloc_vty_add_cmds(); - - install_element(GSMNET_NODE, &cfg_net_neci_cmd); - install_element(GSMNET_NODE, &cfg_net_T3101_cmd); - install_element(GSMNET_NODE, &cfg_net_T3103_cmd); - install_element(GSMNET_NODE, &cfg_net_T3105_cmd); - install_element(GSMNET_NODE, &cfg_net_T3107_cmd); - install_element(GSMNET_NODE, &cfg_net_T3109_cmd); - install_element(GSMNET_NODE, &cfg_net_T3111_cmd); - install_element(GSMNET_NODE, &cfg_net_T3113_cmd); - install_element(GSMNET_NODE, &cfg_net_T3115_cmd); - install_element(GSMNET_NODE, &cfg_net_T3117_cmd); - install_element(GSMNET_NODE, &cfg_net_T3119_cmd); - install_element(GSMNET_NODE, &cfg_net_T3122_cmd); - install_element(GSMNET_NODE, &cfg_net_T3141_cmd); - install_element(GSMNET_NODE, &cfg_net_dtx_cmd); - install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd); - /* See also handover commands added on net level from handover_vty.c */ - - 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_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_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_desc_bs_pa_mfrms_cmd); - install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_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_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_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_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); - /* 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_e1_subslot_cmd); - - 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, &pdch_act_cmd); - install_element(ENABLE_NODE, &lchan_act_cmd); - install_element(ENABLE_NODE, &lchan_mdcx_cmd); - install_element(ENABLE_NODE, &handover_subscr_conn_cmd); - install_element(ENABLE_NODE, &assignment_subscr_conn_cmd); - install_element(ENABLE_NODE, &smscb_cmd_cmd); - install_element(ENABLE_NODE, &ctrl_trap_cmd); - - abis_nm_vty_init(); - abis_om2k_vty_init(); - e1inp_vty_init(); - osmo_fsm_vty_add_cmds(); - - ho_vty_init(); - - bsc_vty_init_extra(); - - return 0; -} diff --git a/src/libbsc/bts_ericsson_rbs2000.c b/src/libbsc/bts_ericsson_rbs2000.c deleted file mode 100644 index 9c8b90ee2..000000000 --- a/src/libbsc/bts_ericsson_rbs2000.c +++ /dev/null @@ -1,212 +0,0 @@ -/* Ericsson RBS-2xxx specific code */ - -/* (C) 2011 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - - -#include - -#include -#include -#include -#include -#include -#include - -#include - -static void bootstrap_om_bts(struct gsm_bts *bts) -{ - LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); - - /* FIXME: this is global init, not bootstrapping */ - abis_om2k_bts_init(bts); - abis_om2k_trx_init(bts->c0); - - /* TODO: Should we wait for a Failure report? */ - om2k_bts_fsm_start(bts); -} - -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 */ -} - -static int shutdown_om(struct gsm_bts *bts) -{ - gsm_bts_mark_all_ts_uninitialized(bts); - - /* FIXME */ - return 0; -} - - -/* Tell LAPD to start start the SAP (send SABM requests) for all signalling - * timeslots in this line */ -static void start_sabm_in_line(struct e1inp_line *line, int start) -{ - struct e1inp_sign_link *link; - int i; - - for (i = 0; i < ARRAY_SIZE(line->ts); i++) { - struct e1inp_ts *ts = &line->ts[i]; - - if (ts->type != E1INP_TS_TYPE_SIGN) - continue; - - llist_for_each_entry(link, &ts->sign.sign_links, list) { - if (!ts->lapd) - continue; - lapd_instance_set_profile(ts->lapd, - &lapd_profile_abis_ericsson); - - if (start) - lapd_sap_start(ts->lapd, link->tei, link->sapi); - else - lapd_sap_stop(ts->lapd, link->tei, link->sapi); - } - } -} - -/* Callback function to be called every time we receive a signal from INPUT */ -static int gbl_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct gsm_bts *bts; - - if (subsys != SS_L_GLOBAL) - return 0; - - switch (signal) { - case S_GLOBAL_BTS_CLOSE_OM: - bts = signal_data; - if (bts->type == GSM_BTS_TYPE_RBS2000) - shutdown_om(signal_data); - break; - } - - return 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) -{ - struct input_signal_data *isd = signal_data; - struct e1inp_ts *e1i_ts; - - if (subsys != SS_L_INPUT) - return 0; - - LOGP(DNM, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__, - get_value_string(e1inp_signal_names, signal)); - switch (signal) { - case S_L_INP_TEI_UP: - switch (isd->link_type) { - case E1INP_SIGN_OML: - if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000) - break; - if (isd->tei == isd->trx->bts->oml_tei) - bootstrap_om_bts(isd->trx->bts); - else - bootstrap_om_trx(isd->trx); - break; - } - break; - case S_L_INP_TEI_DN: - if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000) - break; - 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); - /* 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); - lapd_sap_start(e1i_ts->lapd, isd->tei, isd->sapi); - break; - case S_L_INP_LINE_INIT: - case S_L_INP_LINE_NOALARM: - if (strcasecmp(isd->line->driver->name, "DAHDI") - && strcasecmp(isd->line->driver->name, "MISDN_LAPD") - && strcasecmp(isd->line->driver->name, "UNIXSOCKET")) - break; - start_sabm_in_line(isd->line, 1); - break; - case S_L_INP_LINE_ALARM: - if (strcasecmp(isd->line->driver->name, "DAHDI") - && strcasecmp(isd->line->driver->name, "MISDN_LAPD") - && strcasecmp(isd->line->driver->name, "UNIXSOCKET")) - break; - start_sabm_in_line(isd->line, 0); - break; - } - - return 0; -} - -static void config_write_bts(struct vty *vty, struct gsm_bts *bts) -{ - abis_om2k_config_write_bts(vty, bts); -} - -static int bts_model_rbs2k_start(struct gsm_network *net); - -static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line) -{ - e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); -} - -static bool bts_model_rbs2k_is_ts_ready(const struct gsm_bts_trx_ts *ts) -{ - return ts && ts->mo.nm_state.operational == NM_OPSTATE_ENABLED; -} - -static struct gsm_bts_model model_rbs2k = { - .type = GSM_BTS_TYPE_RBS2000, - .name = "rbs2000", - .start = bts_model_rbs2k_start, - .oml_rcvmsg = &abis_om2k_rcvmsg, - .oml_is_ts_ready = bts_model_rbs2k_is_ts_ready, - .config_write_bts = &config_write_bts, - .e1line_bind_ops = &bts_model_rbs2k_e1line_bind_ops, -}; - -static int bts_model_rbs2k_start(struct gsm_network *net) -{ - model_rbs2k.features.data = &model_rbs2k._features_data[0]; - model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data); - - osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_GPRS); - osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_EGPRS); - osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_HOPPING); - 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/libbsc/bts_init.c b/src/libbsc/bts_init.c deleted file mode 100644 index 18f1ed4c8..000000000 --- a/src/libbsc/bts_init.c +++ /dev/null @@ -1,30 +0,0 @@ -/* (C) 2011 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ -#include - -int bts_init(void) -{ - bts_model_bs11_init(); - bts_model_rbs2k_init(); - bts_model_nanobts_init(); - bts_model_nokia_site_init(); - bts_model_sysmobts_init(); - /* Your new BTS here. */ - return 0; -} diff --git a/src/libbsc/bts_ipaccess_nanobts.c b/src/libbsc/bts_ipaccess_nanobts.c deleted file mode 100644 index 843f264ae..000000000 --- a/src/libbsc/bts_ipaccess_nanobts.c +++ /dev/null @@ -1,591 +0,0 @@ -/* ip.access nanoBTS specific code */ - -/* (C) 2009-2018 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int bts_model_nanobts_start(struct gsm_network *net); -static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line); - -static char *get_oml_status(const struct gsm_bts *bts) -{ - if (bts->oml_link) - return all_trx_rsl_connected_unlocked(bts) ? "connected" : "degraded"; - - return "disconnected"; -} - -static bool oml_is_ts_ready(const struct gsm_bts_trx_ts *ts) -{ - return ts && ts->mo.nm_state.operational == NM_OPSTATE_ENABLED; -} - -struct gsm_bts_model bts_model_nanobts = { - .type = GSM_BTS_TYPE_NANOBTS, - .name = "nanobts", - .start = bts_model_nanobts_start, - .oml_rcvmsg = &abis_nm_rcvmsg, - .oml_status = &get_oml_status, - .oml_is_ts_ready = oml_is_ts_ready, - .e1line_bind_ops = bts_model_nanobts_e1line_bind_ops, - .nm_att_tlvdef = { - .def = { - /* ip.access specifics */ - [NM_ATT_IPACC_DST_IP] = { TLV_TYPE_FIXED, 4 }, - [NM_ATT_IPACC_DST_IP_PORT] = { TLV_TYPE_FIXED, 2 }, - [NM_ATT_IPACC_STREAM_ID] = { TLV_TYPE_TV, }, - [NM_ATT_IPACC_SEC_OML_CFG] = { TLV_TYPE_FIXED, 6 }, - [NM_ATT_IPACC_IP_IF_CFG] = { TLV_TYPE_FIXED, 8 }, - [NM_ATT_IPACC_IP_GW_CFG] = { TLV_TYPE_FIXED, 12 }, - [NM_ATT_IPACC_IN_SERV_TIME] = { TLV_TYPE_FIXED, 4 }, - [NM_ATT_IPACC_LOCATION] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_PAGING_CFG] = { TLV_TYPE_FIXED, 2 }, - [NM_ATT_IPACC_UNIT_ID] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_UNIT_NAME] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_SNMP_CFG] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_NV_FLAGS] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_FREQ_CTRL] = { TLV_TYPE_FIXED, 2 }, - [NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_CUR_SW_CFG] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_TIMING_BUS] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_CGI] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_RAC] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_OBJ_VERSION] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_NSEI] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_BVCI] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_NSVCI] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_NS_CFG] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_BSSGP_CFG] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_NS_LINK_CFG] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_RLC_CFG] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_ALM_THRESH_LIST]= { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_TIB_CONTROL] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_SUPP_FEATURES] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_CODING_SCHEMES] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_RLC_CFG_2] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_HEARTB_TOUT] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_UPTIME] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_RLC_CFG_3] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_SSL_CFG] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_SEC_POSSIBLE] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_IML_SSL_STATE] = { TLV_TYPE_TL16V }, - [NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V }, - }, - }, -}; - - -/* Callback function to be called whenever we get a GSM 12.21 state change event */ -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 *bts; - struct gsm_bts_trx *trx; - 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 */ - - /* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create - * endless loop */ - if (evt != S_NM_STATECHG_OPER) - return 0; - - switch (obj_class) { - case NM_OC_SITE_MANAGER: - bts = container_of(obj, struct gsm_bts, site_mgr); - if ((new_state->operational == NM_OPSTATE_ENABLED && - new_state->availability == NM_AVSTATE_OK) || - (new_state->operational == NM_OPSTATE_DISABLED && - new_state->availability == NM_AVSTATE_OFF_LINE)) - abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff); - break; - case NM_OC_BTS: - bts = obj; - if (new_state->availability == NM_AVSTATE_DEPENDENCY) { - msgb = nanobts_attr_bts_get(bts); - abis_nm_set_bts_attr(bts, msgb->data, msgb->len); - msgb_free(msgb); - abis_nm_chg_adm_state(bts, obj_class, - bts->bts_nr, 0xff, 0xff, - NM_STATE_UNLOCKED); - abis_nm_opstart(bts, obj_class, - bts->bts_nr, 0xff, 0xff); - } - break; - case NM_OC_CHANNEL: - ts = obj; - trx = ts->trx; - if (new_state->operational == NM_OPSTATE_DISABLED && - new_state->availability == NM_AVSTATE_DEPENDENCY) { - enum abis_nm_chan_comb ccomb = - abis_nm_chcomb4pchan(ts->pchan); - if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) { - ipaccess_drop_oml(trx->bts); - return -1; - } - abis_nm_chg_adm_state(trx->bts, obj_class, - trx->bts->bts_nr, trx->nr, ts->nr, - NM_STATE_UNLOCKED); - abis_nm_opstart(trx->bts, obj_class, - trx->bts->bts_nr, trx->nr, ts->nr); - } - break; - case NM_OC_RADIO_CARRIER: - trx = obj; - if (new_state->operational == NM_OPSTATE_DISABLED && - new_state->availability == NM_AVSTATE_OK) - abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr, - trx->nr, 0xff); - 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); - } - 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_opstart(bts, obj_class, bts->bts_nr, - 0, 0xff); - 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); - } - 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 ((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_opstart(bts, obj_class, bts->bts_nr, - nsvc->id, 0xff); - abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr, - nsvc->id, 0xff, - NM_STATE_UNLOCKED); - } - default: - break; - } - return 0; -} - -/* 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); - struct e1inp_sign_link *sign_link = mb->dst; - struct gsm_bts *bts = sign_link->trx->bts; - struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); - - if (!trx) - return -EINVAL; - - if (!is_ipaccess_bts(trx->bts)) - return 0; - - switch (foh->obj_class) { - case NM_OC_BASEB_TRANSC: - abis_nm_chg_adm_state(trx->bts, foh->obj_class, - trx->bts->bts_nr, trx->nr, 0xff, - NM_STATE_UNLOCKED); - abis_nm_opstart(trx->bts, foh->obj_class, - 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); - break; - case NM_OC_RADIO_CARRIER: { - /* - * Locking the radio carrier will make it go - * offline again and we would come here. The - * framework should determine that there was - * no change and avoid recursion. - * - * This code is here to make sure that on start - * a TRX remains locked. - */ - int rc_state = trx->mo.nm_state.administrative; - /* Patch ARFCN into radio attribute */ - struct msgb *msgb = nanobts_attr_radio_get(trx->bts, trx); - abis_nm_set_radio_attr(trx, msgb->data, msgb->len); - msgb_free(msgb); - abis_nm_chg_adm_state(trx->bts, foh->obj_class, - trx->bts->bts_nr, trx->nr, 0xff, - rc_state); - abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr, - trx->nr, 0xff); - break; - } - } - return 0; -} - -static struct gsm_bts_trx_ts *gsm_bts_trx_ts(struct gsm_network *net, - int bts_nr, int trx_nr, int ts_nr) -{ - struct gsm_bts *bts; - struct gsm_bts_trx *trx; - bts = gsm_bts_num(net, bts_nr); - if (!bts) - return NULL; - trx = gsm_bts_trx_by_nr(bts, trx_nr); - if (!trx) - return NULL; - if (ts_nr < 0 || ts_nr > ARRAY_SIZE(trx->ts)) - return NULL; - return &trx->ts[ts_nr]; -} - -static void nm_rx_opstart_ack_chan(struct abis_om_fom_hdr *foh) -{ - struct gsm_bts_trx_ts *ts; - ts = gsm_bts_trx_ts(bsc_gsmnet, foh->obj_inst.bts_nr, foh->obj_inst.trx_nr, foh->obj_inst.ts_nr); - if (!ts) { - LOGP(DNM, LOGL_ERROR, "%s Channel OPSTART ACK for non-existent TS\n", - abis_nm_dump_foh(foh)); - return; - } - - gsm_ts_check_init(ts); -} - -static void nm_rx_opstart_ack(struct abis_om_fom_hdr *foh) -{ - switch (foh->obj_class) { - case NM_OC_CHANNEL: - nm_rx_opstart_ack_chan(foh); - 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) -{ - if (subsys != SS_NM) - return 0; - - switch (signal) { - case S_NM_SW_ACTIV_REP: - return sw_activ_rep(signal_data); - case S_NM_STATECHG_OPER: - case S_NM_STATECHG_ADM: - return nm_statechg_event(signal, signal_data); - case S_NM_OPSTART_ACK: - nm_rx_opstart_ack(signal_data); - return 0; - default: - break; - } - return 0; -} - -static int bts_model_nanobts_start(struct gsm_network *net) -{ - osmo_signal_unregister_handler(SS_NM, bts_ipa_nm_sig_cb, NULL); - osmo_signal_register_handler(SS_NM, bts_ipa_nm_sig_cb, NULL); - return 0; -} - -int bts_model_nanobts_init(void) -{ - bts_model_nanobts.features.data = &bts_model_nanobts._features_data[0]; - bts_model_nanobts.features.data_len = - sizeof(bts_model_nanobts._features_data); - - osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_GPRS); - osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_EGPRS); - osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_MULTI_TSC); - - return gsm_bts_model_register(&bts_model_nanobts); -} - -#define OML_UP 0x0001 -#define RSL_UP 0x0002 - -static struct gsm_bts * -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)) - continue; - - if (bts->ip_access.site_id == site_id && - bts->ip_access.bts_id == bts_id) - return bts; - } - return NULL; -} - -/* These are exported because they are used by the VTY interface. */ -void ipaccess_drop_rsl(struct gsm_bts_trx *trx) -{ - if (!trx->rsl_link) - return; - - LOGP(DLINP, LOGL_NOTICE, "(bts=%d,trx=%d) Dropping RSL link.\n", trx->bts->nr, trx->nr); - e1inp_sign_link_destroy(trx->rsl_link); - trx->rsl_link = NULL; - - if (trx->bts->c0 == trx) - paging_flush_bts(trx->bts, NULL); -} - -void ipaccess_drop_oml(struct gsm_bts *bts) -{ - struct gsm_bts *rdep_bts; - struct gsm_bts_trx *trx; - - if (!bts->oml_link) - return; - - LOGP(DLINP, LOGL_NOTICE, "(bts=%d) Dropping OML link.\n", bts->nr); - e1inp_sign_link_destroy(bts->oml_link); - bts->oml_link = NULL; - bts->uptime = 0; - - /* we have issues reconnecting RSL, drop everything. */ - llist_for_each_entry(trx, &bts->trx_list, list) - ipaccess_drop_rsl(trx); - - gsm_bts_mark_all_ts_uninitialized(bts); - - bts->ip_access.flags = 0; - - /* - * 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 - * should be fine in userspace. - * The oml_link is serving as recursion anchor for us and - * it is set to NULL some lines above. - */ - llist_for_each_entry(rdep_bts, &bts->network->bts_list, list) { - if (!bts_depend_is_depedency(rdep_bts, bts)) - continue; - LOGP(DLINP, LOGL_NOTICE, "Dropping BTS(%u) due BTS(%u).\n", - rdep_bts->nr, bts->nr); - ipaccess_drop_oml(rdep_bts); - } -} - -/* This function is called once the OML/RSL link becomes up. */ -static struct e1inp_sign_link * -ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line, - enum e1inp_sign_type type) -{ - struct gsm_bts *bts; - struct ipaccess_unit *dev = unit_data; - struct e1inp_sign_link *sign_link = NULL; - struct timespec tp; - int rc; - - bts = find_bts_by_unitid(bsc_gsmnet, dev->site_id, dev->bts_id); - if (!bts) { - LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for " - " %u/%u/%u, disconnecting\n", dev->site_id, - dev->bts_id, dev->trx_id); - rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_UNKNOWN_UNIT_ID]); - return NULL; - } - DEBUGP(DLINP, "Identified BTS %u/%u/%u\n", - dev->site_id, dev->bts_id, dev->trx_id); - - switch(type) { - case E1INP_SIGN_OML: - /* remove old OML signal link for this BTS. */ - ipaccess_drop_oml(bts); - - if (!bts_depend_check(bts)) { - LOGP(DLINP, LOGL_NOTICE, - "Dependency not full-filled for %u/%u/%u\n", - dev->site_id, dev->bts_id, dev->trx_id); - return NULL; - } - - /* create new OML link. */ - sign_link = bts->oml_link = - e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML - 1], - 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 */ - 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; - } - break; - case E1INP_SIGN_RSL: { - struct e1inp_ts *ts; - struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, dev->trx_id); - - /* no OML link set yet? give up. */ - if (!bts->oml_link || !trx) - return NULL; - - /* remove old RSL link for this TRX. */ - ipaccess_drop_rsl(trx); - - /* set new RSL link for this TRX. */ - line = bts->oml_link->ts->line; - ts = &line->ts[E1INP_SIGN_RSL + dev->trx_id - 1]; - e1inp_ts_config_sign(ts, line); - sign_link = trx->rsl_link = - e1inp_sign_link_create(ts, E1INP_SIGN_RSL, - trx, trx->rsl_tei, 0); - trx->rsl_link->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, - sign_link->tei, sign_link->sapi); - sign_link->trx->bts->ip_access.flags |= - (RSL_UP << sign_link->trx->nr); - } - break; - } - default: - break; - } - return sign_link; -} - -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 gsm_bts *bts = NULL; - struct e1inp_sign_link *link; - - llist_for_each_entry(link, &ts->sign.sign_links, list) { - /* Get bts pointer from the first element of the list. */ - if (bts == NULL) - bts = link->trx->bts; - /* Cancel RSL connection timeout in case are still waiting for an RSL connection. */ - if (link->trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) - osmo_timer_del(&link->trx->rsl_connect_timeout); - } - if (bts != NULL) - ipaccess_drop_oml(bts); -} - -/* This function is called if we receive one OML/RSL message. */ -static int ipaccess_sign_link(struct msgb *msg) -{ - int ret = 0; - struct e1inp_sign_link *link = msg->dst; - - switch (link->type) { - case E1INP_SIGN_RSL: - ret = abis_rsl_rcvmsg(msg); - break; - case E1INP_SIGN_OML: - ret = abis_nm_rcvmsg(msg); - break; - default: - LOGP(DLINP, LOGL_ERROR, "Unknown signal link type %d\n", - link->type); - msgb_free(msg); - break; - } - return ret; -} - -/* not static, ipaccess-config needs it. */ -struct e1inp_line_ops ipaccess_e1inp_line_ops = { - .cfg = { - .ipa = { - .addr = "0.0.0.0", - .role = E1INP_LINE_R_BSC, - }, - }, - .sign_link_up = ipaccess_sign_link_up, - .sign_link_down = ipaccess_sign_link_down, - .sign_link = ipaccess_sign_link, -}; - -static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line) -{ - e1inp_line_bind_ops(line, &ipaccess_e1inp_line_ops); -} diff --git a/src/libbsc/bts_ipaccess_nanobts_omlattr.c b/src/libbsc/bts_ipaccess_nanobts_omlattr.c deleted file mode 100644 index 1a8d9b07b..000000000 --- a/src/libbsc/bts_ipaccess_nanobts_omlattr.c +++ /dev/null @@ -1,240 +0,0 @@ -/* ip.access nanoBTS specific code, OML attribute table generator */ - -/* (C) 2016 by sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Philipp Maier - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include - -static void patch_16(uint8_t *data, const uint16_t val) -{ - memcpy(data, &val, sizeof(val)); -} - -static void patch_32(uint8_t *data, const uint32_t val) -{ - memcpy(data, &val, sizeof(val)); -} - -struct msgb *nanobts_attr_bts_get(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); - - /* interference avg. period in numbers of SACCH multifr */ - msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, 0x06); - - rlt = gsm_bts_get_radio_link_timeout(bts); - if (rlt == -1) { - /* Osmocom extension: Use infinite radio link timeout */ - buf[0] = 0xFF; - buf[1] = 0x00; - } else { - /* conn fail based on SACCH error rate */ - buf[0] = 0x01; - buf[1] = rlt; - } - msgb_tl16v_put(msgb, NM_ATT_CONN_FAIL_CRIT, 2, buf); - - memcpy(buf, "\x1e\x24\x24\xa8\x34\x21\xa8", 7); - msgb_tv_fixed_put(msgb, NM_ATT_T200, 7, buf); - - msgb_tv_put(msgb, NM_ATT_MAX_TA, 0x3f); - - /* seconds */ - memcpy(buf, "\x00\x01\x0a", 3); - msgb_tv_fixed_put(msgb, NM_ATT_OVERL_PERIOD, 3, buf); - - /* percent */ - msgb_tv_put(msgb, NM_ATT_CCCH_L_T, 10); - - /* seconds */ - msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, 1); - - /* busy threshold in - 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 */ - buf[0] = 0x03; - buf[1] = 0xe8; - if (bts->rach_ldavg_slots != -1) { - buf[0] = (bts->rach_ldavg_slots >> 8) & 0x0f; - buf[1] = bts->rach_ldavg_slots & 0xff; - } - msgb_tv_fixed_put(msgb, NM_ATT_LDAVG_SLOTS, 2, buf); - - /* 10 milliseconds */ - msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, bts->network->T3105 > 0? bts->network->T3105 : 13); - - /* 10 retransmissions of physical config */ - msgb_tv_put(msgb, NM_ATT_NY1, 10); - - buf[0] = (bts->c0->arfcn >> 8) & 0x0f; - buf[1] = bts->c0->arfcn & 0xff; - msgb_tv_fixed_put(msgb, NM_ATT_BCCH_ARFCN, 2, buf); - - msgb_tv_put(msgb, NM_ATT_BSIC, bts->bsic); - - abis_nm_ipaccess_cgi(buf, bts); - msgb_tl16v_put(msgb, NM_ATT_IPACC_CGI, 7, buf); - - return msgb; -} - -struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts) -{ - struct msgb *msgb; - uint8_t buf[256]; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); - - /* NSEI 925 */ - buf[0] = bts->gprs.nse.nsei >> 8; - buf[1] = bts->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); - - return msgb; -} - -struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts) -{ - struct msgb *msgb; - uint8_t buf[256]; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); - - /* 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 */ - 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; - 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; - buf[1] = 0x00; - } - 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 *msgb; - uint8_t buf[256]; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); - - /* 925 */ - buf[0] = bts->gprs.nsvc[0].nsvci >> 8; - buf[1] = bts->gprs.nsvc[0].nsvci & 0xff; - msgb_tl16v_put(msgb, NM_ATT_IPACC_NSVCI, 2, buf); - - /* remote udp port */ - patch_16(&buf[0], htons(bts->gprs.nsvc[0].remote_port)); - /* remote ip address */ - patch_32(&buf[2], htonl(bts->gprs.nsvc[0].remote_ip)); - /* local udp port */ - patch_16(&buf[6], htons(bts->gprs.nsvc[0].local_port)); - msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf); - - return msgb; -} - -struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts, - struct gsm_bts_trx *trx) -{ - struct msgb *msgb; - uint8_t buf[256]; - msgb = msgb_alloc(1024, "nanobts_attr_bts"); - - /* number of -2dB reduction steps / Pn */ - msgb_tv_put(msgb, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2); - - buf[0] = trx->arfcn >> 8; - buf[1] = trx->arfcn & 0xff; - msgb_tl16v_put(msgb, NM_ATT_ARFCN_LIST, 2, buf); - - return msgb; -} diff --git a/src/libbsc/bts_nokia_site.c b/src/libbsc/bts_nokia_site.c deleted file mode 100644 index 4a24c3931..000000000 --- a/src/libbsc/bts_nokia_site.c +++ /dev/null @@ -1,1744 +0,0 @@ -/* Nokia XXXsite family specific code */ - -/* (C) 2011 by Dieter Spaar - * - * 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 . - * - */ - -/* - TODO: Attention: There are some static variables used for states during - configuration. Those variables have to be moved to a BTS specific context, - otherwise there will most certainly be problems if more than one Nokia BTS - is used. -*/ - -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -/* TODO: put in a separate file ? */ - -extern int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg); -/* was static in system_information.c */ -extern int generate_cell_chan_list(uint8_t * chan_list, struct gsm_bts *bts); - -static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts); -static void reset_timer_cb(void *_bts); -static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref); -static int dump_elements(uint8_t * data, int len) __attribute__((unused)); - -static void bootstrap_om_bts(struct gsm_bts *bts) -{ - LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); - - gsm_bts_mark_all_ts_uninitialized(bts); - - if (!bts->nokia.skip_reset) { - if (!bts->nokia.did_reset) - abis_nm_reset(bts, 1); - } else - bts->nokia.did_reset = 1; -} - -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); - - gsm_trx_mark_all_ts_uninitialized(trx); -} - -static int shutdown_om(struct gsm_bts *bts) -{ - /* TODO !? */ - return 0; -} - -#define SAPI_OML 62 -#define SAPI_RSL 0 - -/* - - Tell LAPD to start start the SAP (send SABM requests) for all signalling - timeslots in this line - - Attention: this has to be adapted for mISDN -*/ - -static void start_sabm_in_line(struct e1inp_line *line, int start, int sapi) -{ - struct e1inp_sign_link *link; - int i; - - for (i = 0; i < ARRAY_SIZE(line->ts); i++) { - struct e1inp_ts *ts = &line->ts[i]; - - if (ts->type != E1INP_TS_TYPE_SIGN) - continue; - - llist_for_each_entry(link, &ts->sign.sign_links, list) { - if (sapi != -1 && link->sapi != sapi) - continue; - -#if 0 /* debugging */ - printf("sap start/stop (%d): %d tei=%d sapi=%d\n", - start, i + 1, link->tei, link->sapi); -#endif - - if (start) { - ts->lapd->profile.t200_sec = 1; - ts->lapd->profile.t200_usec = 0; - lapd_sap_start(ts->lapd, link->tei, - link->sapi); - } else - lapd_sap_stop(ts->lapd, link->tei, - link->sapi); - } - } -} - -/* Callback function to be called every time we receive a signal from INPUT */ -static int gbl_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct gsm_bts *bts; - - if (subsys != SS_L_GLOBAL) - return 0; - - switch (signal) { - case S_GLOBAL_BTS_CLOSE_OM: - bts = signal_data; - if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) - shutdown_om(signal_data); - break; - } - - return 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) -{ - struct input_signal_data *isd = signal_data; - - if (subsys != SS_L_INPUT) - return 0; - - switch (signal) { - case S_L_INP_LINE_INIT: - start_sabm_in_line(isd->line, 1, SAPI_OML); /* start only OML */ - break; - case S_L_INP_TEI_DN: - break; - case S_L_INP_TEI_UP: - switch (isd->link_type) { - case E1INP_SIGN_OML: - if (isd->trx->bts->type != GSM_BTS_TYPE_NOKIA_SITE) - break; - - if (isd->tei == isd->trx->bts->oml_tei) - bootstrap_om_bts(isd->trx->bts); - else - bootstrap_om_trx(isd->trx); - break; - } - break; - case S_L_INP_TEI_UNKNOWN: - /* We are receiving LAPD frames with one TEI that we do not - * seem to know, likely that we (the BSC) stopped working - * and lost our local states. However, the BTS is already - * configured, we try to take over the RSL links. */ - start_sabm_in_line(isd->line, 1, SAPI_RSL); - break; - } - - return 0; -} - -static void nm_statechg_evt(unsigned int signal, - struct nm_statechg_signal_data *nsd) -{ - if (nsd->bts->type != GSM_BTS_TYPE_NOKIA_SITE) - return; -} - -static int nm_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - if (subsys != SS_NM) - return 0; - - switch (signal) { - case S_NM_STATECHG_OPER: - case S_NM_STATECHG_ADM: - nm_statechg_evt(signal, signal_data); - break; - default: - break; - } - - return 0; -} - -/* TODO: put in a separate file ? */ - -static const struct value_string nokia_msgt_name[] = { - { 0x80, "NOKIA_BTS_CONF_DATA" }, - { 0x81, "NOKIA_BTS_ACK" }, - { 0x82, "NOKIA_BTS_OMU_STARTED" }, - { 0x83, "NOKIA_BTS_START_DOWNLOAD_REQ" }, - { 0x84, "NOKIA_BTS_MF_REQ" }, - { 0x85, "NOKIA_BTS_AF_REQ" }, - { 0x86, "NOKIA_BTS_RESET_REQ" }, - { 0x87, "NOKIA_reserved" }, - { 0x88, "NOKIA_BTS_CONF_REQ" }, - { 0x89, "NOKIA_BTS_TEST_REQ" }, - { 0x8A, "NOKIA_BTS_TEST_REPORT" }, - { 0x8B, "NOKIA_reserved" }, - { 0x8C, "NOKIA_reserved" }, - { 0x8D, "NOKIA_reserved" }, - { 0x8E, "NOKIA_BTS_CONF_COMPL" }, - { 0x8F, "NOKIA_reserved" }, - { 0x90, "NOKIA_BTS_STM_TEST_REQ" }, - { 0x91, "NOKIA_BTS_STM_TEST_REPORT" }, - { 0x92, "NOKIA_BTS_TRANSMISSION_COMMAND" }, - { 0x93, "NOKIA_BTS_TRANSMISSION_ANSWER" }, - { 0x94, "NOKIA_BTS_HW_DB_UPLOAD_REQ" }, - { 0x95, "NOKIA_BTS_START_HW_DB_DOWNLOAD_REQ" }, - { 0x96, "NOKIA_BTS_HW_DB_SAVE_REQ" }, - { 0x97, "NOKIA_BTS_FLASH_ERASURE_REQ" }, - { 0x98, "NOKIA_BTS_HW_DB_DOWNLOAD_REQ" }, - { 0x99, "NOKIA_BTS_PWR_SUPPLY_CONTROL" }, - { 0x9A, "NOKIA_BTS_ATTRIBUTE_REQ" }, - { 0x9B, "NOKIA_BTS_ATTRIBUTE_REPORT" }, - { 0x9C, "NOKIA_BTS_HW_REQ" }, - { 0x9D, "NOKIA_BTS_HW_REPORT" }, - { 0x9E, "NOKIA_BTS_RTE_TEST_REQ" }, - { 0x9F, "NOKIA_BTS_RTE_TEST_REPORT" }, - { 0xA0, "NOKIA_BTS_HW_DB_VERIFICATION_REQ" }, - { 0xA1, "NOKIA_BTS_CLOCK_REQ" }, - { 0xA2, "NOKIA_AC_CIRCUIT_REQ_NACK" }, - { 0xA3, "NOKIA_AC_INTERRUPTED" }, - { 0xA4, "NOKIA_BTS_NEW_TRE_INFO" }, - { 0xA5, "NOKIA_AC_BSC_CIRCUITS_ALLOCATED" }, - { 0xA6, "NOKIA_BTS_TRE_POLL_LIST" }, - { 0xA7, "NOKIA_AC_CIRCUIT_REQ" }, - { 0xA8, "NOKIA_BTS_BLOCK_CTRL_REQ" }, - { 0xA9, "NOKIA_BTS_GSM_TIME_REQ" }, - { 0xAA, "NOKIA_BTS_GSM_TIME" }, - { 0xAB, "NOKIA_BTS_OUTPUT_CONTROL" }, - { 0xAC, "NOKIA_BTS_STATE_CHANGED" }, - { 0xAD, "NOKIA_BTS_SW_SAVE_REQ" }, - { 0xAE, "NOKIA_BTS_ALARM" }, - { 0xAF, "NOKIA_BTS_CHA_ADM_STATE" }, - { 0xB0, "NOKIA_AC_POOL_SIZE_REPORT" }, - { 0xB1, "NOKIA_AC_POOL_SIZE_INQUIRY" }, - { 0xB2, "NOKIA_BTS_COMMISS_TEST_COMPLETED" }, - { 0xB3, "NOKIA_BTS_COMMISS_TEST_REQ" }, - { 0xB4, "NOKIA_BTS_TRANSP_BTS_TO_BSC" }, - { 0xB5, "NOKIA_BTS_TRANSP_BSC_TO_BTS" }, - { 0xB6, "NOKIA_BTS_LCS_COMMAND" }, - { 0xB7, "NOKIA_BTS_LCS_ANSWER" }, - { 0xB8, "NOKIA_BTS_LMU_FN_OFFSET_COMMAND" }, - { 0xB9, "NOKIA_BTS_LMU_FN_OFFSET_ANSWER" }, - { 0, NULL } -}; - -static const char *get_msg_type_name_string(uint8_t msg_type) -{ - return get_value_string(nokia_msgt_name, msg_type); -} - -static const struct value_string nokia_element_name[] = { - { 0x01, "Ny1" }, - { 0x02, "T3105_F" }, - { 0x03, "Interference band limits" }, - { 0x04, "Interference report timer in secs" }, - { 0x05, "Channel configuration per TS" }, - { 0x06, "BSIC" }, - { 0x07, "RACH report timer in secs" }, - { 0x08, "Hardware database status" }, - { 0x09, "BTS RX level" }, - { 0x0A, "ARFN" }, - { 0x0B, "STM antenna attenuation" }, - { 0x0C, "Cell allocation bitmap" }, - { 0x0D, "Radio definition per TS" }, - { 0x0E, "Frame number" }, - { 0x0F, "Antenna diversity" }, - { 0x10, "T3105_D" }, - { 0x11, "File format" }, - { 0x12, "Last File" }, - { 0x13, "BTS type" }, - { 0x14, "Erasure mode" }, - { 0x15, "Hopping mode" }, - { 0x16, "Floating TRX" }, - { 0x17, "Power supplies" }, - { 0x18, "Reset type" }, - { 0x19, "Averaging period" }, - { 0x1A, "RBER2" }, - { 0x1B, "LAC" }, - { 0x1C, "CI" }, - { 0x1D, "Failure parameters" }, - { 0x1E, "(RF max power reduction)" }, - { 0x1F, "Measured RX_SENS" }, - { 0x20, "Extended cell radius" }, - { 0x21, "reserved" }, - { 0x22, "Success-Failure" }, - { 0x23, "Ack-Nack" }, - { 0x24, "OMU test results" }, - { 0x25, "File identity" }, - { 0x26, "Generation and version code" }, - { 0x27, "SW description" }, - { 0x28, "BCCH LEV" }, - { 0x29, "Test type" }, - { 0x2A, "Subscriber number" }, - { 0x2B, "reserved" }, - { 0x2C, "HSN" }, - { 0x2D, "reserved" }, - { 0x2E, "MS RXLEV" }, - { 0x2F, "MS TXLEV" }, - { 0x30, "RXQUAL" }, - { 0x31, "RX SENS" }, - { 0x32, "Alarm block" }, - { 0x33, "Neighbouring BCCH levels" }, - { 0x34, "STM report type" }, - { 0x35, "MA" }, - { 0x36, "MAIO" }, - { 0x37, "H_FLAG" }, - { 0x38, "TCH_ARFN" }, - { 0x39, "Clock output" }, - { 0x3A, "Transmitted power" }, - { 0x3B, "Clock sync" }, - { 0x3C, "TMS protocol discriminator" }, - { 0x3D, "TMS protocol data" }, - { 0x3E, "FER" }, - { 0x3F, "SWR result" }, - { 0x40, "Object identity" }, - { 0x41, "STM RX Antenna Test" }, - { 0x42, "reserved" }, - { 0x43, "reserved" }, - { 0x44, "Object current state" }, - { 0x45, "reserved" }, - { 0x46, "FU channel configuration" }, - { 0x47, "reserved" }, - { 0x48, "ARFN of a CU" }, - { 0x49, "FU radio definition" }, - { 0x4A, "reserved" }, - { 0x4B, "Severity" }, - { 0x4C, "Diversity selection" }, - { 0x4D, "RX antenna test" }, - { 0x4E, "RX antenna supervision period" }, - { 0x4F, "RX antenna state" }, - { 0x50, "Sector configuration" }, - { 0x51, "Additional info" }, - { 0x52, "SWR parameters" }, - { 0x53, "HW inquiry mode" }, - { 0x54, "reserved" }, - { 0x55, "Availability status" }, - { 0x56, "reserved" }, - { 0x57, "EAC inputs" }, - { 0x58, "EAC outputs" }, - { 0x59, "reserved" }, - { 0x5A, "Position" }, - { 0x5B, "HW unit identity" }, - { 0x5C, "RF test signal attenuation" }, - { 0x5D, "Operational state" }, - { 0x5E, "Logical object identity" }, - { 0x5F, "reserved" }, - { 0x60, "BS_TXPWR_OM" }, - { 0x61, "Loop_Duration" }, - { 0x62, "LNA_Path_Selection" }, - { 0x63, "Serial number" }, - { 0x64, "HW version" }, - { 0x65, "Obj. identity and obj. state" }, - { 0x66, "reserved" }, - { 0x67, "EAC input definition" }, - { 0x68, "EAC id and text" }, - { 0x69, "HW unit status" }, - { 0x6A, "SW release version" }, - { 0x6B, "FW version" }, - { 0x6C, "Bit_Error_Ratio" }, - { 0x6D, "RXLEV_with_Attenuation" }, - { 0x6E, "RXLEV_without_Attenuation" }, - { 0x6F, "reserved" }, - { 0x70, "CU_Results" }, - { 0x71, "reserved" }, - { 0x72, "LNA_Path_Results" }, - { 0x73, "RTE Results" }, - { 0x74, "Real Time" }, - { 0x75, "RX diversity selection" }, - { 0x76, "EAC input config" }, - { 0x77, "Feature support" }, - { 0x78, "File version" }, - { 0x79, "Outputs" }, - { 0x7A, "FU parameters" }, - { 0x7B, "Diagnostic info" }, - { 0x7C, "FU BSIC" }, - { 0x7D, "TRX Configuration" }, - { 0x7E, "Download status" }, - { 0x7F, "RX difference limit" }, - { 0x80, "TRX HW capability" }, - { 0x81, "Common HW config" }, - { 0x82, "Autoconfiguration pool size" }, - { 0x83, "TRE diagnostic info" }, - { 0x84, "TRE object identity" }, - { 0x85, "New TRE Info" }, - { 0x86, "Acknowledgement period" }, - { 0x87, "Synchronization mode" }, - { 0x88, "reserved" }, - { 0x89, "Block Control Data" }, - { 0x8A, "SW load mode" }, - { 0x8B, "Recommended recovery action" }, - { 0x8C, "BSC BCF id" }, - { 0x8D, "Q1 baud rate" }, - { 0x8E, "Allocation status" }, - { 0x8F, "Functional entity number" }, - { 0x90, "Transmission delay" }, - { 0x91, "Loop Duration ms" }, - { 0x92, "Logical channel" }, - { 0x93, "Q1 address" }, - { 0x94, "Alarm detail" }, - { 0x95, "Cabinet type" }, - { 0x96, "HW unit existence" }, - { 0x97, "RF power parameters" }, - { 0x98, "Message scenario" }, - { 0x99, "HW unit max amount" }, - { 0x9A, "Master TRX" }, - { 0x9B, "Transparent data" }, - { 0x9C, "BSC topology info" }, - { 0x9D, "Air i/f modulation" }, - { 0x9E, "LCS Q1 command data" }, - { 0x9F, "Frame number offset" }, - { 0xA0, "Abis TSL" }, - { 0xA1, "Dynamic pool info" }, - { 0xA2, "LCS LLP data" }, - { 0xA3, "LCS Q1 answer data" }, - { 0xA4, "DFCA FU Radio Definition" }, - { 0xA5, "Antenna hopping" }, - { 0xA6, "Field record sequence number" }, - { 0xA7, "Timeslot offslot" }, - { 0xA8, "EPCR capability" }, - { 0xA9, "Connectsite optional element" }, - { 0xAA, "TSC" }, - { 0xAB, "Special TX Power Setting" }, - { 0xAC, "Optional sync settings" }, - { 0xFA, "Abis If parameters" }, - { 0, NULL } -}; - -static const char *get_element_name_string(uint16_t element) -{ - return get_value_string(nokia_element_name, element); -} - -static const struct value_string nokia_bts_types[] = { - { 0x0a, "MetroSite GSM 900" }, - { 0x0b, "MetroSite GSM 1800" }, - { 0x0c, "MetroSite GSM 1900 (PCS)" }, - { 0x0d, "MetroSite GSM 900 & 1800" }, - { 0x0e, "InSite GSM 900" }, - { 0x0f, "InSite GSM 1800" }, - { 0x10, "InSite GSM 1900" }, - { 0x11, "UltraSite GSM 900" }, - { 0x12, "UltraSite GSM 1800" }, - { 0x13, "UltraSite GSM/US-TDMA 1900" }, - { 0x14, "UltraSite GSM 900 & 1800" }, - { 0x16, "UltraSite GSM/US-TDMA 850" }, - { 0x18, "MetroSite GSM/US-TDMA 850" }, - { 0x19, "UltraSite GSM 800/1900" }, - { 0, NULL } -}; - -static const char *get_bts_type_string(uint8_t type) -{ - return get_value_string(nokia_bts_types, type); -} - -static const struct value_string nokia_severity[] = { - { 0, "indeterminate" }, - { 1, "critical" }, - { 2, "major" }, - { 3, "minor" }, - { 4, "warning" }, - { 0, NULL } -}; - -static const char *get_severity_string(uint8_t severity) -{ - return get_value_string(nokia_severity, severity); -} - -/* TODO: put in a separate file ? */ - -/* some message IDs */ - -#define NOKIA_MSG_CONF_DATA 128 -#define NOKIA_MSG_ACK 129 -#define NOKIA_MSG_OMU_STARTED 130 -#define NOKIA_MSG_START_DOWNLOAD_REQ 131 -#define NOKIA_MSG_MF_REQ 132 -#define NOKIA_MSG_RESET_REQ 134 -#define NOKIA_MSG_CONF_REQ 136 -#define NOKIA_MSG_CONF_COMPLETE 142 -#define NOKIA_MSG_BLOCK_CTRL_REQ 168 -#define NOKIA_MSG_STATE_CHANGED 172 -#define NOKIA_MSG_ALARM 174 - -/* some element IDs */ - -#define NOKIA_EI_BTS_TYPE 0x13 -#define NOKIA_EI_ACK 0x23 -#define NOKIA_EI_ADD_INFO 0x51 -#define NOKIA_EI_SEVERITY 0x4B -#define NOKIA_EI_ALARM_DETAIL 0x94 - -#define OM_ALLOC_SIZE 1024 -#define OM_HEADROOM_SIZE 128 - -static uint8_t fu_config_template[] = { - 0x7F, 0x7A, 0x39, - /* ID = 0x7A (FU parameters) ## constructed ## */ - /* length = 57 */ - /* [3] */ - - 0x5F, 0x40, 0x04, - /* ID = 0x40 (Object identity) */ - /* length = 4 */ - /* [6] */ - 0x00, 0x07, 0x01, 0xFF, - - 0x41, 0x02, - /* ID = 0x01 (Ny1) */ - /* length = 2 */ - /* [12] */ - 0x00, 0x05, - - 0x42, 0x02, - /* ID = 0x02 (T3105_F) */ - /* length = 2 */ - /* [16] */ - 0x00, 0x28, /* FIXME: use net->T3105 */ - - 0x50, 0x02, - /* ID = 0x10 (T3105_D) */ - /* length = 2 */ - /* [20] */ - 0x00, 0x28, /* FIXME: use net->T3105 */ - - 0x43, 0x05, - /* ID = 0x03 (Interference band limits) */ - /* length = 5 */ - /* [24] */ - 0x0F, 0x1B, 0x27, 0x33, 0x3F, - - 0x44, 0x02, - /* ID = 0x04 (Interference report timer in secs) */ - /* length = 2 */ - /* [31] */ - 0x00, 0x10, - - 0x47, 0x01, - /* ID = 0x07 (RACH report timer in secs) */ - /* length = 1 */ - /* [35] */ - 0x1E, - - 0x4C, 0x10, - /* ID = 0x0C (Cell allocation bitmap) ####### */ - /* length = 16 */ - /* [38] */ - 0x8F, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x59, 0x01, - /* ID = 0x19 (Averaging period) */ - /* length = 1 */ - /* [56] */ - 0x01, - - 0x5E, 0x01, - /* ID = 0x1E ((RF max power reduction)) */ - /* length = 1 */ - /* [59] */ - 0x00, - - 0x7F, 0x46, 0x11, - /* ID = 0x46 (FU channel configuration) ## constructed ## */ - /* length = 17 */ - /* [63] */ - - 0x5F, 0x40, 0x04, - /* ID = 0x40 (Object identity) */ - /* length = 4 */ - /* [66] */ - 0x00, 0x07, 0x01, 0xFF, - - 0x45, 0x08, - /* ID = 0x05 (Channel configuration per TS) */ - /* length = 8 */ - /* [72] */ - 0x01, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - - 0x7F, 0x65, 0x0B, - /* ID = 0x65 (Obj. identity and obj. state) ## constructed ## */ - /* length = 11 */ - /* [83] */ - - 0x5F, 0x40, 0x04, - /* ID = 0x40 (Object identity) */ - /* length = 4 */ - /* [86] */ - 0x00, 0x04, 0x01, 0xFF, - - 0x5F, 0x44, 0x01, - /* ID = 0x44 (Object current state) */ - /* length = 1 */ - /* [93] */ - 0x03, - - 0x7F, 0x7C, 0x0A, - /* ID = 0x7C (FU BSIC) ## constructed ## */ - /* length = 10 */ - /* [97] */ - - 0x5F, 0x40, 0x04, - /* ID = 0x40 (Object identity) */ - /* length = 4 */ - /* [100] */ - 0x00, 0x07, 0x01, 0xFF, - - 0x46, 0x01, - /* ID = 0x06 (BSIC) */ - /* length = 1 */ - /* [106] */ - 0x00, - - 0x7F, 0x48, 0x0B, - /* ID = 0x48 (ARFN of a CU) ## constructed ## */ - /* length = 11 */ - /* [110] */ - - 0x5F, 0x40, 0x04, - /* ID = 0x40 (Object identity) */ - /* length = 4 */ - /* [113] */ - 0x00, 0x08, 0x01, 0xFF, - - 0x4A, 0x02, - /* ID = 0x0A (ARFN) ####### */ - /* length = 2 */ - /* [119] */ - 0x03, 0x62, - - 0x7F, 0x49, 0x59, - /* ID = 0x49 (FU radio definition) ## constructed ## */ - /* length = 89 */ - /* [124] */ - - 0x5F, 0x40, 0x04, - /* ID = 0x40 (Object identity) */ - /* length = 4 */ - /* [127] */ - 0x00, 0x07, 0x01, 0xFF, - - 0x4D, 0x50, - /* ID = 0x0D (Radio definition per TS) ####### */ - /* length = 80 */ - /* [133] */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* MA */ - 0x03, 0x62, /* HSN, MAIO or ARFCN if no hopping */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x62, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x62, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x62, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x62, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x62, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x62, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x62, -}; - -/* TODO: put in a separate file ? */ - -/* - build the configuration for each TRX -*/ - -static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id, - uint8_t * fu_config, int *hopping) -{ - int i; - - *hopping = 0; - - memcpy(fu_config, fu_config_template, sizeof(fu_config_template)); - - /* set ID */ - - fu_config[6 + 2] = id; - fu_config[66 + 2] = id; - fu_config[86 + 2] = id; - fu_config[100 + 2] = id; - fu_config[113 + 2] = id; - fu_config[127 + 2] = id; - - /* set ARFCN */ - - uint16_t arfcn = trx->arfcn; - - fu_config[119] = arfcn >> 8; - fu_config[119 + 1] = arfcn & 0xFF; - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - - if (ts->hopping.enabled) { - /* reverse order */ - int j; - for (j = 0; j < ts->hopping.ma_len; j++) - fu_config[133 + (i * 10) + (7 - j)] = - ts->hopping.ma_data[j]; - fu_config[133 + 8 + (i * 10)] = ts->hopping.hsn; - fu_config[133 + 8 + 1 + (i * 10)] = ts->hopping.maio; - *hopping = 1; - } else { - fu_config[133 + 8 + (i * 10)] = arfcn >> 8; - fu_config[133 + 8 + 1 + (i * 10)] = arfcn & 0xFF; - } - } - - /* set BSIC */ - - /* - Attention: all TRX except the first one seem to get the TSC - from the CHANNEL ACTIVATION command (in CHANNEL IDENTIFICATION, - GSM 04.08 CHANNEL DESCRIPTION). - There was a bug in rsl_chan_activate_lchan() setting this parameter. - */ - - uint8_t bsic = trx->bts->bsic; - - fu_config[106] = bsic; - - /* set CA */ - - if (generate_cell_chan_list(&fu_config[38], trx->bts) != 0) { - fprintf(stderr, "generate_cell_chan_list failed\n"); - return 0; - } - - /* set channel configuration */ - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - uint8_t chan_config; - - /* - 0 = FCCH + SCH + BCCH + CCCH - 1 = FCCH + SCH + BCCH + CCCH + SDCCH/4 + SACCH/4 - 2 = BCCH + CCCH (This combination is not used in any BTS) - 3 = FCCH + SCH + BCCH + CCCH + SDCCH/4 with SDCCH2 used as CBCH - 4 = SDCCH/8 + SACCH/8 - 5 = SDCCH/8 with SDCCH2 used as CBCH - 6 = TCH/F + FACCH/F + SACCH/F - 7 = E-RACH (Talk family) - 9 = Dual rate (capability for TCH/F and TCH/H) - 10 = reserved for BTS internal use - 11 = PBCCH + PCCCH + PDTCH + PACCH + PTCCH (can be used in GPRS release 2). - 0xFF = spare TS - */ - - if (ts->pchan == GSM_PCHAN_NONE) - chan_config = 0xFF; - else if (ts->pchan == GSM_PCHAN_CCCH) - chan_config = 0; - else if (ts->pchan == GSM_PCHAN_CCCH_SDCCH4) - chan_config = 1; - else if (ts->pchan == GSM_PCHAN_TCH_F) - chan_config = 6; /* 9 should work too */ - else if (ts->pchan == GSM_PCHAN_TCH_H) - chan_config = 9; - else if (ts->pchan == GSM_PCHAN_SDCCH8_SACCH8C) - chan_config = 4; - else if (ts->pchan == GSM_PCHAN_PDCH) - chan_config = 11; - else { - fprintf(stderr, - "unsupported channel config %d for timeslot %d\n", - ts->pchan, i); - return 0; - } - - fu_config[72 + i] = chan_config; - } - return sizeof(fu_config_template); -} - -/* TODO: put in a separate file ? */ - -static uint8_t bts_config_1[] = { - 0x4E, 0x02, - /* ID = 0x0E (Frame number) */ - /* length = 2 */ - /* [2] */ - 0xFF, 0xFF, - - 0x5F, 0x4E, 0x02, - /* ID = 0x4E (RX antenna supervision period) */ - /* length = 2 */ - /* [7] */ - 0xFF, 0xFF, - - 0x5F, 0x50, 0x02, - /* ID = 0x50 (Sector configuration) */ - /* length = 2 */ - /* [12] */ - 0x01, 0x01, -}; - -static uint8_t bts_config_2[] = { - 0x55, 0x02, - /* ID = 0x15 (Hopping mode) */ - /* length = 2 */ - /* [2] */ - 0x01, 0x00, - - 0x5F, 0x75, 0x02, - /* ID = 0x75 (RX diversity selection) */ - /* length = 2 */ - /* [7] */ - 0x01, 0x01, -}; - -static uint8_t bts_config_3[] = { - 0x5F, 0x20, 0x02, - /* ID = 0x20 (Extended cell radius) */ - /* length = 2 */ - /* [3] */ - 0x01, 0x00, -}; - -static uint8_t bts_config_4[] = { - 0x5F, 0x74, 0x09, - /* ID = 0x74 (Real Time) */ - /* length = 9 */ - /* [3] year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */ - 0x07, 0xDB, 0x06, 0x02, 0x0B, 0x20, 0x0C, 0x00, - 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [15] */ - 0x01, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [21] */ - 0x02, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [27] */ - 0x03, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [33] */ - 0x04, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [39] */ - 0x05, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [45] */ - 0x06, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [51] */ - 0x07, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [57] */ - 0x08, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [63] */ - 0x09, 0x01, 0x00, - - 0x5F, 0x76, 0x03, - /* ID = 0x76 (EAC input config) */ - /* length = 3 */ - /* [69] */ - 0x0A, 0x01, 0x00, -}; - -static uint8_t bts_config_insite[] = { - 0x4E, 0x02, - /* ID = 0x0E (Frame number) */ - /* length = 2 */ - /* [2] */ - 0xFF, 0xFF, - - 0x5F, 0x4E, 0x02, - /* ID = 0x4E (RX antenna supervision period) */ - /* length = 2 */ - /* [7] */ - 0xFF, 0xFF, - - 0x5F, 0x50, 0x02, - /* ID = 0x50 (Sector configuration) */ - /* length = 2 */ - /* [12] */ - 0x01, 0x01, - - 0x55, 0x02, - /* ID = 0x15 (Hopping mode) */ - /* length = 2 */ - /* [16] */ - 0x01, 0x00, - - 0x5F, 0x20, 0x02, - /* ID = 0x20 (Extended cell radius) */ - /* length = 2 */ - /* [21] */ - 0x01, 0x00, - - 0x5F, 0x74, 0x09, - /* ID = 0x74 (Real Time) */ - /* length = 9 */ - /* [26] */ - 0x07, 0xDB, 0x07, 0x0A, 0x0F, 0x09, 0x0B, 0x00, - 0x00, -}; - -void set_real_time(uint8_t * real_time) -{ - time_t t; - struct tm *tm; - - t = time(NULL); - tm = localtime(&t); - - /* year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */ - - real_time[0] = (1900 + tm->tm_year) >> 8; - real_time[1] = (1900 + tm->tm_year) & 0xFF; - real_time[2] = tm->tm_mon + 1; - real_time[3] = tm->tm_mday; - real_time[4] = tm->tm_hour; - real_time[5] = tm->tm_min; - real_time[6] = tm->tm_sec; - real_time[7] = 0; - real_time[8] = 0; -} - -/* TODO: put in a separate file ? */ - -/* - build the configuration data -*/ - -static int make_bts_config(uint8_t bts_type, int n_trx, uint8_t * fu_config, - int need_hopping) -{ - /* is it an InSite BTS ? */ - if (bts_type == 0x0E || bts_type == 0x0F || bts_type == 0x10) { /* TODO */ - if (n_trx != 1) { - fprintf(stderr, "InSite has only one TRX\n"); - return 0; - } - if (need_hopping != 0) { - fprintf(stderr, "InSite does not support hopping\n"); - return 0; - } - memcpy(fu_config, bts_config_insite, sizeof(bts_config_insite)); - set_real_time(&fu_config[26]); - return sizeof(bts_config_insite); - } - - int len = 0; - int i; - - memcpy(fu_config + len, bts_config_1, sizeof(bts_config_1)); - - /* set sector configuration */ - fu_config[len + 12 - 1] = 1 + n_trx; /* len */ - for (i = 0; i < n_trx; i++) - fu_config[len + 12 + 1 + i] = ((i + 1) & 0xFF); - - len += (sizeof(bts_config_1) + (n_trx - 1)); - - memcpy(fu_config + len, bts_config_2, sizeof(bts_config_2)); - /* set hopping mode (Baseband and RF hopping work for the MetroSite) */ - if (need_hopping) - fu_config[len + 2 + 1] = 1; /* 0: no hopping, 1: Baseband hopping, 2: RF hopping */ - len += sizeof(bts_config_2); - - /* set extended cell radius for each TRX */ - for (i = 0; i < n_trx; i++) { - memcpy(fu_config + len, bts_config_3, sizeof(bts_config_3)); - fu_config[len + 3] = ((i + 1) & 0xFF); - len += sizeof(bts_config_3); - } - - memcpy(fu_config + len, bts_config_4, sizeof(bts_config_4)); - set_real_time(&fu_config[len + 3]); - len += sizeof(bts_config_4); - - return len; -} - -/* TODO: put in a separate file ? */ - -static struct msgb *nm_msgb_alloc(void) -{ - return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, "OML"); -} - -/* TODO: put in a separate file ? */ - -struct abis_om_nokia_hdr { - uint8_t msg_type; - uint8_t spare; - uint16_t reference; - uint8_t data[0]; -} __attribute__ ((packed)); - -#define ABIS_OM_NOKIA_HDR_SIZE (sizeof(struct abis_om_hdr) + sizeof(struct abis_om_nokia_hdr)) - -static int abis_nm_send(struct gsm_bts *bts, uint8_t msg_type, uint16_t ref, - uint8_t * data, int len_data) -{ - struct abis_om_hdr *oh; - struct abis_om_nokia_hdr *noh; - struct msgb *msg = nm_msgb_alloc(); - - oh = (struct abis_om_hdr *)msgb_put(msg, - ABIS_OM_NOKIA_HDR_SIZE + len_data); - - oh->mdisc = ABIS_OM_MDISC_FOM; - oh->placement = ABIS_OM_PLACEMENT_ONLY; - oh->sequence = 0; - oh->length = sizeof(struct abis_om_nokia_hdr) + len_data; - - noh = (struct abis_om_nokia_hdr *)oh->data; - - noh->msg_type = msg_type; - noh->spare = 0; - noh->reference = htons(ref); - memcpy(noh->data, data, len_data); - - DEBUGPC(DNM, "Sending %s\n", get_msg_type_name_string(msg_type)); - - return abis_nm_sendmsg(bts, msg); -} - -/* TODO: put in a separate file ? */ - -static uint8_t download_req[] = { - 0x5F, 0x25, 0x0B, - /* ID = 0x25 (File identity) */ - /* length = 11 */ - /* [3] */ - 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, - 0x2A, 0x2A, 0x2A, - - 0x5F, 0x78, 0x03, - /* ID = 0x78 (File version) */ - /* length = 3 */ - /* [17] */ - 0x2A, 0x2A, 0x2A, - - 0x5F, 0x81, 0x0A, 0x01, - /* ID = 0x8A (SW load mode) */ - /* length = 1 */ - /* [24] */ - 0x01, - - 0x5F, 0x81, 0x06, 0x01, - /* ID = 0x86 (Acknowledgement period) */ - /* length = 1 */ - /* [29] */ - 0x01, -}; - -static int abis_nm_download_req(struct gsm_bts *bts, uint16_t ref) -{ - uint8_t *data = download_req; - int len_data = sizeof(download_req); - - return abis_nm_send(bts, NOKIA_MSG_START_DOWNLOAD_REQ, ref, data, - len_data); -} - -/* TODO: put in a separate file ? */ - -static uint8_t ack[] = { - 0x5F, 0x23, 0x01, - /* ID = 0x23 (Ack-Nack) */ - /* length = 1 */ - /* [3] */ - 0x01, -}; - -static int abis_nm_ack(struct gsm_bts *bts, uint16_t ref) -{ - uint8_t *data = ack; - int len_data = sizeof(ack); - - return abis_nm_send(bts, NOKIA_MSG_ACK, ref, data, len_data); -} - -/* TODO: put in a separate file ? */ - -static uint8_t reset[] = { - 0x5F, 0x40, 0x04, - /* ID = 0x40 (Object identity) */ - /* length = 4 */ - /* [3] */ - 0x00, 0x01, 0xFF, 0xFF, -}; - -static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref) -{ - uint8_t *data = reset; - int len_data = sizeof(reset); - LOGP(DLINP, LOGL_INFO, "Nokia BTS reset timer: %d\n", bts->nokia.bts_reset_timer_cnf); - return abis_nm_send(bts, NOKIA_MSG_RESET_REQ, ref, data, len_data); -} - -/* TODO: put in a separate file ? */ - -static int abis_nm_send_multi_segments(struct gsm_bts *bts, uint8_t msg_type, - uint16_t ref, uint8_t * data, int len) -{ - int len_remain, len_to_send, max_send; - int seq = 0; - int ret; - - len_remain = len; - - while (len_remain) { - struct abis_om_hdr *oh; - struct abis_om_nokia_hdr *noh; - struct msgb *msg = nm_msgb_alloc(); - - if (seq == 0) - max_send = 256 - sizeof(struct abis_om_nokia_hdr); - else - max_send = 256; - - if (len_remain > max_send) { - len_to_send = max_send; - - if (seq == 0) { - /* first segment */ - oh = (struct abis_om_hdr *)msgb_put(msg, - ABIS_OM_NOKIA_HDR_SIZE - + - len_to_send); - - oh->mdisc = ABIS_OM_MDISC_FOM; - oh->placement = ABIS_OM_PLACEMENT_FIRST; /* first segment of multi-segment message */ - oh->sequence = seq; - oh->length = 0; /* 256 bytes */ - - noh = (struct abis_om_nokia_hdr *)oh->data; - - noh->msg_type = msg_type; - noh->spare = 0; - noh->reference = htons(ref); - memcpy(noh->data, data, len_to_send); - } else { - /* segment in between */ - oh = (struct abis_om_hdr *)msgb_put(msg, - sizeof - (struct - abis_om_hdr) - + - len_to_send); - - oh->mdisc = ABIS_OM_MDISC_FOM; - oh->placement = ABIS_OM_PLACEMENT_MIDDLE; /* segment of multi-segment message */ - oh->sequence = seq; - oh->length = 0; /* 256 bytes */ - - memcpy(oh->data, data, len_to_send); - } - } else { - - len_to_send = len_remain; - - /* check if message fits in a single segment */ - - if (seq == 0) - return abis_nm_send(bts, msg_type, ref, data, - len_to_send); - - /* last segment */ - - oh = (struct abis_om_hdr *)msgb_put(msg, - sizeof(struct - abis_om_hdr) - + len_to_send); - - oh->mdisc = ABIS_OM_MDISC_FOM; - oh->placement = ABIS_OM_PLACEMENT_LAST; /* last segment of multi-segment message */ - oh->sequence = seq; - oh->length = len_to_send; - - memcpy(oh->data, data, len_to_send); - } - - DEBUGPC(DNM, "Sending multi-segment %d\n", seq); - - ret = abis_nm_sendmsg(bts, msg); - if (ret < 0) - return ret; - - nokia_abis_nm_queue_send_next(bts); - - /* next segment */ - len_remain -= len_to_send; - data += len_to_send; - seq++; - } - - return 0; -} - -/* TODO: put in a separate file ? */ - -static int abis_nm_send_config(struct gsm_bts *bts, uint8_t bts_type) -{ - struct gsm_bts_trx *trx; - uint8_t config[2048]; /* TODO: might be too small if lots of TRX are used */ - int len = 0; - int idx = 0; - int ret; - int hopping = 0; - int need_hopping = 0; - - memset(config, 0, sizeof(config)); - - llist_for_each_entry(trx, &bts->trx_list, list) { -#if 0 /* debugging */ - printf("TRX\n"); - printf(" arfcn: %d\n", trx->arfcn); - printf(" bsic: %d\n", trx->bts->bsic); - uint8_t ca[20]; - memset(ca, 0xFF, sizeof(ca)); - ret = generate_cell_chan_list(ca, trx->bts); - printf(" ca (%d): %s\n", ret, osmo_hexdump(ca, sizeof(ca))); - int i; - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - - printf(" pchan %d: %d\n", i, ts->pchan); - } -#endif - ret = make_fu_config(trx, idx + 1, config + len, &hopping); - need_hopping |= hopping; - len += ret; - - idx++; - } - - ret = make_bts_config(bts_type, idx, config + len, need_hopping); - len += ret; - -#if 0 /* debugging */ - dump_elements(config, len); -#endif - - return abis_nm_send_multi_segments(bts, NOKIA_MSG_CONF_DATA, 1, config, - len); -} - -#define GET_NEXT_BYTE if(idx >= len) return 0; \ - ub = data[idx++]; - -static int find_element(uint8_t * data, int len, uint16_t id, uint8_t * value, - int max_value) -{ - uint8_t ub; - int idx = 0; - int found = 0; - int constructed __attribute__((unused)); - uint16_t id_value; - - for (;;) { - - GET_NEXT_BYTE; - - /* encoding bit, construced means that other elements are contained */ - constructed = ((ub & 0x20) ? 1 : 0); - - if ((ub & 0x1F) == 0x1F) { - /* fixed pattern, ID follows */ - GET_NEXT_BYTE; /* ID */ - id_value = ub & 0x7F; - if (ub & 0x80) { - /* extension bit */ - GET_NEXT_BYTE; /* ID low part */ - id_value = (id_value << 7) | (ub & 0x7F); - } - if (id_value == id) - found = 1; - } else { - id_value = (ub & 0x3F); - if (id_value == id) - found = 1; - } - - GET_NEXT_BYTE; /* length */ - - if (found) { - /* get data */ - uint8_t n = ub; - uint8_t i; - for (i = 0; i < n; i++) { - GET_NEXT_BYTE; - if (max_value <= 0) - return -1; /* buffer too small */ - *value = ub; - value++; - max_value--; - } - return n; /* length */ - } else { - /* skip data */ - uint8_t n = ub; - uint8_t i; - for (i = 0; i < n; i++) { - GET_NEXT_BYTE; - } - } - } - return 0; /* not found */ -} - -static int dump_elements(uint8_t * data, int len) -{ - uint8_t ub; - int idx = 0; - int constructed; - uint16_t id_value; - static char indent[100] = ""; /* TODO: move static to BTS context */ - - for (;;) { - - GET_NEXT_BYTE; - - /* encoding bit, construced means that other elements are contained */ - constructed = ((ub & 0x20) ? 1 : 0); - - if ((ub & 0x1F) == 0x1F) { - /* fixed pattern, ID follows */ - GET_NEXT_BYTE; /* ID */ - id_value = ub & 0x7F; - if (ub & 0x80) { - /* extension bit */ - GET_NEXT_BYTE; /* ID low part */ - id_value = (id_value << 7) | (ub & 0x7F); - } - - } else { - id_value = (ub & 0x3F); - } - - GET_NEXT_BYTE; /* length */ - - printf("%s--ID = 0x%02X (%s) %s\n", indent, id_value, - get_element_name_string(id_value), - constructed ? "** constructed **" : ""); - printf("%s length = %d\n", indent, ub); - printf("%s %s\n", indent, osmo_hexdump(data + idx, ub)); - - if (constructed) { - int indent_len = strlen(indent); - strcat(indent, " "); - - dump_elements(data + idx, ub); - - indent[indent_len] = 0; - } - /* skip data */ - uint8_t n = ub; - uint8_t i; - for (i = 0; i < n; i++) { - GET_NEXT_BYTE; - } - } - return 0; -} - -/* TODO: put in a separate file ? */ - -/* taken from abis_nm.c */ - -static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts) -{ - int wait = 0; - struct msgb *msg; - /* the queue is empty */ - while (!llist_empty(&bts->abis_queue)) { - msg = msgb_dequeue(&bts->abis_queue); - wait = OBSC_NM_W_ACK_CB(msg); - abis_sendmsg(msg); - - if (wait) - break; - } - - bts->abis_nm_pend = wait; -} - -/* TODO: put in a separate file ? */ - -/* timer for restarting OML after BTS reset */ - -static void reset_timer_cb(void *_bts) -{ - struct gsm_bts *bts = _bts; - struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; - struct e1inp_line *line; - - bts->nokia.wait_reset = 0; - - /* OML link */ - line = e1inp_line_find(e1_link->e1_nr); - if (!line) { - LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to " - "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr); - return; - } - - start_sabm_in_line(line, 0, -1); /* stop all first */ - start_sabm_in_line(line, 1, SAPI_OML); /* start only OML */ -} - -/* TODO: put in a separate file ? */ - -/* - This is how the configuration is done: - - start OML link - - reset BTS - - receive ACK, wait some time and restart OML link - - receive OMU STARTED message, send START DOWNLOAD REQ - - receive CNF REQ message, send CONF DATA - - receive ACK, start RSL link(s) - ACK some other messages received from the BTS. - - Probably its also possible to configure the BTS without a reset, this - has not been tested yet. -*/ - -static int abis_nm_rcvmsg_fom(struct msgb *mb) -{ - struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)mb->dst; - struct gsm_bts *bts = sign_link->trx->bts; - struct abis_om_hdr *oh = msgb_l2(mb); - struct abis_om_nokia_hdr *noh = msgb_l3(mb); - uint8_t mt = noh->msg_type; - int ret = 0; - uint16_t ref = ntohs(noh->reference); - uint8_t info[256]; - uint8_t ack = 0xFF; - uint8_t severity = 0xFF; - int str_len; - int len_data; - - if (bts->nokia.wait_reset) { - LOGP(DNM, LOGL_INFO, - "Ignore message while waiting for reset\n"); - return ret; - } - - if (oh->length < sizeof(struct abis_om_nokia_hdr)) { - LOGP(DNM, LOGL_ERROR, "Message too short\n"); - return -EINVAL; - } - - len_data = oh->length - sizeof(struct abis_om_nokia_hdr); - LOGP(DNM, LOGL_INFO, "(0x%02X) %s\n", mt, get_msg_type_name_string(mt)); -#if 0 /* debugging */ - dump_elements(noh->data, len_data); -#endif - - switch (mt) { - case NOKIA_MSG_OMU_STARTED: - if (find_element(noh->data, len_data, - NOKIA_EI_BTS_TYPE, &bts->nokia.bts_type, - sizeof(uint8_t)) == sizeof(uint8_t)) - LOGP(DNM, LOGL_INFO, "BTS type = %d (%s)\n", - bts->nokia.bts_type, - get_bts_type_string(bts->nokia.bts_type)); - else - LOGP(DNM, LOGL_ERROR, "BTS type not found\n"); - /* send START_DOWNLOAD_REQ */ - abis_nm_download_req(bts, ref); - break; - case NOKIA_MSG_MF_REQ: - break; - case NOKIA_MSG_CONF_REQ: - /* send ACK */ - abis_nm_ack(bts, ref); - nokia_abis_nm_queue_send_next(bts); - /* send CONF_DATA */ - abis_nm_send_config(bts, bts->nokia.bts_type); - bts->nokia.configured = 1; - break; - case NOKIA_MSG_ACK: - if (find_element - (noh->data, len_data, NOKIA_EI_ACK, &ack, - sizeof(uint8_t)) == sizeof(uint8_t)) { - LOGP(DNM, LOGL_INFO, "ACK = %d\n", ack); - if (ack != 1) { - LOGP(DNM, LOGL_ERROR, "No ACK received (%d)\n", - ack); - /* TODO: properly handle failures (NACK) */ - } - } else - LOGP(DNM, LOGL_ERROR, "ACK not found\n"); - - /* TODO: the assumption for the following is that no NACK was received */ - - /* ACK for reset message ? */ - if (!bts->nokia.did_reset) { - bts->nokia.did_reset = 1; - - /* - TODO: For the InSite processing the received data is - blocked in the driver during reset. - Otherwise the LAPD module might assert because the InSite - sends garbage on the E1 line during reset. - This is done by looking at "wait_reset" in the driver - (function handle_ts1_read()) and ignoring the received data. - It seems to be necessary for the MetroSite too. - */ - bts->nokia.wait_reset = 1; - - osmo_timer_setup(&bts->nokia.reset_timer, - reset_timer_cb, bts); - osmo_timer_schedule(&bts->nokia.reset_timer, bts->nokia.bts_reset_timer_cnf, 0); - - struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; - struct e1inp_line *line; - /* OML link */ - line = e1inp_line_find(e1_link->e1_nr); - if (!line) { - LOGP(DLINP, LOGL_ERROR, - "BTS %u OML link referring to " - "non-existing E1 line %u\n", bts->nr, - e1_link->e1_nr); - return -ENOMEM; - } - - start_sabm_in_line(line, 0, -1); /* stop all first */ - } - - /* ACK for CONF DATA message ? */ - if (bts->nokia.configured != 0) { - /* start TRX (RSL link) */ - - struct gsm_e1_subslot *e1_link = - &sign_link->trx->rsl_e1_link; - struct e1inp_line *line; - - bts->nokia.configured = 0; - - /* RSL Link */ - line = e1inp_line_find(e1_link->e1_nr); - if (!line) { - LOGP(DLINP, LOGL_ERROR, - "TRX (%u/%u) RSL link referring " - "to non-existing E1 line %u\n", - sign_link->trx->bts->nr, sign_link->trx->nr, - e1_link->e1_nr); - return -ENOMEM; - } - /* start TRX */ - start_sabm_in_line(line, 1, SAPI_RSL); /* start only RSL */ - } - break; - case NOKIA_MSG_STATE_CHANGED: - /* send ACK */ - abis_nm_ack(bts, ref); - break; - case NOKIA_MSG_CONF_COMPLETE: - /* send ACK */ - abis_nm_ack(bts, ref); - break; - case NOKIA_MSG_BLOCK_CTRL_REQ: /* seems to be send when something goes wrong !? */ - /* send ACK (do we have to send an ACK ?) */ - abis_nm_ack(bts, ref); - break; - case NOKIA_MSG_ALARM: - find_element(noh->data, len_data, NOKIA_EI_SEVERITY, &severity, - sizeof(severity)); - /* TODO: there might be alarms with both elements set */ - str_len = - find_element(noh->data, len_data, NOKIA_EI_ADD_INFO, info, - sizeof(info)); - if (str_len > 0) { - info[str_len] = 0; - LOGP(DNM, LOGL_INFO, "ALARM Severity %s (%d) : %s\n", - get_severity_string(severity), severity, info); - } else { /* nothing found, try details */ - str_len = - find_element(noh->data, len_data, - NOKIA_EI_ALARM_DETAIL, info, - sizeof(info)); - if (str_len > 0) { - uint16_t code; - info[str_len] = 0; - code = (info[0] << 8) + info[1]; - LOGP(DNM, LOGL_INFO, - "ALARM Severity %s (%d), code 0x%X : %s\n", - get_severity_string(severity), severity, - code, info + 2); - } - } - /* send ACK */ - abis_nm_ack(bts, ref); - break; - } - - nokia_abis_nm_queue_send_next(bts); - - return ret; -} - -/* TODO: put in a separate file ? */ - -int abis_nokia_rcvmsg(struct msgb *msg) -{ - struct abis_om_hdr *oh = msgb_l2(msg); - int rc = 0; - - /* Various consistency checks */ - if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { - 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); - return -EINVAL; - } - msg->l3h = (unsigned char *)oh + sizeof(*oh); - - switch (oh->mdisc) { - case ABIS_OM_MDISC_FOM: - LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_FOM\n"); - rc = abis_nm_rcvmsg_fom(msg); - break; - case ABIS_OM_MDISC_MANUF: - LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_MANUF\n"); - break; - case ABIS_OM_MDISC_MMI: - case ABIS_OM_MDISC_TRAU: - LOGP(DNM, LOGL_ERROR, - "unimplemented ABIS OML message discriminator 0x%x\n", - oh->mdisc); - break; - default: - LOGP(DNM, LOGL_ERROR, - "unknown ABIS OML message discriminator 0x%x\n", - oh->mdisc); - return -EINVAL; - } - - msgb_free(msg); - return rc; -} - -static int bts_model_nokia_site_start(struct gsm_network *net); - -static void bts_model_nokia_site_e1line_bind_ops(struct e1inp_line *line) -{ - e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); -} - -static struct gsm_bts_model model_nokia_site = { - .type = GSM_BTS_TYPE_NOKIA_SITE, - .name = "nokia_site", - .start = bts_model_nokia_site_start, - .oml_rcvmsg = &abis_nokia_rcvmsg, - .e1line_bind_ops = &bts_model_nokia_site_e1line_bind_ops, -}; - -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); - - my_net = net; - - return 0; -} - -int bts_model_nokia_site_init(void) -{ - return gsm_bts_model_register(&model_nokia_site); -} diff --git a/src/libbsc/bts_siemens_bs11.c b/src/libbsc/bts_siemens_bs11.c deleted file mode 100644 index 2d2351702..000000000 --- a/src/libbsc/bts_siemens_bs11.c +++ /dev/null @@ -1,604 +0,0 @@ -/* Siemens BS-11 specific code */ - -/* (C) 2009-2010 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - - -#include - -#include -#include -#include -#include -#include - -static int bts_model_bs11_start(struct gsm_network *net); - -static void bts_model_bs11_e1line_bind_ops(struct e1inp_line *line) -{ - e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); -} - -static struct gsm_bts_model model_bs11 = { - .type = GSM_BTS_TYPE_BS11, - .name = "bs11", - .start = bts_model_bs11_start, - .oml_rcvmsg = &abis_nm_rcvmsg, - .e1line_bind_ops = bts_model_bs11_e1line_bind_ops, - .nm_att_tlvdef = { - .def = { - [NM_ATT_AVAIL_STATUS] = { TLV_TYPE_TLV }, - /* BS11 specifics */ - [NM_ATT_BS11_ESN_FW_CODE_NO] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_ESN_HW_CODE_NO] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_ESN_PCB_SERIAL] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_BOOT_SW_VERS] = { TLV_TYPE_TLV }, - [0xd5] = { TLV_TYPE_TLV }, - [0xa8] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_PASSWORD] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_TXPWR] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_RSSI_OFFS] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_LINE_CFG] = { TLV_TYPE_TV }, - [NM_ATT_BS11_L1_PROT_TYPE] = { TLV_TYPE_TV }, - [NM_ATT_BS11_BIT_ERR_THESH] = { TLV_TYPE_FIXED, 2 }, - [NM_ATT_BS11_DIVERSITY] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV }, - [NM_ATT_BS11_LMT_LOGIN_TIME] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV }, - [NM_ATT_BS11_LMT_USER_NAME] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_BTS_STATE] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_E1_STATE] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_PLL_MODE] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_PLL] = { TLV_TYPE_TLV }, - [NM_ATT_BS11_CCLK_ACCURACY] = { TLV_TYPE_TV }, - [NM_ATT_BS11_CCLK_TYPE] = { TLV_TYPE_TV }, - [0x95] = { TLV_TYPE_FIXED, 2 }, - }, - }, -}; - -/* The following definitions are for OM and NM packets that we cannot yet - * generate by code but we just pass on */ - -// BTS Site Manager, SET ATTRIBUTES - -/* - Object Class: BTS Site Manager - Instance 1: FF - Instance 2: FF - Instance 3: FF -SET ATTRIBUTES - sAbisExternalTime: 2007/09/08 14:36:11 - omLAPDRelTimer: 30sec - shortLAPDIntTimer: 5sec - emergencyTimer1: 10 minutes - emergencyTimer2: 0 minutes -*/ - -unsigned char msg_1[] = -{ - NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, 0xFF, 0xFF, 0xFF, - NM_ATT_BS11_ABIS_EXT_TIME, 0x07, - 0xD7, 0x09, 0x08, 0x0E, 0x24, 0x0B, 0xCE, - 0x02, - 0x00, 0x1E, - NM_ATT_BS11_SH_LAPD_INT_TIMER, - 0x01, 0x05, - 0x42, 0x02, 0x00, 0x0A, - 0x44, 0x02, 0x00, 0x00 -}; - -// BTS, SET BTS ATTRIBUTES - -/* - Object Class: BTS - BTS relat. Number: 0 - Instance 2: FF - Instance 3: FF -SET BTS ATTRIBUTES - bsIdentityCode / BSIC: - PLMN_colour_code: 7h - BS_colour_code: 7h - BTS Air Timer T3105: 4 ,unit 10 ms - btsIsHopping: FALSE - periodCCCHLoadIndication: 1sec - thresholdCCCHLoadIndication: 0% - cellAllocationNumber: 00h = GSM 900 - enableInterferenceClass: 00h = Disabled - fACCHQual: 6 (FACCH stealing flags minus 1) - intaveParameter: 31 SACCH multiframes - interferenceLevelBoundaries: - Interference Boundary 1: 0Ah - Interference Boundary 2: 0Fh - Interference Boundary 3: 14h - Interference Boundary 4: 19h - Interference Boundary 5: 1Eh - mSTxPwrMax: 11 - GSM range: 2=39dBm, 15=13dBm, stepsize 2 dBm - DCS1800 range: 0=30dBm, 15=0dBm, stepsize 2 dBm - PCS1900 range: 0=30dBm, 15=0dBm, stepsize 2 dBm - 30=33dBm, 31=32dBm - ny1: - Maximum number of repetitions for PHYSICAL INFORMATION message (GSM 04.08): 20 - powerOutputThresholds: - Out Power Fault Threshold: -10 dB - Red Out Power Threshold: - 6 dB - Excessive Out Power Threshold: 5 dB - rACHBusyThreshold: -127 dBm - rACHLoadAveragingSlots: 250 ,number of RACH burst periods - rfResourceIndicationPeriod: 125 SACCH multiframes - T200: - SDCCH: 044 in 5 ms - FACCH/Full rate: 031 in 5 ms - FACCH/Half rate: 041 in 5 ms - SACCH with TCH SAPI0: 090 in 10 ms - SACCH with SDCCH: 090 in 10 ms - SDCCH with SAPI3: 090 in 5 ms - SACCH with TCH SAPI3: 135 in 10 ms - tSync: 9000 units of 10 msec - tTrau: 9000 units of 10 msec - enableUmLoopTest: 00h = disabled - enableExcessiveDistance: 00h = Disabled - excessiveDistance: 64km - hoppingMode: 00h = baseband hopping - cellType: 00h = Standard Cell - BCCH ARFCN / bCCHFrequency: 1 -*/ - -static unsigned char bs11_attr_bts[] = -{ - NM_ATT_BSIC, HARDCODED_BSIC, - NM_ATT_BTS_AIR_TIMER, 0x04, - NM_ATT_BS11_BTSLS_HOPPING, 0x00, - NM_ATT_CCCH_L_I_P, 0x01, - NM_ATT_CCCH_L_T, 0x00, - NM_ATT_BS11_CELL_ALLOC_NR, NM_BS11_CANR_GSM, - NM_ATT_BS11_ENA_INTERF_CLASS, 0x01, - NM_ATT_BS11_FACCH_QUAL, 0x06, - /* interference avg. period in numbers of SACCH multifr */ - NM_ATT_INTAVE_PARAM, 0x1F, - NM_ATT_INTERF_BOUND, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x7B, - NM_ATT_CCCH_L_T, 0x23, - NM_ATT_GSM_TIME, 0x28, 0x00, - NM_ATT_ADM_STATE, 0x03, - NM_ATT_RACH_B_THRESH, 0x7F, - NM_ATT_LDAVG_SLOTS, 0x00, 0xFA, - NM_ATT_BS11_RF_RES_IND_PER, 0x7D, - NM_ATT_T200, 0x2C, 0x1F, 0x29, 0x5A, 0x5A, 0x5A, 0x87, - NM_ATT_BS11_TSYNC, 0x23, 0x28, - NM_ATT_BS11_TTRAU, 0x23, 0x28, - NM_ATT_TEST_DUR, 0x01, 0x00, - NM_ATT_OUTST_ALARM, 0x01, 0x00, - NM_ATT_BS11_EXCESSIVE_DISTANCE, 0x01, 0x40, - NM_ATT_BS11_HOPPING_MODE, 0x01, 0x00, - NM_ATT_BS11_PLL, 0x01, 0x00, - NM_ATT_BCCH_ARFCN, 0x00, HARDCODED_ARFCN/*0x01*/, -}; - -// Handover Recognition, SET ATTRIBUTES - -/* -Illegal Contents GSM Formatted O&M Msg - Object Class: Handover Recognition - BTS relat. Number: 0 - Instance 2: FF - Instance 3: FF -SET ATTRIBUTES - enableDelayPowerBudgetHO: 00h = Disabled - enableDistanceHO: 00h = Disabled - enableInternalInterCellHandover: 00h = Disabled - enableInternalIntraCellHandover: 00h = Disabled - enablePowerBudgetHO: 00h = Disabled - enableRXLEVHO: 00h = Disabled - enableRXQUALHO: 00h = Disabled - hoAveragingDistance: 8 SACCH multiframes - hoAveragingLev: - A_LEV_HO: 8 SACCH multiframes - W_LEV_HO: 1 SACCH multiframes - hoAveragingPowerBudget: 16 SACCH multiframes - hoAveragingQual: - A_QUAL_HO: 8 SACCH multiframes - W_QUAL_HO: 2 SACCH multiframes - hoLowerThresholdLevDL: (10 - 110) dBm - hoLowerThresholdLevUL: (5 - 110) dBm - hoLowerThresholdQualDL: 06h = 6.4% < BER < 12.8% - hoLowerThresholdQualUL: 06h = 6.4% < BER < 12.8% - hoThresholdLevDLintra : (20 - 110) dBm - hoThresholdLevULintra: (20 - 110) dBm - hoThresholdMsRangeMax: 20 km - nCell: 06h - timerHORequest: 3 ,unit 2 SACCH multiframes -*/ - -unsigned char msg_3[] = -{ - NM_MT_BS11_SET_ATTR, NM_OC_BS11_HANDOVER, 0x00, 0xFF, 0xFF, - 0xD0, 0x00, /* enableDelayPowerBudgetHO */ - 0x64, 0x00, /* enableDistanceHO */ - 0x67, 0x00, /* enableInternalInterCellHandover */ - 0x68, 0x00, /* enableInternalInterCellHandover */ - 0x6A, 0x00, /* enablePowerBudgetHO */ - 0x6C, 0x00, /* enableRXLEVHO */ - 0x6D, 0x00, /* enableRXQUALHO */ - 0x6F, 0x08, /* hoAveragingDistance */ - 0x70, 0x08, 0x01, /* hoAveragingLev */ - 0x71, 0x10, 0x10, 0x10, - 0x72, 0x08, 0x02, /* hoAveragingQual */ - 0x73, 0x0A, /* hoLowerThresholdLevDL */ - 0x74, 0x05, /* hoLowerThresholdLevUL */ - 0x75, 0x06, /* hoLowerThresholdQualDL */ - 0x76, 0x06, /* hoLowerThresholdQualUL */ - 0x78, 0x14, /* hoThresholdLevDLintra */ - 0x79, 0x14, /* hoThresholdLevULintra */ - 0x7A, 0x14, /* hoThresholdMsRangeMax */ - 0x7D, 0x06, /* nCell */ - NM_ATT_BS11_TIMER_HO_REQUEST, 0x03, - 0x20, 0x01, 0x00, - 0x45, 0x01, 0x00, - 0x48, 0x01, 0x00, - 0x5A, 0x01, 0x00, - 0x5B, 0x01, 0x05, - 0x5E, 0x01, 0x1A, - 0x5F, 0x01, 0x20, - 0x9D, 0x01, 0x00, - 0x47, 0x01, 0x00, - 0x5C, 0x01, 0x64, - 0x5D, 0x01, 0x1E, - 0x97, 0x01, 0x20, - 0xF7, 0x01, 0x3C, -}; - -// Power Control, SET ATTRIBUTES - -/* - Object Class: Power Control - BTS relat. Number: 0 - Instance 2: FF - Instance 3: FF -SET ATTRIBUTES - enableMsPowerControl: 00h = Disabled - enablePowerControlRLFW: 00h = Disabled - pcAveragingLev: - A_LEV_PC: 4 SACCH multiframes - W_LEV_PC: 1 SACCH multiframes - pcAveragingQual: - A_QUAL_PC: 4 SACCH multiframes - W_QUAL_PC: 2 SACCH multiframes - pcLowerThresholdLevDL: 0Fh - pcLowerThresholdLevUL: 0Ah - pcLowerThresholdQualDL: 05h = 3.2% < BER < 6.4% - pcLowerThresholdQualUL: 05h = 3.2% < BER < 6.4% - pcRLFThreshold: 0Ch - pcUpperThresholdLevDL: 14h - pcUpperThresholdLevUL: 0Fh - pcUpperThresholdQualDL: 04h = 1.6% < BER < 3.2% - pcUpperThresholdQualUL: 04h = 1.6% < BER < 3.2% - powerConfirm: 2 ,unit 2 SACCH multiframes - powerControlInterval: 2 ,unit 2 SACCH multiframes - powerIncrStepSize: 02h = 4 dB - powerRedStepSize: 01h = 2 dB - radioLinkTimeoutBs: 64 SACCH multiframes - enableBSPowerControl: 00h = disabled -*/ - -unsigned char msg_4[] = -{ - NM_MT_BS11_SET_ATTR, NM_OC_BS11_PWR_CTRL, 0x00, 0xFF, 0xFF, - NM_ATT_BS11_ENA_MS_PWR_CTRL, 0x00, - NM_ATT_BS11_ENA_PWR_CTRL_RLFW, 0x00, - 0x7E, 0x04, 0x01, /* pcAveragingLev */ - 0x7F, 0x04, 0x02, /* pcAveragingQual */ - 0x80, 0x0F, /* pcLowerThresholdLevDL */ - 0x81, 0x0A, /* pcLowerThresholdLevUL */ - 0x82, 0x05, /* pcLowerThresholdQualDL */ - 0x83, 0x05, /* pcLowerThresholdQualUL */ - 0x84, 0x0C, /* pcRLFThreshold */ - 0x85, 0x14, /* pcUpperThresholdLevDL */ - 0x86, 0x0F, /* pcUpperThresholdLevUL */ - 0x87, 0x04, /* pcUpperThresholdQualDL */ - 0x88, 0x04, /* pcUpperThresholdQualUL */ - 0x89, 0x02, /* powerConfirm */ - 0x8A, 0x02, /* powerConfirmInterval */ - 0x8B, 0x02, /* powerIncrStepSize */ - 0x8C, 0x01, /* powerRedStepSize */ - 0x8D, 0x40, /* radioLinkTimeoutBs */ - 0x65, 0x01, 0x00 // set to 0x01 to enable BSPowerControl -}; - - -// Transceiver, SET TRX ATTRIBUTES (TRX 0) - -/* - Object Class: Transceiver - BTS relat. Number: 0 - Tranceiver number: 0 - Instance 3: FF -SET TRX ATTRIBUTES - aRFCNList (HEX): 0001 - txPwrMaxReduction: 00h = 30dB - radioMeasGran: 254 SACCH multiframes - radioMeasRep: 01h = enabled - memberOfEmergencyConfig: 01h = TRUE - trxArea: 00h = TRX doesn't belong to a concentric cell -*/ - -static unsigned char bs11_attr_radio[] = -{ - NM_ATT_ARFCN_LIST, 0x01, 0x00, HARDCODED_ARFCN /*0x01*/, - NM_ATT_RF_MAXPOWR_R, 0x00, - NM_ATT_BS11_RADIO_MEAS_GRAN, 0x01, 0x05, - NM_ATT_BS11_RADIO_MEAS_REP, 0x01, 0x01, - NM_ATT_BS11_EMRG_CFG_MEMBER, 0x01, 0x01, - NM_ATT_BS11_TRX_AREA, 0x01, 0x00, -}; - -/* - * Patch the various SYSTEM INFORMATION tables to update - * the LAI - */ -static void patch_nm_tables(struct gsm_bts *bts) -{ - uint8_t arfcn_low = bts->c0->arfcn & 0xff; - uint8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f; - - /* T3105 attribute in units of 10ms */ - bs11_attr_bts[2] = bts->network->T3105 / 10; - - /* patch ARFCN into BTS Attributes */ - bs11_attr_bts[69] &= 0xf0; - bs11_attr_bts[69] |= arfcn_high; - bs11_attr_bts[70] = arfcn_low; - - /* patch ARFCN into TRX Attributes */ - bs11_attr_radio[2] &= 0xf0; - bs11_attr_radio[2] |= arfcn_high; - bs11_attr_radio[3] = arfcn_low; - - /* patch the RACH attributes */ - if (bts->rach_b_thresh != -1) - bs11_attr_bts[33] = bts->rach_b_thresh & 0xff; - - if (bts->rach_ldavg_slots != -1) { - uint8_t avg_high = bts->rach_ldavg_slots & 0xff; - uint8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f; - - bs11_attr_bts[35] = avg_high; - bs11_attr_bts[36] = avg_low; - } - - /* patch BSIC */ - bs11_attr_bts[1] = bts->bsic; - - /* patch the power reduction */ - bs11_attr_radio[5] = bts->c0->max_power_red / 2; -} - - -static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts) -{ - enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan); - struct gsm_e1_subslot *e1l = &ts->e1_link; - - abis_nm_set_channel_attr(ts, ccomb); - - if (is_ipaccess_bts(ts->trx->bts)) - return; - - if (ts_is_tch(ts)) - abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts, - e1l->e1_ts_ss); -} - -static void nm_reconfig_trx(struct gsm_bts_trx *trx) -{ - struct gsm_e1_subslot *e1l = &trx->rsl_e1_link; - int i; - - patch_nm_tables(trx->bts); - - switch (trx->bts->type) { - case GSM_BTS_TYPE_BS11: - /* FIXME: discover this by fetching an attribute */ -#if 0 - trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */ -#else - 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; - } - - for (i = 0; i < TRX_NR_TS; i++) - nm_reconfig_ts(&trx->ts[i]); -} - -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; - } - - llist_for_each_entry(trx, &bts->trx_list, list) - nm_reconfig_trx(trx); -} - - -static void bootstrap_om_bs11(struct gsm_bts *bts) -{ - LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); - - /* stop sending event reports */ - abis_nm_event_reports(bts, 0); - - /* begin DB transmission */ - abis_nm_bs11_db_transmission(bts, 1); - - /* end DB transmission */ - abis_nm_bs11_db_transmission(bts, 0); - - /* Reset BTS Site manager resource */ - abis_nm_bs11_reset_resource(bts); - - /* begin DB transmission */ - abis_nm_bs11_db_transmission(bts, 1); - - /* reconfigure BTS with all TRX and all TS */ - nm_reconfig_bts(bts); - - /* end DB transmission */ - abis_nm_bs11_db_transmission(bts, 0); - - /* Reset BTS Site manager resource */ - abis_nm_bs11_reset_resource(bts); - - /* restart sending event reports */ - abis_nm_event_reports(bts, 1); -} - -static int shutdown_om(struct gsm_bts *bts) -{ - /* stop sending event reports */ - abis_nm_event_reports(bts, 0); - - /* begin DB transmission */ - abis_nm_bs11_db_transmission(bts, 1); - - /* end DB transmission */ - abis_nm_bs11_db_transmission(bts, 0); - - /* Reset BTS Site manager resource */ - abis_nm_bs11_reset_resource(bts); - - gsm_bts_mark_all_ts_uninitialized(bts); - - return 0; -} - -/* Callback function to be called every time we receive a signal from INPUT */ -static int gbl_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct gsm_bts *bts; - - if (subsys != SS_L_GLOBAL) - return 0; - - switch (signal) { - case S_GLOBAL_BTS_CLOSE_OM: - bts = signal_data; - if (bts->type == GSM_BTS_TYPE_BS11) - shutdown_om(signal_data); - break; - } - - return 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) -{ - struct input_signal_data *isd = signal_data; - - if (subsys != SS_L_INPUT) - return 0; - - switch (signal) { - case S_L_INP_TEI_UP: - switch (isd->link_type) { - case E1INP_SIGN_OML: - if (isd->trx->bts->type == GSM_BTS_TYPE_BS11) - bootstrap_om_bs11(isd->trx->bts); - break; - } - } - - return 0; -} - -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); - - return 0; -} - -int bts_model_bs11_init(void) -{ - return gsm_bts_model_register(&model_bs11); -} diff --git a/src/libbsc/bts_sysmobts.c b/src/libbsc/bts_sysmobts.c deleted file mode 100644 index 91d1118c5..000000000 --- a/src/libbsc/bts_sysmobts.c +++ /dev/null @@ -1,60 +0,0 @@ -/* sysmocom sysmoBTS specific code */ - -/* (C) 2010-2012 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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; - - 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, sizeof(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); - - return gsm_bts_model_register(&model_sysmobts); -} diff --git a/src/libbsc/bts_unknown.c b/src/libbsc/bts_unknown.c deleted file mode 100644 index 5ecf875c1..000000000 --- a/src/libbsc/bts_unknown.c +++ /dev/null @@ -1,40 +0,0 @@ -/* Generic BTS - VTY code tries to allocate this BTS before type is known */ - -/* (C) 2010 by Daniel Willmann - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - - -#include -#include -#include - -static struct gsm_bts_model model_unknown = { - .type = GSM_BTS_TYPE_UNKNOWN, - .name = "unknown", - .oml_rcvmsg = &abis_nm_rcvmsg, - .nm_att_tlvdef = { - .def = { - }, - }, -}; - -int bts_model_unknown_init(void) -{ - return gsm_bts_model_register(&model_unknown); -} diff --git a/src/libbsc/chan_alloc.c b/src/libbsc/chan_alloc.c deleted file mode 100644 index 4eccff0df..000000000 --- a/src/libbsc/chan_alloc.c +++ /dev/null @@ -1,746 +0,0 @@ -/* GSM Channel allocation routines - * - * (C) 2008 by Harald Welte - * (C) 2008, 2009 by Holger Hans Peter Freyther - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -bool ts_is_usable(const struct gsm_bts_trx_ts *ts) -{ - if (!trx_is_usable(ts->trx)) { - LOGP(DRLL, LOGL_DEBUG, "%s not usable\n", gsm_trx_name(ts->trx)); - return false; - } - - /* If a TCH/F_PDCH TS is busy changing, it is already taken or not - * yet available. */ - if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { - if (ts->flags & TS_F_PDCH_PENDING_MASK) { - LOGP(DRLL, LOGL_DEBUG, "%s in switchover, not available\n", - gsm_ts_and_pchan_name(ts)); - return false; - } - } - - /* If a dynamic channel is busy changing, it is already taken or not - * yet available. */ - if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { - if (ts->dyn.pchan_is != ts->dyn.pchan_want) { - LOGP(DRLL, LOGL_DEBUG, "%s in switchover, not available\n", - gsm_ts_and_pchan_name(ts)); - return false; - } - } - - return true; -} - -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 (!nm_is_running(&trx->mo.nm_state) || - !nm_is_running(&trx->bb_transc.mo.nm_state)) - return false; - } - - return true; -} - -static int trx_count_free_ts(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan) -{ - struct gsm_bts_trx_ts *ts; - int j, ss; - int count = 0; - - if (!trx_is_usable(trx)) - return 0; - - for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { - enum gsm_phys_chan_config ts_pchan_is; - ts = &trx->ts[j]; - if (!ts_is_usable(ts)) - continue; - - ts_pchan_is = ts_pchan(ts); - - if (ts_pchan_is == GSM_PCHAN_PDCH) { - /* Dynamic timeslots in PDCH mode will become TCH if needed. */ - switch (ts->pchan) { - 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; - /* check if all sub-slots are allocated yet */ - for (ss = 0; ss < ts_subslots(ts); ss++) { - struct gsm_lchan *lc = &ts->lchan[ss]; - if (lc->type == GSM_LCHAN_NONE && - lc->state == LCHAN_S_NONE) - count++; - } - } - - return count; -} - -/* Count number of free TS of given pchan type */ -int bts_count_free_ts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) -{ - struct gsm_bts_trx *trx; - int count = 0; - - llist_for_each_entry(trx, &bts->trx_list, list) - count += trx_count_free_ts(trx, pchan); - - return count; -} - -static bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, - enum gsm_phys_chan_config as_pchan) -{ - switch (ts->pchan) { - case GSM_PCHAN_TCH_F_PDCH: - if (ts->flags & TS_F_PDCH_PENDING_MASK) { - /* currently being switched over. Not usable. */ - return false; - } - switch (as_pchan) { - case GSM_PCHAN_TCH_F: - case GSM_PCHAN_PDCH: - /* continue to check below. */ - break; - default: - return false; - } - break; - - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - if (ts->dyn.pchan_is != ts->dyn.pchan_want) { - /* currently being switched over. Not usable. */ - return false; - } - switch (as_pchan) { - case GSM_PCHAN_TCH_F: - case GSM_PCHAN_TCH_H: - case GSM_PCHAN_PDCH: - /* continue to check below. */ - break; - default: - return false; - } - break; - - default: - /* static timeslots never switch. */ - return ts->pchan == as_pchan; - } - - /* Dynamic timeslots -- Checks depending on the current actual pchan mode: */ - switch (ts_pchan(ts)) { - case GSM_PCHAN_NONE: - /* Not initialized, possibly because GPRS was disabled. We may switch. */ - return true; - - case GSM_PCHAN_PDCH: - /* This slot is in PDCH mode and available to switch pchan mode. But check for - * error states: */ - if (ts->lchan->state != LCHAN_S_NONE && ts->lchan->state != LCHAN_S_ACTIVE) - return false; - return true; - - case GSM_PCHAN_TCH_F: - case GSM_PCHAN_TCH_H: - /* No need to switch at all? */ - if (ts_pchan(ts) == as_pchan) - return true; - - /* If any lchan is in use, we can't change the pchan kind */ - { - int ss; - int subslots = ts_subslots(ts); - for (ss = 0; ss < subslots; ss++) { - struct gsm_lchan *lc = &ts->lchan[ss]; - if (lc->type != GSM_LCHAN_NONE || lc->state != LCHAN_S_NONE) - return false; - } - } - return true; - - default: - /* Not implemented. */ - return false; - } -} - -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 gsm_bts_trx_ts *ts; - int j, start, stop, dir, ss; - int check_subslots; - -#define LOGPLCHANALLOC(fmt, args...) \ - LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s as %s: " fmt, \ - gsm_pchan_name(pchan), gsm_pchan_name(as_pchan), ## args) - - if (!trx_is_usable(trx)) { - LOGPLCHANALLOC("%s trx not usable\n", gsm_trx_name(trx)); - return NULL; - } - - 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... */ - if (ts->pchan != 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)); - continue; - } - /* If we need to switch it, after above check we are also allowed to switch it, and we - * will always use the first lchan after the switch. Return that lchan and rely on the - * caller to perform the pchan switchover. */ - if (ts_pchan(ts) != as_pchan) { - LOGPLCHANALLOC("%s is a match, will switch to %s\n", gsm_ts_and_pchan_name(ts), - gsm_pchan_name(as_pchan)); - return ts->lchan; - } - - /* TS is in desired pchan mode. Go ahead and check for an available lchan. */ - check_subslots = ts_subslots(ts); - for (ss = 0; ss < check_subslots; ss++) { - struct gsm_lchan *lc = &ts->lchan[ss]; - if (lc->type == GSM_LCHAN_NONE && - lc->state == LCHAN_S_NONE) { - LOGPLCHANALLOC("%s ss=%d is available\n", gsm_ts_and_pchan_name(ts), - lc->nr); - return lc; - } - LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n", - gsm_ts_and_pchan_name(ts), lc->nr, gsm_lchant_name(lc->type), - gsm_lchans_name(lc->state)); - } - } - - return NULL; -#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) -{ - struct gsm_bts_trx *trx; - struct gsm_lchan *lc; - - 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; - } - } - - return NULL; -} - -static struct gsm_lchan * -_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) -{ - return _lc_dyn_find_bts(bts, pchan, pchan); -} - -/* Allocate a logical channel. - * - * Dynamic channel types: we always prefer a dedicated TS, and only pick + - * switch a dynamic TS if no pure TS of the requested PCHAN is available. - * - * TCH_F/PDCH: if we pick a PDCH ACT style dynamic TS as TCH/F channel, PDCH - * will be disabled in rsl_chan_activate_lchan(); there is no need to check - * whether PDCH mode is currently active, here. - */ -struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, - int allow_bigger) -{ - struct gsm_lchan *lchan = NULL; - enum gsm_phys_chan_config first, first_cbch, second, second_cbch; - - LOGP(DRLL, LOGL_DEBUG, "(bts=%d) lchan_alloc(%s)\n", bts->nr, gsm_lchant_name(type)); - - switch (type) { - case GSM_LCHAN_SDCCH: - if (bts->chan_alloc_reverse) { - first = GSM_PCHAN_SDCCH8_SACCH8C; - first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH; - second = GSM_PCHAN_CCCH_SDCCH4; - second_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH; - } else { - first = GSM_PCHAN_CCCH_SDCCH4; - first_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH; - second = GSM_PCHAN_SDCCH8_SACCH8C; - second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH; - } - - lchan = _lc_find_bts(bts, first); - if (lchan == NULL) - lchan = _lc_find_bts(bts, first_cbch); - if (lchan == NULL) - lchan = _lc_find_bts(bts, second); - if (lchan == NULL) - lchan = _lc_find_bts(bts, second_cbch); - - /* allow to assign bigger channels */ - if (allow_bigger) { - if (lchan == NULL) { - lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); - if (lchan) - type = GSM_LCHAN_TCH_H; - } - - if (lchan == NULL) { - lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); - if (lchan) - type = GSM_LCHAN_TCH_F; - } - - /* try dynamic TCH/F_PDCH */ - if (lchan == NULL) { - lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH, - GSM_PCHAN_TCH_F); - /* TCH/F_PDCH will be used as TCH/F */ - if (lchan) - type = GSM_LCHAN_TCH_F; - } - - /* try fully dynamic TCH/F_TCH/H_PDCH */ - if (lchan == NULL) { - lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_TCH_H_PDCH, - GSM_PCHAN_TCH_H); - if (lchan) - type = GSM_LCHAN_TCH_H; - } - /* - * No need to check fully dynamic channels for TCH/F: - * if no TCH/H was available, neither will be TCH/F. - */ - } - break; - case GSM_LCHAN_TCH_F: - lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); - /* If we don't have TCH/F available, fall-back to TCH/H */ - if (!lchan) { - lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); - if (lchan) - type = GSM_LCHAN_TCH_H; - } - /* If we don't have TCH/H either, 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; - } - - /* 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; - } - /* ...and as TCH/H. */ - 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; - } - break; - case GSM_LCHAN_TCH_H: - lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); - /* If we don't have TCH/H available, fall-back to TCH/F */ - if (!lchan) { - lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); - if (lchan) - type = GSM_LCHAN_TCH_F; - } - /* 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; - } - /* - * No need to check TCH/F_TCH/H_PDCH channels for TCH/F: - * if no TCH/H was available, neither will be TCH/F. - */ - /* If we don't have TCH/F either, try dynamic TCH/F_PDCH */ - if (!lchan) { - lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH, - GSM_PCHAN_TCH_F); - if (lchan) - type = GSM_LCHAN_TCH_F; - } - break; - default: - LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type); - } - - if (lchan) { - lchan->type = type; - - LOGP(DRLL, LOGL_INFO, "%s Allocating lchan=%u as %s\n", - gsm_ts_and_pchan_name(lchan->ts), - lchan->nr, gsm_lchant_name(lchan->type)); - - /* reset measurement report counter and index */ - lchan->meas_rep_count = 0; - lchan->meas_rep_idx = 0; - lchan->meas_rep_last_seen_nr = 255; - - /* clear sapis */ - memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis)); - - /* clear multi rate config */ - memset(&lchan->mr_ms_lv, 0, sizeof(lchan->mr_ms_lv)); - memset(&lchan->mr_bts_lv, 0, sizeof(lchan->mr_bts_lv)); - lchan->broken_reason = ""; - } else { - struct challoc_signal_data sig; - - LOGP(DRLL, LOGL_ERROR, "(bts=%d) Failed to allocate %s channel\n", - bts->nr, gsm_lchant_name(type)); - - sig.bts = bts; - sig.type = type; - osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig); - } - - return lchan; -} - -/* Free a logical channel */ -void lchan_free(struct gsm_lchan *lchan) -{ - struct challoc_signal_data sig; - int i; - - sig.type = lchan->type; - lchan->type = GSM_LCHAN_NONE; - - - if (lchan->conn - && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && lchan->ts->dyn.pchan_is != lchan->ts->dyn.pchan_want)) { - struct lchan_signal_data sig; - - /* We might kill an active channel... */ - sig.lchan = lchan; - sig.mr = NULL; - osmo_signal_dispatch(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig); - } - - /* stop the timer */ - osmo_timer_del(&lchan->T3101); - - /* clear cached measuement reports */ - lchan->meas_rep_idx = 0; - for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) { - lchan->meas_rep[i].flags = 0; - lchan->meas_rep[i].nr = 0; - } - for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) - lchan->neigh_meas[i].arfcn = 0; - - if (lchan->rqd_ref) { - talloc_free(lchan->rqd_ref); - lchan->rqd_ref = NULL; - lchan->rqd_ta = 0; - } - - sig.lchan = lchan; - sig.bts = lchan->ts->trx->bts; - osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_FREED, &sig); - - if (lchan->conn - && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && lchan->ts->dyn.pchan_is != lchan->ts->dyn.pchan_want)) { - LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n"); - lchan->conn = NULL; - } - - /* FIXME: ts_free() the timeslot, if we're the last logical - * channel using it */ -} - -/* - * There was an error with the TRX and we need to forget - * any state so that a lchan can be allocated again after - * the trx is fully usable. - * - * This should be called after lchan_free to force a channel - * be available for allocation again. This means that this - * method will stop the "delay after error"-timer and set the - * state to LCHAN_S_NONE. - */ -void lchan_reset(struct gsm_lchan *lchan) -{ - osmo_timer_del(&lchan->T3101); - osmo_timer_del(&lchan->T3109); - osmo_timer_del(&lchan->T3111); - osmo_timer_del(&lchan->error_timer); - - lchan->type = GSM_LCHAN_NONE; - rsl_lchan_set_state(lchan, LCHAN_S_NONE); -} - -/* Drive the release process of the lchan */ -static void _lchan_handle_release(struct gsm_lchan *lchan, - int sacch_deact, int mode) -{ - /* Release all SAPIs on the local end and continue */ - rsl_release_sapis_from(lchan, 1, RSL_REL_LOCAL_END); - - /* - * Shall we send a RR Release, start T3109 and wait for the - * release indication from the BTS or just take it down (e.g. - * on assignment requests) - */ - if (sacch_deact) { - gsm48_send_rr_release(lchan); - - /* Deactivate the SACCH on the BTS side */ - rsl_deact_sacch(lchan); - rsl_start_t3109(lchan); - } else if (lchan->sapis[0] == LCHAN_SAPI_UNUSED) { - rsl_direct_rf_release(lchan); - } else { - rsl_release_request(lchan, 0, mode); - } -} - -/* Consider releasing the channel now */ -int lchan_release(struct gsm_lchan *lchan, int sacch_deact, enum rsl_rel_mode mode) -{ - DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan)); - rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ); - - lchan->conn = NULL; - _lchan_handle_release(lchan, sacch_deact, mode); - return 1; -} - -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) { - int i; - - /* skip administratively deactivated tranxsceivers */ - if (!nm_is_running(&trx->mo.nm_state) || - !nm_is_running(&trx->bb_transc.mo.nm_state)) - continue; - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - struct load_counter *pl = &cl->pchan[ts->pchan]; - int j; - int subslots; - - /* skip administratively deactivated timeslots */ - if (!nm_is_running(&ts->mo.nm_state)) - continue; - - subslots = ts_subslots(ts); - for (j = 0; j < subslots; j++) { - struct gsm_lchan *lchan = &ts->lchan[j]; - - pl->total++; - - switch (lchan->state) { - case LCHAN_S_NONE: - break; - default: - pl->used++; - break; - } - } - } - } -} - -void network_chan_load(struct pchan_load *pl, struct gsm_network *net) -{ - struct gsm_bts *bts; - - memset(pl, 0, sizeof(*pl)); - - llist_for_each_entry(bts, &net->bts_list, list) - bts_chan_load(pl, bts); -} - -/* Update T3122 wait indicator based on samples of BTS channel load. */ -void -bts_update_t3122_chan_load(struct gsm_bts *bts) -{ - struct pchan_load pl; - uint64_t used = 0; - uint32_t total = 0; - uint64_t load; - uint64_t wait_ind; - static const uint8_t min_wait_ind = GSM_T3122_DEFAULT; - static const uint8_t max_wait_ind = 128; /* max wait ~2 minutes */ - int i; - - /* Ignore BTS that are not in operation, in order to not flood the log with "bogus channel load" - * messages */ - if (!trx_is_usable(bts->c0)) - return; - - /* Sum up current load across all channels. */ - memset(&pl, 0, sizeof(pl)); - bts_chan_load(&pl, bts); - for (i = 0; i < ARRAY_SIZE(pl.pchan); i++) { - struct load_counter *lc = &pl.pchan[i]; - - /* Ignore samples too large for fixed-point calculations (shouldn't happen). */ - if (lc->used > UINT16_MAX || lc->total > UINT16_MAX) { - LOGP(DRLL, LOGL_NOTICE, "(bts=%d) numbers in channel load sample " - "too large (used=%u / total=%u)\n", bts->nr, lc->used, lc->total); - continue; - } - - used += lc->used; - total += lc->total; - } - - /* Check for invalid samples (shouldn't happen). */ - if (total == 0 || used > total) { - LOGP(DRLL, LOGL_NOTICE, "(bts=%d) bogus channel load sample (used=%"PRIu64" / total=%"PRIu32")\n", - bts->nr, used, total); - bts->T3122 = 0; /* disable override of network-wide default value */ - bts->chan_load_samples_idx = 0; /* invalidate other samples collected so far */ - return; - } - - /* If we haven't got enough samples yet, store measurement for later use. */ - if (bts->chan_load_samples_idx < ARRAY_SIZE(bts->chan_load_samples)) { - struct load_counter *sample = &bts->chan_load_samples[bts->chan_load_samples_idx++]; - sample->total = (unsigned int)total; - sample->used = (unsigned int)used; - return; - } - - /* We have enough samples and will overwrite our current samples later. */ - bts->chan_load_samples_idx = 0; - - /* Add all previous samples to the current sample. */ - for (i = 0; i < ARRAY_SIZE(bts->chan_load_samples); i++) { - struct load_counter *sample = &bts->chan_load_samples[i]; - total += sample->total; - used += sample->used; - } - - used <<= 8; /* convert to fixed-point */ - - /* Log channel load average. */ - load = ((used / total) * 100); - LOGP(DRLL, LOGL_DEBUG, "(bts=%d) channel load average is %"PRIu64".%.2"PRIu64"%%\n", - bts->nr, (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); - - /* Calculate new T3122 wait indicator. */ - wait_ind = ((used / total) * max_wait_ind); - wait_ind >>= 8; /* convert from fixed-point to integer */ - if (wait_ind < min_wait_ind) - wait_ind = min_wait_ind; - else if (wait_ind > max_wait_ind) - wait_ind = max_wait_ind; - - LOGP(DRLL, LOGL_DEBUG, "(bts=%d) T3122 wait indicator set to %"PRIu64" seconds\n", bts->nr, wait_ind); - bts->T3122 = (uint8_t)wait_ind; - osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_T3122], wait_ind); -} diff --git a/src/libbsc/e1_config.c b/src/libbsc/e1_config.c deleted file mode 100644 index e7398ed9c..000000000 --- a/src/libbsc/e1_config.c +++ /dev/null @@ -1,299 +0,0 @@ -/* OpenBSC E1 Input code */ - -/* (C) 2008-2010 by Harald Welte - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#define SAPI_L2ML 0 -#define SAPI_OML 62 -#define SAPI_RSL 0 /* 63 ? */ - -/* The e1_reconfig_*() functions below take the configuration present in the - * bts/trx/ts data structures and ensure the E1 configuration reflects the - * timeslot/subslot/TEI configuration */ - -int e1_reconfig_ts(struct gsm_bts_trx_ts *ts) -{ - struct gsm_e1_subslot *e1_link = &ts->e1_link; - struct e1inp_line *line; - - DEBUGP(DLMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr); - - if (!e1_link->e1_ts) { - LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) without E1 timeslot?\n", - ts->nr, ts->trx->nr, ts->trx->bts->nr); - return 0; - } - - line = e1inp_line_find(e1_link->e1_nr); - if (!line) { - LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) referring to " - "non-existing E1 line %u\n", ts->nr, ts->trx->nr, - ts->trx->bts->nr, e1_link->e1_nr); - return -ENOMEM; - } - - return 0; -} - -int e1_reconfig_trx(struct gsm_bts_trx *trx) -{ - struct gsm_e1_subslot *e1_link = &trx->rsl_e1_link; - struct e1inp_ts *sign_ts; - struct e1inp_line *line; - struct e1inp_sign_link *rsl_link; - int i; - - if (!e1_link->e1_ts) { - LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link without " - "timeslot?\n", trx->bts->nr, trx->nr); - return -EINVAL; - } - - /* RSL Link */ - line = e1inp_line_find(e1_link->e1_nr); - if (!line) { - LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link referring " - "to non-existing E1 line %u\n", trx->bts->nr, - trx->nr, e1_link->e1_nr); - return -ENOMEM; - } - sign_ts = &line->ts[e1_link->e1_ts-1]; - e1inp_ts_config_sign(sign_ts, line); - /* Ericsson RBS have a per-TRX OML link in parallel to RSL */ - 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); - if (!oml_link) { - LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) OML link creation " - "failed\n", trx->bts->nr, trx->nr); - return -ENOMEM; - } - if (trx->oml_link) - e1inp_sign_link_destroy(trx->oml_link); - trx->oml_link = oml_link; - } - rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, - trx, trx->rsl_tei, SAPI_RSL); - if (!rsl_link) { - LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link creation " - "failed\n", trx->bts->nr, trx->nr); - return -ENOMEM; - } - if (trx->rsl_link) - e1inp_sign_link_destroy(trx->rsl_link); - trx->rsl_link = rsl_link; - - for (i = 0; i < TRX_NR_TS; i++) - e1_reconfig_ts(&trx->ts[i]); - - return 0; -} - -/* this is the generic callback for all ISDN-based BTS. */ -static int bts_isdn_sign_link(struct msgb *msg) -{ - int ret = -EINVAL; - struct e1inp_sign_link *link = msg->dst; - struct gsm_bts *bts; - - switch (link->type) { - case E1INP_SIGN_OML: - bts = link->trx->bts; - ret = bts->model->oml_rcvmsg(msg); - break; - case E1INP_SIGN_RSL: - if (link->trx->mo.nm_state.administrative == NM_STATE_LOCKED) { - LOGP(DLMI, LOGL_ERROR, "(bts=%d/trx=%d) discarding RSL message received " - "in locked administrative state\n", link->trx->bts->nr, link->trx->nr); - msgb_free(msg); - break; - } - ret = abis_rsl_rcvmsg(msg); - break; - default: - LOGP(DLMI, LOGL_ERROR, "unknown link type %u\n", link->type); - msgb_free(msg); - break; - } - return ret; -} - -struct e1inp_line_ops bts_isdn_e1inp_line_ops = { - .sign_link = bts_isdn_sign_link, -}; - -int e1_reconfig_bts(struct gsm_bts *bts) -{ - struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; - struct e1inp_ts *sign_ts; - struct e1inp_line *line; - struct e1inp_sign_link *oml_link; - struct gsm_bts_trx *trx; - struct timespec tp; - int rc; - - DEBUGP(DLMI, "e1_reconfig_bts(%u)\n", bts->nr); - - line = e1inp_line_find(e1_link->e1_nr); - if (!line) { - LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to " - "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr); - return -ENOMEM; - } - - if (!bts->model->e1line_bind_ops) { - LOGP(DLINP, LOGL_ERROR, "no callback to bind E1 line operations\n"); - return -EINVAL; - } - if (!line->ops) - bts->model->e1line_bind_ops(line); - - /* skip signal link initialization, this is done later for these BTS. */ - if (bts->type == GSM_BTS_TYPE_NANOBTS || - bts->type == GSM_BTS_TYPE_OSMOBTS) - return e1inp_line_update(line); - - /* OML link */ - if (!e1_link->e1_ts) { - LOGP(DLINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n", - bts->nr); - return -EINVAL; - } - - sign_ts = &line->ts[e1_link->e1_ts-1]; - e1inp_ts_config_sign(sign_ts, line); - oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, - bts->c0, bts->oml_tei, SAPI_OML); - if (!oml_link) { - LOGP(DLINP, LOGL_ERROR, "BTS %u OML link creation failed\n", - bts->nr); - return -ENOMEM; - } - 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 */ - - llist_for_each_entry(trx, &bts->trx_list, list) - e1_reconfig_trx(trx); - - /* notify E1 input something has changed */ - return e1inp_line_update(line); -} - -#if 0 -/* do some compiled-in configuration for our BTS/E1 setup */ -int e1_config(struct gsm_bts *bts, int cardnr, int release_l2) -{ - struct e1inp_line *line; - struct e1inp_ts *sign_ts; - struct e1inp_sign_link *oml_link, *rsl_link; - struct gsm_bts_trx *trx = bts->c0; - int base_ts; - - switch (bts->nr) { - case 0: - /* First BTS uses E1 TS 01,02,03,04,05 */ - base_ts = HARDCODED_BTS0_TS - 1; - break; - case 1: - /* Second BTS uses E1 TS 06,07,08,09,10 */ - base_ts = HARDCODED_BTS1_TS - 1; - break; - case 2: - /* Third BTS uses E1 TS 11,12,13,14,15 */ - base_ts = HARDCODED_BTS2_TS - 1; - default: - return -EINVAL; - } - - line = talloc_zero(tall_bsc_ctx, struct e1inp_line); - if (!line) - return -ENOMEM; - - /* create E1 timeslots for signalling and TRAU frames */ - e1inp_ts_config(&line->ts[base_ts+1-1], line, E1INP_TS_TYPE_SIGN); - e1inp_ts_config(&line->ts[base_ts+2-1], line, E1INP_TS_TYPE_TRAU); - e1inp_ts_config(&line->ts[base_ts+3-1], line, E1INP_TS_TYPE_TRAU); - - /* create signalling links for TS1 */ - sign_ts = &line->ts[base_ts+1-1]; - oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, - trx, TEI_OML, SAPI_OML); - rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, - trx, TEI_RSL, SAPI_RSL); - - /* create back-links from bts/trx */ - bts->oml_link = oml_link; - trx->rsl_link = rsl_link; - - /* enable subchannel demuxer on TS2 */ - subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 1); - subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 2); - subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 3); - - /* enable subchannel demuxer on TS3 */ - subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 0); - subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 1); - subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 2); - subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 3); - - trx = gsm_bts_trx_num(bts, 1); - if (trx) { - /* create E1 timeslots for TRAU frames of TRX1 */ - e1inp_ts_config(&line->ts[base_ts+4-1], line, E1INP_TS_TYPE_TRAU); - e1inp_ts_config(&line->ts[base_ts+5-1], line, E1INP_TS_TYPE_TRAU); - - /* create RSL signalling link for TRX1 */ - sign_ts = &line->ts[base_ts+1-1]; - rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, - trx, TEI_RSL+1, SAPI_RSL); - /* create back-links from trx */ - trx->rsl_link = rsl_link; - - /* enable subchannel demuxer on TS2 */ - subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 0); - subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 1); - subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 2); - subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 3); - - /* enable subchannel demuxer on TS3 */ - subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 0); - subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 1); - subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 2); - subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 3); - } - - return mi_setup(cardnr, line, release_l2); -} -#endif diff --git a/src/libbsc/gsm_04_08_utils.c b/src/libbsc/gsm_04_08_utils.c deleted file mode 100644 index f30640fb1..000000000 --- a/src/libbsc/gsm_04_08_utils.c +++ /dev/null @@ -1,725 +0,0 @@ -/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface - * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 - * utility functions - */ - -/* (C) 2008-2009 by Harald Welte - * (C) 2008, 2009 by Holger Hans Peter Freyther - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -/* should ip.access BTS use direct RTP streams between each other (1), - * or should OpenBSC always act as RTP relay/proxy in between (0) ? */ -int ipacc_rtp_direct = 1; - -static int gsm48_sendmsg(struct msgb *msg) -{ - if (msg->lchan) - msg->dst = msg->lchan->ts->trx->rsl_link; - - msg->l3h = msg->data; - return rsl_data_request(msg, 0); -} - -/* Section 9.1.8 / Table 9.9 */ -struct chreq { - uint8_t val; - uint8_t mask; - enum chreq_type type; -}; - -/* If SYSTEM INFORMATION TYPE 4 NECI bit == 1 */ -static const struct chreq chreq_type_neci1[] = { - { 0xa0, 0xe0, CHREQ_T_EMERG_CALL }, - { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_F }, - { 0x68, 0xfc, CHREQ_T_CALL_REEST_TCH_H }, - { 0x6c, 0xfc, CHREQ_T_CALL_REEST_TCH_H_DBL }, - { 0xe0, 0xe0, CHREQ_T_TCH_F }, - { 0x40, 0xf0, CHREQ_T_VOICE_CALL_TCH_H }, - { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H }, - { 0x00, 0xf0, CHREQ_T_LOCATION_UPD }, - { 0x10, 0xf0, CHREQ_T_SDCCH }, - { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI1 }, - { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F }, - { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH }, - { 0x67, 0xff, CHREQ_T_LMU }, - { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH }, - { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH }, - { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH }, - { 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE }, - { 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE }, - { 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE }, - { 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE }, - { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE }, -}; - -/* If SYSTEM INFORMATION TYPE 4 NECI bit == 0 */ -static const struct chreq chreq_type_neci0[] = { - { 0xa0, 0xe0, CHREQ_T_EMERG_CALL }, - { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_H }, - { 0xe0, 0xe0, CHREQ_T_TCH_F }, - { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H }, - { 0x00, 0xe0, CHREQ_T_LOCATION_UPD }, - { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI0 }, - { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F }, - { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH }, - { 0x67, 0xff, CHREQ_T_LMU }, - { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH }, - { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH }, - { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH }, - { 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE }, - { 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE }, - { 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE }, - { 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE }, - { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE }, -}; - -static const enum gsm_chan_t ctype_by_chreq[] = { - [CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_F, - [CHREQ_T_CALL_REEST_TCH_F] = GSM_LCHAN_TCH_F, - [CHREQ_T_CALL_REEST_TCH_H] = GSM_LCHAN_TCH_H, - [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_LCHAN_TCH_H, - [CHREQ_T_SDCCH] = GSM_LCHAN_SDCCH, - [CHREQ_T_TCH_F] = GSM_LCHAN_TCH_F, - [CHREQ_T_VOICE_CALL_TCH_H] = GSM_LCHAN_TCH_H, - [CHREQ_T_DATA_CALL_TCH_H] = GSM_LCHAN_TCH_H, - [CHREQ_T_LOCATION_UPD] = GSM_LCHAN_SDCCH, - [CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_SDCCH, - [CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_SDCCH, - [CHREQ_T_PAG_R_TCH_F] = GSM_LCHAN_TCH_F, - [CHREQ_T_PAG_R_TCH_FH] = GSM_LCHAN_TCH_H, - [CHREQ_T_LMU] = GSM_LCHAN_SDCCH, - [CHREQ_T_RESERVED_SDCCH] = GSM_LCHAN_SDCCH, - [CHREQ_T_PDCH_ONE_PHASE] = GSM_LCHAN_PDTCH, - [CHREQ_T_PDCH_TWO_PHASE] = GSM_LCHAN_PDTCH, - [CHREQ_T_RESERVED_IGNORE] = GSM_LCHAN_UNKNOWN, -}; - -static const enum gsm_chreq_reason_t reason_by_chreq[] = { - [CHREQ_T_EMERG_CALL] = GSM_CHREQ_REASON_EMERG, - [CHREQ_T_CALL_REEST_TCH_F] = GSM_CHREQ_REASON_CALL, - [CHREQ_T_CALL_REEST_TCH_H] = GSM_CHREQ_REASON_CALL, - [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_CHREQ_REASON_CALL, - [CHREQ_T_SDCCH] = GSM_CHREQ_REASON_OTHER, - [CHREQ_T_TCH_F] = GSM_CHREQ_REASON_OTHER, - [CHREQ_T_VOICE_CALL_TCH_H] = GSM_CHREQ_REASON_CALL, - [CHREQ_T_DATA_CALL_TCH_H] = GSM_CHREQ_REASON_OTHER, - [CHREQ_T_LOCATION_UPD] = GSM_CHREQ_REASON_LOCATION_UPD, - [CHREQ_T_PAG_R_ANY_NECI1] = GSM_CHREQ_REASON_PAG, - [CHREQ_T_PAG_R_ANY_NECI0] = GSM_CHREQ_REASON_PAG, - [CHREQ_T_PAG_R_TCH_F] = GSM_CHREQ_REASON_PAG, - [CHREQ_T_PAG_R_TCH_FH] = GSM_CHREQ_REASON_PAG, - [CHREQ_T_LMU] = GSM_CHREQ_REASON_OTHER, - [CHREQ_T_PDCH_ONE_PHASE] = GSM_CHREQ_REASON_PDCH, - [CHREQ_T_PDCH_TWO_PHASE] = GSM_CHREQ_REASON_PDCH, - [CHREQ_T_RESERVED_SDCCH] = GSM_CHREQ_REASON_OTHER, - [CHREQ_T_RESERVED_IGNORE] = GSM_CHREQ_REASON_OTHER, -}; - -/* verify that the two tables match */ -osmo_static_assert(sizeof(ctype_by_chreq) == - sizeof(((struct gsm_network *) NULL)->ctype_by_chreq), assert_size); - -/* - * Update channel types for request based on policy. E.g. in the - * case of a TCH/H network/bsc use TCH/H for the emergency calls, - * for early assignment assign a SDCCH and some other options. - */ -void gsm_net_update_ctype(struct gsm_network *network) -{ - /* copy over the data */ - memcpy(network->ctype_by_chreq, ctype_by_chreq, sizeof(ctype_by_chreq)); - - /* - * Use TCH/H for emergency calls when this cell allows TCH/H. Maybe it - * is better to iterate over the BTS/TRX and check if no TCH/F is available - * and then set it to TCH/H. - */ - if (network->neci) - network->ctype_by_chreq[CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_H; - - if (network->pag_any_tch) { - if (network->neci) { - network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_H; - network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_H; - } else { - network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_F; - network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_F; - } - } -} - -enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, uint8_t ra) -{ - int i; - int length; - const struct chreq *chreq; - - if (network->neci) { - chreq = chreq_type_neci1; - length = ARRAY_SIZE(chreq_type_neci1); - } else { - chreq = chreq_type_neci0; - length = ARRAY_SIZE(chreq_type_neci0); - } - - - for (i = 0; i < length; i++) { - const struct chreq *chr = &chreq[i]; - if ((ra & chr->mask) == chr->val) - return network->ctype_by_chreq[chr->type]; - } - LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra); - return GSM_LCHAN_SDCCH; -} - -int get_reason_by_chreq(uint8_t ra, int neci) -{ - int i; - int length; - const struct chreq *chreq; - - if (neci) { - chreq = chreq_type_neci1; - length = ARRAY_SIZE(chreq_type_neci1); - } else { - chreq = chreq_type_neci0; - length = ARRAY_SIZE(chreq_type_neci0); - } - - for (i = 0; i < length; i++) { - const struct chreq *chr = &chreq[i]; - if ((ra & chr->mask) == chr->val) - return reason_by_chreq[chr->type]; - } - LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST REASON 0x%02x\n", ra); - return GSM_CHREQ_REASON_OTHER; -} - -static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg) -{ - 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); -} - -/* 7.1.7 and 9.1.7: RR CHANnel RELease */ -int gsm48_send_rr_release(struct gsm_lchan *lchan) -{ - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL"); - struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); - uint8_t *cause; - - msg->lchan = lchan; - gh->proto_discr = GSM48_PDISC_RR; - gh->msg_type = GSM48_MT_RR_CHAN_REL; - - cause = msgb_put(msg, 1); - cause[0] = GSM48_RR_CAUSE_NORMAL; - - DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n", - lchan->nr, lchan->type); - - /* Send actual release request to MS */ - return gsm48_sendmsg(msg); -} - -int send_siemens_mrpci(struct gsm_lchan *lchan, - uint8_t *classmark2_lv) -{ - struct rsl_mrpci mrpci; - - if (classmark2_lv[0] < 2) - return -EINVAL; - - mrpci.power_class = classmark2_lv[1] & 0x7; - mrpci.vgcs_capable = classmark2_lv[2] & (1 << 1); - mrpci.vbs_capable = classmark2_lv[2] & (1 <<2); - mrpci.gsm_phase = (classmark2_lv[1]) >> 5 & 0x3; - - return rsl_siemens_mrpci(lchan, &mrpci); -} - -/* Chapter 9.1.9: Ciphering Mode Command */ -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; - - msg->lchan = lchan; - - DEBUGP(DRR, "TX CIPHERING MODE CMD\n"); - - 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; - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - gh->proto_discr = GSM48_PDISC_RR; - gh->msg_type = GSM48_MT_RR_CIPH_M_CMD; - gh->data[0] = (want_imeisv & 0x1) << 4 | (ciph_mod_set & 0xf); - - return rsl_encryption_cmd(msg); -} - -static void gsm48_cell_desc(struct gsm48_cell_desc *cd, - const struct gsm_bts *bts) -{ - cd->ncc = (bts->bsic >> 3 & 0x7); - cd->bcc = (bts->bsic & 0x7); - cd->arfcn_hi = bts->c0->arfcn >> 8; - cd->arfcn_lo = bts->c0->arfcn & 0xff; -} - -void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, - const struct gsm_lchan *lchan) -{ - uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff; - - cd->chan_nr = gsm_lchan2chan_nr(lchan); - if (!lchan->ts->hopping.enabled) { - cd->h0.tsc = gsm_ts_tsc(lchan->ts); - cd->h0.h = 0; - cd->h0.arfcn_high = arfcn >> 8; - cd->h0.arfcn_low = arfcn & 0xff; - } else { - cd->h1.tsc = gsm_ts_tsc(lchan->ts); - cd->h1.h = 1; - cd->h1.maio_high = lchan->ts->hopping.maio >> 2; - cd->h1.maio_low = lchan->ts->hopping.maio & 0x03; - cd->h1.hsn = lchan->ts->hopping.hsn; - } -} - -/*! \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[in] mr multi-rate configuration to encode - * \param[in] modes array describing the AMR modes - * \returns 0 on success */ -int gsm48_multirate_config(uint8_t *lv, const struct amr_multirate_conf *mr, const struct amr_mode *modes) -{ - int num = 0, i; - - for (i = 0; i < 8; i++) { - if (((mr->gsm48_ie[1] >> i) & 1)) - num++; - } - if (num > 4) { - LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with too " - "many modes in config.\n"); - num = 4; - } - if (num < 1) { - LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with no " - "mode in config.\n"); - num = 1; - } - - lv[0] = (num == 1) ? 2 : (num + 2); - memcpy(lv + 1, mr->gsm48_ie, 2); - if (num == 1) - return 0; - - lv[3] = modes[0].threshold & 0x3f; - lv[4] = modes[0].hysteresis << 4; - if (num == 2) - return 0; - lv[4] |= (modes[1].threshold & 0x3f) >> 2; - lv[5] = modes[1].threshold << 6; - lv[5] |= (modes[1].hysteresis & 0x0f) << 2; - if (num == 3) - return 0; - lv[5] |= (modes[2].threshold & 0x3f) >> 4; - lv[6] = modes[2].threshold << 4; - lv[6] |= modes[2].hysteresis & 0x0f; - - return 0; -} - -#define GSM48_HOCMD_CCHDESC_LEN 16 - -/* Chapter 9.1.15: Handover Command */ -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_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)); - - msg->lchan = old_lchan; - 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); - ho->ho_ref = ho_ref; - ho->power_command = power_command; - - if (new_lchan->ts->hopping.enabled) { - struct gsm_bts *bts = new_lchan->ts->trx->bts; - struct gsm48_system_information_type_1 *si1; - uint8_t *cur; - - si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1); - /* Copy the Cell Chan Desc (ARFCNS in this cell) */ - msgb_put_u8(msg, GSM48_IE_CELL_CH_DESC); - cur = msgb_put(msg, GSM48_HOCMD_CCHDESC_LEN); - memcpy(cur, si1->cell_channel_description, - GSM48_HOCMD_CCHDESC_LEN); - /* Copy the Mobile Allocation */ - msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, - new_lchan->ts->hopping.ma_len, - new_lchan->ts->hopping.ma_data); - } - /* FIXME: optional bits for type of synchronization? */ - - msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->tch_mode); - - /* 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); - - 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) -{ - 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)); - - DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode); - - msg->lchan = dest_lchan; - gh->proto_discr = GSM48_PDISC_RR; - gh->msg_type = GSM48_MT_RR_ASS_CMD; - - /* - * fill the channel information element, this code - * should probably be shared with rsl_rx_chan_rqd(), - * gsm48_lchan_modify(). But beware that 10.5.2.5 - * 10.5.2.5.a have slightly different semantic for - * 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); - ass->power_command = power_command; - - /* optional: cell channel description */ - - msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode); - - /* mobile allocation in case of hopping */ - if (lchan->ts->hopping.enabled) { - msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, lchan->ts->hopping.ma_len, - lchan->ts->hopping.ma_data); - } - - /* in case of multi rate we need to attach a config */ - mr_config_for_ms(lchan, msg); - - return gsm48_sendmsg(msg); -} - -/* 9.1.5 Channel mode modify: Modify the mode on the MS side */ -int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t mode) -{ - 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)); - - DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", 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); - cmm->mode = mode; - - /* in case of multi rate we need to attach a config */ - mr_config_for_ms(lchan, msg); - - return gsm48_sendmsg(msg); -} - -int gsm48_rx_rr_modif_ack(struct msgb *msg) -{ - int rc; - struct gsm48_hdr *gh = msgb_l3(msg); - struct gsm48_chan_mode_modify *mod = - (struct gsm48_chan_mode_modify *) gh->data; - - DEBUGP(DRR, "CHANNEL MODE MODIFY ACK\n"); - - if (mod->mode != msg->lchan->tch_mode) { - LOGP(DRR, LOGL_ERROR, "CHANNEL MODE change failed. Wanted: %d Got: %d\n", - msg->lchan->tch_mode, 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; - } - - /* We've successfully modified the MS side of the channel, - * now go on to modify the BTS side of the channel */ - rc = rsl_chan_mode_modify_req(msg->lchan); - - /* FIXME: we not only need to do this after mode modify, but - * also after channel activation */ - if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN) - rsl_ipacc_crcx(msg->lchan); - return rc; -} - -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 gsm_meas_rep_cell *mrc; - - if (gh->msg_type != GSM48_MT_RR_MEAS_REP) - return -EINVAL; - - if (data[0] & 0x80) - rep->flags |= MEAS_REP_F_BA1; - if (data[0] & 0x40) - rep->flags |= MEAS_REP_F_UL_DTX; - if ((data[1] & 0x40) == 0x00) - rep->flags |= MEAS_REP_F_DL_VALID; - - rep->dl.full.rx_lev = data[0] & 0x3f; - rep->dl.sub.rx_lev = data[1] & 0x3f; - rep->dl.full.rx_qual = (data[2] >> 4) & 0x7; - rep->dl.sub.rx_qual = (data[2] >> 1) & 0x7; - - rep->num_cell = ((data[3] >> 6) & 0x3) | ((data[2] & 0x01) << 2); - if (rep->num_cell < 1 || rep->num_cell > 6) { - /* There are no neighbor cell reports present. */ - rep->num_cell = 0; - return 0; - } - - /* 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); - mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5); - if (rep->num_cell < 2) - return 0; - - 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); - mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4); - if (rep->num_cell < 3) - return 0; - - 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); - mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3); - if (rep->num_cell < 4) - return 0; - - 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); - mrc->bsic = data[11] >> 2; - if (rep->num_cell < 5) - return 0; - - 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); - mrc->bsic = (data[13] >> 1) & 0x3f; - if (rep->num_cell < 6) - return 0; - - 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); - mrc->bsic = data[15] & 0x3f; - - return 0; -} - -/* 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"); - - return gsm0808_submit_dtap(conn, msg, 0, 0); -} - -/* 9.2.6 CM service reject */ -int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, - enum gsm48_reject_value value) -{ - struct msgb *msg; - - msg = gsm48_create_mm_serv_rej(value); - if (!msg) { - LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n"); - return -1; - } - - DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value); - - return gsm0808_submit_dtap(conn, msg, 0, 0); -} - -/* 9.1.29 RR Status */ -struct msgb *gsm48_create_rr_status(uint8_t cause) -{ - struct msgb *msg; - struct gsm48_hdr *gh; - - msg = gsm48_msgb_alloc_name("GSM 04.08 RR STATUS"); - if (!msg) - return NULL; - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - gh->proto_discr = GSM48_PDISC_RR; - gh->msg_type = GSM48_MT_RR_STATUS; - gh->data[0] = cause; - - return msg; -} - -/* 9.1.29 RR Status */ -int gsm48_tx_rr_status(struct gsm_subscriber_connection *conn, uint8_t cause) -{ - struct msgb *msg = gsm48_create_rr_status(cause); - if (!msg) - return -1; - return gsm0808_submit_dtap(conn, msg, 0, 0); -} - -struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value) -{ - struct msgb *msg; - struct gsm48_hdr *gh; - - msg = gsm48_msgb_alloc_name("GSM 04.08 SERV REJ"); - if (!msg) - return NULL; - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - gh->proto_discr = GSM48_PDISC_MM; - gh->msg_type = GSM48_MT_MM_CM_SERV_REJ; - gh->data[0] = value; - - return msg; -} - -struct msgb *gsm48_create_loc_upd_rej(uint8_t cause) -{ - struct gsm48_hdr *gh; - struct msgb *msg; - - msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD REJ"); - if (!msg) - return NULL; - - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); - gh->proto_discr = GSM48_PDISC_MM; - gh->msg_type = GSM48_MT_MM_LOC_UPD_REJECT; - gh->data[0] = cause; - return msg; -} - -int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type) -{ - /* Check the size for the classmark */ - if (length < 1 + *classmark2_lv) - return -1; - - uint8_t *mi_lv = classmark2_lv + *classmark2_lv + 1; - if (length < 2 + *classmark2_lv + mi_lv[0]) - return -2; - - *mi_type = mi_lv[1] & GSM_MI_TYPE_MASK; - return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv); -} - -int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length, - char *mi_string, uint8_t *mi_type) -{ - static const uint32_t classmark_offset = - offsetof(struct gsm48_pag_resp, classmark2); - uint8_t *classmark2_lv = (uint8_t *) &resp->classmark2; - return gsm48_extract_mi(classmark2_lv, length - classmark_offset, - mi_string, mi_type); -} diff --git a/src/libbsc/gsm_04_80_utils.c b/src/libbsc/gsm_04_80_utils.c deleted file mode 100644 index d67f3c568..000000000 --- a/src/libbsc/gsm_04_80_utils.c +++ /dev/null @@ -1,40 +0,0 @@ -/* OpenBSC utility functions for 3GPP TS 04.80 */ - -/* (C) 2016 by sysmocom s.m.f.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 . - * - */ - -#include -#include - -int bsc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, - const char *text) -{ - struct msgb *msg = gsm0480_create_ussd_notify(level, text); - if (!msg) - return -1; - return gsm0808_submit_dtap(conn, msg, 0, 0); -} - -int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn) -{ - struct msgb *msg = gsm0480_create_ussd_release_complete(); - if (!msg) - return -1; - return gsm0808_submit_dtap(conn, msg, 0, 0); -} diff --git a/src/libbsc/gsm_data.c b/src/libbsc/gsm_data.c deleted file mode 100644 index ea2aea064..000000000 --- a/src/libbsc/gsm_data.c +++ /dev/null @@ -1,1234 +0,0 @@ -/* (C) 2008-2018 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -void *tall_bsc_ctx = NULL; - -static LLIST_HEAD(bts_models); - -void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr, - uint8_t e1_ts, uint8_t e1_ts_ss) -{ - ts->e1_link.e1_nr = e1_nr; - ts->e1_link.e1_ts = e1_ts; - ts->e1_link.e1_ts_ss = e1_ts_ss; -} - -static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type) -{ - struct gsm_bts_model *model; - - llist_for_each_entry(model, &bts_models, list) { - if (model->type == type) - return model; - } - - return NULL; -} - -int gsm_bts_model_register(struct gsm_bts_model *model) -{ - if (bts_model_find(model->type)) - return -EEXIST; - - tlv_def_patch(&model->nm_att_tlvdef, &abis_nm_att_tlvdef); - llist_add_tail(&model->list, &bts_models); - return 0; -} - -const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = { - { GSM_BTS_TYPE_UNKNOWN, "Unknown BTS Type" }, - { GSM_BTS_TYPE_BS11, "Siemens BTS (BS-11 or compatible)" }, - { 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" }, - { 0, NULL } -}; - -struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr) -{ - struct gsm_bts_trx *trx; - - llist_for_each_entry(trx, &bts->trx_list, list) { - if (trx->nr == nr) - return trx; - } - return NULL; -} - -/* Search for a BTS in the given Location Area; optionally start searching - * with start_bts (for continuing to search after the first result) */ -struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac, - struct gsm_bts *start_bts) -{ - int i; - struct gsm_bts *bts; - int skip = 0; - - if (start_bts) - skip = 1; - - for (i = 0; i < net->num_bts; i++) { - bts = gsm_bts_num(net, i); - - if (skip) { - if (start_bts == bts) - skip = 0; - continue; - } - - if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac) - return bts; - } - return NULL; -} - -static const struct value_string bts_gprs_mode_names[] = { - { BTS_GPRS_NONE, "none" }, - { BTS_GPRS_GPRS, "gprs" }, - { BTS_GPRS_EGPRS, "egprs" }, - { 0, NULL } -}; - -enum bts_gprs_mode bts_gprs_mode_parse(const char *arg, int *valid) -{ - int rc; - - rc = get_string_value(bts_gprs_mode_names, arg); - if (valid) - *valid = rc != -EINVAL; - return rc; -} - -const char *bts_gprs_mode_name(enum bts_gprs_mode mode) -{ - return get_value_string(bts_gprs_mode_names, mode); -} - -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)) { - return 0; - } - if (mode == BTS_GPRS_EGPRS && - !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_EGPRS)) { - return 0; - } - - return 1; -} - -int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type) -{ - struct gsm_bts_model *model; - - model = bts_model_find(type); - if (!model) - return -EINVAL; - - bts->type = type; - bts->model = model; - - if (model->start && !model->started) { - int ret = model->start(bts->network); - if (ret < 0) - return ret; - - model->started = true; - } - - switch (bts->type) { - case GSM_BTS_TYPE_NANOBTS: - case GSM_BTS_TYPE_OSMOBTS: - /* Set the default OML Stream ID to 0xff */ - bts->oml_tei = 0xff; - bts->c0->nominal_power = 23; - break; - case GSM_BTS_TYPE_RBS2000: - INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups); - INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups); - break; - case GSM_BTS_TYPE_BS11: - case GSM_BTS_TYPE_UNKNOWN: - case GSM_BTS_TYPE_NOKIA_SITE: - /* Set default BTS reset timer */ - bts->nokia.bts_reset_timer_cnf = 15; - case _NUM_GSM_BTS_TYPE: - break; - } - - return 0; -} - -struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, - uint8_t bsic) -{ - struct gsm_bts_model *model = bts_model_find(type); - struct gsm_bts *bts; - - if (!model && type != GSM_BTS_TYPE_UNKNOWN) - return NULL; - - bts = gsm_bts_alloc(net, net->num_bts); - if (!bts) - return NULL; - - net->num_bts++; - - bts->type = type; - bts->model = model; - bts->bsic = bsic; - - llist_add_tail(&bts->list, &net->bts_list); - - return bts; -} - -void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts) -{ - *raid = (struct gprs_ra_id){ - .mcc = bts->network->plmn.mcc, - .mnc = bts->network->plmn.mnc, - .mnc_3_digits = bts->network->plmn.mnc_3_digits, - .lac = bts->location_area_code, - .rac = bts->gprs.rac, - }; -} - -void gsm48_ra_id_by_bts(struct gsm48_ra_id *buf, struct gsm_bts *bts) -{ - struct gprs_ra_id raid; - - gprs_ra_id_by_bts(&raid, bts); - gsm48_encode_ra(buf, &raid); -} - -int gsm_parse_reg(void *ctx, regex_t *reg, char **str, int argc, const char **argv) -{ - int ret; - - ret = 0; - if (*str) { - talloc_free(*str); - *str = NULL; - } - regfree(reg); - - if (argc > 0) { - *str = talloc_strdup(ctx, argv[0]); - ret = regcomp(reg, argv[0], 0); - - /* handle compilation failures */ - if (ret != 0) { - talloc_free(*str); - *str = NULL; - } - } - - return ret; -} - -/* Assume there are only 256 possible bts */ -osmo_static_assert(sizeof(((struct gsm_bts *) 0)->nr) == 1, _bts_nr_is_256); -static void depends_calc_index_bit(int bts_nr, int *idx, int *bit) -{ - *idx = bts_nr / (8 * 4); - *bit = bts_nr % (8 * 4); -} - -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; -} - -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); -} - -int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other) -{ - int idx, bit; - depends_calc_index_bit(other->nr, &idx, &bit); - - /* Check if there is a depends bit */ - return (base->depends_on[idx] & (1 << bit)) > 0; -} - -static int bts_is_online(struct gsm_bts *bts) -{ - /* TODO: support E1 BTS too */ - if (!is_ipaccess_bts(bts)) - return 1; - - if (!bts->oml_link) - return 0; - - return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED; -} - -int bts_depend_check(struct gsm_bts *bts) -{ - struct gsm_bts *other_bts; - - llist_for_each_entry(other_bts, &bts->network->bts_list, list) { - if (!bts_depend_is_depedency(bts, other_bts)) - continue; - if (bts_is_online(other_bts)) - continue; - return 0; - } - return 1; -} - -/* get the radio link timeout (based on SACCH decode errors, according - * to algorithm specified in TS 05.08 section 5.2. A value of -1 - * indicates we should use an infinitely long timeout, which only works - * with OsmoBTS as the BTS implementation */ -int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts) -{ - const struct gsm48_cell_options *cell_options = &bts->si_common.cell_options; - - if (bts->infinite_radio_link_timeout) - return -1; - else { - /* Encoding as per Table 10.5.21 of TS 04.08 */ - return (cell_options->radio_link_timeout + 1) << 2; - } -} - -/* set the radio link timeout (based on SACCH decode errors, according - * to algorithm specified in TS 05.08 Section 5.2. A value of -1 - * indicates we should use an infinitely long timeout, which only works - * with OsmoBTS as the BTS implementation */ -void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value) -{ - struct gsm48_cell_options *cell_options = &bts->si_common.cell_options; - - if (value < 0) - bts->infinite_radio_link_timeout = true; - else { - bts->infinite_radio_link_timeout = false; - /* Encoding as per Table 10.5.21 of TS 04.08 */ - if (value < 4) - value = 4; - if (value > 64) - value = 64; - cell_options->radio_link_timeout = (value >> 2) - 1; - } -} - -bool classmark_is_r99(struct gsm_classmark *cm) -{ - int rev_lev = 0; - if (cm->classmark1_set) - rev_lev = cm->classmark1.rev_lev; - else if (cm->classmark2_len > 0) - rev_lev = (cm->classmark2[0] >> 5) & 0x3; - return rev_lev >= 2; -} - -static const struct osmo_stat_item_desc bts_stat_desc[] = { - { "chanloadavg", "Channel load average.", "%", 16, 0 }, - { "T3122", "T3122 IMMEDIATE ASSIGNMENT REJECT wait indicator.", "s", 16, GSM_T3122_DEFAULT }, -}; - -static const struct osmo_stat_item_group_desc bts_statg_desc = { - .group_name_prefix = "bts", - .group_description = "base transceiver station", - .class_id = OSMO_STATS_CLASS_GLOBAL, - .num_items = ARRAY_SIZE(bts_stat_desc), - .item_desc = bts_stat_desc, -}; - -void gsm_abis_mo_reset(struct gsm_abis_mo *mo) -{ - mo->nm_state.operational = NM_OPSTATE_NULL; - mo->nm_state.availability = NM_AVSTATE_POWER_OFF; -} - -static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts, - uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3) -{ - mo->bts = bts; - mo->obj_class = obj_class; - mo->obj_inst.bts_nr = p1; - mo->obj_inst.trx_nr = p2; - mo->obj_inst.ts_nr = p3; - gsm_abis_mo_reset(mo); -} - -const struct value_string bts_attribute_names[] = { - OSMO_VALUE_STRING(BTS_TYPE_VARIANT), - OSMO_VALUE_STRING(BTS_SUB_MODEL), - OSMO_VALUE_STRING(TRX_PHY_VERSION), - { 0, NULL } -}; - -enum bts_attribute str2btsattr(const char *s) -{ - return get_string_value(bts_attribute_names, s); -} - -const char *btsatttr2str(enum bts_attribute v) -{ - return get_value_string(bts_attribute_names, v); -} - -const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = { - { BTS_UNKNOWN, "unknown" }, - { BTS_OSMO_LITECELL15, "osmo-bts-lc15" }, - { BTS_OSMO_OCTPHY, "osmo-bts-octphy" }, - { BTS_OSMO_SYSMO, "osmo-bts-sysmo" }, - { BTS_OSMO_TRX, "omso-bts-trx" }, - { 0, NULL } -}; - -enum gsm_bts_type_variant str2btsvariant(const char *arg) -{ - return get_string_value(osmo_bts_variant_names, arg); -} - -const char *btsvariant2str(enum gsm_bts_type_variant v) -{ - return get_value_string(osmo_bts_variant_names, v); -} - -const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = { - { GSM_BTS_TYPE_UNKNOWN, "unknown" }, - { GSM_BTS_TYPE_BS11, "bs11" }, - { GSM_BTS_TYPE_NANOBTS, "nanobts" }, - { GSM_BTS_TYPE_RBS2000, "rbs2000" }, - { GSM_BTS_TYPE_NOKIA_SITE, "nokia_site" }, - { GSM_BTS_TYPE_OSMOBTS, "sysmobts" }, - { 0, NULL } -}; - -enum gsm_bts_type str2btstype(const char *arg) -{ - return get_string_value(bts_type_names, arg); -} - -const char *btstype2str(enum gsm_bts_type type) -{ - return get_value_string(bts_type_names, type); -} - -const struct value_string gsm_chreq_descs[] = { - { GSM_CHREQ_REASON_EMERG, "emergency call" }, - { GSM_CHREQ_REASON_PAG, "answer to paging" }, - { GSM_CHREQ_REASON_CALL, "call re-establishment" }, - { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" }, - { GSM_CHREQ_REASON_PDCH, "one phase packet access" }, - { GSM_CHREQ_REASON_OTHER, "other" }, - { 0, NULL } -}; - -const struct value_string gsm_pchant_names[13] = { - { GSM_PCHAN_NONE, "NONE" }, - { GSM_PCHAN_CCCH, "CCCH" }, - { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" }, - { GSM_PCHAN_TCH_F, "TCH/F" }, - { GSM_PCHAN_TCH_H, "TCH/H" }, - { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" }, - { GSM_PCHAN_PDCH, "PDCH" }, - { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" }, - { GSM_PCHAN_UNKNOWN, "UNKNOWN" }, - { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" }, - { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" }, - { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" }, - { 0, NULL } -}; - -const struct value_string gsm_pchant_descs[13] = { - { GSM_PCHAN_NONE, "Physical Channel not configured" }, - { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" }, - { GSM_PCHAN_CCCH_SDCCH4, - "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" }, - { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" }, - { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" }, - { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" }, - { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" }, - { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" }, - { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" }, - { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" }, - { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" }, - { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" }, - { 0, NULL } -}; - -const char *gsm_pchan_name(enum gsm_phys_chan_config c) -{ - return get_value_string(gsm_pchant_names, c); -} - -enum gsm_phys_chan_config gsm_pchan_parse(const char *name) -{ - return get_string_value(gsm_pchant_names, name); -} - -/* TODO: move to libosmocore, next to gsm_chan_t_names? */ -const char *gsm_lchant_name(enum gsm_chan_t c) -{ - return get_value_string(gsm_chan_t_names, c); -} - -static const struct value_string lchan_s_names[] = { - { LCHAN_S_NONE, "NONE" }, - { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" }, - { LCHAN_S_ACTIVE, "ACTIVE" }, - { LCHAN_S_INACTIVE, "INACTIVE" }, - { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, - { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, - { LCHAN_S_BROKEN, "BROKEN UNUSABLE" }, - { 0, NULL } -}; - -const char *gsm_lchans_name(enum gsm_lchan_state s) -{ - return get_value_string(lchan_s_names, s); -} - -static const struct value_string chreq_names[] = { - { GSM_CHREQ_REASON_EMERG, "EMERGENCY" }, - { GSM_CHREQ_REASON_PAG, "PAGING" }, - { GSM_CHREQ_REASON_CALL, "CALL" }, - { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" }, - { GSM_CHREQ_REASON_OTHER, "OTHER" }, - { 0, NULL } -}; - -const char *gsm_chreq_name(enum gsm_chreq_reason_t c) -{ - return get_value_string(chreq_names, c); -} - -struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) -{ - struct gsm_bts *bts; - - if (num >= net->num_bts) - return NULL; - - llist_for_each_entry(bts, &net->bts_list, list) { - if (bts->nr == num) - return bts; - } - - return NULL; -} - -struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) -{ - struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx); - int k; - - if (!trx) - return NULL; - - trx->bts = bts; - trx->nr = bts->num_trx++; - trx->mo.nm_state.administrative = NM_STATE_UNLOCKED; - - gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER, - bts->nr, trx->nr, 0xff); - gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC, - bts->nr, trx->nr, 0xff); - - for (k = 0; k < TRX_NR_TS; k++) { - struct gsm_bts_trx_ts *ts = &trx->ts[k]; - int l; - - ts->trx = trx; - ts->nr = k; - ts->pchan = GSM_PCHAN_NONE; - ts->dyn.pchan_is = GSM_PCHAN_NONE; - ts->dyn.pchan_want = GSM_PCHAN_NONE; - ts->tsc = -1; - - gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL, - bts->nr, trx->nr, ts->nr); - - ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data); - ts->hopping.arfcns.data = ts->hopping.arfcns_data; - ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data); - 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); - } - } - - if (trx->nr != 0) - trx->nominal_power = bts->c0->nominal_power; - - llist_add_tail(&trx->list, &bts->trx_list); - - return trx; -} - - -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 struct gprs_rlc_cfg rlc_cfg_default = { - .parameter = { - [RLC_T3142] = 20, - [RLC_T3169] = 5, - [RLC_T3191] = 5, - [RLC_T3193] = 160, /* 10ms */ - [RLC_T3195] = 5, - [RLC_N3101] = 10, - [RLC_N3103] = 4, - [RLC_N3105] = 8, - [CV_COUNTDOWN] = 15, - [T_DL_TBF_EXT] = 250 * 10, /* ms */ - [T_UL_TBF_EXT] = 250 * 10, /* ms */ - }, - .paging = { - .repeat_time = 5 * 50, /* ms */ - .repeat_count = 3, - }, - .cs_mask = 0x1fff, - .initial_cs = 2, - .initial_mcs = 6, -}; - -struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) -{ - struct gsm_bts *bts = talloc_zero(net, struct gsm_bts); - int i; - - if (!bts) - return NULL; - - bts->nr = bts_num; - bts->num_trx = 0; - INIT_LLIST_HEAD(&bts->trx_list); - bts->network = net; - - bts->ms_max_power = 15; /* dBm */ - - gsm_mo_init(&bts->mo, bts, NM_OC_BTS, - bts->nr, 0xff, 0xff); - gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER, - 0xff, 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); - 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)); - - /* init statistics */ - bts->bts_ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr); - if (!bts->bts_ctrs) { - talloc_free(bts); - return NULL; - } - bts->bts_statg = osmo_stat_item_group_alloc(bts, &bts_statg_desc, 0); - - /* create our primary TRX */ - bts->c0 = gsm_bts_trx_alloc(bts); - if (!bts->c0) { - rate_ctr_group_free(bts->bts_ctrs); - osmo_stat_item_group_free(bts->bts_statg); - talloc_free(bts); - return NULL; - } - bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4; - - bts->rach_b_thresh = -1; - bts->rach_ldavg_slots = -1; - - bts->paging.free_chans_need = -1; - INIT_LLIST_HEAD(&bts->paging.pending_requests); - - bts->features.data = &bts->_features_data[0]; - bts->features.data_len = sizeof(bts->_features_data); - - /* si handling */ - bts->bcch_change_mark = 1; - bts->chan_load_avg = 0; - - bts->ho = ho_cfg_init(bts, net->ho); - - /* timer overrides */ - bts->T3122 = 0; /* not overriden by default */ - - bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED; - bts->dtxd = false; - bts->gprs.ctrl_ack_type_use_block = true; /* use RLC/MAC control block */ - bts->neigh_list_manual_mode = 0; - bts->early_classmark_allowed_3g = true; /* 3g Early Classmark Sending controlled by bts->early_classmark_allowed param */ - 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; - bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list; - bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST; - bts->si_common.si2quater_neigh_list.thresh_hi = 0; - osmo_earfcn_init(&bts->si_common.si2quater_neigh_list); - bts->si_common.neigh_list.data = bts->si_common.data.neigh_list; - bts->si_common.neigh_list.data_len = - sizeof(bts->si_common.data.neigh_list); - bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list; - bts->si_common.si5_neigh_list.data_len = - sizeof(bts->si_common.data.si5_neigh_list); - bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc; - bts->si_common.cell_alloc.data_len = - sizeof(bts->si_common.data.cell_alloc); - bts->si_common.rach_control.re = 1; /* no re-establishment */ - 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.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 = net->t3212; /* Use network's current value */ - 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); - - return bts; -} - -/* reset the state of all MO in the BTS */ -void gsm_bts_mo_reset(struct gsm_bts *bts) -{ - struct gsm_bts_trx *trx; - 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) { - gsm_abis_mo_reset(&trx->mo); - gsm_abis_mo_reset(&trx->bb_transc.mo); - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - gsm_abis_mo_reset(&ts->mo); - } - } -} - -struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num) -{ - struct gsm_bts_trx *trx; - - if (num >= bts->num_trx) - return NULL; - - llist_for_each_entry(trx, &bts->trx_list, list) { - if (trx->nr == num) - return trx; - } - - return NULL; -} - -static char ts2str[255]; - -char *gsm_trx_name(const struct gsm_bts_trx *trx) -{ - if (!trx) - snprintf(ts2str, sizeof(ts2str), "(trx=NULL)"); - else - snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)", - trx->bts->nr, trx->nr); - - return ts2str; -} - - -char *gsm_ts_name(const struct gsm_bts_trx_ts *ts) -{ - snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)", - ts->trx->bts->nr, ts->trx->nr, ts->nr); - - return ts2str; -} - -/*! Log timeslot number with full pchan information */ -char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts) -{ - switch (ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - if (ts->dyn.pchan_is == ts->dyn.pchan_want) - snprintf(ts2str, sizeof(ts2str), - "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), - gsm_pchan_name(ts->dyn.pchan_is)); - else - snprintf(ts2str, sizeof(ts2str), - "(bts=%d,trx=%d,ts=%d,pchan=%s" - " switching %s -> %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), - gsm_pchan_name(ts->dyn.pchan_is), - gsm_pchan_name(ts->dyn.pchan_want)); - break; - case GSM_PCHAN_TCH_F_PDCH: - if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) - snprintf(ts2str, sizeof(ts2str), - "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), - (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" - : "TCH/F"); - else - snprintf(ts2str, sizeof(ts2str), - "(bts=%d,trx=%d,ts=%d,pchan=%s" - " switching %s -> %s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan), - (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" - : "TCH/F", - (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" - : "TCH/F"); - break; - default: - snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, - gsm_pchan_name(ts->pchan)); - break; - } - - return ts2str; -} - -char *gsm_lchan_name_compute(const struct gsm_lchan *lchan) -{ - struct gsm_bts_trx_ts *ts = lchan->ts; - - snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)", - ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr); - - return ts2str; -} - -/* obtain the MO structure for a given object instance */ -static inline struct gsm_abis_mo * -gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class, - const struct abis_om_obj_inst *obj_inst) -{ - struct gsm_bts_trx *trx; - struct gsm_abis_mo *mo = NULL; - - switch (obj_class) { - case NM_OC_BTS: - mo = &bts->mo; - break; - case NM_OC_RADIO_CARRIER: - if (obj_inst->trx_nr >= bts->num_trx) { - return NULL; - } - trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); - mo = &trx->mo; - break; - case NM_OC_BASEB_TRANSC: - if (obj_inst->trx_nr >= bts->num_trx) { - return NULL; - } - trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); - mo = &trx->bb_transc.mo; - break; - case NM_OC_CHANNEL: - if (obj_inst->trx_nr >= bts->num_trx) { - return NULL; - } - trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); - if (obj_inst->ts_nr >= TRX_NR_TS) - return NULL; - mo = &trx->ts[obj_inst->ts_nr].mo; - break; - case NM_OC_SITE_MANAGER: - mo = &bts->site_mgr.mo; - break; - case NM_OC_BS11: - switch (obj_inst->bts_nr) { - case BS11_OBJ_CCLK: - mo = &bts->bs11.cclk.mo; - break; - case BS11_OBJ_BBSIG: - if (obj_inst->ts_nr > bts->num_trx) - return NULL; - trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); - mo = &trx->bs11.bbsig.mo; - break; - case BS11_OBJ_PA: - if (obj_inst->ts_nr > bts->num_trx) - return NULL; - trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); - mo = &trx->bs11.pa.mo; - break; - default: - return NULL; - } - break; - case NM_OC_BS11_RACK: - mo = &bts->bs11.rack.mo; - break; - case NM_OC_BS11_ENVABTSE: - if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse)) - return NULL; - mo = &bts->bs11.envabtse[obj_inst->trx_nr].mo; - break; - case NM_OC_GPRS_NSE: - mo = &bts->gprs.nse.mo; - break; - case NM_OC_GPRS_CELL: - mo = &bts->gprs.cell.mo; - break; - case NM_OC_GPRS_NSVC: - if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) - return NULL; - mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo; - break; - } - return mo; -} - -/* obtain the gsm_nm_state data structure for a given object instance */ -struct gsm_nm_state * -gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class, - const struct abis_om_obj_inst *obj_inst) -{ - struct gsm_abis_mo *mo; - - mo = gsm_objclass2mo(bts, obj_class, obj_inst); - if (!mo) - return NULL; - - return &mo->nm_state; -} - -/* obtain the in-memory data structure of a given object instance */ -void * -gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class, - const struct abis_om_obj_inst *obj_inst) -{ - struct gsm_bts_trx *trx; - void *obj = NULL; - - switch (obj_class) { - case NM_OC_BTS: - obj = bts; - break; - case NM_OC_RADIO_CARRIER: - if (obj_inst->trx_nr >= bts->num_trx) { - return NULL; - } - trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); - obj = trx; - break; - case NM_OC_BASEB_TRANSC: - if (obj_inst->trx_nr >= bts->num_trx) { - return NULL; - } - trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); - obj = &trx->bb_transc; - break; - case NM_OC_CHANNEL: - if (obj_inst->trx_nr >= bts->num_trx) { - return NULL; - } - trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); - if (obj_inst->ts_nr >= TRX_NR_TS) - return NULL; - obj = &trx->ts[obj_inst->ts_nr]; - break; - case NM_OC_SITE_MANAGER: - obj = &bts->site_mgr; - break; - case NM_OC_GPRS_NSE: - obj = &bts->gprs.nse; - break; - case NM_OC_GPRS_CELL: - obj = &bts->gprs.cell; - break; - case NM_OC_GPRS_NSVC: - if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) - return NULL; - obj = &bts->gprs.nsvc[obj_inst->trx_nr]; - break; - } - return obj; -} - -/* See Table 10.5.25 of GSM04.08 */ -uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, - uint8_t ts_nr, uint8_t lchan_nr) -{ - uint8_t cbits, chan_nr; - - switch (pchan) { - case GSM_PCHAN_TCH_F: - case GSM_PCHAN_TCH_F_PDCH: - OSMO_ASSERT(lchan_nr == 0); - cbits = 0x01; - break; - case GSM_PCHAN_PDCH: - OSMO_ASSERT(lchan_nr == 0); - cbits = RSL_CHAN_OSMO_PDCH >> 3; - break; - case GSM_PCHAN_TCH_H: - OSMO_ASSERT(lchan_nr < 2); - cbits = 0x02; - cbits += lchan_nr; - break; - case GSM_PCHAN_CCCH_SDCCH4: - case GSM_PCHAN_CCCH_SDCCH4_CBCH: - /* - * As a special hack for BCCH, lchan_nr == 4 may be passed - * here. This should never be sent in an RSL message. - * See osmo-bts-xxx/oml.c:opstart_compl(). - */ - if (lchan_nr == CCCH_LCHAN) - chan_nr = 0; - else - OSMO_ASSERT(lchan_nr < 4); - cbits = 0x04; - cbits += lchan_nr; - break; - case GSM_PCHAN_SDCCH8_SACCH8C: - case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: - OSMO_ASSERT(lchan_nr < 8); - cbits = 0x08; - cbits += lchan_nr; - break; - default: - case GSM_PCHAN_CCCH: - OSMO_ASSERT(lchan_nr == 0); - cbits = 0x10; - break; - } - - chan_nr = (cbits << 3) | (ts_nr & 0x7); - - return chan_nr; -} - -uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan) -{ - enum gsm_phys_chan_config pchan = lchan->ts->pchan; - if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) - return gsm_lchan_as_pchan2chan_nr(lchan, - lchan->ts->dyn.pchan_is); - return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr); -} - -uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan, - enum gsm_phys_chan_config as_pchan) -{ - if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && as_pchan == GSM_PCHAN_PDCH) - return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK); - return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr); -} - -/* 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; - - if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) - lchan = &trx->ts[0].lchan[2]; - else { - int i; - for (i = 0; i < 8; i++) { - if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) { - lchan = &trx->ts[i].lchan[2]; - break; - } - } - } - - return lchan; -} - -/* determine logical channel based on TRX and channel number IE */ -struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, - int *rc) -{ - uint8_t ts_nr = chan_nr & 0x07; - uint8_t cbits = chan_nr >> 3; - uint8_t lch_idx; - struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; - bool ok = true; - - if (rc) - *rc = -EINVAL; - - if (cbits == 0x01) { - lch_idx = 0; /* TCH/F */ - if (ts->pchan != GSM_PCHAN_TCH_F && - ts->pchan != GSM_PCHAN_PDCH && - ts->pchan != GSM_PCHAN_TCH_F_PDCH - && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && (ts->dyn.pchan_is == GSM_PCHAN_TCH_F - || ts->dyn.pchan_want == GSM_PCHAN_TCH_F))) - ok = false; - } else if ((cbits & 0x1e) == 0x02) { - lch_idx = cbits & 0x1; /* TCH/H */ - if (ts->pchan != GSM_PCHAN_TCH_H - && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && (ts->dyn.pchan_is == GSM_PCHAN_TCH_H - || ts->dyn.pchan_want == GSM_PCHAN_TCH_H))) - ok = false; - } else if ((cbits & 0x1c) == 0x04) { - lch_idx = cbits & 0x3; /* SDCCH/4 */ - if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && - ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) - ok = false; - } else if ((cbits & 0x18) == 0x08) { - lch_idx = cbits & 0x7; /* SDCCH/8 */ - if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C && - ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH) - ok = false; - } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { - lch_idx = 0; - if (ts->pchan != GSM_PCHAN_CCCH && - ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && - ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) - ok = false; - /* FIXME: we should not return first sdcch4 !!! */ - } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) { - lch_idx = 0; - if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) - ok = false; - } else - return NULL; - - if (rc && ok) - *rc = 0; - - return &ts->lchan[lch_idx]; -} - -static const uint8_t subslots_per_pchan[] = { - [GSM_PCHAN_NONE] = 0, - [GSM_PCHAN_CCCH] = 0, - [GSM_PCHAN_PDCH] = 0, - [GSM_PCHAN_CCCH_SDCCH4] = 4, - [GSM_PCHAN_TCH_F] = 1, - [GSM_PCHAN_TCH_H] = 2, - [GSM_PCHAN_SDCCH8_SACCH8C] = 8, - [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4, - [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8, - /* - * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be - * part of this, those TS are handled according to their dynamic state. - */ -}; - -/*! Return the actual pchan type, also heeding dynamic TS. */ -enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts) -{ - switch (ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - return ts->dyn.pchan_is; - case GSM_PCHAN_TCH_F_PDCH: - if (ts->flags & TS_F_PDCH_ACTIVE) - return GSM_PCHAN_PDCH; - else - return GSM_PCHAN_TCH_F; - default: - return ts->pchan; - } -} - -/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of - * logical channels available in the timeslot. */ -uint8_t ts_subslots(struct gsm_bts_trx_ts *ts) -{ - return subslots_per_pchan[ts_pchan(ts)]; -} - -static bool pchan_is_tch(enum gsm_phys_chan_config pchan) -{ - switch (pchan) { - case GSM_PCHAN_TCH_F: - case GSM_PCHAN_TCH_H: - return true; - default: - return false; - } -} - -bool ts_is_tch(struct gsm_bts_trx_ts *ts) -{ - return pchan_is_tch(ts_pchan(ts)); -} diff --git a/src/libbsc/handover_cfg.c b/src/libbsc/handover_cfg.c deleted file mode 100644 index 14f5d8e84..000000000 --- a/src/libbsc/handover_cfg.c +++ /dev/null @@ -1,86 +0,0 @@ -/* OsmoBSC handover configuration implementation */ -/* (C) 2009-2010 by Andreas Eversberg - * (C) 2009-2010 by Harald Welte - * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH - * - * All Rights Reserved - * - * Author: Andreas Eversberg - * Neels Hofmeyr - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include - -#include -#include -#include - -struct handover_cfg { - struct handover_cfg *higher_level_cfg; - -#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ - TYPE NAME; \ - bool has_##NAME; - - HO_CFG_ALL_MEMBERS -#undef HO_CFG_ONE_MEMBER -}; - -struct handover_cfg *ho_cfg_init(void *ctx, struct handover_cfg *higher_level_cfg) -{ - struct handover_cfg *ho = talloc_zero(ctx, struct handover_cfg); - OSMO_ASSERT(ho); - ho->higher_level_cfg = higher_level_cfg; - return ho; -} - -#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY_ARG_EVAL, VTY4, VTY5, VTY6) \ -TYPE ho_get_##NAME(struct handover_cfg *ho) \ -{ \ - if (ho->has_##NAME) \ - return ho->NAME; \ - if (ho->higher_level_cfg) \ - return ho_get_##NAME(ho->higher_level_cfg); \ - return VTY_ARG_EVAL(#DEFAULT_VAL); \ -} \ -\ -void ho_set_##NAME(struct handover_cfg *ho, TYPE value) \ -{ \ - ho->NAME = value; \ - ho->has_##NAME = true; \ -} \ -\ -bool ho_isset_##NAME(struct handover_cfg *ho) \ -{ \ - return ho->has_##NAME; \ -} \ -\ -void ho_clear_##NAME(struct handover_cfg *ho) \ -{ \ - ho->has_##NAME = false; \ -} \ -\ -bool ho_isset_on_parent_##NAME(struct handover_cfg *ho) \ -{ \ - return ho->higher_level_cfg \ - && (ho_isset_##NAME(ho->higher_level_cfg) \ - || ho_isset_on_parent_##NAME(ho->higher_level_cfg)); \ -} - -HO_CFG_ALL_MEMBERS -#undef HO_CFG_ONE_MEMBER diff --git a/src/libbsc/handover_decision.c b/src/libbsc/handover_decision.c deleted file mode 100644 index 887c2993f..000000000 --- a/src/libbsc/handover_decision.c +++ /dev/null @@ -1,343 +0,0 @@ -/* Handover Decision making for Inter-BTS (Intra-BSC) Handover. This - * only implements the handover algorithm/decision, but not execution - * of it */ - -/* (C) 2009 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -/* Find BTS by ARFCN and BSIC */ -struct gsm_bts *bts_by_arfcn_bsic(const struct gsm_network *net, - uint16_t arfcn, uint8_t bsic) -{ - struct gsm_bts *bts; - - llist_for_each_entry(bts, &net->bts_list, list) { - if (bts->c0->arfcn == arfcn && - bts->bsic == bsic) - return bts; - } - - return NULL; -} - - -/* issue handover to a cell identified by ARFCN and BSIC */ -static int handover_to_arfcn_bsic(struct gsm_lchan *lchan, - uint16_t arfcn, uint8_t bsic) -{ - struct gsm_bts *new_bts; - - /* resolve the gsm_bts structure for the best neighbor */ - /* FIXME: use some better heuristics here to determine which cell - * using this ARFCN really is closest to the target cell. For - * now we simply assume that each ARFCN will only be used by one - * cell */ - new_bts = bts_by_arfcn_bsic(lchan->ts->trx->bts->network, arfcn, bsic); - if (!new_bts) { - LOGP(DHODEC, LOGL_NOTICE, "unable to determine neighbor BTS " - "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic); - return -EINVAL; - } - - /* and actually try to handover to that cell */ - return bsc_handover_start(HODEC1, lchan, new_bts, lchan->type); -} - -/* did we get a RXLEV for a given cell in the given report? */ -static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr, - uint16_t arfcn, uint8_t bsic) -{ - int i; - - for (i = 0; i < mr->num_cell; i++) { - struct gsm_meas_rep_cell *mrc = &mr->cell[i]; - - /* search for matching report */ - if (!(mrc->arfcn == arfcn && mrc->bsic == bsic)) - continue; - - mrc->flags |= MRC_F_PROCESSED; - return mrc->rxlev; - } - return -ENODEV; -} - -/* obtain averaged rxlev for given neighbor */ -static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) -{ - unsigned int i, idx; - int avg = 0; - - /* reduce window to the actual number of existing measurements */ - if (window > nmp->rxlev_cnt) - window = nmp->rxlev_cnt; - /* this should never happen */ - if (window <= 0) { - LOGP(DHODEC, LOGL_ERROR, "Requested Neighbor RxLev for invalid window size of %d\n", window); - return 0; - } - - idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), - nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), - window); - - for (i = 0; i < window; i++) { - int j = (idx+i) % ARRAY_SIZE(nmp->rxlev); - - avg += nmp->rxlev[j]; - } - - return avg / window; -} - -/* find empty or evict bad neighbor */ -static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan) -{ - int j, worst = 999999; - struct neigh_meas_proc *nmp_worst = NULL; - - /* first try to find an empty/unused slot */ - for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { - struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; - if (!nmp->arfcn) - return nmp; - } - - /* no empty slot found. evict worst neighbor from list */ - for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { - struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; - int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG); - if (!nmp_worst || avg < worst) { - worst = avg; - nmp_worst = nmp; - } - } - - return nmp_worst; -} - -/* process neighbor cell measurement reports */ -static void process_meas_neigh(struct gsm_meas_rep *mr) -{ - int i, j, idx; - - /* for each reported cell, try to update global state */ - for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) { - struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j]; - unsigned int idx; - int rxlev; - - /* skip unused entries */ - if (!nmp->arfcn) - continue; - - rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic); - idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); - if (rxlev >= 0) { - nmp->rxlev[idx] = rxlev; - nmp->last_seen_nr = mr->nr; - } else - nmp->rxlev[idx] = 0; - nmp->rxlev_cnt++; - } - - /* iterate over list of reported cells, check if we did not - * process all of them */ - for (i = 0; i < mr->num_cell; i++) { - struct gsm_meas_rep_cell *mrc = &mr->cell[i]; - struct neigh_meas_proc *nmp; - - if (mrc->flags & MRC_F_PROCESSED) - continue; - - nmp = find_evict_neigh(mr->lchan); - - nmp->arfcn = mrc->arfcn; - nmp->bsic = mrc->bsic; - - nmp->rxlev_cnt = 0; - idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); - nmp->rxlev[idx] = mrc->rxlev; - nmp->rxlev_cnt++; - nmp->last_seen_nr = mr->nr; - - mrc->flags |= MRC_F_PROCESSED; - } -} - -/* attempt to do a handover */ -static int attempt_handover(struct gsm_meas_rep *mr) -{ - struct gsm_bts *bts = mr->lchan->ts->trx->bts; - struct neigh_meas_proc *best_cell = NULL; - unsigned int best_better_db = 0; - int i, rc; - - /* find the best cell in this report that is at least RXLEV_HYST - * better than the current serving cell */ - - for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) { - struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i]; - int avg, better; - - /* skip empty slots */ - if (nmp->arfcn == 0) - continue; - - /* caculate average rxlev for this cell over the window */ - avg = neigh_meas_avg(nmp, ho_get_hodec1_rxlev_neigh_avg_win(bts->ho)); - - /* check if hysteresis is fulfilled */ - if (avg < mr->dl.full.rx_lev + ho_get_hodec1_pwr_hysteresis(bts->ho)) - continue; - - better = avg - mr->dl.full.rx_lev; - if (better > best_better_db) { - best_cell = nmp; - best_better_db = better; - } - } - - if (!best_cell) - return 0; - - LOGP(DHODEC, LOGL_INFO, "%s: Cell on ARFCN %u is better: ", - gsm_ts_name(mr->lchan->ts), best_cell->arfcn); - if (!ho_get_ho_active(bts->ho)) { - LOGPC(DHODEC, LOGL_INFO, "Skipping, Handover disabled\n"); - return 0; - } - - rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic); - switch (rc) { - case 0: - LOGPC(DHODEC, LOGL_INFO, "Starting handover: meas report number %d \n", mr->nr); - break; - case -ENOSPC: - LOGPC(DHODEC, LOGL_INFO, "No channel available\n"); - break; - case -EBUSY: - LOGPC(DHODEC, LOGL_INFO, "Handover already active\n"); - break; - default: - LOGPC(DHODEC, LOGL_ERROR, "Unknown error\n"); - } - return rc; -} - -/* process an already parsed measurement report and decide if we want to - * attempt a handover */ -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; - - /* If this cell does not use handover algorithm 1, then we're not responsible. */ - if (ho_get_algorithm(bts->ho) != 1) - return; - - /* we currently only do handover for TCH channels */ - switch (mr->lchan->type) { - case GSM_LCHAN_TCH_F: - case GSM_LCHAN_TCH_H: - break; - default: - 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, - 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)) { - LOGPC(DHO, LOGL_INFO, "HO cause: Interference HO av_rxlev=%d dBm\n", - rxlev2dbm(av_rxlev)); - attempt_handover(mr); - return; - } - - /* Bad Quality */ - if (meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5)) { - LOGPC(DHO, LOGL_INFO, "HO cause: Bad Quality av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev)); - attempt_handover(mr); - return; - } - - /* Low Level */ - if (rxlev2dbm(av_rxlev) <= -110) { - LOGPC(DHO, LOGL_INFO, "HO cause: Low Level av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev)); - attempt_handover(mr); - return; - } - - /* Distance */ - if (mr->ms_l1.ta > ho_get_hodec1_max_distance(bts->ho)) { - LOGPC(DHO, LOGL_INFO, "HO cause: Distance av_rxlev=%d dBm ta=%d \n", - rxlev2dbm(av_rxlev), mr->ms_l1.ta); - attempt_handover(mr); - return; - } - - /* Power Budget AKA Better Cell */ - pwr_interval = ho_get_hodec1_pwr_interval(bts->ho); - /* handover_cfg.h defines pwr_interval as [1..99], but since we're using it in a modulo below, - * assert non-zero to clarify. */ - OSMO_ASSERT(pwr_interval); - if ((mr->nr % pwr_interval) == pwr_interval - 1) - attempt_handover(mr); -} - -struct handover_decision_callbacks hodec1_callbacks = { - .hodec_id = HODEC1, - .on_measurement_report = on_measurement_report, -}; - -void handover_decision_1_init(void) -{ - handover_decision_callbacks_register(&hodec1_callbacks); -} diff --git a/src/libbsc/handover_decision_2.c b/src/libbsc/handover_decision_2.c deleted file mode 100644 index 7ac54df95..000000000 --- a/src/libbsc/handover_decision_2.c +++ /dev/null @@ -1,1830 +0,0 @@ -/* Handover Decision Algorithm 2 for intra-BSC (inter-BTS) handover, public API for OsmoBSC. */ - -/* (C) 2009 by Andreas Eversberg - * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH - * - * All Rights Reserved - * - * Author: Andreas Eversberg - * Neels Hofmeyr - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define LOGPHOBTS(bts, level, fmt, args...) \ - LOGP(DHODEC, level, "(BTS %u) " fmt, bts->nr, ## args) - -#define LOGPHOLCHAN(lchan, level, fmt, args...) \ - LOGP(DHODEC, level, "(lchan %u.%u%u%u %s) (subscr %s) " fmt, \ - lchan->ts->trx->bts->nr, \ - lchan->ts->trx->nr, \ - lchan->ts->nr, \ - lchan->nr, \ - gsm_pchan_name(lchan->ts->pchan), \ - bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ - ## args) - -#define LOGPHOLCHANTOBTS(lchan, new_bts, level, fmt, args...) \ - LOGP(DHODEC, level, "(lchan %u.%u%u%u %s)->(BTS %u) (subscr %s) " fmt, \ - lchan->ts->trx->bts->nr, \ - lchan->ts->trx->nr, \ - lchan->ts->nr, \ - lchan->nr, \ - gsm_pchan_name(lchan->ts->pchan), \ - new_bts->nr, \ - bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ - ## args) - -#define REQUIREMENT_A_TCHF 0x01 -#define REQUIREMENT_B_TCHF 0x02 -#define REQUIREMENT_C_TCHF 0x04 -#define REQUIREMENT_A_TCHH 0x10 -#define REQUIREMENT_B_TCHH 0x20 -#define REQUIREMENT_C_TCHH 0x40 -#define REQUIREMENT_TCHF_MASK (REQUIREMENT_A_TCHF | REQUIREMENT_B_TCHF | REQUIREMENT_C_TCHF) -#define REQUIREMENT_TCHH_MASK (REQUIREMENT_A_TCHH | REQUIREMENT_B_TCHH | REQUIREMENT_C_TCHH) -#define REQUIREMENT_A_MASK (REQUIREMENT_A_TCHF | REQUIREMENT_A_TCHH) -#define REQUIREMENT_B_MASK (REQUIREMENT_B_TCHF | REQUIREMENT_B_TCHH) -#define REQUIREMENT_C_MASK (REQUIREMENT_C_TCHF | REQUIREMENT_C_TCHH) - -struct ho_candidate { - struct gsm_lchan *lchan; /* candidate for whom */ - struct gsm_bts *bts; /* target BTS */ - uint8_t requirements; /* what is fulfilled */ - int avg; /* average RX level */ -}; - -enum ho_reason { - HO_REASON_INTERFERENCE, - HO_REASON_BAD_QUALITY, - HO_REASON_LOW_RXLEVEL, - HO_REASON_MAX_DISTANCE, - HO_REASON_BETTER_CELL, - HO_REASON_CONGESTION, -}; - -static const struct value_string ho_reason_names[] = { - { HO_REASON_INTERFERENCE, "interference (bad quality)" }, - { HO_REASON_BAD_QUALITY, "bad quality" }, - { HO_REASON_LOW_RXLEVEL, "low rxlevel" }, - { HO_REASON_MAX_DISTANCE, "maximum allowed distance" }, - { HO_REASON_BETTER_CELL, "better cell" }, - { HO_REASON_CONGESTION, "congestion" }, - {0, NULL} -}; - -static const char *ho_reason_name(int value) -{ - return get_value_string(ho_reason_names, value); -} - - -static bool hodec2_initialized = false; -static enum ho_reason global_ho_reason; - -static void congestion_check_cb(void *arg); - -/* 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) -{ - int congestion_check_interval_s; - bool was_active; - - /* Don't setup timers from VTY config parsing before the main program has actually initialized - * the data structures. */ - if (!hodec2_initialized) - return; - - was_active = net->hodec2.congestion_check_timer.active; - if (was_active) - osmo_timer_del(&net->hodec2.congestion_check_timer); - - congestion_check_interval_s = net->hodec2.congestion_check_interval_s; - if (congestion_check_interval_s < 1) { - if (was_active) - LOGP(DHODEC, LOGL_NOTICE, "HO algorithm 2: Disabling congestion check\n"); - return; - } - - LOGP(DHODEC, LOGL_DEBUG, "HO algorithm 2: next periodical congestion check in %u seconds\n", - congestion_check_interval_s); - - osmo_timer_setup(&net->hodec2.congestion_check_timer, - congestion_check_cb, net); - osmo_timer_schedule(&net->hodec2.congestion_check_timer, - congestion_check_interval_s, 0); -} - -void hodec2_on_change_congestion_check_interval(struct gsm_network *net, unsigned int new_interval) -{ - net->hodec2.congestion_check_interval_s = new_interval; - reinit_congestion_timer(net); -} - -static void conn_penalty_time_add(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, - int penalty_time) -{ - 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, bts, penalty_time); -} - -static unsigned int conn_penalty_time_remaining(struct gsm_subscriber_connection *conn, - struct gsm_bts *bts) -{ - if (!conn->hodec2.penalty_timers) - return 0; - return penalty_timers_remaining(conn->hodec2.penalty_timers, bts); -} - -/* 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) -{ - int i; - - for (i = 0; i < mr->num_cell; i++) { - struct gsm_meas_rep_cell *mrc = &mr->cell[i]; - - if (mrc->arfcn != arfcn) - continue; - if (mrc->bsic != bsic) - continue; - - return mrc; - } - return NULL; -} - -/* obtain averaged rxlev for given neighbor */ -static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) -{ - unsigned int i, idx; - int avg = 0; - - /* reduce window to the actual number of existing measurements */ - if (window > nmp->rxlev_cnt) - window = nmp->rxlev_cnt; - /* this should never happen */ - if (window <= 0) - return 0; - - idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), - nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), - window); - - for (i = 0; i < window; i++) { - int j = (idx+i) % ARRAY_SIZE(nmp->rxlev); - - avg += nmp->rxlev[j]; - } - - return avg / window; -} - -/* Find empty slot or the worst neighbor. */ -static struct neigh_meas_proc *find_unused_or_worst_neigh(struct gsm_lchan *lchan) -{ - struct neigh_meas_proc *nmp_worst = NULL; - int worst; - int j; - - /* First try to find an empty/unused slot. */ - for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { - struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; - if (!nmp->arfcn) - return nmp; - } - - /* No empty slot found. Return worst neighbor to be evicted. */ - worst = 0; /* (overwritten on first loop, but avoid compiler warning) */ - for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { - struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; - int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG); - if (nmp_worst && avg >= worst) - continue; - worst = avg; - nmp_worst = nmp; - } - - return nmp_worst; -} - -/* process neighbor cell measurement reports */ -static void process_meas_neigh(struct gsm_meas_rep *mr) -{ - int i, j, idx; - - /* For each reported cell, try to update measurements we already have from previous reports. */ - for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) { - struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j]; - unsigned int idx; - struct gsm_meas_rep_cell *mrc; - - /* skip unused entries */ - if (!nmp->arfcn) - continue; - - mrc = cell_in_rep(mr, nmp->arfcn, nmp->bsic); - idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); - if (mrc) { - nmp->rxlev[idx] = mrc->rxlev; - nmp->last_seen_nr = mr->nr; - LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u rxlev=%d last_seen_nr=%u\n", - nmp->arfcn, mrc->rxlev, nmp->last_seen_nr); - mrc->flags |= MRC_F_PROCESSED; - } else { - nmp->rxlev[idx] = 0; - LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u not in report (last_seen_nr=%u)\n", - nmp->arfcn, nmp->last_seen_nr); - } - nmp->rxlev_cnt++; - } - - /* Add cells that we don't know about yet, if necessary overwriting previous records that reflect - * cells with worse receive levels */ - for (i = 0; i < mr->num_cell; i++) { - struct gsm_meas_rep_cell *mrc = &mr->cell[i]; - struct neigh_meas_proc *nmp; - - if (mrc->flags & MRC_F_PROCESSED) - continue; - - nmp = find_unused_or_worst_neigh(mr->lchan); - - nmp->arfcn = mrc->arfcn; - nmp->bsic = mrc->bsic; - - nmp->rxlev_cnt = 0; - idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); - nmp->rxlev[idx] = mrc->rxlev; - nmp->rxlev_cnt++; - nmp->last_seen_nr = mr->nr; - LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u new in report rxlev=%d last_seen_nr=%u\n", - nmp->arfcn, mrc->rxlev, nmp->last_seen_nr); - - mrc->flags |= MRC_F_PROCESSED; - } -} - -static bool codec_type_is_supported(struct gsm_subscriber_connection *conn, - enum gsm0808_speech_codec_type type) -{ - int i; - struct gsm0808_speech_codec_list *clist = &conn->codec_list; - - if (!conn->codec_list_present) { - /* We don't have a list of supported codecs. This should never happen. */ - LOGPHOLCHAN(conn->lchan, LOGL_ERROR, - "No Speech Codec List present, accepting all codecs\n"); - return true; - } - - for (i = 0; i < clist->len; i++) { - if (clist->codec[i].type == type) { - LOGPHOLCHAN(conn->lchan, LOGL_DEBUG, "%s supported\n", - gsm0808_speech_codec_type_name(type)); - return true; - } - } - LOGPHOLCHAN(conn->lchan, LOGL_DEBUG, "Codec not supported by MS or not allowed by MSC: %s\n", - gsm0808_speech_codec_type_name(type)); - return false; -} - -/* - * Check what requirements the given cell fulfills. - * A bit mask of fulfilled requirements is returned. - * - * Target cell requirement A -- ability to service the call - * - * In order to successfully handover/assign to a better cell, the target cell - * must be able to continue the current call. Therefore the cell must fulfill - * the following criteria: - * - * * The handover must be enabled for the target cell, if it differs from the - * originating cell. - * * The assignment must be enabled for the cell, if it equals the current - * cell. - * * The handover penalty timer must not run for the cell. - * * If FR, EFR or HR codec is used, the cell must support this codec. - * * If FR or EFR codec is used, the cell must have a TCH/F slot type - * available. - * * If HR codec is used, the cell must have a TCH/H slot type available. - * * If AMR codec is used, the cell must have a TCH/F slot available, if AFS - * is supported by mobile and BTS. - * * If AMR codec is used, the cell must have a TCH/H slot available, if AHS - * is supported by mobile and BTS. - * * osmo-nitb with built-in MNCC application: - * o If AMR codec is used, the cell must support AMR codec with equal codec - * rate or rates. (not meaning TCH types) - * * If defined, the number of maximum unsynchronized handovers to this cell - * may not be exceeded. (This limits processing load for random access - * bursts.) - * - * - * Target cell requirement B -- avoid congestion - * - * In order to prevent congestion of a target cell, the cell must fulfill the - * requirement A, but also: - * - * * The minimum free channels, that are defined for that cell must be - * maintained after handover/assignment. - * * The minimum free channels are defined for TCH/F and TCH/H slot types - * individually. - * - * - * Target cell requirement C -- balance congestion - * - * In order to balance congested cells, the target cell must fulfill the - * requirement A, but also: - * - * * The target cell (which is congested also) must have more or equal free - * slots after handover/assignment. - * * 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) -{ - int count; - uint8_t requirement = 0; - unsigned int penalty_time; - struct gsm_bts *current_bts = lchan->ts->trx->bts; - - /* 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; - } - } else { - if (!ho_get_ho_active(bts->ho)) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "not a candidate, handover is disabled in target BTS\n"); - return 0; - } - } - - /* the handover penalty timer must not run for this bts */ - penalty_time = conn_penalty_time_remaining(lchan->conn, bts); - if (penalty_time) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, target BTS still in penalty time" - " (%u seconds left)\n", penalty_time); - return 0; - } - - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "tch_mode='%s' type='%s'\n", - get_value_string(gsm48_chan_mode_names, lchan->tch_mode), - gsm_lchant_name(lchan->type)); - - /* compatibility check for codecs. - * if so, the candidates for full rate and half rate are selected */ - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_V1: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: /* mandatory */ - requirement |= REQUIREMENT_A_TCHF; - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "tch_mode='%s' type='%s' supported\n", - get_value_string(gsm48_chan_mode_names, lchan->tch_mode), - gsm_lchant_name(lchan->type)); - break; - case GSM_LCHAN_TCH_H: - if (!bts->codec.hr) { - LOGPHOLCHANTOBTS(lchan, 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)); - break; - } - if (codec_type_is_supported(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; - } - break; - case GSM48_CMODE_SPEECH_EFR: - if (!bts->codec.efr) { - LOGPHOBTS(bts, LOGL_DEBUG, "EFR not supported\n"); - break; - } - if (codec_type_is_supported(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"); - break; - } - if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3)) - requirement |= REQUIREMENT_A_TCHF; - if (codec_type_is_supported(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"); - return 0; - } - - /* 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; - } - - /* remove slot types that are not available */ - if (!tchf_count && requirement & REQUIREMENT_A_TCHF) { - LOGPHOLCHANTOBTS(lchan, 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, - "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; - } - - /* omit same channel type on same BTS (will not change anything) */ - if (bts == current_bts) { - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "removing TCH/F, already on TCH/F in this cell\n"); - requirement &= ~(REQUIREMENT_A_TCHF); - break; - case GSM_LCHAN_TCH_H: - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "removing TCH/H, already on TCH/H in this cell\n"); - requirement &= ~(REQUIREMENT_A_TCHH); - break; - default: - break; - } - - if (!requirement) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, - "Reassignment within cell not an option, no differing channel types available\n"); - return 0; - } - } - -#ifdef LEGACY - // 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) { - case GSM_LCHAN_TCH_F: - if ((requirement & REQUIREMENT_A_TCHF) - && !!memcmp(¤t_bts->mr_full, &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, - 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, - sizeof(struct amr_multirate_conf))) - requirement &= ~(REQUIREMENT_A_TCHF); - if ((requirement & REQUIREMENT_A_TCHH) - && !!memcmp(¤t_bts->mr_half, &bts->mr_half, - sizeof(struct amr_multirate_conf))) - requirement &= ~(REQUIREMENT_A_TCHH); - break; - default: - break; - } - - if (!requirement) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "not a candidate, cannot provide identical codec rate\n"); - return 0; - } - } -#endif - - /* the maximum number of unsynchonized handovers must no be exceeded */ - if (current_bts != bts - && bsc_ho_count(bts, true) >= ho_get_hodec2_ho_max(bts->ho)) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "not a candidate, number of allowed handovers (%d) would be exceeded\n", - ho_get_hodec2_ho_max(bts->ho)); - return 0; - } - - /* Requirement B */ - - /* 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)) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "TCH/F would not be congested after HO\n"); - requirement |= REQUIREMENT_B_TCHF; - } else { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "TCH/F would be congested after HO\n"); - } - } - if (requirement & REQUIREMENT_A_TCHH) { - if (tchh_count - 1 >= ho_get_hodec2_tchh_min_slots(bts->ho)) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "TCH/H would not be congested after HO\n"); - requirement |= REQUIREMENT_B_TCHH; - } else { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "TCH/H would be congested after HO\n"); - } - } - - /* 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); - if (requirement & REQUIREMENT_A_TCHF) { - if (tchf_count - 1 >= count + 1) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "TCH/F would be less congested in target than source cell after HO\n"); - requirement |= REQUIREMENT_C_TCHF; - } else { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "TCH/F would not be less congested in target than source cell after HO\n"); - } - } - if (requirement & REQUIREMENT_A_TCHH) { - if (tchh_count - 1 >= count + 1) { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "TCH/H would be less congested in target than source cell after HO\n"); - requirement |= REQUIREMENT_C_TCHH; - } else { - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, - "TCH/H would not be less congested in target than source cell after HO\n"); - } - } - - LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "requirements=0x%x\n", requirement); - - /* return mask of fulfilled requirements */ - return requirement; -} - -/* Trigger handover or assignment depending on the target BTS */ -static int trigger_handover_or_assignment(struct gsm_lchan *lchan, struct gsm_bts *new_bts, uint8_t requirements) -{ - struct gsm_bts *current_bts = lchan->ts->trx->bts; - int afs_bias = 0; - bool full_rate = false; - - if (current_bts == new_bts) - LOGPHOLCHAN(lchan, LOGL_NOTICE, "Triggering Assignment\n"); - else - LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_NOTICE, "Triggering Handover\n"); - - /* 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); - - /* select TCH rate, prefer TCH/F if AFS is improved */ - switch (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; - } - full_rate = true; - break; - } - /* change to half rate */ - if (!(requirements & REQUIREMENT_TCHH_MASK)) { - LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, - "neither TCH/F nor TCH/H requested, aborting ho/as\n"); - return -EINVAL; - } - break; - case GSM_LCHAN_TCH_H: - /* change to full rate if AFS is improved and a candidate */ - if (afs_bias > 0 && (requirements & REQUIREMENT_TCHF_MASK)) { - full_rate = true; - LOGPHOLCHAN(lchan, LOGL_DEBUG, "[Improve AHS->AFS]\n"); - break; - } - /* change to full rate if the only candidate */ - if ((requirements & REQUIREMENT_TCHF_MASK) - && !(requirements & REQUIREMENT_TCHH_MASK)) { - full_rate = true; - break; - } - /* keep on half rate */ - if (!(requirements & REQUIREMENT_TCHH_MASK)) { - LOGPHOLCHANTOBTS(lchan, new_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; - } - break; - default: - LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, "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", - full_rate ? "TCH/F" : "TCH/H", - ho_reason_name(global_ho_reason)); - else - LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_NOTICE, - "Triggering handover to %s, due to %s\n", - full_rate ? "TCH/F" : "TCH/H", - ho_reason_name(global_ho_reason)); - - return bsc_handover_start(HODEC2, lchan, current_bts == new_bts? NULL : new_bts, - full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H); -} - -/* debug collected candidates */ -static inline void debug_candidate(struct ho_candidate *candidate, - int neighbor, int8_t rxlev, int tchf_count, int tchh_count) -{ - if (neighbor) - LOGP(DHODEC, LOGL_DEBUG, " - neighbor BTS %d, RX level " - "%d -> %d\n", candidate->bts->nr, rxlev2dbm(rxlev), - rxlev2dbm(candidate->avg)); - else - LOGP(DHODEC, LOGL_DEBUG, " - current BTS %d, RX level %d\n", - candidate->bts->nr, rxlev2dbm(candidate->avg)); - - LOGP(DHODEC, LOGL_DEBUG, " o free TCH/F slots %d, minimum required " - "%d\n", tchf_count, ho_get_hodec2_tchf_min_slots(candidate->bts->ho)); - LOGP(DHODEC, LOGL_DEBUG, " o free TCH/H slots %d, minimum required " - "%d\n", tchh_count, ho_get_hodec2_tchh_min_slots(candidate->bts->ho)); - - if ((candidate->requirements & REQUIREMENT_TCHF_MASK)) - LOGP(DHODEC, LOGL_DEBUG, " o requirement "); - else - LOGP(DHODEC, LOGL_DEBUG, " o no requirement "); - if ((candidate->requirements & REQUIREMENT_A_TCHF)) - LOGPC(DHODEC, LOGL_DEBUG, "A "); - if ((candidate->requirements & REQUIREMENT_B_TCHF)) - LOGPC(DHODEC, LOGL_DEBUG, "B "); - if ((candidate->requirements & REQUIREMENT_C_TCHF)) - LOGPC(DHODEC, LOGL_DEBUG, "C "); - LOGPC(DHODEC, LOGL_DEBUG, "fulfilled for TCHF"); - if (!(candidate->requirements & REQUIREMENT_TCHF_MASK)) /* nothing */ - LOGPC(DHODEC, LOGL_DEBUG, " (no %s possible)\n", - (neighbor) ? "handover" : "assignment"); - else if ((candidate->requirements & REQUIREMENT_TCHF_MASK) - == REQUIREMENT_A_TCHF) /* only A */ - LOGPC(DHODEC, LOGL_DEBUG, " (more congestion after %s)\n", - (neighbor) ? "handover" : "assignment"); - else if ((candidate->requirements & REQUIREMENT_B_TCHF)) /* B incl. */ - LOGPC(DHODEC, LOGL_DEBUG, " (not congested after %s)\n", - (neighbor) ? "handover" : "assignment"); - else /* so it must include C */ - LOGPC(DHODEC, LOGL_DEBUG, " (less or equally congested after " - "%s)\n", (neighbor) ? "handover" : "assignment"); - - if ((candidate->requirements & REQUIREMENT_TCHH_MASK)) - LOGP(DHODEC, LOGL_DEBUG, " o requirement "); - else - LOGP(DHODEC, LOGL_DEBUG, " o no requirement "); - if ((candidate->requirements & REQUIREMENT_A_TCHH)) - LOGPC(DHODEC, LOGL_DEBUG, "A "); - if ((candidate->requirements & REQUIREMENT_B_TCHH)) - LOGPC(DHODEC, LOGL_DEBUG, "B "); - if ((candidate->requirements & REQUIREMENT_C_TCHH)) - LOGPC(DHODEC, LOGL_DEBUG, "C "); - LOGPC(DHODEC, LOGL_DEBUG, "fulfilled for TCHH"); - if (!(candidate->requirements & REQUIREMENT_TCHH_MASK)) /* nothing */ - LOGPC(DHODEC, LOGL_DEBUG, " (no %s possible)\n", - (neighbor) ? "handover" : "assignment"); - else if ((candidate->requirements & REQUIREMENT_TCHH_MASK) - == REQUIREMENT_A_TCHH) /* only A */ - LOGPC(DHODEC, LOGL_DEBUG, " (more congestion after %s)\n", - (neighbor) ? "handover" : "assignment"); - else if ((candidate->requirements & REQUIREMENT_B_TCHH)) /* B incl. */ - LOGPC(DHODEC, LOGL_DEBUG, " (not congested after %s)\n", - (neighbor) ? "handover" : "assignment"); - else /* so it must include C */ - LOGPC(DHODEC, LOGL_DEBUG, " (less or equally congested after " - "%s)\n", (neighbor) ? "handover" : "assignment"); -} - -/* 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) -{ - 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 = &clist[*candidates]; - c->lchan = lchan; - c->bts = bts; - c->requirements = check_requirements(lchan, bts, tchf_count, tchh_count); - c->avg = av_rxlev; - debug_candidate(c, 0, 0, tchf_count, tchh_count); - (*candidates)++; -} - -/* 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, - int *neighbors_count) -{ - struct gsm_bts *bts = lchan->ts->trx->bts; - int tchf_count, tchh_count; - struct gsm_bts *neighbor_bts; - int avg; - struct ho_candidate *c; - int min_rxlev; - - /* skip empty slots */ - if (nmp->arfcn == 0) - return; - - if (neighbors_count) - (*neighbors_count)++; - - /* skip if measurement report is old */ - if (nmp->last_seen_nr != lchan->meas_rep_last_seen_nr) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, "neighbor ARFCN %u measurement report is old" - " (nmp->last_seen_nr=%u lchan->meas_rep_last_seen_nr=%u)\n", - nmp->arfcn, nmp->last_seen_nr, lchan->meas_rep_last_seen_nr); - return; - } - - neighbor_bts = bts_by_arfcn_bsic(bts->network, nmp->arfcn, nmp->bsic); - if (!neighbor_bts) { - LOGPHOBTS(bts, LOGL_DEBUG, "neighbor ARFCN %u does not belong to this network\n", - nmp->arfcn); - return; - } - - /* in case we have measurements of our bts, due to misconfiguration */ - if (neighbor_bts == bts) { - LOGPHOBTS(bts, LOGL_ERROR, "Configuration error: this BTS appears as its own neighbor\n"); - return; - } - - /* caculate average rxlev for this cell over the window */ - avg = neigh_meas_avg(nmp, ho_get_hodec2_rxlev_neigh_avg_win(bts->ho)); - - /* 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)) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, - "BTS %d is not a candidate, because RX level (%d) is lower" - " or equal than current RX level (%d) + hysteresis (%d)\n", - neighbor_bts->nr, rxlev2dbm(avg), rxlev2dbm(av_rxlev), pwr_hyst); - return; - } - } - - /* if the minimum level is not reached */ - min_rxlev = ho_get_hodec2_min_rxlev(neighbor_bts->ho); - if (rxlev2dbm(avg) < min_rxlev) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, - "BTS %d is not a candidate, because RX level (%d) is lower" - " than its minimum required RX level (%d)\n", - neighbor_bts->nr, rxlev2dbm(avg), min_rxlev); - return; - } - - 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 = &clist[*candidates]; - c->lchan = lchan; - c->bts = neighbor_bts; - c->requirements = check_requirements(lchan, neighbor_bts, tchf_count, - tchh_count); - c->avg = avg; - debug_candidate(c, 1, av_rxlev, tchf_count, tchh_count); - (*candidates)++; -} - -static void collect_candidates_for_lchan(struct gsm_lchan *lchan, - struct ho_candidate *clist, unsigned int *candidates, - int *_av_rxlev, bool include_weaker_rxlev) -{ - struct gsm_bts *bts = lchan->ts->trx->bts; - int av_rxlev; - unsigned int candidates_was; - bool assignment; - bool handover; - int neighbors_count = 0; - unsigned int rxlev_avg_win = ho_get_hodec2_rxlev_avg_win(bts->ho); - - OSMO_ASSERT(candidates); - candidates_was = *candidates; - - /* caculate 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; - - /* in case there is no measurment report (yet) */ - if (av_rxlev < 0) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not collecting candidates, not enough measurements" - " (got %d, want %u)\n", - lchan->meas_rep_count, rxlev_avg_win); - return; - } - - assignment = ho_get_hodec2_as_active(bts->ho); - handover = ho_get_ho_active(bts->ho); - - LOGPHOLCHAN(lchan, LOGL_DEBUG, "Collecting candidates for%s%s%s\n", - assignment ? " Assignment" : "", - assignment && handover ? " and" : "", - handover ? " Handover" : ""); - - if (assignment) - collect_assignment_candidate(lchan, clist, candidates, av_rxlev); - - 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); - } - } - - LOGPHOLCHAN(lchan, LOGL_DEBUG, "adding %u candidates from %u neighbors, total %u\n", - *candidates - candidates_was, neighbors_count, *candidates); -} - -/* - * Search for a alternative / better cell. - * - * Do not trigger handover/assignment on slots which have already ongoing - * handover/assignment processes. If no AFS improvement offset is given, try to - * maintain the same TCH rate, if available. - * Do not perform this process, if handover and assignment are disabled for - * the current cell. - * Do not perform handover, if the minimum acceptable RX level - * is not reched for this cell. - * - * If one or more 'better cells' are available, check the current and neighbor - * cell measurements in descending order of their RX levels (down-link): - * - * * Select the best candidate that fulfills requirement B (no congestion - * after handover/assignment) and trigger handover or assignment. - * * If no candidate fulfills requirement B, select the best candidate that - * fulfills requirement C (less or equally congested cells after handover) - * and trigger handover or assignment. - * * If no candidate fulfills requirement C, do not perform handover nor - * assignment. - * - * If the RX level (down-link) or RX quality (down-link) of the current cell is - * below minimum acceptable level, or if the maximum allowed timing advance is - * reached or exceeded, check the RX levels (down-link) of the current and - * neighbor cells in descending order of their levels: (bad BTS case) - * - * * Select the best candidate that fulfills requirement B (no congestion after - * handover/assignment) and trigger handover or assignment. - * * If no candidate fulfills requirement B, select the best candidate that - * fulfills requirement C (less or equally congested cells after handover) - * and trigger handover or assignment. - * * If no candidate fulfills requirement C, select the best candidate that - * fulfills requirement A (ignore congestion after handover or assignment) - * and trigger handover or assignment. - * * If no candidate fulfills requirement A, do not perform handover nor - * assignment. - * - * RX levels (down-link) of current and neighbor cells: - * - * * The RX levels of the current cell and neighbor cells are improved by a - * given offset, if AFS (AMR on TCH/F) is used or is a candidate for - * handover/assignment. - * * If AMR is used, the requirement for handover is checked for TCH/F and - * TCH/H. Both results (if any) are used as a candidate. - * * If AMR is used, the requirement for assignment to a different TCH slot - * rate is checked. The result (if available) is used as a candidate. - * - * 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) -{ - struct gsm_bts *bts = lchan->ts->trx->bts; - int ahs = (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR - && lchan->type == GSM_LCHAN_TCH_H); - int av_rxlev; - struct ho_candidate clist[1 + ARRAY_SIZE(lchan->neigh_meas)]; - unsigned int candidates = 0; - int i; - struct ho_candidate *best_cand = NULL; - unsigned int best_better_db; - bool best_applied_afs_bias = false; - int better; - - /* check for disabled handover/assignment at the current cell */ - if (!ho_get_hodec2_as_active(bts->ho) - && !ho_get_ho_active(bts->ho)) { - LOGP(DHODEC, LOGL_INFO, "Skipping, Handover and Assignment both disabled in this cell\n"); - return 0; - } - - collect_candidates_for_lchan(lchan, clist, &candidates, &av_rxlev, 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. */ - if (!candidates) - goto no_candidates; - - /* select best candidate that fulfills requirement B: no congestion after HO */ - best_better_db = 0; - for (i = 0; i < candidates; i++) { - int afs_bias; - if (!(clist[i].requirements & REQUIREMENT_B_MASK)) - continue; - - better = clist[i].avg - av_rxlev; - /* Apply AFS bias? */ - afs_bias = 0; - if (ahs && (clist[i].requirements & REQUIREMENT_B_TCHF)) - afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); - better += afs_bias; - if (better > best_better_db) { - best_cand = &clist[i]; - best_better_db = better; - best_applied_afs_bias = afs_bias? true : false; - } - } - - /* perform handover, if there is a candidate */ - if (best_cand) { - LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d%s\n", - rxlev2dbm(best_cand->avg), - best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); - return trigger_handover_or_assignment(lchan, best_cand->bts, - best_cand->requirements & REQUIREMENT_B_MASK); - } - - /* select best candidate that fulfills requirement C: less or equal congestion after HO */ - best_better_db = 0; - for (i = 0; i < candidates; i++) { - int afs_bias; - if (!(clist[i].requirements & REQUIREMENT_C_MASK)) - continue; - - better = clist[i].avg - av_rxlev; - /* Apply AFS bias? */ - afs_bias = 0; - if (ahs && (clist[i].requirements & REQUIREMENT_C_TCHF)) - afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); - better += afs_bias; - if (better > best_better_db) { - best_cand = &clist[i]; - best_better_db = better; - best_applied_afs_bias = afs_bias? true : false; - } - } - - /* perform handover, if there is a candidate */ - if (best_cand) { - LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d%s\n", - rxlev2dbm(best_cand->avg), - best_applied_afs_bias? " (applied AHS -> AFS rxlev bias)" : ""); - return trigger_handover_or_assignment(lchan, best_cand->bts, - best_cand->requirements & REQUIREMENT_C_MASK); - } - - /* we are done in case the MS RXLEV/RXQUAL/TA aren't critical and we're avoiding congestion. */ - if (!include_weaker_rxlev) - goto no_candidates; - - /* Select best candidate that fulfills requirement A: can service the call. - * From above we know that there are no options that avoid congestion. Here we're trying to find - * *any* free lchan that has no critically low RXLEV and is able to handle the MS. */ - best_better_db = 0; - for (i = 0; i < candidates; i++) { - int afs_bias; - if (!(clist[i].requirements & REQUIREMENT_A_MASK)) - continue; - - better = clist[i].avg - av_rxlev; - /* Apply AFS bias? */ - afs_bias = 0; - if (ahs && (clist[i].requirements & REQUIREMENT_A_TCHF)) - afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); - better += afs_bias; - if (better > best_better_db) { - best_cand = &clist[i]; - best_better_db = better; - best_applied_afs_bias = afs_bias? true : false; - } - } - - /* perform handover, if there is a candidate */ - if (best_cand) { - LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d" - " with greater congestion found%s\n", - rxlev2dbm(best_cand->avg), - best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); - return trigger_handover_or_assignment(lchan, best_cand->bts, - best_cand->requirements & REQUIREMENT_A_MASK); - } - - /* Damn, all is congested, has too low RXLEV or cannot service the voice call due to codec - * restrictions or because all lchans are taken. */ - -no_candidates: - if (include_weaker_rxlev) - LOGPHOLCHAN(lchan, LOGL_INFO, "No alternative lchan found\n"); - else - LOGPHOLCHAN(lchan, LOGL_INFO, "No better/less congested neighbor cell found\n"); - - return 0; -} - -/* - * Handover/assignment check, if measurement report is received - * - * Do not trigger handover/assignment on slots which have already ongoing - * handover/assignment processes. - * - * In case of handover triggered because maximum allowed timing advance is - * exceeded, the handover penalty timer is started for the originating cell. - * - */ -static void on_measurement_report(struct gsm_meas_rep *mr) -{ - struct gsm_lchan *lchan = mr->lchan; - struct gsm_bts *bts = lchan->ts->trx->bts; - int av_rxlev = -EINVAL, av_rxqual = -EINVAL; - unsigned int pwr_interval; - - /* we currently only do handover for TCH channels */ - switch (mr->lchan->type) { - case GSM_LCHAN_TCH_F: - case GSM_LCHAN_TCH_H: - break; - default: - return; - } - - if (log_check_level(DHODEC, LOGL_DEBUG)) { - int i; - LOGPHOLCHAN(lchan, LOGL_DEBUG, "MEASUREMENT REPORT (%d neighbors)\n", - mr->num_cell); - for (i = 0; i < mr->num_cell; i++) { - struct gsm_meas_rep_cell *mrc = &mr->cell[i]; - LOGPHOLCHAN(lchan, LOGL_DEBUG, - " %d: arfcn=%u bsic=%u neigh_idx=%u rxlev=%u flags=%x\n", - i, mrc->arfcn, mrc->bsic, mrc->neigh_idx, mrc->rxlev, mrc->flags); - } - } - - /* parse actual neighbor cell info */ - if (mr->num_cell > 0 && mr->num_cell < 7) - process_meas_neigh(mr); - - /* check for ongoing handover/assignment */ - if (!lchan->conn) { - LOGPHOLCHAN(lchan, LOGL_ERROR, "Skipping, No subscriber connection???\n"); - return; - } - if (lchan->conn->secondary_lchan) { - LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Initial Assignment is still ongoing\n"); - return; - } - if (lchan->conn->ho) { - LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Handover already triggered\n"); - return; - } - - LOGPHOLCHAN(lchan, LOGL_DEBUG, "HODEC2: evaluating measurement report\n"); - - /* get average levels. if not enought 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)); - if (av_rxlev < 0 && av_rxqual < 0) { - LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Not enough recent measurements\n"); - return; - } - if (av_rxlev >= 0) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, "Measurement report: average RX level = %d\n", - rxlev2dbm(av_rxlev)); - } - if (av_rxqual >= 0) { - LOGPHOLCHAN(lchan, LOGL_DEBUG, "Measurement report: average RX quality = %d\n", - av_rxqual); - } - - /* improve levels in case of AFS, if defined */ - if (lchan->type == GSM_LCHAN_TCH_F - && lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { - int rxlev_bias = ho_get_hodec2_afs_bias_rxlev(bts->ho); - int rxqual_bias = ho_get_hodec2_afs_bias_rxqual(bts->ho); - if (av_rxlev >= 0 && rxlev_bias) { - int imp = av_rxlev + rxlev_bias; - LOGPHOLCHAN(lchan, LOGL_INFO, "Virtually improving RX level from %d to %d," - " due to AFS bias\n", rxlev2dbm(av_rxlev), rxlev2dbm(imp)); - av_rxlev = imp; - } - if (av_rxqual >= 0 && rxqual_bias) { - int imp = av_rxqual - rxqual_bias; - if (imp < 0) - imp = 0; - LOGPHOLCHAN(lchan, LOGL_INFO, "Virtually improving RX quality from %d to %d," - " due to AFS bias\n", rxlev2dbm(av_rxqual), rxlev2dbm(imp)); - av_rxqual = imp; - } - } - - /* Bad Quality */ - if (av_rxqual >= 0 && av_rxqual > ho_get_hodec2_min_rxqual(bts->ho)) { - if (rxlev2dbm(av_rxlev) > -85) { - global_ho_reason = HO_REASON_INTERFERENCE; - LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment" - " due to interference (bad quality)\n"); - } else { - global_ho_reason = HO_REASON_BAD_QUALITY; - LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment due to bad quality\n"); - } - find_alternative_lchan(lchan, true); - return; - } - - /* Low Level */ - if (av_rxlev >= 0 && rxlev2dbm(av_rxlev) < ho_get_hodec2_min_rxlev(bts->ho)) { - global_ho_reason = HO_REASON_LOW_RXLEVEL; - LOGPHOLCHAN(lchan, LOGL_INFO, "Attempting handover/assignment due to low rxlev\n"); - find_alternative_lchan(lchan, true); - return; - } - - /* Max Distance */ - if (lchan->meas_rep_count > 0 - && lchan->rqd_ta > ho_get_hodec2_max_distance(bts->ho)) { - global_ho_reason = HO_REASON_MAX_DISTANCE; - LOGPHOLCHAN(lchan, LOGL_INFO, "Attempting handover due to high TA\n"); - /* start penalty timer to prevent comming back too - * early. it must be started before selecting a better cell, - * so there is no assignment selected, due to running - * penalty timer. */ - conn_penalty_time_add(lchan->conn, bts, ho_get_hodec2_penalty_max_dist(bts->ho)); - find_alternative_lchan(lchan, true); - return; - } - - /* pwr_interval's range is 1-99, clarifying that no div-zero shall happen in modulo below: */ - pwr_interval = ho_get_hodec2_pwr_interval(bts->ho); - OSMO_ASSERT(pwr_interval); - - /* try handover to a better cell */ - if (av_rxlev >= 0 && (mr->nr % pwr_interval) == 0) { - LOGPHOLCHAN(lchan, LOGL_INFO, "Looking whether a cell has better RXLEV\n"); - global_ho_reason = HO_REASON_BETTER_CELL; - find_alternative_lchan(lchan, false); - } -} - -/* - * Handover/assignment check after timer timeout: - * - * Even if handover process tries to prevent a congestion, a cell might get - * congested due to new call setups or handovers to prevent loss of radio link. - * A cell is congested, if not the minimum number of free slots are available. - * The minimum number can be defined for TCH/F and TCH/H individually. - * - * Do not perform congestion check, if no minimum free slots are defined for - * a cell. - * Do not trigger handover/assignment on slots which have already ongoing - * handover/assignment processes. If no AFS improvement offset is given, try to - * maintain the same TCH rate, if available. - * Do not perform this process, if handover and assignment are disabled for - * the current cell. - * Do not perform handover, if the minimum acceptable RX level - * is not reched for this cell. - * Only check candidates that will solve/reduce congestion. - * - * If a cell is congested, all slots are checked for all their RX levels - * (down-link) of the current and neighbor cell measurements in descending - * order of their RX levels: - * - * * Select the best candidate that fulfills requirement B (no congestion after - * handover/assignment), trigger handover or assignment. Candidates that will - * cause an assignment from AHS (AMR on TCH/H) to AFS (AMR on TCH/F) are - * omitted. - * o This process repeated until the minimum required number of free slots - * are restored or if all cell measurements are checked. The process ends - * then, otherwise: - * * Select the worst candidate that fulfills requirement B, trigger - * assignment. Note that only assignment candidates for changing from AHS to - * AFS are left. - * o This process repeated until the minimum required number of free slots - * are restored or if all cell measurements are checked. The process ends - * then, otherwise: - * * Select the best candidates that fulfill requirement C (less or equally - * congested cells after handover/assignment), trigger handover or - * assignment. Candidates that will cause an assignment from AHS (AMR on - * TCH/H) to AFS (AMR on TCH/F) are omitted. - * o This process repeated until the minimum required number of free slots - * are restored or if all cell measurements are checked. The process ends - * then, otherwise: - * * Select the worst candidate that fulfills requirement C, trigger - * assignment. Note that only assignment candidates for changing from AHS to - * AFS are left. - * o This process repeated until the minimum required number of free slots - * are restored or if all cell measurements are checked. - */ -static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int tchh_congestion) -{ - struct gsm_lchan *lc; - struct gsm_bts_trx *trx; - struct gsm_bts_trx_ts *ts; - 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; - int rc = 0; - int any_ho = 0; - int is_improved = 0; - - if (tchf_congestion < 0) - tchf_congestion = 0; - if (tchh_congestion < 0) - tchh_congestion = 0; - - LOGPHOBTS(bts, LOGL_INFO, "congested: %d TCH/F and %d TCH/H should be moved\n", - tchf_congestion, tchh_congestion); - - /* allocate array of all bts */ - clist = talloc_zero_array(tall_bsc_ctx, struct ho_candidate, - bts->num_trx * 8 * 2 * (1 + ARRAY_SIZE(lc->neigh_meas))); - if (!clist) - return 0; - - candidates = 0; - - /* loop through all active lchan and collect candidates */ - llist_for_each_entry(trx, &bts->trx_list, list) { - if (!trx_is_usable(trx)) - continue; - - for (i = 0; i < 8; i++) { - ts = &trx->ts[i]; - if (!ts_is_usable(ts)) - continue; - - /* (Do not consider dynamic TS that are in PDCH mode) */ - switch (ts_pchan(ts)) { - case GSM_PCHAN_TCH_F: - lc = &ts->lchan[0]; - /* omit if channel not active */ - if (lc->type != GSM_LCHAN_TCH_F - || lc->state != LCHAN_S_ACTIVE) - break; - /* omit if there is an ongoing ho/as */ - if (!lc->conn || lc->conn->secondary_lchan - || lc->conn->ho) - break; - /* We desperately want to resolve congestion, ignore rxlev when - * collecting candidates by passing include_weaker_rxlev=true. */ - collect_candidates_for_lchan(lc, clist, &candidates, NULL, true); - break; - case GSM_PCHAN_TCH_H: - for (j = 0; j < 2; j++) { - lc = &ts->lchan[j]; - /* omit if channel not active */ - if (lc->type != GSM_LCHAN_TCH_H - || lc->state != LCHAN_S_ACTIVE) - continue; - /* omit of there is an ongoing ho/as */ - if (!lc->conn - || lc->conn->secondary_lchan - || lc->conn->ho) - continue; - /* We desperately want to resolve congestion, ignore rxlev when - * collecting candidates by passing include_weaker_rxlev=true. */ - collect_candidates_for_lchan(lc, clist, &candidates, NULL, true); - } - break; - default: - break; - } - } - } - - if (!candidates) { - LOGPHOBTS(bts, LOGL_DEBUG, "No neighbor cells qualify to solve congestion\n"); - goto exit; - } - if (log_check_level(DHODEC, LOGL_DEBUG)) { - LOGPHOBTS(bts, LOGL_DEBUG, "Considering %u candidates to solve congestion:\n", candidates); - for (i = 0; i < candidates; i++) { - LOGPHOLCHANTOBTS(clist[i].lchan, clist[i].bts, LOGL_DEBUG, - "#%d: req=0x%x avg-rxlev=%d\n", - i, clist[i].requirements, clist[i].avg); - } - } - -#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; - - 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; - LOGP(DHODEC, 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 */ - if (best_cand) { - any_ho = 1; - LOGPHOLCHAN(best_cand->lchan, LOGL_INFO, - "Best candidate BTS %u (RX level %d) without congestion found\n", - best_cand->bts->nr, rxlev2dbm(best_cand->avg)); - if (is_improved) - LOGP(DHODEC, LOGL_INFO, "(is improved due to " - "AHS -> AFS)\n"); - trigger_handover_or_assignment(best_cand->lchan, best_cand->bts, - best_cand->requirements & REQUIREMENT_B_MASK); -#if 0 - /* if there is still congestion, mark lchan as deleted - * and redo this process */ - if (best_cand->lchan->type == GSM_LCHAN_TCH_H) - tchh_congestion--; - else - tchf_congestion--; - if (tchf_congestion > 0 || tchh_congestion > 0) { - delete_lchan = best_cand->lchan; - best_cand = NULL; - goto next_b1; - } -#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 best candidate that fulfills requirement B" - " (omitting change from AHS to AFS)\n"); - -#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; - - 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; - 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; - LOGP(DHODEC, LOGL_INFO, "Worst candidate for assignment " - "(RX level %d) from TCH/H -> TCH/F without congestion " - "found\n", rxlev2dbm(worst_cand->avg)); - if (is_improved) - LOGP(DHODEC, LOGL_INFO, "(is improved due to " - "AHS -> AFS)\n"); - trigger_handover_or_assignment(worst_cand->lchan, - worst_cand->bts, - 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; - } - - LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a worst candidate that fulfills requirement B," - " selecting candidates that change from AHS to AFS only\n"); - -#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; - - 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; - LOGP(DHODEC, 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 */ - if (best_cand) { - any_ho = 1; - LOGP(DHODEC, LOGL_INFO, "Best candidate BTS %d (RX level %d) " - "with less or equal congestion found\n", - best_cand->bts->nr, rxlev2dbm(best_cand->avg)); - if (is_improved) - LOGP(DHODEC, LOGL_INFO, "(is improved due to " - "AHS -> AFS)\n"); - trigger_handover_or_assignment(best_cand->lchan, best_cand->bts, - best_cand->requirements & REQUIREMENT_C_MASK); -#if 0 - /* if there is still congestion, mark lchan as deleted - * and redo this process */ - if (best_cand->lchan->type == GSM_LCHAN_TCH_H) - tchh_congestion--; - else - tchf_congestion--; - if (tchf_congestion > 0 || tchh_congestion > 0) { - delete_lchan = best_cand->lchan; - best_cand = NULL; - goto next_c1; - } -#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 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; - - 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; - LOGP(DHODEC, LOGL_INFO, "Worst candidate for assignment " - "(RX level %d) from TCH/H -> TCH/F with less or equal " - "congestion found\n", rxlev2dbm(worst_cand->avg)); - if (is_improved) - LOGP(DHODEC, LOGL_INFO, "(is improved due to " - "AHS -> AFS)\n"); - trigger_handover_or_assignment(worst_cand->lchan, - worst_cand->bts, - 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"); - - -exit: - /* free array */ - talloc_free(clist); - - if (tchf_congestion <= 0 && tchh_congestion <= 0) - LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d solved!\n", - bts->nr); - else if (any_ho) - LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d reduced!\n", - bts->nr); - else - LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d can't be reduced/solved!\n", bts->nr); - - return rc; -} - -static void bts_congestion_check(struct gsm_bts *bts) -{ - int min_free_tchf, min_free_tchh; - int tchf_count, tchh_count; - - global_ho_reason = HO_REASON_CONGESTION; - - /* only check BTS if TRX 0 is usable */ - if (!trx_is_usable(bts->c0)) { - LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: TRX 0 not usable\n"); - return; - } - - /* only check BTS if handover or assignment is enabled */ - if (!ho_get_hodec2_as_active(bts->ho) - && !ho_get_ho_active(bts->ho)) { - LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: Assignment and Handover both disabled\n"); - return; - } - - min_free_tchf = ho_get_hodec2_tchf_min_slots(bts->ho); - min_free_tchh = ho_get_hodec2_tchh_min_slots(bts->ho); - - /* only check BTS with congestion level set */ - if (!min_free_tchf && !min_free_tchh) { - LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: no minimum for free TCH/F nor TCH/H set\n"); - return; - } - - tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F); - tchh_count = bts_count_free_ts(bts, GSM_PCHAN_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); - - /* only check BTS if congested */ - if (tchf_count >= min_free_tchf && tchh_count >= 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); -} - -void hodec2_congestion_check(struct gsm_network *net) -{ - struct gsm_bts *bts; - - llist_for_each_entry(bts, &net->bts_list, list) - bts_congestion_check(bts); -} - -static void congestion_check_cb(void *arg) -{ - struct gsm_network *net = arg; - hodec2_congestion_check(net); - reinit_congestion_timer(net); -} - -void on_ho_chan_activ_nack(struct bsc_handover *ho) -{ - struct gsm_bts *new_bts = ho->new_lchan->ts->trx->bts; - - LOGPHO(ho, LOGL_ERROR, "Channel Activate Nack for %s, starting penalty timer\n", ho->inter_cell? "Handover" : "Assignment"); - - /* if channel failed, wait 10 seconds before allowing to retry handover */ - conn_penalty_time_add(ho->old_lchan->conn, new_bts, 10); /* FIXME configurable */ -} - -void on_ho_failure(struct bsc_handover *ho) -{ - struct gsm_bts *old_bts = ho->old_lchan->ts->trx->bts; - struct gsm_bts *new_bts = ho->new_lchan->ts->trx->bts; - struct gsm_subscriber_connection *conn = ho->old_lchan->conn; - - if (!conn) { - LOGPHO(ho, LOGL_ERROR, "HO failure, but no conn"); - return; - } - - if (conn->hodec2.failures >= ho_get_hodec2_retries(old_bts->ho)) { - int penalty = ho->inter_cell - ? ho_get_hodec2_penalty_failed_ho(old_bts->ho) - : ho_get_hodec2_penalty_failed_as(old_bts->ho); - LOGPHO(ho, LOGL_NOTICE, "%s failed, starting penalty timer (%d s)\n", - ho->inter_cell ? "Handover" : "Assignment", - penalty); - conn->hodec2.failures = 0; - conn_penalty_time_add(conn, new_bts, penalty); - } else { - conn->hodec2.failures++; - LOGPHO(ho, LOGL_NOTICE, "%s failed, allowing handover decision to try again" - " (%d/%d attempts)\n", - ho->inter_cell ? "Handover" : "Assignment", - conn->hodec2.failures, ho_get_hodec2_retries(old_bts->ho)); - } -} - -struct handover_decision_callbacks hodec2_callbacks = { - .hodec_id = 2, - .on_measurement_report = on_measurement_report, - .on_ho_chan_activ_nack = on_ho_chan_activ_nack, - .on_ho_failure = on_ho_failure, -}; - -void hodec2_init(struct gsm_network *net) -{ - handover_decision_callbacks_register(&hodec2_callbacks); - hodec2_initialized = true; - reinit_congestion_timer(net); -} diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c deleted file mode 100644 index 960bf6993..000000000 --- a/src/libbsc/handover_logic.c +++ /dev/null @@ -1,473 +0,0 @@ -/* Handover Logic for Inter-BTS (Intra-BSC) Handover. This does not - * actually implement the handover algorithm/decision, but executes a - * handover decision */ - -/* (C) 2009 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static LLIST_HEAD(bsc_handovers); -static LLIST_HEAD(handover_decision_callbacks); - -static void handover_free(struct bsc_handover *ho) -{ - osmo_timer_del(&ho->T3103); - llist_del(&ho->list); - talloc_free(ho); -} - -static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan) -{ - struct bsc_handover *ho; - - llist_for_each_entry(ho, &bsc_handovers, list) { - if (ho->new_lchan == new_lchan) - return ho; - } - - return NULL; -} - -static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan) -{ - struct bsc_handover *ho; - - llist_for_each_entry(ho, &bsc_handovers, list) { - if (ho->old_lchan == old_lchan) - return ho; - } - - return NULL; -} - -/*! Hand over the specified logical channel to the specified new BTS and possibly change the lchan type. - * This is the main entry point for the actual handover algorithm, after the decision whether to initiate - * HO to a specific BTS. To not change the lchan type, pass old_lchan->type. */ -int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts, - enum gsm_chan_t new_lchan_type) -{ - struct gsm_subscriber_connection *conn; - struct bsc_handover *ho; - static uint8_t ho_ref = 0; - bool do_assignment; - - OSMO_ASSERT(old_lchan); - - /* don't attempt multiple handovers for the same lchan at - * the same time */ - if (bsc_ho_by_old_lchan(old_lchan)) - return -EBUSY; - - conn = old_lchan->conn; - if (!conn) { - LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n"); - return -ENOSPC; - } - - if (!new_bts) - new_bts = old_lchan->ts->trx->bts; - OSMO_ASSERT(new_bts); - - do_assignment = (new_bts == old_lchan->ts->trx->bts); - - ho = talloc_zero(conn, struct bsc_handover); - if (!ho) { - LOGP(DHO, LOGL_FATAL, "Out of Memory\n"); - return -ENOMEM; - } - ho->from_hodec_id = from_hodec_id; - ho->old_lchan = old_lchan; - ho->new_bts = new_bts; - ho->new_lchan_type = new_lchan_type; - ho->ho_ref = ho_ref++; - ho->inter_cell = !do_assignment; - ho->async = true; - llist_add(&ho->list, &bsc_handovers); - - conn->ho = ho; - - DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Initiating %s...\n", - old_lchan->ts->trx->bts->nr, - old_lchan->ts->trx->nr, - old_lchan->ts->nr, - old_lchan->nr, - gsm_pchan_name(old_lchan->ts->pchan), - new_bts->nr, - gsm_lchant_name(new_lchan_type), - do_assignment ? "Assignment" : "Handover"); - - return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HO_START, NULL); -} - -/*! Start actual handover. Call bsc_handover_start() instead; The only legal caller is the GSCON FSM in - * bsc_subscr_conn_fsm.c. */ -int bsc_handover_start_gscon(struct gsm_subscriber_connection *conn) -{ - int rc; - struct gsm_network *network = conn->network; - struct bsc_handover *ho = conn->ho; - struct gsm_lchan *old_lchan; - struct gsm_lchan *new_lchan; - - if (!ho) { - LOGP(DHO, LOGL_ERROR, "%s: Requested to start handover, but conn->ho is NULL\n", - bsc_subscr_name(conn->bsub)); - return -EINVAL; - } - - OSMO_ASSERT(ho->old_lchan && ho->new_bts); - - if (ho->old_lchan->conn != conn) { - LOGP(DHO, LOGL_ERROR, - "%s: Requested to start handover, but the lchan does not belong to this conn\n", - bsc_subscr_name(conn->bsub)); - return -EINVAL; - } - - rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]); - - ho->new_lchan = lchan_alloc(ho->new_bts, ho->new_lchan_type, 0); - if (!ho->new_lchan) { - LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(ho->new_lchan_type)); - rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]); - return -ENOSPC; - } - - LOGPHO(ho, LOGL_INFO, "Triggering %s\n", ho->inter_cell? "Handover" : "Assignment"); - - /* copy some parameters from old lchan */ - old_lchan = ho->old_lchan; - new_lchan = ho->new_lchan; - memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr)); - if (!ho->inter_cell) { - new_lchan->ms_power = old_lchan->ms_power; - new_lchan->rqd_ta = old_lchan->rqd_ta; - } else { - new_lchan->ms_power = - ms_pwr_ctl_lvl(ho->new_bts->band, ho->new_bts->ms_max_power); - /* FIXME: do we have a better idea of the timing advance? */ - //new_lchan->rqd_ta = old_lchan->rqd_ta; - } - new_lchan->bs_power = old_lchan->bs_power; - new_lchan->rsl_cmode = old_lchan->rsl_cmode; - new_lchan->tch_mode = old_lchan->tch_mode; - memcpy(&new_lchan->mr_ms_lv, &old_lchan->mr_ms_lv, sizeof(new_lchan->mr_ms_lv)); - memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, sizeof(new_lchan->mr_bts_lv)); - - new_lchan->conn = conn; - - rc = rsl_chan_activate_lchan(new_lchan, - ho->async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC, - ho->ho_ref); - if (rc < 0) { - LOGPHO(ho, LOGL_INFO, "%s Failure: activate lchan rc = %d\n", - ho->inter_cell? "Handover" : "Assignment", rc); - lchan_free(new_lchan); - ho->new_lchan = NULL; - bsc_clear_handover(conn, 0); - return rc; - } - - rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ); - /* we continue in the SS_LCHAN handler / ho_chan_activ_ack */ - - return 0; -} - -/* clear any operation for this connection */ -void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan) -{ - struct bsc_handover *ho = conn->ho; - - if (!ho) - return; - - if (ho->new_lchan) { - ho->new_lchan->conn = NULL; - if (free_lchan) - lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END); - ho->new_lchan = NULL; - } - - handover_free(ho); - conn->ho = NULL; -} - -/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */ -static void ho_T3103_cb(void *_ho) -{ - struct bsc_handover *ho = _ho; - struct gsm_network *net = ho->new_lchan->ts->trx->bts->network; - - DEBUGP(DHO, "HO T3103 expired\n"); - rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]); - - /* Inform the GSCON FSM about the timed out handover */ - osmo_fsm_inst_dispatch(ho->old_lchan->conn->fi, GSCON_EV_HO_TIMEOUT, NULL); - - bsc_clear_handover(ho->old_lchan->conn, 1); -} - -/* RSL has acknowledged activation of the new lchan */ -static int ho_chan_activ_ack(struct gsm_lchan *new_lchan) -{ - struct bsc_handover *ho; - - /* we need to check if this channel activation is related to - * a handover at all (and if, which particular handover) */ - ho = bsc_ho_by_new_lchan(new_lchan); - if (!ho) - return -ENODEV; - - LOGPHO(ho, LOGL_INFO, "Channel Activate Ack, send %s COMMAND\n", ho->inter_cell? "HANDOVER" : "ASSIGNMENT"); - - /* we can now send the 04.08 HANDOVER COMMAND to the MS - * using the old lchan */ - - gsm48_send_ho_cmd(ho->old_lchan, new_lchan, new_lchan->ms_power, ho->ho_ref); - - /* start T3103. We can continue either with T3103 expiration, - * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */ - osmo_timer_setup(&ho->T3103, ho_T3103_cb, ho); - osmo_timer_schedule(&ho->T3103, 10, 0); - - /* create a RTP connection */ - if (is_ipaccess_bts(new_lchan->ts->trx->bts)) - rsl_ipacc_crcx(new_lchan); - - return 0; -} - -/* RSL has not acknowledged activation of the new lchan */ -static int ho_chan_activ_nack(struct gsm_lchan *new_lchan) -{ - struct bsc_handover *ho; - struct handover_decision_callbacks *hdc; - - ho = bsc_ho_by_new_lchan(new_lchan); - if (!ho) { - /* This lchan is not involved in a handover. */ - return 0; - } - - hdc = handover_decision_callbacks_get(ho->from_hodec_id); - if (hdc && hdc->on_ho_chan_activ_nack) - hdc->on_ho_chan_activ_nack(ho); - - bsc_clear_handover(new_lchan->conn, 0); - return 0; -} - -/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */ -static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) -{ - struct gsm_network *net; - struct bsc_handover *ho; - - ho = bsc_ho_by_new_lchan(new_lchan); - if (!ho) { - LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); - return -ENODEV; - } - - net = new_lchan->ts->trx->bts->network; - - LOGPHO(ho, LOGL_INFO, "%s Complete\n", ho->inter_cell ? "Handover" : "Assignment"); - - rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED]); - - osmo_timer_del(&ho->T3103); - - /* Replace the ho lchan with the primary one */ - if (ho->old_lchan != new_lchan->conn->lchan) - LOGPHO(ho, LOGL_ERROR, "Primary lchan changed during handover.\n"); - - if (new_lchan->conn->ho != ho) - LOGPHO(ho, LOGL_ERROR, "Handover channel changed during this handover.\n"); - - new_lchan->conn->lchan = new_lchan; - ho->old_lchan->conn = NULL; - - lchan_release(ho->old_lchan, 0, RSL_REL_LOCAL_END); - - handover_free(ho); - new_lchan->conn->ho = NULL; - - /* Inform the GSCON FSM that the handover is complete */ - osmo_fsm_inst_dispatch(new_lchan->conn->fi, GSCON_EV_HO_COMPL, NULL); - return 0; -} - -/* GSM 04.08 HANDOVER FAIL has been received */ -static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) -{ - struct gsm_network *net = old_lchan->ts->trx->bts->network; - struct bsc_handover *ho; - struct handover_decision_callbacks *hdc; - - ho = bsc_ho_by_old_lchan(old_lchan); - if (!ho) { - LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); - return -ENODEV; - } - - hdc = handover_decision_callbacks_get(ho->from_hodec_id); - if (hdc && hdc->on_ho_failure) - hdc->on_ho_failure(ho); - - rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]); - - bsc_clear_handover(ho->new_lchan->conn, 1); - - /* Inform the GSCON FSM that the handover failed */ - osmo_fsm_inst_dispatch(old_lchan->conn->fi, GSCON_EV_HO_FAIL, NULL); - return 0; -} - -/* GSM 08.58 HANDOVER DETECT has been received */ -static int ho_rsl_detect(struct gsm_lchan *new_lchan) -{ - struct bsc_handover *ho; - - ho = bsc_ho_by_new_lchan(new_lchan); - if (!ho) { - LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); - return -ENODEV; - } - - LOGPHO(ho, LOGL_DEBUG, "Handover RACH detected\n"); - - /* This is just for logging on the DHO category. The actual MGCP switchover happens in - * osmo_bsc_mgcp.c by receiving the same S_LCHAN_HANDOVER_DETECT signal. - * (Calling mgcp_handover() directly currently breaks linking in utils/...) */ - - return 0; -} - -static int ho_meas_rep(struct gsm_meas_rep *mr) -{ - struct handover_decision_callbacks *hdc; - enum hodec_id hodec_id = ho_get_algorithm(mr->lchan->ts->trx->bts->ho); - - hdc = handover_decision_callbacks_get(hodec_id); - if (!hdc || !hdc->on_measurement_report) - return 0; - hdc->on_measurement_report(mr); - return 0; -} - -static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct lchan_signal_data *lchan_data; - struct gsm_lchan *lchan; - - lchan_data = signal_data; - switch (subsys) { - case SS_LCHAN: - lchan = lchan_data->lchan; - switch (signal) { - case S_LCHAN_ACTIVATE_ACK: - return ho_chan_activ_ack(lchan); - case S_LCHAN_ACTIVATE_NACK: - return ho_chan_activ_nack(lchan); - case S_LCHAN_HANDOVER_DETECT: - return ho_rsl_detect(lchan); - case S_LCHAN_HANDOVER_COMPL: - return ho_gsm48_ho_compl(lchan); - case S_LCHAN_HANDOVER_FAIL: - return ho_gsm48_ho_fail(lchan); - case S_LCHAN_MEAS_REP: - return ho_meas_rep(lchan_data->mr); - } - break; - default: - break; - } - - return 0; -} - -/* Return the old lchan or NULL. This is meant for audio handling */ -struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan) -{ - struct bsc_handover *ho; - ho = bsc_ho_by_new_lchan(new_lchan); - if (!ho) - return NULL; - return ho->old_lchan; -} - -static __attribute__((constructor)) void on_dso_load_ho_logic(void) -{ - osmo_signal_register_handler(SS_LCHAN, ho_logic_sig_cb, NULL); -} - -/* Count number of currently ongoing handovers - * inter_cell: if true, count only handovers between two cells. If false, count only handovers within one - * cell. */ -int bsc_ho_count(struct gsm_bts *bts, bool inter_cell) -{ - struct bsc_handover *ho; - int count = 0; - - llist_for_each_entry(ho, &bsc_handovers, list) { - if (ho->inter_cell != inter_cell) - continue; - if (ho->new_lchan->ts->trx->bts == bts) - count++; - } - - return count; -} - -void handover_decision_callbacks_register(struct handover_decision_callbacks *hdc) -{ - llist_add_tail(&hdc->entry, &handover_decision_callbacks); -} - -struct handover_decision_callbacks *handover_decision_callbacks_get(int hodec_id) -{ - struct handover_decision_callbacks *hdc; - llist_for_each_entry(hdc, &handover_decision_callbacks, entry) { - if (hdc->hodec_id == hodec_id) - return hdc; - } - return NULL; -} diff --git a/src/libbsc/handover_vty.c b/src/libbsc/handover_vty.c deleted file mode 100644 index 51e448e03..000000000 --- a/src/libbsc/handover_vty.c +++ /dev/null @@ -1,177 +0,0 @@ -/* OsmoBSC interface to quagga VTY for handover parameters */ -/* (C) 2009-2010 by Andreas Eversberg - * (C) 2009-2010 by Harald Welte - * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH - * - * All Rights Reserved - * - * Author: Andreas Eversberg - * Neels Hofmeyr - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include - -static struct handover_cfg *ho_cfg_from_vty(struct vty *vty) -{ - switch (vty->node) { - case GSMNET_NODE: - return gsmnet_from_vty(vty)->ho; - case BTS_NODE: - OSMO_ASSERT(vty->index); - return ((struct gsm_bts *)vty->index)->ho; - default: - OSMO_ASSERT(false); - } -} - - -#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, \ - VTY_DOC) \ -DEFUN(cfg_ho_##NAME, cfg_ho_##NAME##_cmd, \ - VTY_CMD_PREFIX VTY_CMD " (" VTY_CMD_ARG "|default)", \ - VTY_DOC \ - "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n") \ -{ \ - struct handover_cfg *ho = ho_cfg_from_vty(vty); \ - const char *val = argv[0]; \ - if (!strcmp(val, "default")) { \ - const char *msg; \ - if (ho_isset_##NAME(ho)) {\ - ho_clear_##NAME(ho); \ - msg = "setting removed, now is"; \ - } else \ - msg = "already was unset, still is"; \ - vty_out(vty, "%% '" VTY_CMD_PREFIX VTY_CMD "' %s " VTY_WRITE_FMT "%s%s", \ - msg, VTY_WRITE_CONV( ho_get_##NAME(ho) ), \ - ho_isset_on_parent_##NAME(ho)? " (set on higher level node)" : "", \ - VTY_NEWLINE); \ - } \ - else \ - ho_set_##NAME(ho, VTY_ARG_EVAL(val)); \ - return CMD_SUCCESS; \ -} - -HO_CFG_ALL_MEMBERS -#undef HO_CFG_ONE_MEMBER - - -/* Aliases of 'handover' for 'handover1' for backwards compat */ -#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, \ - VTY_DOC) \ -ALIAS_DEPRECATED(cfg_ho_##NAME, cfg_ho_##NAME##_cmd_alias, \ - "handover " VTY_CMD " (" VTY_CMD_ARG "|default)", \ - "Legacy alias for 'handover1': " VTY_DOC \ - "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n"); - -HODEC1_CFG_ALL_MEMBERS -#undef HO_CFG_ONE_MEMBER - -static inline const int a2congestion_check_interval(const char *arg) -{ - if (!strcmp(arg, "disabled")) - return 0; - return atoi(arg); -} - -static inline const char *congestion_check_interval2a(int val) -{ - static char str[9]; - if (val < 1 - || snprintf(str, sizeof(str), "%d", val) >= sizeof(str)) - return "disabled"; - return str; -} - -DEFUN(cfg_net_ho_congestion_check_interval, cfg_net_ho_congestion_check_interval_cmd, - "handover2 congestion-check (disabled|<1-999>|now)", - HO_CFG_STR_HANDOVER2 - "Configure congestion check interval" HO_CFG_STR_2 - "Disable congestion checking, do not handover based on cell overload\n" - "Congestion check interval in seconds (default " - OSMO_STRINGIFY_VAL(HO_CFG_CONGESTION_CHECK_DEFAULT) ")\n" - "Manually trigger a congestion check to run right now\n") -{ - if (!strcmp(argv[0], "now")) { - hodec2_congestion_check(gsmnet_from_vty(vty)); - return CMD_SUCCESS; - } - - hodec2_on_change_congestion_check_interval(gsmnet_from_vty(vty), - a2congestion_check_interval(argv[0])); - return CMD_SUCCESS; -} - -static void ho_vty_write(struct vty *vty, const char *indent, struct handover_cfg *ho) -{ -#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, \ - VTY_DOC) \ - if (ho_isset_##NAME(ho)) \ - vty_out(vty, "%s" VTY_CMD_PREFIX VTY_CMD " " VTY_WRITE_FMT "%s", indent, \ - VTY_WRITE_CONV( ho_get_##NAME(ho) ), VTY_NEWLINE); - - HO_CFG_ALL_MEMBERS -#undef HO_CFG_ONE_MEMBER -} - -void ho_vty_write_bts(struct vty *vty, struct gsm_bts *bts) -{ - ho_vty_write(vty, " ", bts->ho); -} - -void ho_vty_write_net(struct vty *vty, struct gsm_network *net) -{ - ho_vty_write(vty, " ", net->ho); - - if (net->hodec2.congestion_check_interval_s != HO_CFG_CONGESTION_CHECK_DEFAULT) - vty_out(vty, " handover2 congestion-check %s%s", - congestion_check_interval2a(net->hodec2.congestion_check_interval_s), - VTY_NEWLINE); -} - -static void ho_vty_init_cmds(int parent_node) -{ -#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ - install_element(parent_node, &cfg_ho_##NAME##_cmd); - - HO_CFG_ALL_MEMBERS -#undef HO_CFG_ONE_MEMBER - - /* Aliases of 'handover' for 'handover1' for backwards compat */ -#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ - install_element(parent_node, &cfg_ho_##NAME##_cmd_alias); - -HODEC1_CFG_ALL_MEMBERS -#undef HO_CFG_ONE_MEMBER -} - -void ho_vty_init() -{ - ho_vty_init_cmds(GSMNET_NODE); - install_element(GSMNET_NODE, &cfg_net_ho_congestion_check_interval_cmd); - - ho_vty_init_cmds(BTS_NODE); -} - diff --git a/src/libbsc/meas_feed.c b/src/libbsc/meas_feed.c deleted file mode 100644 index 2e80754d4..000000000 --- a/src/libbsc/meas_feed.c +++ /dev/null @@ -1,185 +0,0 @@ -/* UDP-Feed of measurement reports */ - -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -struct meas_feed_state { - struct osmo_wqueue wqueue; - char scenario[31+1]; - char *dst_host; - uint16_t dst_port; -}; - -static struct meas_feed_state g_mfs = {}; - -static int process_meas_rep(struct gsm_meas_rep *mr) -{ - struct msgb *msg; - struct meas_feed_meas *mfm; - struct bsc_subscr *bsub; - - /* 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"); - return 0; - } - if (!mr->lchan->conn) { - LOGP(DMEAS, LOGL_DEBUG, "meas_feed: lchan without conn, not sending report\n"); - return 0; - } - - bsub = mr->lchan->conn->bsub; - - msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed"); - if (!msg) - return 0; - - /* fill in the header */ - mfm = (struct meas_feed_meas *) msgb_put(msg, sizeof(*mfm)); - mfm->hdr.msg_type = MEAS_FEED_MEAS; - mfm->hdr.version = MEAS_FEED_VERSION; - - /* fill in MEAS_FEED_MEAS specific header */ - if (bsub) - osmo_strlcpy(mfm->imsi, bsub->imsi, sizeof(mfm->imsi)); - /* This used to be a human readable meaningful name set in the old osmo-nitb's subscriber - * database. Now we're several layers away from that (and possibly don't even have a name in - * osmo-hlr either), hence this is a legacy item now that we should leave empty ... *but*: - * here in the BSC we often don't know the subscriber's full identity information. For example, - * we might only know the TMSI, and hence would pass an empty IMSI above. So after all, feed - * bsc_subscr_name(), which possibly will feed the IMSI again, but in case only the TMSI is known - * would add that to the information set as "TMSI:0x12345678". */ - osmo_strlcpy(mfm->name, bsc_subscr_name(bsub), sizeof(mfm->name)); - osmo_strlcpy(mfm->scenario, g_mfs.scenario, sizeof(mfm->scenario)); - - /* copy the entire measurement report */ - memcpy(&mfm->mr, mr, sizeof(mfm->mr)); - - /* copy channel information */ - /* we assume that the measurement report always belong to some timeslot */ - mfm->lchan_type = (uint8_t)mr->lchan->type; - mfm->pchan_type = (uint8_t)mr->lchan->ts->pchan; - mfm->bts_nr = mr->lchan->ts->trx->bts->nr; - mfm->trx_nr = mr->lchan->ts->trx->nr; - mfm->ts_nr = mr->lchan->ts->nr; - mfm->ss_nr = mr->lchan->nr; - - /* and send it to the socket */ - if (osmo_wqueue_enqueue(&g_mfs.wqueue, msg) != 0) { - LOGP(DMEAS, LOGL_ERROR, "meas_feed %s: sending measurement report failed\n", - gsm_lchan_name(mr->lchan)); - msgb_free(msg); - } else - LOGP(DMEAS, LOGL_DEBUG, "meas_feed %s: sent measurement report\n", - gsm_lchan_name(mr->lchan)); - - return 0; -} - -static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct lchan_signal_data *sdata = signal_data; - - if (subsys != SS_LCHAN) - return 0; - - if (signal == S_LCHAN_MEAS_REP) - process_meas_rep(sdata->mr); - - return 0; -} - -static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg) -{ - return write(ofd->fd, msgb_data(msg), msgb_length(msg)); -} - -static int feed_read_cb(struct osmo_fd *ofd) -{ - int rc; - char buf[256]; - - rc = read(ofd->fd, buf, sizeof(buf)); - ofd->fd &= ~BSC_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"); - } - - 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_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM, - IPPROTO_UDP, dst_host, dst_port, - OSMO_SOCK_F_CONNECT); - if (rc < 0) - return rc; - - g_mfs.wqueue.bfd.when &= ~BSC_FD_READ; - - if (g_mfs.dst_host) - talloc_free(g_mfs.dst_host); - g_mfs.dst_host = talloc_strdup(NULL, dst_host); - g_mfs.dst_port = dst_port; - - return 0; -} - -void meas_feed_cfg_get(char **host, uint16_t *port) -{ - *port = g_mfs.dst_port; - *host = g_mfs.dst_host; -} - -void meas_feed_scenario_set(const char *name) -{ - osmo_strlcpy(g_mfs.scenario, name, sizeof(g_mfs.scenario)); -} - -const char *meas_feed_scenario_get(void) -{ - return g_mfs.scenario; -} diff --git a/src/libbsc/meas_rep.c b/src/libbsc/meas_rep.c deleted file mode 100644 index 73d9a1f21..000000000 --- a/src/libbsc/meas_rep.c +++ /dev/null @@ -1,134 +0,0 @@ -/* Measurement Report Processing */ - -/* (C) 2009 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include - -#include -#include - -static int get_field(const struct gsm_meas_rep *rep, - enum meas_rep_field field) -{ - switch (field) { - case MEAS_REP_DL_RXLEV_FULL: - if (!(rep->flags & MEAS_REP_F_DL_VALID)) - return -EINVAL; - return rep->dl.full.rx_lev; - case MEAS_REP_DL_RXLEV_SUB: - if (!(rep->flags & MEAS_REP_F_DL_VALID)) - return -EINVAL; - return rep->dl.sub.rx_lev; - case MEAS_REP_DL_RXQUAL_FULL: - if (!(rep->flags & MEAS_REP_F_DL_VALID)) - return -EINVAL; - return rep->dl.full.rx_qual; - case MEAS_REP_DL_RXQUAL_SUB: - if (!(rep->flags & MEAS_REP_F_DL_VALID)) - return -EINVAL; - return rep->dl.sub.rx_qual; - case MEAS_REP_UL_RXLEV_FULL: - return rep->ul.full.rx_lev; - case MEAS_REP_UL_RXLEV_SUB: - return rep->ul.sub.rx_lev; - case MEAS_REP_UL_RXQUAL_FULL: - return rep->ul.full.rx_qual; - case MEAS_REP_UL_RXQUAL_SUB: - return rep->ul.sub.rx_qual; - } - - return 0; -} - - -unsigned int calc_initial_idx(unsigned int array_size, - unsigned int meas_rep_idx, - unsigned int num_values) -{ - int offs, idx; - - /* from which element do we need to start if we're interested - * in an average of 'num' elements */ - offs = meas_rep_idx - num_values; - - if (offs < 0) - idx = array_size + offs; - else - idx = offs; - - return idx; -} - -/* obtain an average over the last 'num' fields in the meas reps */ -int get_meas_rep_avg(const struct gsm_lchan *lchan, - enum meas_rep_field field, unsigned int num) -{ - unsigned int i, idx; - int avg = 0, valid_num = 0; - - if (num < 1) - return -EINVAL; - - if (num > lchan->meas_rep_count) - return -EINVAL; - - idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), - lchan->meas_rep_idx, num); - - for (i = 0; i < num; i++) { - int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep); - int val = get_field(&lchan->meas_rep[j], field); - - if (val >= 0) { - avg += val; - valid_num++; - } - } - - if (valid_num == 0) - return -EINVAL; - - return avg / valid_num; -} - -/* 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) -{ - unsigned int i, idx; - int count = 0; - - idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), - lchan->meas_rep_idx, m); - - for (i = 0; i < m; i++) { - int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep); - int val = get_field(&lchan->meas_rep[j], field); - - if (val >= be) /* implies that val < 0 will not count */ - count++; - - if (count >= n) - return 1; - } - - return 0; -} diff --git a/src/libbsc/net_init.c b/src/libbsc/net_init.c deleted file mode 100644 index f03a2e12f..000000000 --- a/src/libbsc/net_init.c +++ /dev/null @@ -1,117 +0,0 @@ -/* (C) 2008-2010 by Harald Welte - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include -#include - -/* XXX hard-coded for now */ -#define T3122_CHAN_LOAD_SAMPLE_INTERVAL 1 /* in seconds */ - -static void update_t3122_chan_load_timer(void *data) -{ - struct gsm_network *net = data; - struct gsm_bts *bts; - - llist_for_each_entry(bts, &net->bts_list, list) - bts_update_t3122_chan_load(bts); - - /* Keep this timer ticking. */ - osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0); -} - -struct gsm_network *bsc_network_init(void *ctx) -{ - struct gsm_network *net; - - net = talloc_zero(ctx, struct gsm_network); - if (!net) - return NULL; - - net->plmn = (struct osmo_plmn_id){ - .mcc = 1, - .mnc = 1, - }; - - net->dyn_ts_allow_tch_f = true; - - /* Permit a compile-time default of A5/3 and A5/1 */ - net->a5_encryption_mask = (1 << 3) | (1 << 1); - - /* Use 30 min periodic update interval as sane default */ - net->t3212 = 5; - - INIT_LLIST_HEAD(&net->subscr_conns); - - net->bsc_subscribers = talloc_zero(net, struct llist_head); - INIT_LLIST_HEAD(net->bsc_subscribers); - - net->bsc_data = talloc_zero(net, struct osmo_bsc_data); - if (!net->bsc_data) { - talloc_free(net); - return NULL; - } - - /* Init back pointer */ - net->bsc_data->auto_off_timeout = -1; - net->bsc_data->network = net; - INIT_LLIST_HEAD(&net->bsc_data->mscs); - - net->num_bts = 0; - net->T3101 = GSM_T3101_DEFAULT; - net->T3103 = GSM_T3103_DEFAULT; - net->T3105 = GSM_T3105_DEFAULT; - net->T3107 = GSM_T3107_DEFAULT; - net->T3109 = GSM_T3109_DEFAULT; - net->T3111 = GSM_T3111_DEFAULT; - net->T3113 = GSM_T3113_DEFAULT; - net->T3115 = GSM_T3115_DEFAULT; - net->T3117 = GSM_T3117_DEFAULT; - net->T3119 = GSM_T3119_DEFAULT; - net->T3122 = GSM_T3122_DEFAULT; - net->T3141 = GSM_T3141_DEFAULT; - - net->ho = ho_cfg_init(net, NULL); - net->hodec2.congestion_check_interval_s = HO_CFG_CONGESTION_CHECK_DEFAULT; - - INIT_LLIST_HEAD(&net->bts_list); - - /* - * At present all BTS in the network share one channel load timeout. - * If this becomes a problem for networks with a lot of BTS, this - * code could be refactored to run the timeout individually per BTS. - */ - 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 statistics */ - net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0); - if (!net->bsc_ctrs) { - talloc_free(net); - return NULL; - } - - gsm_net_update_ctype(net); - - return net; -} - diff --git a/src/libbsc/osmo_bsc_lcls.c b/src/libbsc/osmo_bsc_lcls.c deleted file mode 100644 index c2b076090..000000000 --- a/src/libbsc/osmo_bsc_lcls.c +++ /dev/null @@ -1,766 +0,0 @@ -/* (C) 2018 by Harald Welte - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct value_string lcls_event_names[] = { - { LCLS_EV_UPDATE_CFG_CSC, "UPDATE_CFG_CSC" }, - { LCLS_EV_APPLY_CFG_CSC, "APPLY_CFG_CSC" }, - { LCLS_EV_CORRELATED, "CORRELATED" }, - { LCLS_EV_OTHER_ENABLED, "OTHER_ENABLED" }, - { LCLS_EV_OTHER_BREAK, "OTHER_BREAK" }, - { LCLS_EV_OTHER_DEAD, "OTHER_DEAD" }, - { 0, NULL } -}; - - -/*********************************************************************** - * Utility functions - ***********************************************************************/ - -enum gsm0808_lcls_status lcls_get_status(struct gsm_subscriber_connection *conn) -{ - if (!conn->lcls.fi) - return 0xff; - - switch (conn->lcls.fi->state) { - case ST_NO_LCLS: - return 0xff; - case ST_NOT_YET_LS: - return GSM0808_LCLS_STS_NOT_YET_LS; - case ST_NOT_POSSIBLE_LS: - return GSM0808_LCLS_STS_NOT_POSSIBLE_LS; - case ST_NO_LONGER_LS: - return GSM0808_LCLS_STS_NO_LONGER_LS; - case ST_REQ_LCLS_NOT_SUPP: - return GSM0808_LCLS_STS_REQ_LCLS_NOT_SUPP; - case ST_LOCALLY_SWITCHED: - case ST_LOCALLY_SWITCHED_WAIT_BREAK: - case ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK: - return GSM0808_LCLS_STS_LOCALLY_SWITCHED; - } - OSMO_ASSERT(0); -} - -static void lcls_send_notify(struct gsm_subscriber_connection *conn) -{ - enum gsm0808_lcls_status status = lcls_get_status(conn); - struct msgb *msg; - - if (status == 0xff) - return; - - LOGPFSM(conn->lcls.fi, "Sending BSSMAP LCLS NOTIFICATION (%s)\n", - gsm0808_lcls_status_name(status)); - msg = gsm0808_create_lcls_notification(status, false); - osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, msg); -} - -static struct gsm_subscriber_connection * -find_conn_with_same_gcr(struct gsm_subscriber_connection *conn_local) -{ - struct gsm_network *net = conn_local->network; - struct gsm_subscriber_connection *conn_other; - - llist_for_each_entry(conn_other, &net->subscr_conns, entry) { - /* don't report back the same connection */ - if (conn_other == conn_local) - continue; - /* don't consider any conn where GCR length is not the same as before */ - if (conn_other->lcls.global_call_ref_len != conn_local->lcls.global_call_ref_len) - continue; - if (!memcmp(conn_other->lcls.global_call_ref, conn_local->lcls.global_call_ref, - conn_local->lcls.global_call_ref_len)) - return conn_other; - } - return NULL; -} - -static bool lcls_is_supported_config(enum gsm0808_lcls_config cfg) -{ - /* this is the only configuration that we support for now */ - if (cfg == GSM0808_LCLS_CFG_BOTH_WAY) - return true; - else - return false; -} - -/* LCLS Call Leg Correlation as per 23.284 4.3 / 48.008 3.1.33.2.1 */ -static int lcls_perform_correlation(struct gsm_subscriber_connection *conn_local) -{ - struct gsm_subscriber_connection *conn_other; - - /* We can only correlate if a GCR is present */ - OSMO_ASSERT(conn_local->lcls.global_call_ref_len); - /* We can only correlate if we're not in active LS */ - OSMO_ASSERT(conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED && - conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_BREAK && - conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK); - - conn_other = conn_local->lcls.other; - if (conn_other) { - LOGPFSM(conn_local->lcls.fi, "Breaking previous correlation with %s\n", - osmo_fsm_inst_name(conn_other->lcls.fi)); - OSMO_ASSERT(conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED && - conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_BREAK && - conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK); - conn_local->lcls.other->lcls.other = NULL; - conn_local->lcls.other = NULL; - } - - conn_other = find_conn_with_same_gcr(conn_local); - if (!conn_other) { - /* we found no other call with same GCR: not possible */ - LOGPFSM(conn_local->lcls.fi, "Unsuccessful correlation\n"); - return -ENODEV; - } - - /* store pointer to "other" in "local" */ - conn_local->lcls.other = conn_other; - - LOGPFSM(conn_local->lcls.fi, "Successfully correlated with %s\n", - osmo_fsm_inst_name(conn_other->lcls.fi)); - - /* notify other conn about our correlation */ - osmo_fsm_inst_dispatch(conn_other->lcls.fi, LCLS_EV_CORRELATED, conn_local); - - return 0; -} - - -struct lcls_cfg_csc { - enum gsm0808_lcls_config config; - enum gsm0808_lcls_control control; -}; - -/* Update the connections LCLS configuration and return old/previous configuration. - * \returns (staticallly allocated) old configuration; NULL if new config not supported */ -static struct lcls_cfg_csc *update_lcls_cfg_csc(struct gsm_subscriber_connection *conn, - struct lcls_cfg_csc *new_cfg_csc) -{ - static struct lcls_cfg_csc old_cfg_csc; - old_cfg_csc.config = conn->lcls.config; - old_cfg_csc.control = conn->lcls.control; - - if (new_cfg_csc->config != 0xff) { - if (!lcls_is_supported_config(new_cfg_csc->config)) - return NULL; - if (conn->lcls.config != new_cfg_csc->config) { - /* TODO: logging */ - conn->lcls.config = new_cfg_csc->config; - } - } - if (new_cfg_csc->control != 0xff) { - if (conn->lcls.control != new_cfg_csc->control) { - /* TODO: logging */ - conn->lcls.control = new_cfg_csc->control; - } - } - - return &old_cfg_csc; -} - -/* Attempt to update conn->lcls with the new config/csc provided. If new config is - * unsupported, change into LCLS NOT SUPPORTED state and return -EINVAL. */ -static int lcls_handle_cfg_update(struct gsm_subscriber_connection *conn, void *data) -{ - struct lcls_cfg_csc *new_cfg_csc, *old_cfg_csc; - - new_cfg_csc = (struct lcls_cfg_csc *) data; - old_cfg_csc = update_lcls_cfg_csc(conn, new_cfg_csc); - if (!old_cfg_csc) { - osmo_fsm_inst_state_chg(conn->lcls.fi, ST_REQ_LCLS_NOT_SUPP, 0, 0); - return -EINVAL; - } - return 0; -} - -/* notify the LCLS FSM about new LCLS Config and/or CSC */ -void lcls_update_config(struct gsm_subscriber_connection *conn, - const uint8_t *config, const uint8_t *control) -{ - struct lcls_cfg_csc new_cfg = { - .config = 0xff, - .control = 0xff, - }; - /* nothing to update, skip it */ - if (!config && !control) - return; - if (config) - new_cfg.config = *config; - if (control) - new_cfg.control = *control; - osmo_fsm_inst_dispatch(conn->lcls.fi, LCLS_EV_UPDATE_CFG_CSC, &new_cfg); -} - -/* apply the configuration, may be changed before by lcls_update_config */ -void lcls_apply_config(struct gsm_subscriber_connection *conn) -{ - osmo_fsm_inst_dispatch(conn->lcls.fi, LCLS_EV_APPLY_CFG_CSC, NULL); -} - -static void lcls_break_local_switching(struct gsm_subscriber_connection *conn) -{ - struct mgcp_conn_peer peer; - struct sockaddr_in *sin; - - LOGPFSM(conn->lcls.fi, "=== HERE IS WHERE WE DISABLE LCLS\n"); - if (!conn->user_plane.fi_msc) { - /* the MGCP FSM has died, e.g. due to some MGCP/SDP parsing error */ - LOGPFSML(conn->lcls.fi, LOGL_NOTICE, "Cannot disable LCLS without MSC-side MGCP FSM\n"); - return; - } - - sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; - OSMO_ASSERT(sin->sin_family == AF_INET); - - memset(&peer, 0, sizeof(peer)); - peer.port = htons(sin->sin_port); - osmo_strlcpy(peer.addr, inet_ntoa(sin->sin_addr), sizeof(peer.addr)); - mgcp_conn_modify(conn->user_plane.fi_msc, 0, &peer); -} - -static bool lcls_enable_possible(struct gsm_subscriber_connection *conn) -{ - struct gsm_subscriber_connection *other_conn = conn->lcls.other; - OSMO_ASSERT(other_conn); - - if (!lcls_is_supported_config(conn->lcls.config)) { - LOGPFSM(conn->lcls.fi, "Not enabling LS due to unsupported local config\n"); - return false; - } - - if (!lcls_is_supported_config(other_conn->lcls.config)) { - LOGPFSM(conn->lcls.fi, "Not enabling LS due to unsupported other config\n"); - return false; - } - - if (conn->lcls.control != GSM0808_LCLS_CSC_CONNECT) { - LOGPFSM(conn->lcls.fi, "Not enabling LS due to insufficient local control\n"); - return false; - } - - if (other_conn->lcls.control != GSM0808_LCLS_CSC_CONNECT) { - LOGPFSM(conn->lcls.fi, "Not enabling LS due to insufficient other control\n"); - return false; - } - - return true; -} - -/*********************************************************************** - * State callback functions - ***********************************************************************/ - -static void lcls_no_lcls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - /* we're just starting and cannot yet have a correlated call */ - OSMO_ASSERT(conn->lcls.other == NULL); - - if (conn->sccp.msc->lcls_mode == BSC_LCLS_MODE_DISABLED) { - LOGPFSML(fi, LOGL_DEBUG, "LCLS disabled for this MSC, ignoring %s\n", - osmo_fsm_event_name(fi->fsm, event)); - return; - } - - /* If there's no GCR set, we can never leave this state */ - if (conn->lcls.global_call_ref_len == 0) { - LOGPFSML(fi, LOGL_NOTICE, "No GCR set, ignoring %s\n", - osmo_fsm_event_name(fi->fsm, event)); - return; - } - - switch (event) { - case LCLS_EV_UPDATE_CFG_CSC: - if (lcls_handle_cfg_update(conn, data) != 0) - return; - return; - case LCLS_EV_APPLY_CFG_CSC: - if (conn->lcls.config == 0xff) - return; - if (lcls_perform_correlation(conn) != 0) { - /* Correlation leads to no result: Not Possible to LS */ - osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); - return; - } - /* we now have two correlated calls */ - OSMO_ASSERT(conn->lcls.other); - if (lcls_enable_possible(conn)) { - /* Local Switching now active */ - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); - } else { - /* Couldn't be enabled: Not yet LS */ - osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); - } - break; - default: - OSMO_ASSERT(0); - break; - } -} - -static void lcls_not_yet_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - /* not yet locally switched means that we have correlation but no instruction - * to actually connect them yet */ - OSMO_ASSERT(conn->lcls.other); - - switch (event) { - case LCLS_EV_UPDATE_CFG_CSC: - if (lcls_handle_cfg_update(conn, data) != 0) - return; - return; - case LCLS_EV_APPLY_CFG_CSC: - if (lcls_enable_possible(conn)) { - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); - } - break; - case LCLS_EV_OTHER_ENABLED: - OSMO_ASSERT(conn->lcls.other == data); - if (lcls_enable_possible(conn)) { - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); - /* Send LCLS-NOTIFY to inform MSC */ - lcls_send_notify(conn); - } else { - /* we couldn't enable our side, so ask other side to break */ - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); - } - break; - case LCLS_EV_CORRELATED: - /* other call informs us that he correlated with us */ - conn->lcls.other = data; - break; - case LCLS_EV_OTHER_DEAD: - OSMO_ASSERT(conn->lcls.other == data); - conn->lcls.other = NULL; - osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); - /* Send LCLS-NOTIFY to inform MSC */ - lcls_send_notify(conn); - break; - default: - OSMO_ASSERT(0); - break; - } -} - -static void lcls_not_possible_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - OSMO_ASSERT(conn->lcls.other == NULL); - - switch (event) { - case LCLS_EV_UPDATE_CFG_CSC: - if (lcls_handle_cfg_update(conn, data) != 0) - return; - return; - case LCLS_EV_APPLY_CFG_CSC: - if (lcls_perform_correlation(conn) != 0) { - /* no correlation result: Remain in NOT_POSSIBLE_LS */ - return; - } - /* we now have two correlated calls */ - OSMO_ASSERT(conn->lcls.other); - if (lcls_enable_possible(conn)) { - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); - } else { - osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); - } - break; - case LCLS_EV_CORRELATED: - /* other call informs us that he correlated with us */ - conn->lcls.other = data; - osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); - /* Send NOTIFY about the fact that correlation happened */ - lcls_send_notify(conn); - break; - case LCLS_EV_OTHER_DEAD: - OSMO_ASSERT(conn->lcls.other == data); - conn->lcls.other = NULL; - break; - default: - OSMO_ASSERT(0); - break; - } -} - -static void lcls_no_longer_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - OSMO_ASSERT(conn->lcls.other); - - switch (event) { - case LCLS_EV_UPDATE_CFG_CSC: - if (lcls_handle_cfg_update(conn, data) != 0) - return; - if (lcls_enable_possible(conn)) { - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); - } - break; - case LCLS_EV_OTHER_ENABLED: - OSMO_ASSERT(conn->lcls.other == data); - if (lcls_enable_possible(conn)) { - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); - /* Send LCLS-NOTIFY to inform MSC */ - lcls_send_notify(conn); - } else { - /* we couldn't enable our side, so ask other side to break */ - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); - } - break; - case LCLS_EV_CORRELATED: - /* other call informs us that he correlated with us */ - conn->lcls.other = data; - break; - case LCLS_EV_OTHER_DEAD: - OSMO_ASSERT(conn->lcls.other == data); - conn->lcls.other = NULL; - osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); - /* Send LCLS-NOTIFY to inform MSC */ - lcls_send_notify(conn); - break; - default: - OSMO_ASSERT(0); - break; - } -} - -static void lcls_req_lcls_not_supp_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - /* we could have a correlated other call or not */ - - switch (event) { - case LCLS_EV_UPDATE_CFG_CSC: - if (lcls_handle_cfg_update(conn, data) != 0) - return; - //FIXME osmo_fsm_inst_state_chg(fi, - return; - case LCLS_EV_APPLY_CFG_CSC: - if (lcls_perform_correlation(conn) != 0) { - osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); - return; - } - /* we now have two correlated calls */ - OSMO_ASSERT(conn->lcls.other); - if (!lcls_is_supported_config(conn->lcls.config)) - return; - if (lcls_enable_possible(conn)) - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); - else - osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); - break; - case LCLS_EV_CORRELATED: - /* other call informs us that he correlated with us */ - conn->lcls.other = data; - break; - case LCLS_EV_OTHER_DEAD: - OSMO_ASSERT(conn->lcls.other == data); - conn->lcls.other = NULL; - break; - default: - OSMO_ASSERT(0); - break; - } - -} - -static void lcls_locally_switched_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - OSMO_ASSERT(conn->lcls.other); - - switch (event) { - case LCLS_EV_UPDATE_CFG_CSC: - if (lcls_handle_cfg_update(conn, data) != 0) { - lcls_break_local_switching(conn); - return; - } - break; - case LCLS_EV_APPLY_CFG_CSC: - if (conn->lcls.control == GSM0808_LCLS_CSC_RELEASE_LCLS) { - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK, 0, 0); - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); - /* FIXME: what if there's a new config included? */ - return; - } - /* TODO: Handle any changes of "config" once we support bi-casting etc. */ - break; - case LCLS_EV_OTHER_BREAK: - OSMO_ASSERT(conn->lcls.other == data); - osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED_WAIT_BREAK, 0, 0); - break; - case LCLS_EV_OTHER_DEAD: - OSMO_ASSERT(conn->lcls.other == data); - conn->lcls.other = NULL; - osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); - /* Send LCLS-NOTIFY to inform MSC */ - lcls_send_notify(conn); - break; - default: - OSMO_ASSERT(0); - break; - } -} - - -static void lcls_locally_switched_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct gsm_subscriber_connection *conn = fi->priv; - struct gsm_subscriber_connection *conn_other = conn->lcls.other; - struct mgcp_conn_peer peer; - struct sockaddr_in *sin; - - OSMO_ASSERT(conn_other); - - LOGPFSM(fi, "=== HERE IS WHERE WE ENABLE LCLS\n"); - if (!conn->user_plane.fi_msc) { - LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without MSC-side MGCP FSM. FIXME\n"); - return; - } - - sin = (struct sockaddr_in *)&conn_other->user_plane.aoip_rtp_addr_local; - OSMO_ASSERT(sin->sin_family == AF_INET); - - memset(&peer, 0, sizeof(peer)); - peer.port = htons(sin->sin_port); - osmo_strlcpy(peer.addr, inet_ntoa(sin->sin_addr), sizeof(peer.addr)); - mgcp_conn_modify(conn->user_plane.fi_msc, 0, &peer); - -} - -static void lcls_locally_switched_wait_break_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - OSMO_ASSERT(conn->lcls.other); - - switch (event) { - case LCLS_EV_UPDATE_CFG_CSC: - if (lcls_handle_cfg_update(conn, data) != 0) { - lcls_break_local_switching(conn); - return; - } - break; - case LCLS_EV_APPLY_CFG_CSC: - if (conn->lcls.control == GSM0808_LCLS_CSC_RELEASE_LCLS) { - lcls_break_local_switching(conn); - osmo_fsm_inst_state_chg(fi, ST_NO_LONGER_LS, 0, 0); - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); - /* no NOTIFY here, as the caller will be returning status in LCLS-CTRL-ACK */ - /* FIXME: what if there's a new config included? */ - return; - } - /* TODO: Handle any changes of "config" once we support bi-casting etc. */ - break; - case LCLS_EV_OTHER_BREAK: - /* we simply ignore it, must be a re-transmission */ - break; - case LCLS_EV_OTHER_DEAD: - OSMO_ASSERT(conn->lcls.other == data); - conn->lcls.other = NULL; - break; - default: - lcls_locally_switched_fn(fi, event, data); - break; - } -} - -static void lcls_locally_switched_wait_other_break_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - OSMO_ASSERT(conn->lcls.other); - - switch (event) { - case LCLS_EV_UPDATE_CFG_CSC: - if (lcls_handle_cfg_update(conn, data) != 0) { - lcls_break_local_switching(conn); - return; - } - /* TODO: Handle any changes of "config" once we support bi-casting etc. */ - break; - case LCLS_EV_OTHER_BREAK: - case LCLS_EV_OTHER_DEAD: - OSMO_ASSERT(conn->lcls.other == data); - lcls_break_local_switching(conn); - osmo_fsm_inst_state_chg(fi, ST_NO_LONGER_LS, 0, 0); - /* Send LCLS-NOTIFY to inform MSC */ - lcls_send_notify(conn); - break; - default: - lcls_locally_switched_fn(fi, event, data); - break; - } -} - -static void lcls_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) -{ - struct gsm_subscriber_connection *conn = fi->priv; - - if (conn->lcls.other) { - /* inform the "other" side that we're dead, so it can disabe LS and send NOTIFY */ - if (conn->lcls.other->fi) - osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_DEAD, conn); - conn->lcls.other = NULL; - } -} - - -/*********************************************************************** - * FSM Definition - ***********************************************************************/ - -#define S(x) (1 << (x)) - -static const struct osmo_fsm_state lcls_fsm_states[] = { - [ST_NO_LCLS] = { - .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | - S(LCLS_EV_APPLY_CFG_CSC), - .out_state_mask = S(ST_NO_LCLS) | - S(ST_NOT_YET_LS) | - S(ST_NOT_POSSIBLE_LS) | - S(ST_REQ_LCLS_NOT_SUPP) | - S(ST_LOCALLY_SWITCHED), - .name = "NO_LCLS", - .action = lcls_no_lcls_fn, - }, - [ST_NOT_YET_LS] = { - .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | - S(LCLS_EV_APPLY_CFG_CSC) | - S(LCLS_EV_CORRELATED) | - S(LCLS_EV_OTHER_ENABLED) | - S(LCLS_EV_OTHER_DEAD), - .out_state_mask = S(ST_NOT_YET_LS) | - S(ST_REQ_LCLS_NOT_SUPP) | - S(ST_LOCALLY_SWITCHED), - .name = "NOT_YET_LS", - .action = lcls_not_yet_ls_fn, - }, - [ST_NOT_POSSIBLE_LS] = { - .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | - S(LCLS_EV_APPLY_CFG_CSC) | - S(LCLS_EV_CORRELATED), - .out_state_mask = S(ST_NOT_YET_LS) | - S(ST_NOT_POSSIBLE_LS) | - S(ST_REQ_LCLS_NOT_SUPP) | - S(ST_LOCALLY_SWITCHED), - .name = "NOT_POSSIBLE_LS", - .action = lcls_not_possible_ls_fn, - }, - [ST_NO_LONGER_LS] = { - .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | - S(LCLS_EV_APPLY_CFG_CSC) | - S(LCLS_EV_CORRELATED) | - S(LCLS_EV_OTHER_ENABLED) | - S(LCLS_EV_OTHER_DEAD), - .out_state_mask = S(ST_NO_LONGER_LS) | - S(ST_REQ_LCLS_NOT_SUPP) | - S(ST_LOCALLY_SWITCHED), - .name = "NO_LONGER_LS", - .action = lcls_no_longer_ls_fn, - }, - [ST_REQ_LCLS_NOT_SUPP] = { - .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | - S(LCLS_EV_APPLY_CFG_CSC) | - S(LCLS_EV_CORRELATED) | - S(LCLS_EV_OTHER_DEAD), - .out_state_mask = S(ST_NOT_YET_LS) | - S(ST_REQ_LCLS_NOT_SUPP) | - S(ST_LOCALLY_SWITCHED), - .name = "REQ_LCLS_NOT_SUPP", - .action = lcls_req_lcls_not_supp_fn, - }, - [ST_LOCALLY_SWITCHED] = { - .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | - S(LCLS_EV_APPLY_CFG_CSC) | - S(LCLS_EV_OTHER_BREAK) | - S(LCLS_EV_OTHER_DEAD), - .out_state_mask = S(ST_NO_LONGER_LS) | - S(ST_NOT_POSSIBLE_LS) | - S(ST_REQ_LCLS_NOT_SUPP) | - S(ST_LOCALLY_SWITCHED_WAIT_BREAK) | - S(ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK) | - S(ST_LOCALLY_SWITCHED), - .name = "LOCALLY_SWITCHED", - .action = lcls_locally_switched_fn, - .onenter = lcls_locally_switched_onenter, - }, - /* received an "other" break, waiting for the local break */ - [ST_LOCALLY_SWITCHED_WAIT_BREAK] = { - .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | - S(LCLS_EV_APPLY_CFG_CSC) | - S(LCLS_EV_OTHER_BREAK) | - S(LCLS_EV_OTHER_DEAD), - .out_state_mask = S(ST_NO_LONGER_LS) | - S(ST_REQ_LCLS_NOT_SUPP) | - S(ST_LOCALLY_SWITCHED) | - S(ST_LOCALLY_SWITCHED_WAIT_BREAK), - .name = "LOCALLY_SWITCHED_WAIT_BREAK", - .action = lcls_locally_switched_wait_break_fn, - }, - /* received a local break, waiting for the "other" break */ - [ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK] = { - .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | - S(LCLS_EV_OTHER_BREAK) | - S(LCLS_EV_OTHER_DEAD), - .out_state_mask = S(ST_NO_LONGER_LS) | - S(ST_REQ_LCLS_NOT_SUPP) | - S(ST_LOCALLY_SWITCHED) | - S(ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK), - .name = "LOCALLY_SWITCHED_WAIT_OTHER_BREAK", - .action = lcls_locally_switched_wait_other_break_fn, - }, - - -}; - -struct osmo_fsm lcls_fsm = { - .name = "LCLS", - .states = lcls_fsm_states, - .num_states = ARRAY_SIZE(lcls_fsm_states), - .allstate_event_mask = 0, - .allstate_action = NULL, - .cleanup = lcls_fsm_cleanup, - .timer_cb = NULL, - .log_subsys = DLCLS, - .event_names = lcls_event_names, -}; diff --git a/src/libbsc/paging.c b/src/libbsc/paging.c deleted file mode 100644 index d6bff2a42..000000000 --- a/src/libbsc/paging.c +++ /dev/null @@ -1,473 +0,0 @@ -/* Paging helper and manager.... */ -/* (C) 2009,2013 by Holger Hans Peter Freyther - * 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 . - * - */ - -/* - * Relevant specs: - * 12.21: - * - 9.4.12 for CCCH Local Threshold - * - * 05.58: - * - 8.5.2 CCCH Load indication - * - 9.3.15 Paging Load - * - * Approach: - * - Send paging command to subscriber - * - On Channel Request we will remember the reason - * - After the ACK we will request the identity - * - Then we will send assign the gsm_subscriber and - * - and call a callback - */ - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -void *tall_paging_ctx = NULL; - -#define PAGING_TIMER 0, 500000 - -/* - * TODO MSCSPLIT: the paging in libbsc is closely tied to MSC land in that the - * MSC realm callback functions used to be invoked from the BSC/BTS level. So - * this entire file needs to be rewired for use with an A interface. - */ - -/* - * 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) -{ - osmo_timer_del(&to_be_deleted->T3113); - llist_del(&to_be_deleted->entry); - bsc_subscr_put(to_be_deleted->bsub); - talloc_free(to_be_deleted); -} - -static void page_ms(struct gsm_paging_request *request) -{ - uint8_t mi[128]; - unsigned int mi_len; - unsigned int page_group; - struct gsm_bts *bts = request->bts; - - log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub); - - LOGP(DPAG, LOGL_INFO, "(bts=%d) Going to send paging commands: imsi: %s tmsi: " - "0x%08x for ch. type %d (attempt %d)\n", bts->nr, request->bsub->imsi, - request->bsub->tmsi, request->chan_type, request->attempts); - - if (request->bsub->tmsi == GSM_RESERVED_TMSI) - mi_len = gsm48_generate_mid_from_imsi(mi, request->bsub->imsi); - else - mi_len = gsm48_generate_mid_from_tmsi(mi, request->bsub->tmsi); - - page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc, - str_to_imsi(request->bsub->imsi)); - gsm0808_page(bts, page_group, mi_len, mi, request->chan_type); - log_set_context(LOG_CTX_BSC_SUBSCR, NULL); -} - -static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts) -{ - if (llist_empty(&paging_bts->pending_requests)) - return; - - if (!osmo_timer_pending(&paging_bts->work_timer)) - osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER); -} - - -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; - - LOGP(DPAG, LOGL_NOTICE, "(bts=%d) No PCH LOAD IND, adding 20 slots)\n", - paging_bts->bts->nr); - paging_bts->available_slots = 20; - paging_handle_pending_requests(paging_bts); -} - -/*! count the number of free channels for given RSL channel type required - * \param[in] BTS on which we shall count - * \param[in] rsl_type the RSL channel needed type - * \returns number of free channels matching \a rsl_type in \a bts */ -static int can_send_pag_req(struct gsm_bts *bts, int rsl_type) -{ - struct pchan_load pl; - int count; - - memset(&pl, 0, sizeof(pl)); - bts_chan_load(&pl, bts); - - switch (rsl_type) { - case RSL_CHANNEED_TCH_F: - case RSL_CHANNEED_TCH_ForH: - goto count_tch; - break; - case RSL_CHANNEED_SDCCH: - goto count_sdcch; - break; - case RSL_CHANNEED_ANY: - default: - if (bts->network->pag_any_tch) - goto count_tch; - else - goto count_sdcch; - break; - } - - return 0; - - /* could available SDCCH */ -count_sdcch: - count = 0; - count += pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].total - - pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].used; - count += pl.pchan[GSM_PCHAN_CCCH_SDCCH4].total - - pl.pchan[GSM_PCHAN_CCCH_SDCCH4].used; - return bts->paging.free_chans_need > count; - -count_tch: - count = 0; - count += pl.pchan[GSM_PCHAN_TCH_F].total - - pl.pchan[GSM_PCHAN_TCH_F].used; - if (bts->network->neci) - count += pl.pchan[GSM_PCHAN_TCH_H].total - - pl.pchan[GSM_PCHAN_TCH_H].used; - return bts->paging.free_chans_need > count; -} - -/* - * 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. - */ -static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts) -{ - struct gsm_paging_request *request = NULL; - - /* - * 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 */ - 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; - } - - /* Skip paging if the bts is down. */ - if (!request->bts->oml_link) - goto skip_paging; - - /* 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); -} - -static void paging_worker(void *data) -{ - struct gsm_bts_paging_state *paging_bts = data; - - paging_handle_pending_requests(paging_bts); -} - -/*! initialize the bts paging state, if it hasn't been initialized yet */ -static void paging_init_if_needed(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; -} - -/*! 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) -{ - struct gsm_paging_request *req; - - llist_for_each_entry(req, &bts->pending_requests, entry) { - if (bsub == req->bsub) - return 1; - } - - return 0; -} - -/*! Call-back once T3113 (paging timeout) expires for given paging_request */ -static void paging_T3113_expired(void *data) -{ - struct gsm_paging_request *req = (struct gsm_paging_request *)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)); - - /* must be destroyed before calling cbfn, to prevent double free */ - rate_ctr_inc(&req->bts->bts_ctrs->ctr[BTS_CTR_PAGING_EXPIRED]); - - /* destroy it now. Do not access req afterwards */ - paging_remove_request(&req->bts->paging, req); -} - -/*! Start paging + paging timer for given subscriber on given BTS - * \param bts BTS on which to page - * \param[in] bsub subscriber we want to page - * \param[in] type type of radio channel we're requirign - * \param[in] msc MSC which has issue this paging - * \returns 0 on success, negative on error */ -static int _paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub, int type, - struct bsc_msc_data *msc) -{ - struct gsm_bts_paging_state *bts_entry = &bts->paging; - struct gsm_paging_request *req; - - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ATTEMPTED]); - - if (paging_pending_request(bts_entry, bsub)) { - LOGP(DPAG, LOGL_INFO, "(bts=%d) Paging request already pending for %s\n", - bts->nr, bsc_subscr_name(bsub)); - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ALREADY]); - return -EEXIST; - } - - LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Start paging of subscriber %s\n", bts->nr, - bsc_subscr_name(bsub)); - req = talloc_zero(tall_paging_ctx, struct gsm_paging_request); - OSMO_ASSERT(req); - req->bsub = bsc_subscr_get(bsub); - req->bts = bts; - req->chan_type = type; - req->msc = msc; - osmo_timer_setup(&req->T3113, paging_T3113_expired, req); - osmo_timer_schedule(&req->T3113, bts->network->T3113, 0); - llist_add_tail(&req->entry, &bts_entry->pending_requests); - paging_schedule_if_needed(bts_entry); - - return 0; -} - -/*! Handle PAGING request from MSC for one (matching) BTS - * \param bts BTS on which to page - * \param[in] bsub subscriber we want to page - * \param[in] type type of radio channel we're requirign - * \param[in] msc MSC which has issue this paging - * returns 1 on success; 0 in case of error (e.g. TRX down) */ -int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub, int type, - struct bsc_msc_data *msc) -{ - int rc; - - /* skip all currently inactive TRX */ - 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(bts, bsub, type, msc); - if (rc < 0) - return 0; - return 1; -} - -/*! Stop paging a given subscriber on a given BTS. - * If \a conn is non-NULL, we also call the paging call-back function - * to notify the paging originator that paging has completed. - * \param[in] bts BTS on which we shall stop paging - * \param[in] bsub subscriber which we shall stop paging - * \param[in] conn connection to the subscriber (if any) - * \param[in] msg message received from subscrbier (if any) - * \returns 0 if an active paging request was stopped, an error code otherwise. */ -/* we consciously ignore the type of the request here */ -static int _paging_request_stop(struct gsm_bts *bts, struct bsc_subscr *bsub, - struct gsm_subscriber_connection *conn, - struct msgb *msg) -{ - struct gsm_bts_paging_state *bts_entry = &bts->paging; - struct gsm_paging_request *req, *req2; - - paging_init_if_needed(bts); - - llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests, - entry) { - if (req->bsub == bsub) { - /* now give up the data structure */ - paging_remove_request(&bts->paging, req); - LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Stop paging %s\n", bts->nr, - bsc_subscr_name(bsub)); - return 0; - } - } - - return -ENOENT; -} - -/*! Stop paging on all other bts' - * \param[in] bts_list list of BTSs to iterate - * \param[in] _bts BTS which has received a paging response - * \param[in] bsub subscriber - * \param[in] msgb L3 message that we have received from \a bsub on \a _bts */ -void paging_request_stop(struct llist_head *bts_list, - struct gsm_bts *_bts, struct bsc_subscr *bsub, - struct gsm_subscriber_connection *conn, - struct msgb *msg) -{ - struct gsm_bts *bts; - - log_set_context(LOG_CTX_BSC_SUBSCR, bsub); - - /* Stop this first and dispatch the request */ - if (_bts) { - if (_paging_request_stop(_bts, bsub, conn, msg) == 0) { - rate_ctr_inc(&_bts->bts_ctrs->ctr[BTS_CTR_PAGING_RESPONDED]); - rate_ctr_inc(&_bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_RESPONDED]); - } - } - - /* Make sure to cancel this everywhere else */ - llist_for_each_entry(bts, bts_list, list) { - /* Sort of an optimization. */ - if (bts == _bts) - continue; - _paging_request_stop(bts, bsub, NULL, NULL); - } -} - - -/*! Update the BTS paging buffer slots on given BTS */ -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); -} - -/*! Count the number of pending paging requests on given BTS */ -unsigned int paging_pending_requests_nr(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; -} - -/*! Find any paging data for the given subscriber at the given BTS. */ -struct bsc_msc_data *paging_get_msc(struct gsm_bts *bts, struct bsc_subscr *bsub) -{ - struct gsm_paging_request *req; - - llist_for_each_entry(req, &bts->paging.pending_requests, entry) - if (req->bsub == bsub) - return req->msc; - - return NULL; -} - -/*! Flush all paging requests at a given BTS for a given MSC (or NULL if all MSC should be flushed). */ -void paging_flush_bts(struct gsm_bts *bts, struct bsc_msc_data *msc) -{ - struct gsm_paging_request *req, *req2; - - paging_init_if_needed(bts); - - llist_for_each_entry_safe(req, req2, &bts->paging.pending_requests, entry) { - if (msc && req->msc != msc) - continue; - /* now give up the data structure */ - LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Stop paging %s (flush)\n", bts->nr, - bsc_subscr_name(req->bsub)); - paging_remove_request(&bts->paging, req); - } -} - -/*! Flush all paging requests issued by \a msc on any BTS in \a net */ -void paging_flush_network(struct gsm_network *net, struct bsc_msc_data *msc) -{ - struct gsm_bts *bts; - - llist_for_each_entry(bts, &net->bts_list, list) - paging_flush_bts(bts, msc); -} diff --git a/src/libbsc/pcu_sock.c b/src/libbsc/pcu_sock.c deleted file mode 100644 index 64422c724..000000000 --- a/src/libbsc/pcu_sock.c +++ /dev/null @@ -1,740 +0,0 @@ -/* pcu_sock.c: Connect from PCU via unix domain socket */ - -/* (C) 2008-2010 by Harald Welte - * (C) 2009-2012 by Andreas Eversberg - * (C) 2012 by Holger Hans Peter Freyther - * 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 - * (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. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -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 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", -}; - -/* Check if BTS has a PCU connection */ -static bool pcu_connected(struct gsm_bts *bts) -{ - struct pcu_sock_state *state = bts->pcu_state; - - if (!state) - return false; - if (state->conn_bfd.fd <= 0) - return false; - return true; -} - -/* - * PCU messages - */ - -/* Set up an message buffer to package an pcu interface message */ -struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr) -{ - struct msgb *msg; - struct gsm_pcu_if *pcu_prim; - - msg = msgb_alloc(sizeof(struct gsm_pcu_if), "pcu_sock_tx"); - if (!msg) - return NULL; - - msgb_put(msg, sizeof(struct gsm_pcu_if)); - pcu_prim = (struct gsm_pcu_if *) msg->data; - pcu_prim->msg_type = msg_type; - pcu_prim->bts_nr = bts_nr; - - return msg; -} - -/* Helper function exclusivly used by pcu_if_signal_cb() */ -static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) { - if (ts->pchan == GSM_PCHAN_PDCH) - return true; - if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { - /* When we're busy deactivating the PDCH, we first set - * DEACT_PENDING, tell the PCU about it and wait for a - * response. So DEACT_PENDING means "no PDCH" to the PCU. - * Similarly, when we're activating PDCH, we set the - * ACT_PENDING and wait for an activation response from the - * PCU, so ACT_PENDING means "is PDCH". */ - if (ts->flags & TS_F_PDCH_ACTIVE) - return !(ts->flags & TS_F_PDCH_DEACT_PENDING); - else - return (ts->flags & TS_F_PDCH_ACT_PENDING); - } - if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { - /* - * When we're busy de-/activating the PDCH, we first set - * ts->dyn.pchan_want, tell the PCU about it and wait for a - * response. So only care about dyn.pchan_want here. - */ - return ts->dyn.pchan_want == GSM_PCHAN_PDCH; - } - return false; -} - -/* Send BTS properties to the PCU */ -static int pcu_tx_info_ind(struct gsm_bts *bts) -{ - struct msgb *msg; - 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_trx *trx; - struct gsm_bts_trx_ts *ts; - int i, j; - - OSMO_ASSERT(bts); - OSMO_ASSERT(bts->network); - - LOGP(DPCU, LOGL_INFO, "Sending info for BTS %d\n",bts->nr); - - rlcc = &bts->gprs.cell.rlc_cfg; - - msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr); - if (!msg) - return -ENOMEM; - - pcu_prim = (struct gsm_pcu_if *) msg->data; - 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; - - /* RAI */ - info_ind->mcc = bts->network->plmn.mcc; - info_ind->mnc = bts->network->plmn.mnc; - info_ind->mnc_3_digits = bts->network->plmn.mnc_3_digits; - info_ind->lac = bts->location_area_code; - info_ind->rac = bts->gprs.rac; - - /* NSE */ - info_ind->nsei = bts->gprs.nse.nsei; - memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7); - memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11); - - /* cell attributes */ - info_ind->cell_id = bts->cell_identity; - info_ind->repeat_time = rlcc->paging.repeat_time; - info_ind->repeat_count = rlcc->paging.repeat_count; - info_ind->bvci = bts->gprs.cell.bvci; - info_ind->t3142 = rlcc->parameter[RLC_T3142]; - info_ind->t3169 = rlcc->parameter[RLC_T3169]; - info_ind->t3191 = rlcc->parameter[RLC_T3191]; - info_ind->t3193_10ms = rlcc->parameter[RLC_T3193]; - info_ind->t3195 = rlcc->parameter[RLC_T3195]; - info_ind->n3101 = rlcc->parameter[RLC_N3101]; - info_ind->n3103 = rlcc->parameter[RLC_N3103]; - info_ind->n3105 = rlcc->parameter[RLC_N3105]; - info_ind->cv_countdown = rlcc->parameter[CV_COUNTDOWN]; - if (rlcc->cs_mask & (1 << GPRS_CS1)) - info_ind->flags |= PCU_IF_FLAG_CS1; - if (rlcc->cs_mask & (1 << GPRS_CS2)) - info_ind->flags |= PCU_IF_FLAG_CS2; - if (rlcc->cs_mask & (1 << GPRS_CS3)) - info_ind->flags |= PCU_IF_FLAG_CS3; - if (rlcc->cs_mask & (1 << GPRS_CS4)) - info_ind->flags |= PCU_IF_FLAG_CS4; - if (bts->gprs.mode == BTS_GPRS_EGPRS) { - if (rlcc->cs_mask & (1 << GPRS_MCS1)) - info_ind->flags |= PCU_IF_FLAG_MCS1; - if (rlcc->cs_mask & (1 << GPRS_MCS2)) - info_ind->flags |= PCU_IF_FLAG_MCS2; - if (rlcc->cs_mask & (1 << GPRS_MCS3)) - info_ind->flags |= PCU_IF_FLAG_MCS3; - if (rlcc->cs_mask & (1 << GPRS_MCS4)) - info_ind->flags |= PCU_IF_FLAG_MCS4; - if (rlcc->cs_mask & (1 << GPRS_MCS5)) - info_ind->flags |= PCU_IF_FLAG_MCS5; - if (rlcc->cs_mask & (1 << GPRS_MCS6)) - info_ind->flags |= PCU_IF_FLAG_MCS6; - if (rlcc->cs_mask & (1 << GPRS_MCS7)) - info_ind->flags |= PCU_IF_FLAG_MCS7; - if (rlcc->cs_mask & (1 << GPRS_MCS8)) - info_ind->flags |= PCU_IF_FLAG_MCS8; - 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" - info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT]; -#warning "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]; - info_ind->nsvci[i] = nsvc->nsvci; - info_ind->local_port[i] = nsvc->local_port; - info_ind->remote_port[i] = nsvc->remote_port; - info_ind->remote_ip[i] = nsvc->remote_ip; - } - - for (i = 0; i < ARRAY_SIZE(info_ind->trx); i++) { - 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 (j = 0; j < ARRAY_SIZE(trx->ts); j++) { - ts = &trx->ts[j]; - if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED - && ts_should_be_pdch(ts)) { - info_ind->trx[i].pdch_mask |= (1 << j); - info_ind->trx[i].tsc[j] = - (ts->tsc >= 0) ? ts->tsc : bts->bsic & 7; - LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: " - "available (tsc=%d arfcn=%d)\n", - trx->nr, ts->nr, - info_ind->trx[i].tsc[j], - info_ind->trx[i].arfcn); - } - } - } - - return pcu_sock_send(bts, msg); -} - -void pcu_info_update(struct gsm_bts *bts) -{ - if (pcu_connected(bts)) - pcu_tx_info_ind(bts); -} - -/* Forward rach indication to PCU */ -int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn, - uint8_t is_11bit, enum ph_burst_type burst_type) -{ - struct msgb *msg; - struct gsm_pcu_if *pcu_prim; - 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); - return -ENODEV; - } - - LOGP(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); - if (!msg) - return -ENOMEM; - pcu_prim = (struct gsm_pcu_if *) msg->data; - rach_ind = &pcu_prim->u.rach_ind; - - rach_ind->sapi = PCU_IF_SAPI_RACH; - rach_ind->ra = ra; - rach_ind->qta = qta; - rach_ind->fn = fn; - rach_ind->is_11bit = is_11bit; - rach_ind->burst_type = burst_type; - - return pcu_sock_send(bts, msg); -} - -/* Confirm the sending of an immediate assignment to the pcu */ -int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli) -{ - struct msgb *msg; - struct gsm_pcu_if *pcu_prim; - struct gsm_pcu_if_data_cnf_dt *data_cnf_dt; - - LOGP(DPCU, LOGL_INFO, "Sending PCH confirm with direct TLLI\n"); - - msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_DT, 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_dt->sapi = PCU_IF_SAPI_PCH; - data_cnf_dt->tlli = tlli; - - return pcu_sock_send(bts, msg); -} - -/* we need to decode the raw RR paging messsage (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) -{ - struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) raw_rr_msg; - uint8_t chan_needed; - unsigned int mi_len; - uint8_t *mi; - int rc; - - switch (p1->msg_type) { - case GSM48_MT_RR_PAG_REQ_1: - chan_needed = (p1->cneed2 << 2) | p1->cneed1; - mi_len = p1->data[0]; - mi = p1->data+1; - LOGP(DPCU, LOGL_ERROR, "PCU Sends paging " - "request type %02x (chan_needed=%02x, mi_len=%u, mi=%s)\n", - p1->msg_type, chan_needed, mi_len, - osmo_hexdump_nospc(mi,mi_len)); - /* NOTE: We will have to add 2 to mi_len and subtract 2 from - * the mi pointer because rsl_paging_cmd() will perform the - * reverse operations. This is because rsl_paging_cmd() is - * normally expected to chop off the element identifier (0xC0) - * and the length field. In our parameter, we do not have - * those fields included. */ - rc = rsl_paging_cmd(bts, paging_group, mi_len+2, mi-2, - chan_needed, true); - break; - case GSM48_MT_RR_PAG_REQ_2: - case GSM48_MT_RR_PAG_REQ_3: - LOGP(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 " - "request type %02x\n", p1->msg_type); - rc = -EINVAL; - break; - } - - return rc; -} - -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; - - LOGP(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; - 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 */ - - if (data_req->len < 5) { - LOGP(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); - - 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 (rc) { - msgb_free(msg); - rc = -EIO; - } - break; - default: - LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " - "unsupported sapi %d\n", data_req->sapi); - rc = -EINVAL; - } - - return rc; -} - -static int pcu_rx(struct gsm_network *net, uint8_t msg_type, - struct gsm_pcu_if *pcu_prim) -{ - int rc = 0; - struct gsm_bts *bts; - - /* FIXME: allow multiple BTS */ - bts = llist_entry(net->bts_list.next, struct gsm_bts, list); - - switch (msg_type) { - case PCU_IF_MSG_DATA_REQ: - case PCU_IF_MSG_PAG_REQ: - rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req); - break; - default: - LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n", - msg_type); - rc = -EINVAL; - } - - return rc; -} - -/* - * PCU socket interface - */ - -static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg) -{ - struct pcu_sock_state *state = bts->pcu_state; - struct osmo_fd *conn_bfd; - struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data; - - if (!state) { - if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) - LOGP(DPCU, LOGL_INFO, "PCU socket not created, " - "dropping message\n"); - msgb_free(msg); - return -EINVAL; - } - conn_bfd = &state->conn_bfd; - if (conn_bfd->fd <= 0) { - if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) - LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, " - "dropping message\n"); - msgb_free(msg); - return -EIO; - } - msgb_enqueue(&state->upqueue, msg); - conn_bfd->when |= BSC_FD_WRITE; - - return 0; -} - -static void pcu_sock_close(struct pcu_sock_state *state) -{ - 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 |= BSC_FD_READ; - -#if 0 - /* remove si13, ... */ - bts->si_valid &= ~(1 << SYSINFO_TYPE_13); - osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); -#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]; - if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED - && ts->pchan == GSM_PCHAN_PDCH) { - printf("l1sap_chan_rel(trx,gsm_lchan2chan_nr(ts->lchan));\n"); - } - } - } - - /* flush the queue */ - while (!llist_empty(&state->upqueue)) { - struct msgb *msg = msgb_dequeue(&state->upqueue); - msgb_free(msg); - } -} - -static int pcu_sock_read(struct osmo_fd *bfd) -{ - struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data; - struct gsm_pcu_if *pcu_prim; - struct msgb *msg; - int rc; - - msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx"); - if (!msg) - return -ENOMEM; - - pcu_prim = (struct gsm_pcu_if *) msg->tail; - - rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); - if (rc == 0) - goto close; - - if (rc < 0) { - if (errno == EAGAIN) - return 0; - goto close; - } - - rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim); - - /* as we always synchronously process the message in pcu_rx() and - * its callbacks, we can free the message here. */ - msgb_free(msg); - - return rc; - -close: - msgb_free(msg); - pcu_sock_close(state); - return -1; -} - -static int pcu_sock_write(struct osmo_fd *bfd) -{ - 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 &= ~BSC_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 |= BSC_FD_WRITE; - break; - } - goto close; - } - -dontsend: - /* _after_ we send it, we can deueue */ - msg2 = msgb_dequeue(&state->upqueue); - assert(msg == msg2); - msgb_free(msg); - } - return 0; - -close: - pcu_sock_close(state); - - return -1; -} - -static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags) -{ - int rc = 0; - - if (flags & BSC_FD_READ) - rc = pcu_sock_read(bfd); - if (rc < 0) - return rc; - - if (flags & BSC_FD_WRITE) - rc = pcu_sock_write(bfd); - - return rc; -} - -/* accept connection comming 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 sockaddr_un un_addr; - socklen_t len; - int rc; - - len = sizeof(un_addr); - rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); - if (rc < 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"); - /* We already have one PCU connected, this is all we support */ - state->listen_bfd.when &= ~BSC_FD_READ; - close(rc); - return 0; - } - - conn_bfd->fd = rc; - conn_bfd->when = BSC_FD_READ; - conn_bfd->cb = pcu_sock_cb; - conn_bfd->data = state; - - if (osmo_fd_register(conn_bfd) != 0) { - LOGP(DPCU, LOGL_ERROR, "Failed to register new connection " - "fd\n"); - close(conn_bfd->fd); - conn_bfd->fd = -1; - return -1; - } - - LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n"); - - return 0; -} - -/* Open connection to PCU */ -int pcu_sock_init(const char *path, struct gsm_bts *bts) -{ - struct pcu_sock_state *state; - struct osmo_fd *bfd; - int rc; - - state = talloc_zero(NULL, struct pcu_sock_state); - if (!state) - return -ENOMEM; - - INIT_LLIST_HEAD(&state->upqueue); - state->net = bts->network; - state->conn_bfd.fd = -1; - - bfd = &state->listen_bfd; - - bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, - OSMO_SOCK_F_BIND); - if (bfd->fd < 0) { - LOGP(DPCU, LOGL_ERROR, "Could not create unix socket: %s\n", - strerror(errno)); - talloc_free(state); - return -1; - } - - bfd->when = BSC_FD_READ; - bfd->cb = pcu_sock_accept; - bfd->data = state; - - rc = osmo_fd_register(bfd); - if (rc < 0) { - LOGP(DPCU, LOGL_ERROR, "Could not register listen fd: %d\n", - rc); - close(bfd->fd); - talloc_free(state); - return rc; - } - - bts->pcu_state = state; - return 0; -} - -/* Close connection to PCU */ -void pcu_sock_exit(struct gsm_bts *bts) -{ - struct pcu_sock_state *state = bts->pcu_state; - struct osmo_fd *bfd, *conn_bfd; - - if (!state) - return; - - conn_bfd = &state->conn_bfd; - if (conn_bfd->fd > 0) - pcu_sock_close(state); - bfd = &state->listen_bfd; - close(bfd->fd); - osmo_fd_unregister(bfd); - talloc_free(state); - bts->pcu_state = NULL; -} - diff --git a/src/libbsc/penalty_timers.c b/src/libbsc/penalty_timers.c deleted file mode 100644 index b80fec946..000000000 --- a/src/libbsc/penalty_timers.c +++ /dev/null @@ -1,129 +0,0 @@ -/* (C) 2018 by sysmocom - s.f.m.c. GmbH - * - * All Rights Reserved - * - * Author: Neels Hofmeyr - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include - -#include - -#include -#include - -struct penalty_timers { - struct llist_head timers; -}; - -struct penalty_timer { - struct llist_head entry; - 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; -} - -void penalty_timers_add(struct penalty_timers *pt, void *for_object, int timeout) -{ - struct penalty_timer *timer; - unsigned int now; - unsigned int then; - now = time_now(); - - if (timeout <= 0) - return; - - then = now + timeout; - - /* timer already running for that BTS? */ - llist_for_each_entry(timer, &pt->timers, entry) { - if (timer->for_object != for_object) - continue; - /* raise, if running timer will timeout earlier or has timed - * out already, otherwise keep later timeout */ - if (timer->timeout < then) - timer->timeout = then; - return; - } - - /* add new timer */ - timer = talloc_zero(pt, struct penalty_timer); - if (!timer) - return; - - timer->for_object = for_object; - timer->timeout = then; - - llist_add_tail(&timer->entry, &pt->timers); -} - -unsigned int penalty_timers_remaining(struct penalty_timers *pt, void *for_object) -{ - struct penalty_timer *timer; - unsigned int now = time_now(); - unsigned int max_remaining = 0; - llist_for_each_entry(timer, &pt->timers, entry) { - unsigned int remaining; - if (timer->for_object != for_object) - continue; - if (now >= timer->timeout) - continue; - remaining = timer->timeout - now; - if (remaining > max_remaining) - max_remaining = remaining; - } - return max_remaining; -} - -void penalty_timers_clear(struct penalty_timers *pt, void *for_object) -{ - struct penalty_timer *timer, *timer2; - llist_for_each_entry_safe(timer, timer2, &pt->timers, entry) { - if (for_object && timer->for_object != for_object) - 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/libbsc/rest_octets.c b/src/libbsc/rest_octets.c deleted file mode 100644 index 9f2b4c0ab..000000000 --- a/src/libbsc/rest_octets.c +++ /dev/null @@ -1,878 +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 - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -/* 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 = budget - 25; - 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); - - /* No Serving Cell Priority Parameters Descr. */ - bitvec_set_bit(bv, 0); - /* 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); - - appended = append_eutran_neib_cell(bv, bts, rem); - if (!appended) { /* appending is impossible within current budget: rollback */ - bv->cur_bit = old; - return; - } - - /* 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) - - ::= -{L | H } -{L | H } -{ < 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) > } -; -*/ -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 > } } ; - - < 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 ** = } } ; - < Extension Information > ::= - { 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit > - < BEP_PERIOD : bit(4) > } - < PFC_FEATURE_MODE : bit > - < DTM_SUPPORT : 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 */ - if (gco->supports_egprs_11bit_rach == 0) { - bitvec_set_bit(bv, - gco->ext_info.use_egprs_p_ch_req); - } else { - bitvec_set_bit(bv, 0); - } - - /* 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/libbsc/system_information.c b/src/libbsc/system_information.c deleted file mode 100644 index d99153f24..000000000 --- a/src/libbsc/system_information.c +++ /dev/null @@ -1,1210 +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) 2008-2010 by Harald Welte - * (C) 2012 Holger Hans Peter Freyther - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -/* - * DCS1800 and PCS1900 have overlapping ARFCNs. We would need to set the - * ARFCN_PCS flag on the 1900 ARFCNs but this would increase cell_alloc - * and other arrays to make sure (ARFCN_PCS + 1024)/8 ARFCNs fit into the - * 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) -{ - enum gsm_band band = gsm_arfcn2band(arfcn); - - /* normal case */ - if (band == bts->band) - return 1; - /* deal with ARFCN_PCS not set */ - if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) - return 1; - - return 0; -} - -static int is_dcs_net(const struct gsm_bts *bts) -{ - if (bts->band == GSM_BAND_850) - return 0; - if (bts->band == GSM_BAND_1900) - return 0; - return 1; -} - -/* Return p(n) for given NR_OF_TDD_CELLS - see Table 9.1.54.1a, 3GPP TS 44.018 */ -unsigned range1024_p(unsigned n) -{ - switch (n) { - case 0: return 0; - case 1: return 10; - case 2: return 19; - case 3: return 28; - case 4: return 36; - case 5: return 44; - case 6: return 52; - case 7: return 60; - case 8: return 67; - case 9: return 74; - case 10: return 81; - case 11: return 88; - case 12: return 95; - case 13: return 102; - case 14: return 109; - case 15: return 116; - case 16: return 122; - default: return 0; - } -} - -/* Return q(m) for given NR_OF_TDD_CELLS - see Table 9.1.54.1b, 3GPP TS 44.018 */ -unsigned range512_q(unsigned m) -{ - switch (m) { - case 0: return 0; - case 1: return 9; - case 2: return 17; - case 3: return 25; - case 4: return 32; - case 5: return 39; - case 6: return 46; - case 7: return 53; - case 8: return 59; - case 9: return 65; - case 10: return 71; - case 11: return 77; - case 12: return 83; - case 13: return 89; - case 14: return 95; - case 15: return 101; - case 16: return 106; - case 17: return 111; - case 18: return 116; - case 19: return 121; - case 20: return 126; - default: return 0; - } -} - -size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e) -{ - unsigned i, ret = 0; - - if (!e) - return 0; - - for (i = 0; i < e->length; i++) - if (e->arfcn[i] != OSMO_EARFCN_INVALID) - ret++; - - return ret; -} - -/* generate SI2quater messages, return rest octets length of last generated message or negative error code */ -static int make_si2quaters(struct gsm_bts *bts, bool counting) -{ - int rc; - bool memory_exceeded = true; - struct gsm48_system_information_type_2quater *si2q; - - for (bts->si2q_index = 0; bts->si2q_index < SI2Q_MAX_NUM; bts->si2q_index++) { - si2q = GSM_BTS_SI2Q(bts, bts->si2q_index); - if (counting) { /* that's legitimate if we're called for counting purpose: */ - if (bts->si2q_count < bts->si2q_index) - bts->si2q_count = bts->si2q_index; - } else { - memset(si2q, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - - si2q->header.l2_plen = GSM48_LEN2PLEN(22); - si2q->header.rr_protocol_discriminator = GSM48_PDISC_RR; - si2q->header.skip_indicator = 0; - si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater; - } - - rc = rest_octets_si2quater(si2q->rest_octets, bts); - if (rc < 0) - return rc; - - if (bts->u_offset >= bts->si_common.uarfcn_length && - bts->e_offset >= si2q_earfcn_count(&bts->si_common.si2quater_neigh_list)) { - memory_exceeded = false; - break; - } - } - - if (memory_exceeded) - return -ENOMEM; - - return rc; -} - -/* we generate SI2q rest octets twice to get proper estimation but it's one time cost anyway */ -uint8_t si2q_num(struct gsm_bts *bts) -{ - int rc = make_si2quaters(bts, true); - uint8_t num = bts->si2q_index + 1; /* number of SI2quater messages */ - - /* N. B: si2q_num() should NEVER be called during actual SI2q rest octets generation - we're not re-entrant because of the following code: */ - bts->u_offset = 0; - bts->e_offset = 0; - - if (rc < 0) - return 0xFF; /* return impossible index as an indicator of error in generating SI2quater */ - - return num; -} - -/* 3GPP TS 44.018, Table 9.1.54.1 - prepend diversity bit to scrambling code */ -static inline uint16_t encode_fdd(uint16_t scramble, bool diversity) -{ - if (diversity) - return scramble | (1 << 9); - return scramble; -} - -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); - - if (r < 0) - return r; - - if (e->thresh_hi && thresh_hi != e->thresh_hi) - r = 1; - - e->thresh_hi = thresh_hi; - - if (thresh_lo != EARFCN_THRESH_LOW_INVALID) { - if (e->thresh_lo_valid && e->thresh_lo != thresh_lo) - r = EARFCN_THRESH_LOW_INVALID; - e->thresh_lo = thresh_lo; - e->thresh_lo_valid = true; - } - - if (qrx != EARFCN_QRXLV_INVALID) { - if (e->qrxlm_valid && e->qrxlm != qrx) - r = EARFCN_QRXLV_INVALID + 1; - e->qrxlm = qrx; - e->qrxlm_valid = true; - } - - if (prio != EARFCN_PRIO_INVALID) { - if (e->prio_valid && e->prio != prio) - r = EARFCN_PRIO_INVALID; - e->prio = prio; - e->prio_valid = true; - } - - return r; -} - -/* Scrambling Code as defined in 3GPP TS 25.213 is 9 bit long so number below is unreacheable upper bound */ -#define SC_BOUND 600 - -/* Find position for a given UARFCN (take SC into consideration if it's available) in a sorted list - N. B: we rely on the assumption that (uarfcn, scramble) tuple is unique in the lists */ -static int uarfcn_sc_pos(const struct gsm_bts *bts, uint16_t uarfcn, uint16_t scramble) -{ - const uint16_t *sc = bts->si_common.data.scramble_list; - uint16_t i, scramble0 = encode_fdd(scramble, false), scramble1 = encode_fdd(scramble, true); - for (i = 0; i < bts->si_common.uarfcn_length; i++) - if (uarfcn == bts->si_common.data.uarfcn_list[i]) { - if (scramble < SC_BOUND) { - if (scramble0 == sc[i] || scramble1 == sc[i]) - return i; - } else - return i; - } - - return -1; -} - -int bts_uarfcn_del(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble) -{ - uint16_t *ual = bts->si_common.data.uarfcn_list, *scl = bts->si_common.data.scramble_list; - size_t len = bts->si_common.uarfcn_length; - int pos = uarfcn_sc_pos(bts, arfcn, scramble); - - if (pos < 0) - return -EINVAL; - - if (pos != len - 1) { /* move the tail if necessary */ - memmove(ual + pos, ual + pos + 1, 2 * (len - pos + 1)); - memmove(scl + pos, scl + pos + 1, 2 * (len - pos + 1)); - } - - bts->si_common.uarfcn_length--; - return 0; -} - -int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool diversity) -{ - 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), - *ual = bts->si_common.data.uarfcn_list, - *scl = bts->si_common.data.scramble_list; - - 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; - - /* move the tail to make space for inserting if necessary */ - if (i < len) { - memmove(ual + i + 1, ual + i, (len - i) * 2); - memmove(scl + i + 1, scl + i, (len - i) * 2); - } - - /* insert into appropriate position */ - ual[i] = arfcn; - scl[i] = scr; - bts->si_common.uarfcn_length++; - /* try to generate SI2q */ - si2q = si2q_num(bts); - - if (si2q <= SI2Q_MAX_NUM) { - bts->si2q_count = si2q - 1; - return 0; - } - - /* rollback after unsuccessful generation */ - bts_uarfcn_del(bts, arfcn, scramble); - return -ENOSPC; -} - -static inline int use_arfcn(const struct gsm_bts *bts, const bool bis, const bool ter, - const bool pgsm, const int arfcn) -{ - if (bts->force_combined_si) - return !bis && !ter; - if (!bis && !ter && band_compatible(bts, arfcn)) - return 1; - /* Correct but somehow broken with either the nanoBTS or the iPhone5 */ - if (bis && pgsm && band_compatible(bts, arfcn) && (arfcn < 1 || arfcn > 124)) - return 1; - if (ter && !band_compatible(bts, arfcn)) - return 1; - return 0; -} - -/* Frequency Lists as per TS 04.08 10.5.2.13 */ - -/* 10.5.2.13.2: Bit map 0 format */ -static int freq_list_bm0_set_arfcn(uint8_t *chan_list, unsigned int arfcn) -{ - unsigned int byte, bit; - - if (arfcn > 124 || arfcn < 1) { - LOGP(DRR, LOGL_ERROR, "Bitmap 0 only supports ARFCN 1...124\n"); - return -EINVAL; - } - - /* the bitmask is from 1..124, not from 0..123 */ - arfcn--; - - byte = arfcn / 8; - bit = arfcn % 8; - - chan_list[GSM48_CELL_CHAN_DESC_SIZE-1-byte] |= (1 << bit); - - return 0; -} - -/* 10.5.2.13.7: Variable bit map format */ -static int freq_list_bmrel_set_arfcn(uint8_t *chan_list, unsigned int arfcn) -{ - unsigned int byte, bit; - unsigned int min_arfcn; - unsigned int bitno; - - min_arfcn = (chan_list[0] & 1) << 9; - min_arfcn |= chan_list[1] << 1; - min_arfcn |= (chan_list[2] >> 7) & 1; - - /* The lower end of our bitmaks is always implicitly included */ - if (arfcn == min_arfcn) - return 0; - - if (((arfcn - min_arfcn) & 1023) > 111) { - LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn); - return -EINVAL; - } - - bitno = (arfcn - min_arfcn) & 1023; - byte = bitno / 8; - bit = bitno % 8; - - chan_list[2 + byte] |= 1 << (7 - bit); - - return 0; -} - -/* generate a variable bitmap */ -static inline int enc_freq_lst_var_bitmap(uint8_t *chan_list, - struct bitvec *bv, const struct gsm_bts *bts, - bool bis, bool ter, int min, bool pgsm) -{ - int i; - - /* set it to 'Variable bitmap format' */ - chan_list[0] = 0x8e; - - chan_list[0] |= (min >> 9) & 1; - chan_list[1] = (min >> 1); - chan_list[2] = (min & 1) << 7; - - for (i = 0; i < bv->data_len*8; i++) { - /* see notes in bitvec2freq_list */ - if (bitvec_get_bit_pos(bv, i) - && ((!bis && !ter && band_compatible(bts,i)) - || (bis && pgsm && band_compatible(bts,i) && (i < 1 || i > 124)) - || (ter && !band_compatible(bts, i)))) { - int rc = freq_list_bmrel_set_arfcn(chan_list, i); - if (rc < 0) - return rc; - } - } - - return 0; -} - -int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w, - int f0, uint8_t *chan_list) -{ - /* - * Manipulate the ARFCN list according to the rules in J4 depending - * on the selected range. - */ - int rc, f0_included; - - range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included); - - rc = 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); - default: - return -ERANGE; - }; - - return f0_included; -} - -/* generate a frequency list with the range 512 format */ -static inline int enc_freq_lst_range(uint8_t *chan_list, - 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_used = 0; - int i, range, f0; - - /* - * Select ARFCNs according to the rules in bitvec2freq_list - */ - for (i = 0; i < bv->data_len * 8; ++i) { - /* More ARFCNs than the maximum */ - if (arfcns_used > ARRAY_SIZE(arfcns)) - return -1; - /* Check if we can select it? */ - if (bitvec_get_bit_pos(bv, i) && use_arfcn(bts, bis, ter, pgsm, i)) - arfcns[arfcns_used++] = i; - } - - /* - * Check if the given list of ARFCNs can be encoded. - */ - range = range_enc_determine_range(arfcns, arfcns_used, &f0); - if (range == ARFCN_RANGE_INVALID) - return -2; - - memset(w, 0, sizeof(w)); - return range_encode(range, arfcns, arfcns_used, w, f0, chan_list); -} - -/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ -static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv, - const struct gsm_bts *bts, bool bis, bool ter) -{ - int i, rc, min = -1, max = -1, arfcns = 0; - bool pgsm = false; - memset(chan_list, 0, 16); - - if (bts->band == GSM_BAND_900 - && bts->c0->arfcn >= 1 && bts->c0->arfcn <= 124) - pgsm = true; - /* 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; - } - } - return 0; - } - - for (i = 0; i < bv->data_len*8; i++) { - /* in case of SI2 or SI5 allow all neighbours in same band - * in case of SI*bis, allow neighbours in same band ouside pgsm - * in case of SI*ter, allow neighbours in different bands - */ - if (!bitvec_get_bit_pos(bv, i)) - continue; - if (!use_arfcn(bts, bis, ter, pgsm, i)) - continue; - /* count the arfcns we want to carry */ - arfcns += 1; - - /* 955..1023 < 0..885 */ - if (min < 0) - min = i; - if (i >= 955 && min < 955) - min = i; - if (i >= 955 && min >= 955 && i < min) - min = i; - if (i < 955 && min < 955 && i < min) - min = i; - if (max < 0) - max = i; - if (i < 955 && max >= 955) - max = i; - if (i >= 955 && max >= 955 && i > max) - max = i; - if (i < 955 && max < 955 && i > max) - max = i; - } - - if (max == -1) { - /* Empty set, use 'bit map 0 format' */ - chan_list[0] = 0; - return 0; - } - - /* Now find the best encoding */ - if (((max - min) & 1023) <= 111) - return enc_freq_lst_var_bitmap(chan_list, bv, bts, bis, - ter, min, pgsm); - - /* Attempt to do the range encoding */ - rc = enc_freq_lst_range(chan_list, bv, bts, bis, ter, pgsm); - if (rc >= 0) - return 0; - - LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, arfcns=%d " - "can not generate ARFCN list", min, max, arfcns); - return -EINVAL; -} - -/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ -/* static*/ int generate_cell_chan_list(uint8_t *chan_list, 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); - - /* first we generate a bitvec of all TRX ARFCN's in our BTS */ - 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]; - /* 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); - } - } - } - - /* then we generate a GSM 04.08 frequency list from the bitvec */ - return bitvec2freq_list(chan_list, bv, bts, false, false); -} - -/*! generate a cell channel list as per Section 10.5.2.22 of 04.08 - * \param[out] chan_list caller-provided output buffer - * \param[in] bts BTS descriptor used for input data - * \param[in] si5 Are we generating SI5xxx (true) or SI2xxx (false) - * \param[in] bis Are we generating SIXbis (true) or not (false) - * \param[in] ter Are we generating SIXter (true) or not (false) - */ -static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts, - bool si5, bool bis, bool ter) -{ - struct gsm_bts *cur_bts; - struct bitvec *bv; - int rc; - - if (si5 && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) - bv = &bts->si_common.si5_neigh_list; - else - bv = &bts->si_common.neigh_list; - - /* Generate list of neighbor cells if we are in automatic mode */ - if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) { - /* Zero-initialize the bit-vector */ - memset(bv->data, 0, bv->data_len); - - /* first we generate a bitvec of the BCCH ARFCN's in our BSC */ - llist_for_each_entry(cur_bts, &bts->network->bts_list, list) { - if (cur_bts == bts) - continue; - bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1); - } - } - - /* then we generate a GSM 04.08 frequency list from the bitvec */ - rc = bitvec2freq_list(chan_list, bv, bts, bis, ter); - if (rc < 0) - return rc; - - /* Set BA-IND depending on whether we're generating SI2 or SI5. - * 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 */ - if (si5) - chan_list[0] |= 0x10; - else - chan_list[0] &= ~0x10; - - return rc; -} - -static int list_arfcn(uint8_t *chan_list, uint8_t mask, char *text) -{ - int n = 0, i; - struct gsm_sysinfo_freq freq[1024]; - - memset(freq, 0, sizeof(freq)); - gsm48_decode_freq_list(freq, chan_list, 16, 0xce, 1); - for (i = 0; i < 1024; i++) { - if (freq[i].mask) { - if (!n) - LOGP(DRR, LOGL_INFO, "%s", text); - LOGPC(DRR, LOGL_INFO, " %d", i); - n++; - } - } - if (n) - LOGPC(DRR, LOGL_INFO, "\n"); - - return n; -} - -static int generate_si1(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - int rc; - struct gsm48_system_information_type_1 *si1 = (struct gsm48_system_information_type_1 *) GSM_BTS_SI(bts, t); - - memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - - si1->header.l2_plen = GSM48_LEN2PLEN(21); - si1->header.rr_protocol_discriminator = GSM48_PDISC_RR; - si1->header.skip_indicator = 0; - si1->header.system_information = GSM48_MT_RR_SYSINFO_1; - - rc = generate_cell_chan_list(si1->cell_channel_description, bts); - if (rc < 0) - return rc; - list_arfcn(si1->cell_channel_description, 0xce, "Serving cell:"); - - si1->rach_control = bts->si_common.rach_control; - if (acc_ramp_is_enabled(&bts->acc_ramp)) - acc_ramp_apply(&si1->rach_control, &bts->acc_ramp); - - /* - * 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)); - - return sizeof(*si1) + rc; -} - -static int generate_si2(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - int rc; - struct gsm48_system_information_type_2 *si2 = (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, t); - - memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - - si2->header.l2_plen = GSM48_LEN2PLEN(22); - si2->header.rr_protocol_discriminator = GSM48_PDISC_RR; - si2->header.skip_indicator = 0; - si2->header.system_information = GSM48_MT_RR_SYSINFO_2; - - rc = generate_bcch_chan_list(si2->bcch_frequency_list, bts, false, false, false); - if (rc < 0) - return rc; - list_arfcn(si2->bcch_frequency_list, 0xce, - "SI2 Neighbour cells in same band:"); - - si2->ncc_permitted = bts->si_common.ncc_permitted; - si2->rach_control = bts->si_common.rach_control; - if (acc_ramp_is_enabled(&bts->acc_ramp)) - acc_ramp_apply(&si2->rach_control, &bts->acc_ramp); - - return sizeof(*si2); -} - -static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - int rc; - struct gsm48_system_information_type_2bis *si2b = - (struct gsm48_system_information_type_2bis *) GSM_BTS_SI(bts, t); - int n; - - memset(si2b, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - - si2b->header.l2_plen = GSM48_LEN2PLEN(22); - si2b->header.rr_protocol_discriminator = GSM48_PDISC_RR; - si2b->header.skip_indicator = 0; - si2b->header.system_information = GSM48_MT_RR_SYSINFO_2bis; - - rc = generate_bcch_chan_list(si2b->bcch_frequency_list, bts, false, true, false); - if (rc < 0) - return rc; - n = list_arfcn(si2b->bcch_frequency_list, 0xce, - "Neighbour cells in same band, but outside P-GSM:"); - if (n) { - /* indicate in SI2 and SI2bis: there is an extension */ - struct gsm48_system_information_type_2 *si2 = - (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, SYSINFO_TYPE_2); - si2->bcch_frequency_list[0] |= 0x20; - si2b->bcch_frequency_list[0] |= 0x20; - } else - bts->si_valid &= ~(1 << SYSINFO_TYPE_2bis); - - si2b->rach_control = bts->si_common.rach_control; - if (acc_ramp_is_enabled(&bts->acc_ramp)) - acc_ramp_apply(&si2b->rach_control, &bts->acc_ramp); - - /* SI2bis Rest Octets as per 3GPP TS 44.018 §10.5.2.33 */ - rc = rest_octets_si2bis(si2b->rest_octets); - - return sizeof(*si2b) + rc; -} - -static int generate_si2ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - int rc; - struct gsm48_system_information_type_2ter *si2t = - (struct gsm48_system_information_type_2ter *) GSM_BTS_SI(bts, t); - int n; - - memset(si2t, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - - si2t->header.l2_plen = GSM48_LEN2PLEN(22); - si2t->header.rr_protocol_discriminator = GSM48_PDISC_RR; - si2t->header.skip_indicator = 0; - si2t->header.system_information = GSM48_MT_RR_SYSINFO_2ter; - - rc = generate_bcch_chan_list(si2t->ext_bcch_frequency_list, bts, false, false, true); - if (rc < 0) - return rc; - n = list_arfcn(si2t->ext_bcch_frequency_list, 0x8e, - "Neighbour cells in different band:"); - if (!n) - bts->si_valid &= ~(1 << SYSINFO_TYPE_2ter); - - /* SI2ter Rest Octets as per 3GPP TS 44.018 §10.5.2.33a */ - rc = rest_octets_si2ter(si2t->rest_octets); - - return sizeof(*si2t) + rc; -} - -/* SI2quater messages are optional - we only generate them when neighbor UARFCNs or EARFCNs are configured */ -static inline bool si2quater_not_needed(struct gsm_bts *bts) -{ - unsigned i = MAX_EARFCN_LIST; - - if (bts->si_common.si2quater_neigh_list.arfcn) - for (i = 0; i < MAX_EARFCN_LIST; i++) - if (bts->si_common.si2quater_neigh_list.arfcn[i] != OSMO_EARFCN_INVALID) - break; - - if (!bts->si_common.uarfcn_length && i == MAX_EARFCN_LIST) { - bts->si_valid &= ~(1 << SYSINFO_TYPE_2quater); /* mark SI2q as invalid if no (E|U)ARFCNs are present */ - return true; - } - - return false; -} - -static int generate_si2quater(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - int rc; - struct gsm48_system_information_type_2quater *si2q; - - if (si2quater_not_needed(bts)) /* generate rest_octets for SI2q only when necessary */ - return GSM_MACBLOCK_LEN; - - bts->u_offset = 0; - bts->e_offset = 0; - bts->si2q_index = 0; - bts->si2q_count = si2q_num(bts) - 1; - - rc = make_si2quaters(bts, false); - if (rc < 0) - return rc; - - OSMO_ASSERT(bts->si2q_count == bts->si2q_index); - OSMO_ASSERT(bts->si2q_count <= SI2Q_MAX_NUM); - - return sizeof(*si2q) + rc; -} - -static struct gsm48_si_ro_info si_info = { - .selection_params = { - .present = 0, - }, - .power_offset = { - .present = 0, - }, - .si2ter_indicator = false, - .early_cm_ctrl = true, - .scheduling = { - .present = 0, - }, - .gprs_ind = { - .si13_position = 0, - .ra_colour = 0, - .present = 1, - }, - .early_cm_restrict_3g = false, - .si2quater_indicator = false, - .lsa_params = { - .present = 0, - }, - .cell_id = 0, /* FIXME: doesn't the bts have this? */ - .break_ind = 0, -}; - -static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - int rc; - struct gsm48_system_information_type_3 *si3 = (struct gsm48_system_information_type_3 *) GSM_BTS_SI(bts, t); - - memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - - si3->header.l2_plen = GSM48_LEN2PLEN(18); - si3->header.rr_protocol_discriminator = GSM48_PDISC_RR; - si3->header.skip_indicator = 0; - si3->header.system_information = GSM48_MT_RR_SYSINFO_3; - - si3->cell_identity = htons(bts->cell_identity); - gsm48_generate_lai2(&si3->lai, bts_lai(bts)); - si3->control_channel_desc = bts->si_common.chan_desc; - si3->cell_options = bts->si_common.cell_options; - si3->cell_sel_par = bts->si_common.cell_sel_par; - si3->rach_control = bts->si_common.rach_control; - if (acc_ramp_is_enabled(&bts->acc_ramp)) - acc_ramp_apply(&si3->rach_control, &bts->acc_ramp); - - /* allow/disallow DTXu */ - gsm48_set_dtx(&si3->cell_options, bts->dtxu, bts->dtxu, true); - - if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) { - LOGP(DRR, LOGL_INFO, "SI 2ter is included.\n"); - si_info.si2ter_indicator = true; - } else { - si_info.si2ter_indicator = false; - } - if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater)) { - LOGP(DRR, LOGL_INFO, "SI 2quater is included, based on %zu EARFCNs and %zu UARFCNs.\n", - si2q_earfcn_count(&bts->si_common.si2quater_neigh_list), bts->si_common.uarfcn_length); - si_info.si2quater_indicator = true; - } else { - si_info.si2quater_indicator = false; - } - si_info.early_cm_ctrl = bts->early_classmark_allowed; - si_info.early_cm_restrict_3g = !bts->early_classmark_allowed_3g; - - /* SI3 Rest Octets (10.5.2.34), containing - 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); - - return sizeof(*si3) + rc; -} - -static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - int rc; - struct gsm48_system_information_type_4 *si4 = (struct gsm48_system_information_type_4 *) GSM_BTS_SI(bts, t); - struct gsm_lchan *cbch_lchan; - uint8_t *restoct = si4->data; - - /* length of all IEs present except SI4 rest octets and l2_plen */ - int l2_plen = sizeof(*si4) - 1; - - memset(si4, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - - si4->header.rr_protocol_discriminator = GSM48_PDISC_RR; - si4->header.skip_indicator = 0; - si4->header.system_information = GSM48_MT_RR_SYSINFO_4; - - gsm48_generate_lai2(&si4->lai, bts_lai(bts)); - si4->cell_sel_par = bts->si_common.cell_sel_par; - si4->rach_control = bts->si_common.rach_control; - if (acc_ramp_is_enabled(&bts->acc_ramp)) - acc_ramp_apply(&si4->rach_control, &bts->acc_ramp); - - /* Optional: CBCH Channel Description + CBCH Mobile Allocation */ - cbch_lchan = gsm_bts_get_cbch(bts); - if (cbch_lchan) { - struct gsm48_chan_desc cd; - gsm48_lchan2chan_desc(&cd, cbch_lchan); - tv_fixed_put(si4->data, GSM48_IE_CBCH_CHAN_DESC, 3, - (uint8_t *) &cd); - l2_plen += 3 + 1; - restoct += 3 + 1; - /* we don't use hopping and thus don't need a CBCH MA */ - } - - si4->header.l2_plen = GSM48_LEN2PLEN(l2_plen); - - /* SI4 Rest Octets (10.5.2.35), containing - Optional Power offset, GPRS Indicator, - Cell Identity, LSA ID, Selection Parameter */ - rc = rest_octets_si4(restoct, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - restoct); - - return l2_plen + 1 + rc; -} - -static int generate_si5(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - struct gsm48_system_information_type_5 *si5; - uint8_t *output = GSM_BTS_SI(bts, t); - int rc, l2_plen = 18; - - 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: - *output++ = GSM48_LEN2PLEN(l2_plen); - l2_plen++; - break; - default: - break; - } - - si5 = (struct gsm48_system_information_type_5 *) output; - - /* l2 pseudo length, not part of msg: 18 */ - si5->rr_protocol_discriminator = GSM48_PDISC_RR; - si5->skip_indicator = 0; - si5->system_information = GSM48_MT_RR_SYSINFO_5; - rc = generate_bcch_chan_list(si5->bcch_frequency_list, bts, true, false, false); - if (rc < 0) - return rc; - list_arfcn(si5->bcch_frequency_list, 0xce, - "SI5 Neighbour cells in same band:"); - - /* 04.08 9.1.37: L2 Pseudo Length of 18 */ - return l2_plen; -} - -static int generate_si5bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - struct gsm48_system_information_type_5bis *si5b; - uint8_t *output = GSM_BTS_SI(bts, t); - int rc, l2_plen = 18; - int n; - - 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: - *output++ = GSM48_LEN2PLEN(l2_plen); - l2_plen++; - break; - default: - break; - } - - si5b = (struct gsm48_system_information_type_5bis *) output; - - /* l2 pseudo length, not part of msg: 18 */ - si5b->rr_protocol_discriminator = GSM48_PDISC_RR; - si5b->skip_indicator = 0; - si5b->system_information = GSM48_MT_RR_SYSINFO_5bis; - rc = generate_bcch_chan_list(si5b->bcch_frequency_list, bts, true, true, false); - if (rc < 0) - return rc; - n = list_arfcn(si5b->bcch_frequency_list, 0xce, - "Neighbour cells in same band, but outside P-GSM:"); - if (n) { - /* indicate in SI5 and SI5bis: there is an extension */ - struct gsm48_system_information_type_5 *si5 = - (struct gsm48_system_information_type_5 *) GSM_BTS_SI(bts, SYSINFO_TYPE_5)+1; - si5->bcch_frequency_list[0] |= 0x20; - si5b->bcch_frequency_list[0] |= 0x20; - } else - bts->si_valid &= ~(1 << SYSINFO_TYPE_5bis); - - /* 04.08 9.1.37: L2 Pseudo Length of 18 */ - return l2_plen; -} - -static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - struct gsm48_system_information_type_5ter *si5t; - uint8_t *output = GSM_BTS_SI(bts, t); - int rc, l2_plen = 18; - int n; - - 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: - *output++ = GSM48_LEN2PLEN(l2_plen); - l2_plen++; - break; - default: - break; - } - - si5t = (struct gsm48_system_information_type_5ter *) output; - - /* l2 pseudo length, not part of msg: 18 */ - si5t->rr_protocol_discriminator = GSM48_PDISC_RR; - si5t->skip_indicator = 0; - si5t->system_information = GSM48_MT_RR_SYSINFO_5ter; - rc = generate_bcch_chan_list(si5t->bcch_frequency_list, bts, true, false, true); - if (rc < 0) - return rc; - n = list_arfcn(si5t->bcch_frequency_list, 0x8e, - "Neighbour cells in different band:"); - if (!n) - bts->si_valid &= ~(1 << SYSINFO_TYPE_5ter); - - /* 04.08 9.1.37: L2 Pseudo Length of 18 */ - return l2_plen; -} - -static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts) -{ - struct gsm48_system_information_type_6 *si6; - uint8_t *output = GSM_BTS_SI(bts, t); - int l2_plen = 11; - int rc; - - 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: - *output++ = GSM48_LEN2PLEN(l2_plen); - l2_plen++; - break; - default: - break; - } - - si6 = (struct gsm48_system_information_type_6 *) output; - - /* l2 pseudo length, not part of msg: 11 */ - si6->rr_protocol_discriminator = GSM48_PDISC_RR; - si6->skip_indicator = 0; - si6->system_information = GSM48_MT_RR_SYSINFO_6; - si6->cell_identity = htons(bts->cell_identity); - gsm48_generate_lai2(&si6->lai, bts_lai(bts)); - si6->cell_options = bts->si_common.cell_options; - si6->ncc_permitted = bts->si_common.ncc_permitted; - /* allow/disallow DTXu */ - 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)); - - 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, - .supports_egprs_11bit_rach = 0, - .ext_info = { - /* The values below are just guesses ! */ - .egprs_supported = 0, - .use_egprs_p_ch_req = 1, - .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 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); - int ret; - - memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); - - si13->header.rr_protocol_discriminator = GSM48_PDISC_RR; - 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_default.cell_opts.supports_egprs_11bit_rach = - bts->gprs.supports_egprs_11bit_rach; - - ret = rest_octets_si13(si13->rest_octets, &si13_default); - if (ret < 0) - return ret; - - /* length is coded in bit 2 an up */ - si13->header.l2_plen = 0x01; - - return sizeof (*si13) + ret; -} - -typedef int (*gen_si_fn_t)(enum osmo_sysinfo_type t, struct gsm_bts *bts); - -static const gen_si_fn_t gen_si_fn[_MAX_SYSINFO_TYPE] = { - [SYSINFO_TYPE_1] = &generate_si1, - [SYSINFO_TYPE_2] = &generate_si2, - [SYSINFO_TYPE_2bis] = &generate_si2bis, - [SYSINFO_TYPE_2ter] = &generate_si2ter, - [SYSINFO_TYPE_2quater] = &generate_si2quater, - [SYSINFO_TYPE_3] = &generate_si3, - [SYSINFO_TYPE_4] = &generate_si4, - [SYSINFO_TYPE_5] = &generate_si5, - [SYSINFO_TYPE_5bis] = &generate_si5bis, - [SYSINFO_TYPE_5ter] = &generate_si5ter, - [SYSINFO_TYPE_6] = &generate_si6, - [SYSINFO_TYPE_13] = &generate_si13, -}; - -int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) -{ - gen_si_fn_t gen_si; - - 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; - case BTS_GPRS_NONE: - si_info.gprs_ind.present = 0; - break; - } - - memcpy(&si_info.selection_params, - &bts->si_common.cell_ro_sel_par, - sizeof(struct gsm48_si_selection_params)); - - gen_si = gen_si_fn[si_type]; - if (!gen_si) - return -EINVAL; - - return gen_si(si_type, bts); -} diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am index cc9674396..a459a928b 100644 --- a/src/osmo-bsc/Makefile.am +++ b/src/osmo-bsc/Makefile.am @@ -26,21 +26,65 @@ bin_PROGRAMS = \ $(NULL) osmo_bsc_SOURCES = \ - osmo_bsc_main.c \ - osmo_bsc_vty.c \ + a_reset.c \ + abis_nm.c \ + abis_nm_vty.c \ + abis_om2000.c \ + abis_om2000_vty.c \ + abis_rsl.c \ + acc_ramp.c \ + arfcn_range_encode.c \ + bsc_api.c \ + bsc_ctrl_commands.c \ + bsc_ctrl_lookup.c \ + bsc_dyn_ts.c \ + bsc_init.c \ + bsc_rf_ctrl.c \ + bsc_rll.c \ + bsc_subscr_conn_fsm.c \ + bsc_subscriber.c \ + bsc_vty.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_unknown.c \ + chan_alloc.c \ + e1_config.c \ + gsm_04_08_utils.c \ + gsm_04_80_utils.c \ + gsm_data.c \ + handover_cfg.c \ + handover_decision.c \ + handover_decision_2.c \ + handover_logic.c \ + handover_vty.c \ + meas_feed.c \ + meas_rep.c \ + net_init.c \ osmo_bsc_api.c \ + osmo_bsc_audio.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_msc.c \ osmo_bsc_sigtran.c \ - osmo_bsc_filter.c \ - osmo_bsc_bssap.c \ - osmo_bsc_audio.c \ - osmo_bsc_ctrl.c \ + osmo_bsc_vty.c \ + paging.c \ + pcu_sock.c \ + penalty_timers.c \ + rest_octets.c \ + system_information.c \ $(NULL) osmo_bsc_LDADD = \ $(top_builddir)/src/libfilter/libfilter.a \ - $(top_builddir)/src/libbsc/libbsc.a \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ diff --git a/src/osmo-bsc/a_reset.c b/src/osmo-bsc/a_reset.c new file mode 100644 index 000000000..b8f8c8cbd --- /dev/null +++ b/src/osmo-bsc/a_reset.c @@ -0,0 +1,205 @@ +/* (C) 2017 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RESET_RESEND_INTERVAL 2 /* sec */ +#define RESET_RESEND_TIMER_NO 4 /* See also 3GPP TS 48.008 Chapter 3.1.4.1.3.1 */ +#define BAD_CONNECTION_THRESOLD 3 /* connection failures */ + +/* Reset context data (callbacks, state machine etc...) */ +struct reset_ctx { + /* Connection failure counter. When this counter + * reaches a certain threshold, the reset procedure + * will be triggered */ + int conn_loss_counter; + + /* Callback function to be called when a connection + * failure is detected and a rest must occur */ + void (*cb)(void *priv); + + /* Privated data for the callback function */ + void *priv; +}; + +enum reset_fsm_states { + ST_DISC, /* Disconnected from remote end */ + ST_CONN, /* We have a confirmed connection */ +}; + +enum reset_fsm_evt { + EV_RESET_ACK, /* got reset acknowlegement from remote end */ + EV_N_DISCONNECT, /* lost a connection */ + EV_N_CONNECT, /* made a successful connection */ +}; + +static const struct value_string fsm_event_names[] = { + OSMO_VALUE_STRING(EV_RESET_ACK), + OSMO_VALUE_STRING(EV_N_DISCONNECT), + OSMO_VALUE_STRING(EV_N_CONNECT), + {0, NULL} +}; + +/* Disconnected state */ +static void fsm_disc_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; + OSMO_ASSERT(reset_ctx); + LOGPFSML(fi, LOGL_NOTICE, "SIGTRAN connection succeded.\n"); + + reset_ctx->conn_loss_counter = 0; + osmo_fsm_inst_state_chg(fi, ST_CONN, 0, 0); +} + +/* Connected state */ +static void fsm_conn_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; + OSMO_ASSERT(reset_ctx); + + switch (event) { + case EV_N_DISCONNECT: + if (reset_ctx->conn_loss_counter >= BAD_CONNECTION_THRESOLD) { + LOGPFSML(fi, LOGL_NOTICE, "SIGTRAN connection down, reconnecting...\n"); + osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); + } else + reset_ctx->conn_loss_counter++; + break; + case EV_N_CONNECT: + reset_ctx->conn_loss_counter = 0; + break; + } +} + +/* Timer callback to retransmit the reset signal */ +static int fsm_reset_ack_timeout_cb(struct osmo_fsm_inst *fi) +{ + struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; + OSMO_ASSERT(reset_ctx); + + LOGPFSML(fi, LOGL_NOTICE, "(re)sending BSSMAP RESET message...\n"); + + reset_ctx->cb(reset_ctx->priv); + + osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); + return 0; +} + +static struct osmo_fsm_state reset_fsm_states[] = { + [ST_DISC] = { + .in_event_mask = (1 << EV_RESET_ACK), + .out_state_mask = (1 << ST_DISC) | (1 << ST_CONN), + .name = "DISC", + .action = fsm_disc_cb, + }, + [ST_CONN] = { + .in_event_mask = (1 << EV_N_DISCONNECT) | (1 << EV_N_CONNECT), + .out_state_mask = (1 << ST_DISC) | (1 << ST_CONN), + .name = "CONN", + .action = fsm_conn_cb, + }, +}; + +/* State machine definition */ +static struct osmo_fsm fsm = { + .name = "A-RESET", + .states = reset_fsm_states, + .num_states = ARRAY_SIZE(reset_fsm_states), + .log_subsys = DMSC, + .timer_cb = fsm_reset_ack_timeout_cb, + .event_names = fsm_event_names, +}; + +/* Create and start state machine which handles the reset/reset-ack procedure */ +struct osmo_fsm_inst *a_reset_alloc(void *ctx, const char *name, void *cb, void *priv) +{ + OSMO_ASSERT(name); + + struct reset_ctx *reset_ctx; + struct osmo_fsm_inst *reset_fsm; + + /* Register the fsm description (if not already done) */ + if (osmo_fsm_find_by_name(fsm.name) != &fsm) + osmo_fsm_register(&fsm); + + /* Allocate and configure a new fsm instance */ + reset_ctx = talloc_zero(ctx, struct reset_ctx); + OSMO_ASSERT(reset_ctx); + reset_ctx->priv = priv; + reset_ctx->cb = cb; + reset_ctx->conn_loss_counter = 0; + reset_fsm = osmo_fsm_inst_alloc(&fsm, ctx, reset_ctx, LOGL_DEBUG, name); + OSMO_ASSERT(reset_fsm); + + /* kick off reset-ack sending mechanism */ + osmo_fsm_inst_state_chg(reset_fsm, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); + + return reset_fsm; +} + +/* Confirm that we sucessfully received a reset acknowlege message */ +void a_reset_ack_confirm(struct osmo_fsm_inst *reset_fsm) +{ + OSMO_ASSERT(reset_fsm); + osmo_fsm_inst_dispatch(reset_fsm, EV_RESET_ACK, NULL); +} + +/* Report a failed connection */ +void a_reset_conn_fail(struct osmo_fsm_inst *reset_fsm) +{ + /* If no reset context is supplied, just drop the info */ + if (!reset_fsm) + return; + + osmo_fsm_inst_dispatch(reset_fsm, EV_N_DISCONNECT, NULL); +} + +/* Report a successful connection */ +void a_reset_conn_success(struct osmo_fsm_inst *reset_fsm) +{ + /* If no reset context is supplied, just drop the info */ + if (!reset_fsm) + return; + + osmo_fsm_inst_dispatch(reset_fsm, EV_N_CONNECT, NULL); +} + +/* Check if we have a connection to a specified msc */ +bool a_reset_conn_ready(struct osmo_fsm_inst *reset_fsm) +{ + /* If no reset context is supplied, we assume that + * the connection can't be ready! */ + if (!reset_fsm) + return false; + + if (reset_fsm->state == ST_CONN) + return true; + + return false; +} diff --git a/src/osmo-bsc/abis_bs11.c b/src/osmo-bsc/abis_bs11.c new file mode 100644 index 000000000..8b721fa77 --- /dev/null +++ b/src/osmo-bsc/abis_bs11.c @@ -0,0 +1,21 @@ +/* Siemens BS11 specific Abis implementations */ + +/* (C) 2008-2018 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + diff --git a/src/osmo-bsc/abis_nm.c b/src/osmo-bsc/abis_nm.c new file mode 100644 index 000000000..9dc1f62cb --- /dev/null +++ b/src/osmo-bsc/abis_nm.c @@ -0,0 +1,2971 @@ +/* GSM Network Management (OML) messages on the A-bis interface + * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */ + +/* (C) 2008-2018 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OM_ALLOC_SIZE 1024 +#define OM_HEADROOM_SIZE 128 +#define IPACC_SEGMENT_SIZE 245 + +#define LOGPFOH(ss, lvl, foh, fmt, args ...) LOGP(ss, lvl, "%s: " fmt, abis_nm_dump_foh(foh), ## args) +#define DEBUGPFOH(ss, foh, fmt, args ...) LOGPFOH(ss, LOGL_DEBUG, foh, fmt, ## args) + +int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len) +{ + if (!bts->model) + return -EIO; + return tlv_parse(tp, &bts->model->nm_att_tlvdef, buf, len, 0, 0); +} + +static int is_in_arr(enum abis_nm_msgtype mt, const enum abis_nm_msgtype *arr, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (arr[i] == mt) + return 1; + } + + return 0; +} + +#if 0 +/* is this msgtype the usual ACK/NACK type ? */ +static int is_ack_nack(enum abis_nm_msgtype mt) +{ + return !is_in_arr(mt, no_ack_nack, ARRAY_SIZE(no_ack_nack)); +} +#endif + +/* is this msgtype a report ? */ +static int is_report(enum abis_nm_msgtype mt) +{ + return is_in_arr(mt, abis_nm_reports, ARRAY_SIZE(abis_nm_reports)); +} + +#define MT_ACK(x) (x+1) +#define MT_NACK(x) (x+2) + +static void fill_om_hdr(struct abis_om_hdr *oh, uint8_t len) +{ + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_ONLY; + oh->sequence = 0; + oh->length = len; +} + +static struct abis_om_fom_hdr *fill_om_fom_hdr(struct abis_om_hdr *oh, uint8_t len, + uint8_t msg_type, uint8_t obj_class, + uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr) +{ + struct abis_om_fom_hdr *foh = + (struct abis_om_fom_hdr *) oh->data; + + fill_om_hdr(oh, len+sizeof(*foh)); + foh->msg_type = msg_type; + foh->obj_class = obj_class; + foh->obj_inst.bts_nr = bts_nr; + foh->obj_inst.trx_nr = trx_nr; + foh->obj_inst.ts_nr = ts_nr; + return foh; +} + +static struct msgb *nm_msgb_alloc(void) +{ + return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, + "OML"); +} + +int _abis_nm_sendmsg(struct msgb *msg) +{ + msg->l2h = msg->data; + + if (!msg->dst) { + LOGP(DNM, LOGL_ERROR, "%s: msg->dst == NULL\n", __func__); + return -EINVAL; + } + + return abis_sendmsg(msg); +} + +/* Send a OML NM Message from BSC to BTS */ +static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg) +{ + msg->dst = bts->oml_link; + + /* queue OML messages */ + if (llist_empty(&bts->abis_queue) && !bts->abis_nm_pend) { + bts->abis_nm_pend = OBSC_NM_W_ACK_CB(msg); + return _abis_nm_sendmsg(msg); + } else { + msgb_enqueue(&bts->abis_queue, msg); + return 0; + } + +} + +int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + OBSC_NM_W_ACK_CB(msg) = 1; + return abis_nm_queue_msg(bts, msg); +} + +static int abis_nm_sendmsg_direct(struct gsm_bts *bts, struct msgb *msg) +{ + OBSC_NM_W_ACK_CB(msg) = 0; + return abis_nm_queue_msg(bts, msg); +} + +static int abis_nm_rcvmsg_sw(struct msgb *mb); + +/* Update the administrative state of a given object in our in-memory data + * structures and send an event to the higher layer */ +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 nm_statechg_signal_data nsd; + + memset(&nsd, 0, sizeof(nsd)); + + nsd.obj = gsm_objclass2obj(bts, obj_class, obj_inst); + if (!nsd.obj) + return -EINVAL; + nm_state = gsm_objclass2nmstate(bts, obj_class, obj_inst); + 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.obj_inst = obj_inst; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd); + + nm_state->administrative = adm_state; + + return 0; +} + +static int abis_nm_rx_statechg_rep(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 = sign_link->trx->bts; + struct tlv_parsed tp; + struct gsm_nm_state *nm_state, new_state; + + memset(&new_state, 0, sizeof(new_state)); + + 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; + + 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); + DEBUGPC(DNM, "OP_STATE=%s ", + abis_nm_opstate_name(new_state.operational)); + } + if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) { + if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0) + new_state.availability = 0xff; + else + 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); + } else + new_state.availability = 0xff; + if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) { + 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)); + } + DEBUGPC(DNM, "\n"); + + if ((new_state.administrative != 0 && nm_state->administrative == 0) || + new_state.operational != nm_state->operational || + new_state.availability != nm_state->availability) { + /* Update the operational 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; + } +#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; +} + +static inline void log_oml_fail_rep(const struct gsm_bts *bts, const char *type, + const char *severity, const uint8_t *p_val, + const char *text) +{ + enum abis_nm_pcause_type pcause = p_val[0]; + enum abis_mm_event_causes cause = osmo_load16be(p_val + 1); + + LOGPC(DNM, LOGL_ERROR, "BTS %u: Failure Event Report: ", bts->nr); + if (type) + LOGPC(DNM, LOGL_ERROR, "Type=%s, ", type); + if (severity) + LOGPC(DNM, LOGL_ERROR, "Severity=%s, ", severity); + + LOGPC(DNM, LOGL_ERROR, "Probable cause=%s: ", + get_value_string(abis_nm_pcause_type_names, pcause)); + + if (pcause == NM_PCAUSE_T_MANUF) + LOGPC(DNM, LOGL_ERROR, "%s, ", + get_value_string(abis_mm_event_cause_names, cause)); + else + LOGPC(DNM, LOGL_ERROR, "%02X %02X ", p_val[1], p_val[2]); + + if (text) { + LOGPC(DNM, LOGL_ERROR, "Additional Text=%s. ", text); + } + + LOGPC(DNM, LOGL_ERROR, "\n"); +} + +static inline void handle_manufact_report(struct gsm_bts *bts, const uint8_t *p_val, const char *type, + const char *severity, const char *text) +{ + enum abis_mm_event_causes cause = osmo_load16be(p_val + 1); + + switch (cause) { + case OSMO_EVT_PCU_VERS: + if (text) { + LOGPC(DNM, LOGL_NOTICE, "BTS %u reported connected PCU version %s\n", bts->nr, text); + osmo_strlcpy(bts->pcu_version, text, sizeof(bts->pcu_version)); + } else { + LOGPC(DNM, LOGL_ERROR, "BTS %u reported PCU disconnection.\n", bts->nr); + bts->pcu_version[0] = '\0'; + } + break; + default: + log_oml_fail_rep(bts, type, severity, p_val, text); + }; +} + +static int rx_fail_evt_rep(struct msgb *mb, struct gsm_bts *bts) +{ + 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 tlv_parsed tp; + int rc = 0; + const uint8_t *p_val = NULL; + char *p_text = NULL; + const char *e_type = NULL, *severity = NULL; + + abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, + oh->length-sizeof(*foh)); + + if (TLVP_PRESENT(&tp, NM_ATT_ADD_TEXT)) { + p_val = TLVP_VAL(&tp, NM_ATT_ADD_TEXT); + p_text = talloc_strndup(tall_bsc_ctx, (const char *) p_val, + TLVP_LEN(&tp, NM_ATT_ADD_TEXT)); + } + + if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE)) + e_type = abis_nm_event_type_name(*TLVP_VAL(&tp, + NM_ATT_EVENT_TYPE)); + + if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY)) + severity = abis_nm_severity_name(*TLVP_VAL(&tp, + NM_ATT_SEVERITY)); + + if (TLVP_PRESENT(&tp, NM_ATT_PROB_CAUSE)) { + p_val = TLVP_VAL(&tp, NM_ATT_PROB_CAUSE); + + switch (p_val[0]) { + case NM_PCAUSE_T_MANUF: + handle_manufact_report(bts, p_val, e_type, severity, + p_text); + break; + default: + log_oml_fail_rep(bts, e_type, severity, p_val, p_text); + }; + } else { + LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: Failure Event Report without " + "Probable Cause?!\n", bts->nr); + rc = -EINVAL; + } + + if (p_text) + talloc_free(p_text); + + return rc; +} + +static int abis_nm_rcvmsg_report(struct msgb *mb, struct gsm_bts *bts) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + uint8_t mt = foh->msg_type; + + switch (mt) { + case NM_MT_STATECHG_EVENT_REP: + return abis_nm_rx_statechg_rep(mb); + break; + case NM_MT_SW_ACTIVATED_REP: + DEBUGPFOH(DNM, foh, "Software Activated Report\n"); + osmo_signal_dispatch(SS_NM, S_NM_SW_ACTIV_REP, mb); + break; + case NM_MT_FAILURE_EVENT_REP: + rx_fail_evt_rep(mb, bts); + osmo_signal_dispatch(SS_NM, S_NM_FAIL_REP, mb); + break; + case NM_MT_TEST_REP: + DEBUGPFOH(DNM, foh, "Test Report\n"); + osmo_signal_dispatch(SS_NM, S_NM_TEST_REP, mb); + break; + default: + LOGPFOH(DNM, LOGL_NOTICE, foh, "unknown NM report MT 0x%02x\n", mt); + break; + }; + + return 0; +} + +/* Activate the specified software into the BTS */ +static int ipacc_sw_activate(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, + uint8_t i2, const struct abis_nm_sw_desc *sw_desc) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint16_t len = abis_nm_sw_desc_len(sw_desc, true); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, obj_class, i0, i1, i2); + abis_nm_put_sw_desc(msg, sw_desc, true); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_select_newest_sw(const struct abis_nm_sw_desc *sw_descr, + const size_t size) +{ + int res = 0; + int i; + + for (i = 1; i < size; ++i) { + if (memcmp(sw_descr[res].file_version, sw_descr[i].file_version, + OSMO_MIN(sw_descr[i].file_version_len, + sw_descr[res].file_version_len)) < 0) { + res = i; + } + } + + return res; +} + +static inline bool handle_attr(const struct gsm_bts *bts, enum bts_attribute id, uint8_t *val, uint8_t len) +{ + switch (id) { + case BTS_TYPE_VARIANT: + LOGP(DNM, LOGL_NOTICE, "BTS%u reported variant: %s\n", bts->nr, val); + break; + case BTS_SUB_MODEL: + LOGP(DNM, LOGL_NOTICE, "BTS%u reported submodel: %s\n", bts->nr, val); + break; + default: + return false; + } + return true; +} + +/* Parse Attribute Response Info - return pointer to the actual content */ +static inline const uint8_t *parse_attr_resp_info_unreported(uint8_t bts_nr, const uint8_t *ari, uint16_t ari_len, uint16_t *out_len) +{ + uint8_t num_unreported = ari[0], i; + + DEBUGP(DNM, "BTS%u Get Attributes Response Info: %u bytes total with %u unreported attributes\n", + bts_nr, ari_len, num_unreported); + + /* +1 because we have to account for number of unreported attributes, prefixing the list: */ + for (i = 0; i < num_unreported; i++) + LOGP(DNM, LOGL_ERROR, "BTS%u Attribute %s is unreported\n", + bts_nr, get_value_string(abis_nm_att_names, ari[i + 1])); + + /* the data starts right after the list of unreported attributes + space for length of that list */ + *out_len = ari_len - (num_unreported + 2); + + 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 inline const uint8_t *parse_attr_resp_info_manuf_id(struct gsm_bts *bts, const uint8_t *data, uint16_t *data_len) +{ + struct tlv_parsed tp; + uint16_t m_id_len = 0; + uint8_t adjust = 0, i; + + abis_nm_tlv_parse(&tp, bts, data, *data_len); + if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_ID, 2)) { + m_id_len = TLVP_LEN(&tp, NM_ATT_MANUF_ID); + + /* log potential BTS feature vector overflow */ + if (m_id_len > sizeof(bts->_features_data)) + LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: feature vector is truncated to %u bytes\n", + bts->nr, MAX_BTS_FEATURES/8); + + /* check that max. expected BTS attribute is above given feature vector length */ + if (m_id_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, m_id_len); + + memcpy(bts->_features_data, TLVP_VAL(&tp, NM_ATT_MANUF_ID), sizeof(bts->_features_data)); + adjust = m_id_len + 3; /* adjust for parsed TL16V struct */ + + 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)); + } + + *data_len -= adjust; + + return data + adjust; +} + +/* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.28 Manufacturer Dependent State */ +static inline const uint8_t *parse_attr_resp_info_manuf_state(const struct gsm_bts_trx *trx, const uint8_t *data, uint16_t *data_len) +{ + struct tlv_parsed tp; + const uint8_t *power; + uint8_t adjust = 0; + + if (!trx) /* this attribute does not make sense on BTS level, only on TRX level */ + return data; + + abis_nm_tlv_parse(&tp, trx->bts, data, *data_len); + if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_STATE, 1)) { + power = TLVP_VAL(&tp, NM_ATT_MANUF_STATE); + LOGP(DNM, LOGL_NOTICE, "%s Get Attributes Response: nominal power is %u\n", gsm_trx_name(trx), *power); + adjust = 2; /* adjust for parsed TV struct */ + } + + *data_len -= adjust; + + return data + adjust; +} + +/* Handle 3GPP TS 52.021 §9.4.64 Get Attribute Response Info */ +static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *trx) +{ + 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 tlv_parsed tp; + const uint8_t *data; + int i; + uint16_t data_len; + int rc; + struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR]; + + DEBUGPFOH(DNM, foh, "Get Attributes Response for BTS%u\n", bts->nr); + + abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); + if (!TLVP_PRES_LEN(&tp, NM_ATT_GET_ARI, 1)) { + LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: Get Attr Response without Response Info?!\n", + bts->nr); + return -EINVAL; + } + + data = parse_attr_resp_info_unreported(bts->nr, TLVP_VAL(&tp, NM_ATT_GET_ARI), TLVP_LEN(&tp, NM_ATT_GET_ARI), + &data_len); + + data = parse_attr_resp_info_manuf_state(trx, data, &data_len); + data = parse_attr_resp_info_manuf_id(bts, data, &data_len); + + /* after parsing manufacturer-specific attributes there's list of replies in form of sw-conf structure: */ + rc = abis_nm_get_sw_conf(data, data_len, &sw_descr[0], ARRAY_SIZE(sw_descr)); + if (rc > 0) { + for (i = 0; i < rc; i++) { + if (!handle_attr(bts, str2btsattr((const char *)sw_descr[i].file_id), + sw_descr[i].file_version, sw_descr[i].file_version_len)) + LOGPFOH(DNM, LOGL_NOTICE, foh, "BTS%u: ARI reported sw[%d/%d]: %s " + "is %s\n", bts->nr, i, rc, sw_descr[i].file_id, + sw_descr[i].file_version); + } + } else { + LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: failed to parse SW-Config part of " + "Get Attribute Response Info: %s\n", bts->nr, strerror(-rc)); + } + + return 0; +} + +/* 3GPP TS 52.021 §6.2.5 */ +static int abis_nm_rx_sw_act_req(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 tlv_parsed tp; + const uint8_t *sw_config; + int ret, sw_config_len, len; + struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR]; + + DEBUGPFOH(DNM, foh, "Software Activate Request, ACKing and Activating\n"); + + 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)); + 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)); + 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)) { + LOGPFOH(DNM, LOGL_ERROR, foh, "SW config not found! Can't continue.\n"); + return -EINVAL; + } else { + DEBUGP(DNM, "Found SW config: %s\n", osmo_hexdump(sw_config, sw_config_len)); + } + + /* Parse up to two sw descriptions from the data */ + len = abis_nm_get_sw_conf(sw_config, sw_config_len, &sw_descr[0], + ARRAY_SIZE(sw_descr)); + if (len <= 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "Failed to parse SW Config.\n"); + return -EINVAL; + } + + ret = abis_nm_select_newest_sw(&sw_descr[0], len); + DEBUGPFOH(DNM, foh, "Selected sw description %d of %d\n", ret, len); + + return ipacc_sw_activate(sign_link->trx->bts, foh->obj_class, + foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, + foh->obj_inst.ts_nr, + &sw_descr[ret]); +} + +/* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */ +static int abis_nm_rx_chg_adm_state_ack(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 tlv_parsed tp; + uint8_t adm_state; + + abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) + return -EINVAL; + + adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); + + return update_admstate(sign_link->trx->bts, foh->obj_class, &foh->obj_inst, adm_state); +} + +static int abis_nm_rx_lmt_event(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 tlv_parsed tp; + + 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); + DEBUGPC(DNM, "LOG%s ", onoff ? "ON" : "OFF"); + } + if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) && + TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) >= 1) { + uint8_t level = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV); + DEBUGPC(DNM, "Level=%u ", level); + } + if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_NAME) && + TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_NAME) >= 1) { + char *name = (char *) TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_NAME); + DEBUGPC(DNM, "Username=%s ", name); + } + DEBUGPC(DNM, "\n"); + /* FIXME: parse LMT LOGON TIME */ + return 0; +} + +static int abis_nm_rx_opstart_ack(struct msgb *mb) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + DEBUGPFOH(DNM, foh, "Opstart ACK\n"); + osmo_signal_dispatch(SS_NM, S_NM_OPSTART_ACK, foh); + return 0; +} + +bool all_trx_rsl_connected_unlocked(const struct gsm_bts *bts) +{ + const struct gsm_bts_trx *trx; + + if (bts->mo.nm_state.administrative == NM_STATE_LOCKED) + return false; + + if (bts->gprs.mode != BTS_GPRS_NONE) { + if (bts->gprs.cell.mo.nm_state.administrative == NM_STATE_LOCKED) + return false; + + if (bts->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) + return false; + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (!trx->rsl_link) + return false; + + if (!trx_is_usable(trx)) + return false; + + if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) + return false; + } + + return true; +} + +char *get_model_oml_status(const struct gsm_bts *bts) +{ + if (bts->model->oml_status) + return bts->model->oml_status(bts); + + return "unknown"; +} + +void abis_nm_queue_send_next(struct gsm_bts *bts) +{ + int wait = 0; + struct msgb *msg; + /* the queue is empty */ + while (!llist_empty(&bts->abis_queue)) { + msg = msgb_dequeue(&bts->abis_queue); + wait = OBSC_NM_W_ACK_CB(msg); + _abis_nm_sendmsg(msg); + + if (wait) + break; + } + + bts->abis_nm_pend = wait; +} + +/* Receive a OML NM Message from BTS */ +static int abis_nm_rcvmsg_fom(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; + uint8_t mt = foh->msg_type; + /* sign_link might get deleted via osmo_signal_dispatch -> save bts */ + struct gsm_bts *bts = sign_link->trx->bts; + int ret = 0; + + /* check for unsolicited message */ + if (is_report(mt)) + return abis_nm_rcvmsg_report(mb, bts); + + if (is_in_arr(mt, abis_nm_sw_load_msgs, ARRAY_SIZE(abis_nm_sw_load_msgs))) + return abis_nm_rcvmsg_sw(mb); + + if (is_in_arr(mt, abis_nm_nacks, ARRAY_SIZE(abis_nm_nacks))) { + struct nm_nack_signal_data nack_data; + struct tlv_parsed tp; + + LOGPFOH(DNM, LOGL_NOTICE, foh, "%s NACK ", abis_nm_nack_name(mt)); + + abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + DEBUGPC(DNM, "CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + DEBUGPC(DNM, "\n"); + + nack_data.msg = mb; + nack_data.mt = mt; + nack_data.bts = bts; + osmo_signal_dispatch(SS_NM, S_NM_NACK, &nack_data); + abis_nm_queue_send_next(bts); + return 0; + } +#if 0 + /* check if last message is to be acked */ + if (is_ack_nack(nmh->last_msgtype)) { + if (mt == MT_ACK(nmh->last_msgtype)) { + DEBUGP(DNM, "received ACK (0x%x)\n", foh->msg_type); + /* we got our ACK, continue sending the next msg */ + } else if (mt == MT_NACK(nmh->last_msgtype)) { + /* we got a NACK, signal this to the caller */ + DEBUGP(DNM, "received NACK (0x%x)\n", foh->msg_type); + /* FIXME: somehow signal this to the caller */ + } else { + /* really strange things happen */ + return -EINVAL; + } + } +#endif + + switch (mt) { + case NM_MT_CHG_ADM_STATE_ACK: + ret = abis_nm_rx_chg_adm_state_ack(mb); + break; + case NM_MT_SW_ACT_REQ: + ret = abis_nm_rx_sw_act_req(mb); + break; + case NM_MT_BS11_LMT_SESSION: + ret = abis_nm_rx_lmt_event(mb); + break; + case NM_MT_OPSTART_ACK: + abis_nm_rx_opstart_ack(mb); + break; + case NM_MT_SET_CHAN_ATTR_ACK: + DEBUGPFOH(DNM, foh, "Set Channel Attributes ACK\n"); + break; + case NM_MT_SET_RADIO_ATTR_ACK: + DEBUGPFOH(DNM, foh, "Set Radio Carrier Attributes ACK\n"); + break; + case NM_MT_CONN_MDROP_LINK_ACK: + DEBUGPFOH(DNM, foh, "CONN MDROP LINK ACK\n"); + break; + case NM_MT_IPACC_RESTART_ACK: + DEBUGPFOH(DNM, foh, "IPA Restart ACK\n"); + osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_ACK, NULL); + break; + case NM_MT_IPACC_RESTART_NACK: + LOGPFOH(DNM, LOGL_NOTICE, foh, "IPA Restart NACK\n"); + osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_NACK, NULL); + break; + case NM_MT_SET_BTS_ATTR_ACK: + DEBUGPFOH(DNM, foh, "Set BTS Attribute ACK\n"); + 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)); + break; + default: + LOGPFOH(DNM, LOGL_ERROR, foh, "Unhandled message %s\n", + get_value_string(abis_nm_msgtype_names, mt)); + } + + abis_nm_queue_send_next(bts); + return ret; +} + +static int abis_nm_rx_ipacc(struct msgb *mb); + +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; + + 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); + break; + default: + LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this " + "BTS type (%u)\n", bts_type); + rc = 0; + break; + } + + return rc; +} + +/* High-Level API */ +/* Entry-point where L2 OML from BTS enters the NM code */ +int abis_nm_rcvmsg(struct msgb *msg) +{ + struct abis_om_hdr *oh = msgb_l2(msg); + int rc = 0; + + /* Various consistency checks */ + if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { + LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", + oh->placement); + if (oh->placement != ABIS_OM_PLACEMENT_FIRST) { + rc = -EINVAL; + goto err; + } + } + if (oh->sequence != 0) { + LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", + oh->sequence); + rc = -EINVAL; + goto err; + } +#if 0 + unsigned int l2_len = msg->tail - (uint8_t *)msgb_l2(msg); + unsigned int hlen = sizeof(*oh) + sizeof(struct abis_om_fom_hdr); + if (oh->length + hlen > l2_len) { + LOGP(DNM, LOGL_ERROR, "ABIS OML truncated message (%u > %u)\n", + oh->length + sizeof(*oh), l2_len); + return -EINVAL; + } + if (oh->length + hlen < l2_len) + LOGP(DNM, LOGL_ERROR, "ABIS OML message with extra trailer?!? (oh->len=%d, sizeof_oh=%d l2_len=%d\n", oh->length, sizeof(*oh), l2_len); +#endif + msg->l3h = (unsigned char *)oh + sizeof(*oh); + + switch (oh->mdisc) { + case ABIS_OM_MDISC_FOM: + rc = abis_nm_rcvmsg_fom(msg); + break; + case ABIS_OM_MDISC_MANUF: + rc = abis_nm_rcvmsg_manuf(msg); + break; + case ABIS_OM_MDISC_MMI: + case ABIS_OM_MDISC_TRAU: + LOGP(DNM, LOGL_ERROR, "unimplemented ABIS OML message discriminator 0x%x\n", + oh->mdisc); + break; + default: + LOGP(DNM, LOGL_ERROR, "unknown ABIS OML message discriminator 0x%x\n", + oh->mdisc); + rc = -EINVAL; + break; + } +err: + msgb_free(msg); + return rc; +} + +#if 0 +/* initialized all resources */ +struct abis_nm_h *abis_nm_init(struct abis_nm_cfg *cfg) +{ + struct abis_nm_h *nmh; + + nmh = malloc(sizeof(*nmh)); + if (!nmh) + return NULL; + + nmh->cfg = cfg; + + return nmh; +} + +/* free all resources */ +void abis_nm_fini(struct abis_nm_h *nmh) +{ + free(nmh); +} +#endif + +/* Here we are trying to define a high-level API that can be used by + * the actual BSC implementation. However, the architecture is currently + * still under design. Ideally the calls to this API would be synchronous, + * while the underlying stack behind the APi runs in a traditional select + * based state machine. + */ + +/* 6.2 Software Load: */ +enum sw_state { + SW_STATE_NONE, + SW_STATE_WAIT_INITACK, + SW_STATE_WAIT_SEGACK, + SW_STATE_WAIT_ENDACK, + SW_STATE_WAIT_ACTACK, + SW_STATE_ERROR, +}; + +struct abis_nm_sw { + struct gsm_bts *bts; + int trx_nr; + gsm_cbfn *cbfn; + void *cb_data; + int forced; + + /* this will become part of the SW LOAD INITIATE */ + uint8_t obj_class; + uint8_t obj_instance[3]; + + uint8_t file_id[255]; + uint8_t file_id_len; + + uint8_t file_version[255]; + uint8_t file_version_len; + + uint8_t window_size; + uint8_t seg_in_window; + + int fd; + FILE *stream; + enum sw_state state; + int last_seg; +}; + +static struct abis_nm_sw g_sw; + +static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg) +{ + if (sw->bts->type == GSM_BTS_TYPE_NANOBTS) { + msgb_v_put(msg, NM_ATT_SW_DESCR); + msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, + sw->file_version); + } else if (sw->bts->type == GSM_BTS_TYPE_BS11) { + msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + 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"); + } +} + +/* 6.2.1 / 8.3.1: Load Data Initiate */ +static int sw_load_init(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t len = 3*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_LOAD_INIT, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + sw_add_file_id_and_ver(sw, msg); + msgb_tv_put(msg, NM_ATT_WINDOW_SIZE, sw->window_size); + + return abis_nm_sendmsg(sw->bts, msg); +} + +static int is_last_line(FILE *stream) +{ + char next_seg_buf[256]; + long pos; + + /* check if we're sending the last line */ + pos = ftell(stream); + + /* Did ftell fail? Then we are at the end for sure */ + if (pos < 0) + return 1; + + if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) { + int rc = fseek(stream, pos, SEEK_SET); + if (rc < 0) + return rc; + return 1; + } + + fseek(stream, pos, SEEK_SET); + return 0; +} + +/* 6.2.2 / 8.3.2 Load Data Segment */ +static int sw_load_segment(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + char seg_buf[256]; + char *line_buf = seg_buf+2; + unsigned char *tlv; + int len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + if (fgets(line_buf, sizeof(seg_buf)-2, sw->stream) == NULL) { + perror("fgets reading segment"); + return -EINVAL; + } + seg_buf[0] = 0x00; + + /* check if we're sending the last line */ + sw->last_seg = is_last_line(sw->stream); + if (sw->last_seg) + seg_buf[1] = 0; + else + seg_buf[1] = 1 + sw->seg_in_window++; + + len = strlen(line_buf) + 2; + tlv = msgb_put(msg, TLV_GROSS_LEN(len)); + tlv_put(tlv, NM_ATT_BS11_FILE_DATA, len, (uint8_t *)seg_buf); + /* BS11 wants CR + LF in excess of the TLV length !?! */ + tlv[1] -= 2; + + /* we only now know the exact length for the OM hdr */ + len = strlen(line_buf)+2; + break; + case GSM_BTS_TYPE_NANOBTS: { + osmo_static_assert(sizeof(seg_buf) >= IPACC_SEGMENT_SIZE, buffer_big_enough); + len = read(sw->fd, &seg_buf, IPACC_SEGMENT_SIZE); + if (len < 0) { + perror("read failed"); + return -EINVAL; + } + + if (len != IPACC_SEGMENT_SIZE) + sw->last_seg = 1; + + ++sw->seg_in_window; + msgb_tl16v_put(msg, NM_ATT_IPACC_FILE_DATA, len, (const uint8_t *) seg_buf); + len += 3; + break; + } + default: + LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n"); + /* FIXME: Other BTS types */ + return -1; + } + + fill_om_fom_hdr(oh, len, NM_MT_LOAD_SEG, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + return abis_nm_sendmsg_direct(sw->bts, msg); +} + +/* 6.2.4 / 8.3.4 Load Data End */ +static int sw_load_end(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_LOAD_END, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + sw_add_file_id_and_ver(sw, msg); + return abis_nm_sendmsg(sw->bts, msg); +} + +/* Activate the specified software into the BTS */ +static int sw_activate(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + /* FIXME: this is BS11 specific format */ + msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, + sw->file_version); + + 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) +{ + 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"); + return -1; + } + + if (strncmp(firmware_header.magic, " SDP", 4) != 0) { + LOGP(DNM, LOGL_ERROR, "The magic number1 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"); + return -1; + } + + + if (fstat(sw->fd, &stat) == -1) { + LOGP(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"); + 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.\n" + "There might be checksums in the file that are not\n" + "verified and incomplete firmware might be flashed.\n" + "There is absolutely no WARRANTY that flashing will\n" + "work.\n"); + return 0; +} + +static int sw_open_file(struct abis_nm_sw *sw, const char *fname) +{ + char file_id[12+1]; + char file_version[80+1]; + int rc; + + sw->fd = open(fname, O_RDONLY); + if (sw->fd < 0) + return sw->fd; + + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + sw->stream = fdopen(sw->fd, "r"); + if (!sw->stream) { + perror("fdopen"); + return -1; + } + /* read first line and parse file ID and VERSION */ + rc = fscanf(sw->stream, "@(#)%12s:%80s\r\n", + file_id, file_version); + if (rc != 2) { + perror("parsing header line of software file"); + return -1; + } + strcpy((char *)sw->file_id, file_id); + sw->file_id_len = strlen(file_id); + strcpy((char *)sw->file_version, file_version); + sw->file_version_len = strlen(file_version); + /* rewind to start of file */ + rewind(sw->stream); + break; + case GSM_BTS_TYPE_NANOBTS: + /* TODO: extract that from the filename or content */ + rc = parse_sdp_header(sw); + if (rc < 0) { + fprintf(stderr, "Could not parse the ipaccess SDP header\n"); + return -1; + } + + strcpy((char *)sw->file_id, "id"); + sw->file_id_len = 3; + strcpy((char *)sw->file_version, "version"); + sw->file_version_len = 8; + break; + default: + /* We don't know how to treat them yet */ + close(sw->fd); + return -EINVAL; + } + + return 0; +} + +static void sw_close_file(struct abis_nm_sw *sw) +{ + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + fclose(sw->stream); + break; + default: + close(sw->fd); + break; + } +} + +/* Fill the window */ +static int sw_fill_window(struct abis_nm_sw *sw) +{ + int rc; + + while (sw->seg_in_window < sw->window_size) { + rc = sw_load_segment(sw); + if (rc < 0) + return rc; + if (sw->last_seg) + break; + } + return 0; +} + +/* callback function from abis_nm_rcvmsg() handler */ +static int abis_nm_rcvmsg_sw(struct msgb *mb) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct e1inp_sign_link *sign_link = mb->dst; + int rc = -1; + struct abis_nm_sw *sw = &g_sw; + enum sw_state old_state = sw->state; + + //DEBUGP(DNM, "state %u, NM MT 0x%02x\n", sw->state, foh->msg_type); + + switch (sw->state) { + case SW_STATE_WAIT_INITACK: + switch (foh->msg_type) { + case NM_MT_LOAD_INIT_ACK: + /* fill window with segments */ + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_ACK, mb, + sw->cb_data, NULL); + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + abis_nm_queue_send_next(sign_link->trx->bts); + break; + case NM_MT_LOAD_INIT_NACK: + if (sw->forced) { + DEBUGPFOH(DNM, foh, "FORCED: Ignoring Software Load Init NACK\n"); + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_ACK, mb, + sw->cb_data, NULL); + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + } else { + LOGPFOH(DNM, LOGL_NOTICE, foh, "Software Load Init NACK\n"); + /* FIXME: cause */ + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_NACK, mb, + sw->cb_data, NULL); + sw->state = SW_STATE_ERROR; + } + abis_nm_queue_send_next(sign_link->trx->bts); + break; + } + break; + case SW_STATE_WAIT_SEGACK: + switch (foh->msg_type) { + case NM_MT_LOAD_SEG_ACK: + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_SEG_ACK, mb, + sw->cb_data, NULL); + sw->seg_in_window = 0; + if (!sw->last_seg) { + /* fill window with more segments */ + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + } else { + /* end the transfer */ + sw->state = SW_STATE_WAIT_ENDACK; + rc = sw_load_end(sw); + } + abis_nm_queue_send_next(sign_link->trx->bts); + break; + case NM_MT_LOAD_ABORT: + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_ABORT, mb, + sw->cb_data, NULL); + break; + } + break; + case SW_STATE_WAIT_ENDACK: + switch (foh->msg_type) { + case NM_MT_LOAD_END_ACK: + sw_close_file(sw); + DEBUGPFOH(DNM, foh, "Software Load End (BTS %u)\n", sw->bts->nr); + sw->state = SW_STATE_NONE; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_ACK, mb, + sw->cb_data, NULL); + rc = 0; + abis_nm_queue_send_next(sign_link->trx->bts); + break; + case NM_MT_LOAD_END_NACK: + if (sw->forced) { + DEBUGPFOH(DNM, foh, "FORCED: Ignoring Software Load End NACK\n"); + sw->state = SW_STATE_NONE; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_ACK, mb, + sw->cb_data, NULL); + } else { + LOGPFOH(DNM, LOGL_NOTICE, foh, "Software Load End NACK\n"); + /* FIXME: cause */ + sw->state = SW_STATE_ERROR; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_NACK, mb, + sw->cb_data, NULL); + } + abis_nm_queue_send_next(sign_link->trx->bts); + break; + } + case SW_STATE_WAIT_ACTACK: + switch (foh->msg_type) { + case NM_MT_ACTIVATE_SW_ACK: + /* we're done */ + LOGPFOH(DNM, LOGL_INFO, foh, "Activate Software DONE!\n"); + sw->state = SW_STATE_NONE; + rc = 0; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_ACTIVATE_SW_ACK, mb, + sw->cb_data, NULL); + abis_nm_queue_send_next(sign_link->trx->bts); + break; + case NM_MT_ACTIVATE_SW_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "Activate Software NACK\n"); + /* FIXME: cause */ + sw->state = SW_STATE_ERROR; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_ACTIVATE_SW_NACK, mb, + sw->cb_data, NULL); + abis_nm_queue_send_next(sign_link->trx->bts); + break; + } + case SW_STATE_NONE: + switch (foh->msg_type) { + case NM_MT_ACTIVATE_SW_ACK: + rc = 0; + break; + } + break; + case SW_STATE_ERROR: + break; + } + + if (rc) + LOGPFOH(DNM, LOGL_ERROR, foh, "unexpected NM MT 0x%02x in state %u -> %u\n", + foh->msg_type, old_state, sw->state); + + return rc; +} + +/* Load the specified software into the BTS */ +int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname, + uint8_t win_size, int forced, + gsm_cbfn *cbfn, void *cb_data) +{ + struct abis_nm_sw *sw = &g_sw; + int rc; + + DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n", bts->nr, fname); + + if (sw->state != SW_STATE_NONE) + return -EBUSY; + + sw->bts = bts; + sw->trx_nr = trx_nr; + + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + sw->obj_class = NM_OC_SITE_MANAGER; + sw->obj_instance[0] = 0xff; + sw->obj_instance[1] = 0xff; + sw->obj_instance[2] = 0xff; + break; + case GSM_BTS_TYPE_NANOBTS: + sw->obj_class = NM_OC_BASEB_TRANSC; + sw->obj_instance[0] = sw->bts->nr; + sw->obj_instance[1] = sw->trx_nr; + sw->obj_instance[2] = 0xff; + break; + case GSM_BTS_TYPE_UNKNOWN: + default: + LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n"); + return -1; + break; + } + sw->window_size = win_size; + sw->state = SW_STATE_WAIT_INITACK; + sw->cbfn = cbfn; + sw->cb_data = cb_data; + sw->forced = forced; + + rc = sw_open_file(sw, fname); + if (rc < 0) { + sw->state = SW_STATE_NONE; + return rc; + } + + return sw_load_init(sw); +} + +int abis_nm_software_load_status(struct gsm_bts *bts) +{ + struct abis_nm_sw *sw = &g_sw; + struct stat st; + int rc, percent; + + rc = fstat(sw->fd, &st); + if (rc < 0) { + perror("ERROR during stat"); + return rc; + } + + if (sw->stream) + percent = (ftell(sw->stream) * 100) / st.st_size; + else + percent = (lseek(sw->fd, 0, SEEK_CUR) * 100) / st.st_size; + return percent; +} + +/* Activate the specified software into the BTS */ +int abis_nm_software_activate(struct gsm_bts *bts, const char *fname, + gsm_cbfn *cbfn, void *cb_data) +{ + struct abis_nm_sw *sw = &g_sw; + int rc; + + DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n", bts->nr, fname); + + if (sw->state != SW_STATE_NONE) + return -EBUSY; + + sw->bts = bts; + sw->obj_class = NM_OC_SITE_MANAGER; + sw->obj_instance[0] = 0xff; + sw->obj_instance[1] = 0xff; + sw->obj_instance[2] = 0xff; + sw->state = SW_STATE_WAIT_ACTACK; + sw->cbfn = cbfn; + sw->cb_data = cb_data; + + /* Open the file in order to fill some sw struct members */ + rc = sw_open_file(sw, fname); + if (rc < 0) { + sw->state = SW_STATE_NONE; + return rc; + } + sw_close_file(sw); + + return sw_activate(sw); +} + +static void fill_nm_channel(struct abis_nm_channel *ch, uint8_t bts_port, + uint8_t ts_nr, uint8_t subslot_nr) +{ + ch->attrib = NM_ATT_ABIS_CHANNEL; + ch->bts_port = bts_port; + ch->timeslot = ts_nr; + ch->subslot = subslot_nr; +} + +int abis_nm_establish_tei(struct gsm_bts *bts, uint8_t trx_nr, + uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot, + uint8_t tei) +{ + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + uint8_t len = sizeof(*ch) + 2; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ESTABLISH_TEI, NM_OC_RADIO_CARRIER, + bts->bts_nr, trx_nr, 0xff); + + msgb_tv_put(msg, NM_ATT_TEI, tei); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + + return abis_nm_sendmsg(bts, msg); +} + +/* connect signalling of one (BTS,TRX) to a particular timeslot on the E1 */ +int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx, + uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot) +{ + struct gsm_bts *bts = trx->bts; + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_SIGN, + NM_OC_RADIO_CARRIER, bts->bts_nr, trx->nr, 0xff); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + + return abis_nm_sendmsg(bts, msg); +} + +#if 0 +int abis_nm_disc_terr_sign(struct abis_nm_h *h, struct abis_om_obj_inst *inst, + struct abis_nm_abis_channel *chan) +{ +} +#endif + +int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts, + uint8_t e1_port, uint8_t e1_timeslot, + uint8_t e1_subslot) +{ + struct gsm_bts *bts = ts->trx->bts; + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_TRAF, + NM_OC_CHANNEL, bts->bts_nr, ts->trx->nr, ts->nr); + + 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); + + return abis_nm_sendmsg(bts, msg); +} + +#if 0 +int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst, + struct abis_nm_abis_channel *chan, + uint8_t subchan) +{ +} +#endif + +/* 3GPP TS 52.021 § 8.11.1 */ +int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, + const uint8_t *attr, uint8_t attr_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + + if (bts->type != GSM_BTS_TYPE_OSMOBTS) { + LOGPC(DNM, LOGL_NOTICE, "Getting attributes from BTS%d type %s is not supported.\n", + bts->nr, btstype2str(bts->type)); + return -EINVAL; + } + + DEBUGP(DNM, "Get Attr (bts=%d)\n", bts->nr); + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_GET_ATTR, obj_class, + bts_nr, trx_nr, ts_nr); + msgb_tl16v_put(msg, NM_ATT_LIST_REQ_ATTR, attr_len, attr); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.6.1 */ +int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t *cur; + + DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr); + + 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); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.6.2 */ +int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, uint8_t *attr, int attr_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t *cur; + + DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER, + trx->bts->bts_nr, trx->nr, 0xff); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_update_max_power_red(struct gsm_bts_trx *trx) +{ + uint8_t attr[] = { NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2 }; + return abis_nm_set_radio_attr(trx, attr, ARRAY_SIZE(attr)); +} + +static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb, + const char **reason) +{ + int i; + + *reason = "Reason unknown"; + + /* As it turns out, the BS-11 has some very peculiar restrictions + * on the channel combinations it allows */ + switch (ts->trx->bts->type) { + case GSM_BTS_TYPE_BS11: + switch (chan_comb) { + case NM_CHANC_TCHHalf: + case NM_CHANC_TCHHalf2: + case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + /* not supported */ + *reason = "TCH/H is not supported."; + return -EINVAL; + case NM_CHANC_SDCCH: + /* only one SDCCH/8 per TRX */ + for (i = 0; i < TRX_NR_TS; i++) { + if (i == ts->nr) + continue; + if (ts->trx->ts[i].nm_chan_comb == + NM_CHANC_SDCCH) { + *reason = "Only one SDCCH/8 per TRX allowed."; + return -EINVAL; + } + } + /* not allowed for TS0 of BCCH-TRX */ + if (ts->trx == ts->trx->bts->c0 && + ts->nr == 0) { + *reason = "SDCCH/8 must be on TS0."; + return -EINVAL; + } + + /* not on the same TRX that has a BCCH+SDCCH4 + * combination */ + if (ts->trx != ts->trx->bts->c0 && + (ts->trx->ts[0].nm_chan_comb == 5 || + ts->trx->ts[0].nm_chan_comb == 8)) { + *reason = "SDCCH/8 and BCCH must be on the same TRX."; + return -EINVAL; + } + break; + case NM_CHANC_mainBCCH: + case NM_CHANC_BCCHComb: + /* allowed only for TS0 of C0 */ + if (ts->trx != ts->trx->bts->c0 || ts->nr != 0) { + *reason = "Main BCCH must be on TS0."; + return -EINVAL; + } + break; + case NM_CHANC_BCCH: + /* allowed only for TS 2/4/6 of C0 */ + if (ts->trx != ts->trx->bts->c0) { + *reason = "BCCH must be on C0."; + return -EINVAL; + } + if (ts->nr != 2 && ts->nr != 4 && ts->nr != 6) { + *reason = "BCCH must be on TS 2/4/6."; + return -EINVAL; + } + break; + case 8: /* this is not like 08.58, but in fact + * FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */ + /* FIXME: only one CBCH allowed per cell */ + break; + } + break; + case GSM_BTS_TYPE_NANOBTS: + switch (ts->nr) { + case 0: + if (ts->trx->nr == 0) { + /* only on TRX0 */ + switch (chan_comb) { + case NM_CHANC_BCCH: + case NM_CHANC_mainBCCH: + case NM_CHANC_BCCHComb: + return 0; + break; + default: + *reason = "TS0 of TRX0 must carry a BCCH."; + return -EINVAL; + } + } else { + switch (chan_comb) { + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + default: + *reason = "TS0 must carry a TCH/F or TCH/H."; + return -EINVAL; + } + } + break; + case 1: + if (ts->trx->nr == 0) { + switch (chan_comb) { + case NM_CHANC_SDCCH_CBCH: + if (ts->trx->ts[0].nm_chan_comb == + NM_CHANC_mainBCCH) + return 0; + *reason = "TS0 must be the main BCCH for CBCH."; + return -EINVAL; + case NM_CHANC_SDCCH: + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + case NM_CHANC_IPAC_TCHFull_PDCH: + case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + return 0; + default: + *reason = "TS1 must carry a CBCH, SDCCH or TCH."; + return -EINVAL; + } + } else { + switch (chan_comb) { + case NM_CHANC_SDCCH: + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + default: + *reason = "TS1 must carry a SDCCH or TCH."; + return -EINVAL; + } + } + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + switch (chan_comb) { + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + case NM_CHANC_IPAC_PDCH: + case NM_CHANC_IPAC_TCHFull_PDCH: + case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + if (ts->trx->nr == 0) + return 0; + else { + *reason = "PDCH must be on TRX0."; + return -EINVAL; + } + } + break; + } + *reason = "Unknown combination"; + return -EINVAL; + case GSM_BTS_TYPE_OSMOBTS: + /* no known restrictions */ + return 0; + default: + /* unknown BTS type */ + return 0; + } + return 0; +} + +/* Chapter 8.6.3 */ +int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb) +{ + struct gsm_bts *bts = ts->trx->bts; + struct abis_om_hdr *oh; + struct abis_om_fom_hdr *foh; + uint8_t zero = 0x00; + struct msgb *msg = nm_msgb_alloc(); + uint8_t len = 2 + 2; + const char *reason = NULL; + + if (bts->type == GSM_BTS_TYPE_BS11) + len += 4 + 2 + 2 + 3; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + foh = fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR, NM_OC_CHANNEL, bts->bts_nr, + ts->trx->nr, ts->nr); + + DEBUGPFOH(DNM, foh, "Set Chan Attr %s\n", gsm_ts_name(ts)); + if (verify_chan_comb(ts, chan_comb, &reason) < 0) { + msgb_free(msg); + LOGPFOH(DNM, LOGL_ERROR, foh, "Invalid Channel Combination %d on %s. Reason: %s\n", + chan_comb, gsm_ts_name(ts), reason); + return -EINVAL; + } + ts->nm_chan_comb = chan_comb; + + msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb); + if (ts->hopping.enabled) { + unsigned int i; + uint8_t *len; + + msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn); + msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio); + + /* build the ARFCN list */ + msgb_put_u8(msg, NM_ATT_ARFCN_LIST); + len = msgb_put(msg, 1); + *len = 0; + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) { + msgb_put_u16(msg, i); + /* At least BS-11 wants a TLV16 here */ + if (bts->type == GSM_BTS_TYPE_BS11) + *len += 1; + else + *len += sizeof(uint16_t); + } + } + } + msgb_tv_put(msg, NM_ATT_TSC, gsm_ts_tsc(ts)); /* training sequence */ + if (bts->type == GSM_BTS_TYPE_BS11) + msgb_tlv_put(msg, 0x59, 1, &zero); + + DEBUGPFOH(DNM, foh, "%s(): sending %s\n", __func__, msgb_hexdump(msg)); + return abis_nm_sendmsg(bts, msg); +} + +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) +{ + 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; + + if (nack) { + len += 2; + msgtype = NM_MT_SW_ACT_REQ_NACK; + } + + 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); + + if (attr) { + uint8_t *ptr = msgb_put(msg, att_len); + memcpy(ptr, attr, att_len); + } + if (nack) + msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP); + + return abis_nm_sendmsg_direct(bts, msg); +} + +int abis_nm_raw_msg(struct gsm_bts *bts, int len, uint8_t *rawmsg) +{ + struct msgb *msg = nm_msgb_alloc(); + struct abis_om_hdr *oh; + uint8_t *data; + + oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh)); + fill_om_hdr(oh, len); + data = msgb_put(msg, len); + memcpy(data, rawmsg, len); + + return abis_nm_sendmsg(bts, msg); +} + +/* Siemens specific commands */ +static int __simple_cmd(struct gsm_bts *bts, uint8_t msg_type) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, msg_type, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.9.2 */ +int abis_nm_opstart(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, uint8_t i2) +{ + struct abis_om_hdr *oh; + struct abis_om_fom_hdr *foh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + foh = fill_om_fom_hdr(oh, 0, NM_MT_OPSTART, obj_class, i0, i1, i2); + + DEBUGPFOH(DNM, foh, "Sending OPSTART\n"); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.8.5 */ +int abis_nm_chg_adm_state(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, + uint8_t i1, uint8_t i2, enum abis_nm_adm_state adm_state) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2, NM_MT_CHG_ADM_STATE, obj_class, i0, i1, i2); + msgb_tv_put(msg, NM_ATT_ADM_STATE, adm_state); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_conn_mdrop_link(struct gsm_bts *bts, uint8_t e1_port0, uint8_t ts0, + uint8_t e1_port1, uint8_t ts1) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t *attr; + + DEBUGP(DNM, "CONNECT MDROP LINK E1=(%u,%u) -> E1=(%u, %u)\n", + e1_port0, ts0, e1_port1, ts1); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 6, NM_MT_CONN_MDROP_LINK, + NM_OC_SITE_MANAGER, 0x00, 0x00, 0x00); + + attr = msgb_put(msg, 3); + attr[0] = NM_ATT_MDROP_LINK; + attr[1] = e1_port0; + attr[2] = ts0; + + attr = msgb_put(msg, 3); + attr[0] = NM_ATT_MDROP_NEXT; + attr[1] = e1_port1; + attr[2] = ts1; + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.7.1 */ +int abis_nm_perform_test(struct gsm_bts *bts, uint8_t obj_class, + uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, + uint8_t test_nr, uint8_t auton_report, struct msgb *msg) +{ + struct abis_om_hdr *oh; + + DEBUGP(DNM, "PEFORM TEST %s\n", abis_nm_test_name(test_nr)); + + if (!msg) + msg = nm_msgb_alloc(); + + msgb_tv_push(msg, NM_ATT_AUTON_REPORT, auton_report); + msgb_tv_push(msg, NM_ATT_TEST_NO, test_nr); + oh = (struct abis_om_hdr *) msgb_push(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, msgb_l3len(msg), NM_MT_PERF_TEST, + obj_class, bts_nr, trx_nr, ts_nr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_event_reports(struct gsm_bts *bts, int on) +{ + if (on == 0) + return __simple_cmd(bts, NM_MT_STOP_EVENT_REP); + else + return __simple_cmd(bts, NM_MT_REST_EVENT_REP); +} + +/* Siemens (or BS-11) specific commands */ + +int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect) +{ + if (reconnect == 0) + return __simple_cmd(bts, NM_MT_BS11_DISCONNECT); + else + return __simple_cmd(bts, NM_MT_BS11_RECONNECT); +} + +int abis_nm_bs11_restart(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_RESTART); +} + + +struct bs11_date_time { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; +} __attribute__((packed)); + + +void get_bs11_date_time(struct bs11_date_time *aet) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + aet->sec = tm->tm_sec; + aet->min = tm->tm_min; + aet->hour = tm->tm_hour; + aet->day = tm->tm_mday; + aet->month = tm->tm_mon; + aet->year = htons(1900 + tm->tm_year); +} + +int abis_nm_bs11_reset_resource(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_RESET_RESOURCE); +} + +int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin) +{ + if (begin) + return __simple_cmd(bts, NM_MT_BS11_BEGIN_DB_TX); + else + return __simple_cmd(bts, NM_MT_BS11_END_DB_TX); +} + +int abis_nm_bs11_create_object(struct gsm_bts *bts, + enum abis_bs11_objtype type, uint8_t idx, + uint8_t attr_len, const uint8_t *attr) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t *cur; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_BS11_CREATE_OBJ, + NM_OC_BS11, type, 0, idx); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_delete_object(struct gsm_bts *bts, + enum abis_bs11_objtype type, uint8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, + NM_OC_BS11, type, 0, idx); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, uint8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t zero = 0x00; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_CREATE_OBJ, + NM_OC_BS11_ENVABTSE, 0, idx, 0xff); + msgb_tlv_put(msg, 0x99, 1, &zero); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_create_bport(struct gsm_bts *bts, uint8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_CREATE_OBJ, NM_OC_BS11_BPORT, + idx, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_delete_bport(struct gsm_bts *bts, uint8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, NM_OC_BS11_BPORT, + idx, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +static const uint8_t sm_attr[] = { NM_ATT_TEI, NM_ATT_ABIS_CHANNEL }; +int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(sm_attr), NM_MT_GET_ATTR, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(sm_attr), sm_attr); + + return abis_nm_sendmsg(bts, msg); +} + +/* like abis_nm_conn_terr_traf + set_tei */ +int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, uint8_t e1_port, + uint8_t e1_timeslot, uint8_t e1_subslot, + uint8_t tei) +{ + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch)+2, NM_MT_BS11_SET_ATTR, + NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + msgb_tv_put(msg, NM_ATT_TEI, tei); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, uint8_t level) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, + NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr); + msgb_tlv_put(msg, NM_ATT_BS11_TXPWR, 1, &level); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t attr = NM_ATT_BS11_TXPWR; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t attr[] = { NM_ATT_BS11_PLL_MODE }; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_LI, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_cclk(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t attr[] = { NM_ATT_BS11_CCLK_ACCURACY, + NM_ATT_BS11_CCLK_TYPE }; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_CCLK, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr); + + return abis_nm_sendmsg(bts, msg); + +} + +//static const uint8_t bs11_logon_c7[] = { 0x07, 0xd9, 0x01, 0x11, 0x0d, 0x10, 0x20 }; + +int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on) +{ + return abis_nm_bs11_logon(bts, 0x02, "FACTORY", on); +} + +int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on) +{ + return abis_nm_bs11_logon(bts, 0x03, "FIELD ", on); +} + +int abis_nm_bs11_logon(struct gsm_bts *bts, uint8_t level, const char *name, int on) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time bdt; + + get_bs11_date_time(&bdt); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + if (on) { + uint8_t len = 3*2 + sizeof(bdt) + + 1 + strlen(name); + fill_om_fom_hdr(oh, len, NM_MT_BS11_LMT_LOGON, + NM_OC_BS11_BTSE, 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_LOGIN_TIME, + sizeof(bdt), (uint8_t *) &bdt); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_ACC_LEV, + 1, &level); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_NAME, + strlen(name), (uint8_t *)name); + } else { + fill_om_fom_hdr(oh, 0, NM_MT_BS11_LMT_LOGOFF, + NM_OC_BS11_BTSE, 0xff, 0xff, 0xff); + } + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + + if (strlen(password) != 10) + return -EINVAL; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+strlen(password), NM_MT_BS11_SET_ATTR, + NM_OC_BS11, BS11_OBJ_TRX1, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_BS11_PASSWORD, 10, (const uint8_t *)password); + + return abis_nm_sendmsg(bts, msg); +} + +/* change the BS-11 PLL Mode to either locked (E1 derived) or standalone */ +int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + uint8_t tlv_value; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11, + BS11_OBJ_LI, 0x00, 0x00); + + if (locked) + tlv_value = BS11_LI_PLL_LOCKED; + else + tlv_value = BS11_LI_PLL_STANDALONE; + + msgb_tlv_put(msg, NM_ATT_BS11_PLL_MODE, 1, &tlv_value); + + return abis_nm_sendmsg(bts, msg); +} + +/* Set the calibration value of the PLL (work value/set value) + * It depends on the login which one is changed */ +int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + uint8_t tlv_value[2]; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11, + BS11_OBJ_TRX1, 0x00, 0x00); + + tlv_value[0] = value>>8; + tlv_value[1] = value&0xff; + + msgb_tlv_put(msg, NM_ATT_BS11_PLL, 2, tlv_value); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_state(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_GET_STATE); +} + +/* BS11 SWL */ + +void *tall_fle_ctx = NULL; + +struct abis_nm_bs11_sw { + struct gsm_bts *bts; + char swl_fname[PATH_MAX]; + uint8_t win_size; + int forced; + struct llist_head file_list; + gsm_cbfn *user_cb; /* specified by the user */ +}; +static struct abis_nm_bs11_sw _g_bs11_sw, *g_bs11_sw = &_g_bs11_sw; + +struct file_list_entry { + struct llist_head list; + char fname[PATH_MAX]; +}; + +struct file_list_entry *fl_dequeue(struct llist_head *queue) +{ + struct llist_head *lh; + + if (llist_empty(queue)) + return NULL; + + lh = queue->next; + llist_del(lh); + + return llist_entry(lh, struct file_list_entry, list); +} + +static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw) +{ + char linebuf[255]; + struct llist_head *lh, *lh2; + FILE *swl; + int rc = 0; + + swl = fopen(bs11_sw->swl_fname, "r"); + if (!swl) + 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 (fgets(linebuf, sizeof(linebuf), swl)) { + char file_id[12+1]; + char file_version[80+1]; + struct file_list_entry *fle; + static char dir[PATH_MAX]; + + if (strlen(linebuf) < 4) + continue; + + rc = sscanf(linebuf+4, "%12s:%80s\r\n", file_id, file_version); + if (rc < 0) { + perror("ERR parsing SWL file"); + rc = -EINVAL; + goto out; + } + if (rc < 2) + continue; + + fle = talloc_zero(tall_fle_ctx, struct file_list_entry); + if (!fle) { + rc = -ENOMEM; + goto out; + } + + /* construct new filename */ + osmo_strlcpy(dir, bs11_sw->swl_fname, sizeof(dir)); + strncat(fle->fname, dirname(dir), sizeof(fle->fname) - 1); + strcat(fle->fname, "/"); + strncat(fle->fname, file_id, sizeof(fle->fname) - 1 -strlen(fle->fname)); + + llist_add_tail(&fle->list, &bs11_sw->file_list); + } + +out: + fclose(swl); + return rc; +} + +/* bs11 swload specific callback, passed to abis_nm core swload */ +static int bs11_swload_cbfn(unsigned int hook, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct abis_nm_bs11_sw *bs11_sw = data; + struct file_list_entry *fle; + int rc = 0; + + switch (event) { + case NM_MT_LOAD_END_ACK: + fle = fl_dequeue(&bs11_sw->file_list); + if (fle) { + /* start download the next file of our file list */ + rc = abis_nm_software_load(bs11_sw->bts, 0xff, fle->fname, + bs11_sw->win_size, + bs11_sw->forced, + &bs11_swload_cbfn, bs11_sw); + talloc_free(fle); + } else { + /* activate the SWL */ + rc = abis_nm_software_activate(bs11_sw->bts, + bs11_sw->swl_fname, + bs11_swload_cbfn, + bs11_sw); + } + break; + case NM_MT_LOAD_SEG_ACK: + case NM_MT_LOAD_END_NACK: + case NM_MT_LOAD_INIT_ACK: + case NM_MT_LOAD_INIT_NACK: + case NM_MT_ACTIVATE_SW_NACK: + case NM_MT_ACTIVATE_SW_ACK: + default: + /* fallthrough to the user callback */ + if (bs11_sw->user_cb) + rc = bs11_sw->user_cb(hook, event, msg, NULL, NULL); + break; + } + + return rc; +} + +/* Siemens provides a SWL file that is a mere listing of all the other + * files that are part of a software release. We need to upload first + * the list file, and then each file that is listed in the list file */ +int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname, + uint8_t win_size, int forced, gsm_cbfn *cbfn) +{ + struct abis_nm_bs11_sw *bs11_sw = g_bs11_sw; + struct file_list_entry *fle; + int rc = 0; + + INIT_LLIST_HEAD(&bs11_sw->file_list); + bs11_sw->bts = bts; + bs11_sw->win_size = win_size; + bs11_sw->user_cb = cbfn; + bs11_sw->forced = forced; + + osmo_strlcpy(bs11_sw->swl_fname, fname, sizeof(bs11_sw->swl_fname)); + rc = bs11_read_swl_file(bs11_sw); + if (rc < 0) + return rc; + + /* dequeue next item in file list */ + fle = fl_dequeue(&bs11_sw->file_list); + if (!fle) + return -EINVAL; + + /* start download the next file of our file list */ + rc = abis_nm_software_load(bts, 0xff, fle->fname, win_size, forced, + bs11_swload_cbfn, bs11_sw); + talloc_free(fle); + return rc; +} + +#if 0 +static uint8_t req_attr_btse[] = { + NM_ATT_ADM_STATE, NM_ATT_BS11_LMT_LOGON_SESSION, + NM_ATT_BS11_LMT_LOGIN_TIME, NM_ATT_BS11_LMT_USER_ACC_LEV, + NM_ATT_BS11_LMT_USER_NAME, + + 0xaf, NM_ATT_BS11_RX_OFFSET, NM_ATT_BS11_VENDOR_NAME, + + NM_ATT_BS11_SW_LOAD_INTENDED, NM_ATT_BS11_SW_LOAD_SAFETY, + + NM_ATT_BS11_SW_LOAD_STORED }; + +static uint8_t req_attr_btsm[] = { + NM_ATT_ABIS_CHANNEL, NM_ATT_TEI, NM_ATT_BS11_ABIS_EXT_TIME, + NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xce, NM_ATT_FILE_ID, + NM_ATT_FILE_VERSION, NM_ATT_OPER_STATE, 0xe8, NM_ATT_BS11_ALL_TEST_CATG, + NM_ATT_SW_DESCR, NM_ATT_GET_ARI }; +#endif + +static uint8_t req_attr[] = { + NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xa8, NM_ATT_OPER_STATE, + 0xd5, 0xa1, NM_ATT_BS11_ESN_FW_CODE_NO, NM_ATT_BS11_ESN_HW_CODE_NO, + 0x42, NM_ATT_BS11_ESN_PCB_SERIAL, NM_ATT_BS11_PLL }; + +int abis_nm_bs11_get_serno(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + /* SiemensHW CCTRL object */ + fill_om_fom_hdr(oh, 2+sizeof(req_attr), NM_MT_GET_ATTR, NM_OC_BS11, + 0x03, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(req_attr), req_attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_ext_time(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time aet; + + get_bs11_date_time(&aet); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + /* SiemensHW CCTRL object */ + fill_om_fom_hdr(oh, 2+sizeof(aet), NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_BS11_ABIS_EXT_TIME, sizeof(aet), (uint8_t *) &aet); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, uint8_t bport) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t attr = NM_ATT_BS11_LINE_CFG; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11_BPORT, bport, 0xff, 0x02); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, uint8_t bport, enum abis_bs11_line_cfg line_cfg) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time aet; + + get_bs11_date_time(&aet); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2, NM_MT_BS11_SET_ATTR, NM_OC_BS11_BPORT, + bport, 0xff, 0x02); + msgb_tv_put(msg, NM_ATT_BS11_LINE_CFG, line_cfg); + + return abis_nm_sendmsg(bts, msg); +} + +/* ip.access nanoBTS specific commands */ +static const char ipaccess_magic[] = "com.ipaccess"; + + +static int abis_nm_rx_ipacc(struct msgb *msg) +{ + struct in_addr addr; + struct abis_om_hdr *oh = msgb_l2(msg); + struct abis_om_fom_hdr *foh; + uint8_t idstrlen = oh->data[0]; + struct tlv_parsed tp; + struct ipacc_ack_signal_data signal; + struct e1inp_sign_link *sign_link = msg->dst; + + foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen); + + if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) { + LOGPFOH(DNM, LOGL_ERROR, foh, "id string is not com.ipaccess !?!\n"); + return -EINVAL; + } + + abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + + DEBUGPFOH(DNM, foh, "IPACCESS(0x%02x): ", foh->msg_type); + + switch (foh->msg_type) { + case NM_MT_IPACC_RSL_CONNECT_ACK: + DEBUGPC(DNM, "RSL CONNECT ACK "); + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP)) { + memcpy(&addr, + TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP), sizeof(addr)); + + DEBUGPC(DNM, "IP=%s ", inet_ntoa(addr)); + } + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP_PORT)) + DEBUGPC(DNM, "PORT=%u ", + ntohs(*((uint16_t *) + TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP_PORT)))); + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_STREAM_ID)) + DEBUGPC(DNM, "STREAM=0x%02x ", + *TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID)); + DEBUGPC(DNM, "\n"); + osmo_timer_del(&sign_link->trx->rsl_connect_timeout); + break; + case NM_MT_IPACC_RSL_CONNECT_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "RSL CONNECT NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + osmo_timer_del(&sign_link->trx->rsl_connect_timeout); + break; + case NM_MT_IPACC_SET_NVATTR_ACK: + DEBUGPFOH(DNM, foh, "SET NVATTR ACK\n"); + /* FIXME: decode and show the actual attributes */ + break; + case NM_MT_IPACC_SET_NVATTR_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "SET NVATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + case NM_MT_IPACC_GET_NVATTR_ACK: + DEBUGPFOH(DNM, foh, "GET NVATTR ACK\n"); + /* FIXME: decode and show the actual attributes */ + break; + case NM_MT_IPACC_GET_NVATTR_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "GET NVATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + case NM_MT_IPACC_SET_ATTR_ACK: + DEBUGPFOH(DNM, foh, "SET ATTR ACK\n"); + break; + case NM_MT_IPACC_SET_ATTR_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "SET ATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + default: + DEBUGPC(DNM, "unknown\n"); + break; + } + + /* signal handling */ + switch (foh->msg_type) { + case NM_MT_IPACC_RSL_CONNECT_NACK: + case NM_MT_IPACC_SET_NVATTR_NACK: + case NM_MT_IPACC_GET_NVATTR_NACK: + signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr); + signal.msg_type = foh->msg_type; + osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal); + break; + case NM_MT_IPACC_SET_NVATTR_ACK: + signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr); + signal.msg_type = foh->msg_type; + osmo_signal_dispatch(SS_NM, S_NM_IPACC_ACK, &signal); + break; + default: + break; + } + + return 0; +} + +/* send an ip-access manufacturer specific message */ +int abis_nm_ipaccess_msg(struct gsm_bts *bts, uint8_t msg_type, + uint8_t obj_class, uint8_t bts_nr, + uint8_t trx_nr, uint8_t ts_nr, + uint8_t *attr, int attr_len) +{ + struct msgb *msg = nm_msgb_alloc(); + struct abis_om_hdr *oh; + struct abis_om_fom_hdr *foh; + uint8_t *data; + + /* construct the 12.21 OM header, observe the erroneous length */ + oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh)); + fill_om_hdr(oh, sizeof(*foh) + attr_len); + oh->mdisc = ABIS_OM_MDISC_MANUF; + + /* add the ip.access magic */ + data = msgb_put(msg, sizeof(ipaccess_magic)+1); + *data++ = sizeof(ipaccess_magic); + memcpy(data, ipaccess_magic, sizeof(ipaccess_magic)); + + /* fill the 12.21 FOM header */ + foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh)); + foh->msg_type = msg_type; + foh->obj_class = obj_class; + foh->obj_inst.bts_nr = bts_nr; + foh->obj_inst.trx_nr = trx_nr; + foh->obj_inst.ts_nr = ts_nr; + + if (attr && attr_len) { + data = msgb_put(msg, attr_len); + memcpy(data, attr, attr_len); + } + + return abis_nm_sendmsg(bts, msg); +} + +/* set some attributes in NVRAM */ +int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, uint8_t *attr, + int attr_len) +{ + return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_SET_NVATTR, + NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, attr, + attr_len); +} + +static void rsl_connect_timeout(void *data) +{ + struct gsm_bts_trx *trx = data; + struct ipacc_ack_signal_data signal; + + LOGP(DRSL, LOGL_NOTICE, "(bts=%d,trx=%d) RSL connection request timed out\n", trx->bts->nr, trx->nr); + + /* Fake an RSL CONECT NACK message from the BTS. */ + signal.trx = trx; + signal.msg_type = NM_MT_IPACC_RSL_CONNECT_NACK; + 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 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)); + + /* if ip == 0, we use the default IP */ + if (ip == 0) + attr_len -= 5; + + LOGP(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); + if (error == 0) + osmo_timer_schedule(&trx->rsl_connect_timeout, 60, 0); + + return error; +} + +/* restart / reboot an ip.access nanoBTS */ +int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + 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); + + return abis_nm_sendmsg_direct(trx->bts, msg); +} + +int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, uint8_t obj_class, + uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, + uint8_t *attr, uint8_t attr_len) +{ + return abis_nm_ipaccess_msg(bts, NM_MT_IPACC_SET_ATTR, + obj_class, bts_nr, trx_nr, ts_nr, + attr, attr_len); +} + +void abis_nm_ipaccess_cgi(uint8_t *buf, struct gsm_bts *bts) +{ + struct gsm48_ra_id *_buf = (struct gsm48_ra_id*)buf; + uint16_t ci = htons(bts->cell_identity); + /* we simply reuse the GSM48 function and write the Cell ID over the position where the RAC + * starts */ + gsm48_ra_id_by_bts(_buf, bts); + memcpy(&_buf->rac, &ci, sizeof(ci)); +} + +void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason) +{ + uint8_t new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; + + + if (!trx->bts || !trx->bts->oml_link) { + /* Set initial state which will be sent when BTS connects. */ + trx->mo.nm_state.administrative = new_state; + return; + } + + LOGP(DNM, LOGL_NOTICE, "(bts=%d,trx=%d) Requesting administrative state change %s -> %s [%s]\n", + trx->bts->nr, trx->nr, + get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative), + get_value_string(abis_nm_adm_state_names, new_state), reason); + + abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER, + trx->bts->bts_nr, trx->nr, 0xff, + new_state); +} + +static const struct value_string ipacc_testres_names[] = { + { NM_IPACC_TESTRES_SUCCESS, "SUCCESS" }, + { NM_IPACC_TESTRES_TIMEOUT, "TIMEOUT" }, + { NM_IPACC_TESTRES_NO_CHANS, "NO CHANNELS" }, + { NM_IPACC_TESTRES_PARTIAL, "PARTIAL" }, + { NM_IPACC_TESTRES_STOPPED, "STOPPED" }, + { 0, NULL } +}; + +const char *ipacc_testres_name(uint8_t res) +{ + return get_value_string(ipacc_testres_names, res); +} + +void ipac_parse_cgi(struct osmo_cell_global_id *cid, const uint8_t *buf) +{ + osmo_plmn_from_bcd(buf, &cid->lai.plmn); + cid->lai.lac = ntohs(*((uint16_t *)&buf[3])); + cid->cell_identity = ntohs(*((uint16_t *)&buf[5])); +} + +/* parse BCCH information IEI from wire format to struct ipac_bcch_info */ +int ipac_parse_bcch_info(struct ipac_bcch_info *binf, uint8_t *buf) +{ + uint8_t *cur = buf; + uint16_t len __attribute__((unused)); + + memset(binf, 0, sizeof(*binf)); + + if (cur[0] != NM_IPAC_EIE_BCCH_INFO) + return -EINVAL; + cur++; + + len = ntohs(*(uint16_t *)cur); + cur += 2; + + binf->info_type = ntohs(*(uint16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL) + binf->freq_qual = *cur >> 2; + + binf->arfcn = (*cur++ & 3) << 8; + binf->arfcn |= *cur++; + + if (binf->info_type & IPAC_BINF_RXLEV) + binf->rx_lev = *cur & 0x3f; + cur++; + + if (binf->info_type & IPAC_BINF_RXQUAL) + binf->rx_qual = *cur & 0x7; + cur++; + + if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL) + binf->freq_err = ntohs(*(uint16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FRAME_OFFSET) + binf->frame_offset = ntohs(*(uint16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET) + binf->frame_nr_offset = ntohl(*(uint32_t *)cur); + cur += 4; + +#if 0 + /* Somehow this is not set correctly */ + if (binf->info_type & IPAC_BINF_BSIC) +#endif + binf->bsic = *cur & 0x3f; + cur++; + + ipac_parse_cgi(&binf->cgi, cur); + cur += 7; + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2) { + memcpy(binf->ba_list_si2, cur, sizeof(binf->ba_list_si2)); + cur += sizeof(binf->ba_list_si2); + } + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2bis) { + memcpy(binf->ba_list_si2bis, cur, + sizeof(binf->ba_list_si2bis)); + cur += sizeof(binf->ba_list_si2bis); + } + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2ter) { + memcpy(binf->ba_list_si2ter, cur, + sizeof(binf->ba_list_si2ter)); + cur += sizeof(binf->ba_list_si2ter); + } + + return 0; +} + +void abis_nm_clear_queue(struct gsm_bts *bts) +{ + struct msgb *msg; + + while (!llist_empty(&bts->abis_queue)) { + msg = msgb_dequeue(&bts->abis_queue); + msgb_free(msg); + } + + bts->abis_nm_pend = 0; +} diff --git a/src/osmo-bsc/abis_nm_ipaccess.c b/src/osmo-bsc/abis_nm_ipaccess.c new file mode 100644 index 000000000..964b92e26 --- /dev/null +++ b/src/osmo-bsc/abis_nm_ipaccess.c @@ -0,0 +1,89 @@ +/* GSM Network Management (OML) messages on the A-bis interface + * Extensions for the ip.access A-bis over IP protocol*/ + +/* (C) 2008-2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +/* A list of all the 'embedded' attributes of ip.access */ +enum ipa_embedded_att { + IPA_ATT_ARFCN_WHITELIST = 0x01, + IPA_ATT_ARFCN_BLACKLIST = 0x02, + IPA_ATT_FREQ_ERR_LIST = 0x03, + IPA_ATT_CHAN_USAGE_LIST = 0x04, + IPA_ATT_BCCH_INF_TYPE = 0x05, + IPA_ATT_BCCH_INF = 0x06, + IPA_ATT_CONFIG = 0x07, + IPA_ATT_RESULT_DETAILS = 0x08, + IPA_ATT_RXLEV_THRESH = 0x09, + IPA_ATT_FREQ_SYNC_OPT = 0x0a, + IPA_ATT_MAC_ADDR = 0x0b, + IPA_ATT_HW_SW_COMPAT_NR = 0x0c, + IPA_ATT_MANUF_SER_NR = 0x0d, + IPA_ATT_OEM_ID = 0x0e, + IPA_ATT_DATETIME_MANUF = 0x0f, + IPA_ATT_DATETIME_CALIB = 0x10, + IPA_ATT_BEACON_INF = 0x11, + IPA_ATT_FREQ_ERR = 0x12, + IPA_ATT_SNMP_COMM_STRING = 0x13, + IPA_ATT_SNMP_TRAP_ADDR = 0x14, + IPA_ATT_SNMP_TRAP_PORT = 0x15, + IPA_ATT_SNMP_MAN_ADDR = 0x16, + IPA_ATT_SNMP_SYS_CONTACT = 0x17, + IPA_ATT_FACTORY_ID = 0x18, + IPA_ATT_FACTORY_SERIAL = 0x19, + IPA_ATT_LOGGED_EVT_IND = 0x1a, + IPA_ATT_LOCAL_ADD_TEXT = 0x1b, + IPA_ATT_FREQ_BANDS = 0x1c, + IPA_ATT_MAX_TA = 0x1d, + IPA_ATT_CIPH_ALG = 0x1e, + IPA_ATT_CHAN_TYPES = 0x1f, + IPA_ATT_CHAN_MODES = 0x20, + IPA_ATT_GPRS_CODING_SCHEMES = 0x21, + IPA_ATT_RTP_FEATURES = 0x22, + IPA_ATT_RSL_FEATURES = 0x23, + IPA_ATT_BTS_HW_CLASS = 0x24, + IPA_ATT_BTS_ID = 0x25, + IPA_ATT_BCAST_L2_MSG = 0x26, +}; + +/* append an ip.access channel list to the given msgb */ +static int ipa_chan_list_append(struct msgb *msg, uint8_t ie, + uint16_t *arfcns, int arfcn_count) +{ + int i; + uint8_t *u8; + uint16_t *u16; + + /* tag */ + u8 = msgb_push(msg, 1); + *u8 = ie; + + /* length in octets */ + u16 = msgb_push(msg, 2); + *u16 = htons(arfcn_count * 2); + + for (i = 0; i < arfcn_count; i++) { + u16 = msgb_push(msg, 2); + *u16 = htons(arfcns[i]); + } + + return 0; +} diff --git a/src/osmo-bsc/abis_nm_vty.c b/src/osmo-bsc/abis_nm_vty.c new file mode 100644 index 000000000..3019eb828 --- /dev/null +++ b/src/osmo-bsc/abis_nm_vty.c @@ -0,0 +1,188 @@ +/* VTY interface for A-bis OML (Netowrk Management) */ + +/* (C) 2009-2018 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static struct cmd_node oml_node = { + OML_NODE, + "%s(oml)# ", + 1, +}; + +struct oml_node_state { + struct gsm_bts *bts; + uint8_t obj_class; + 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" \ + "BTS Object\n" \ + "Radio Carrier Object\n" \ + "Baseband Transceiver Object\n" \ + "Channel (Timeslot) Object\n" \ + "Adjacent Object (Siemens)\n" \ + "Handover Object (Siemens)\n" \ + "Power Control Object (Siemens)\n" \ + "BTSE Object (Siemens)\n" \ + "Rack Object (Siemens)\n" \ + "Test Object (Siemens)\n" \ + "ENVABTSE Object (Siemens)\n" \ + "BPORT Object (Siemens)\n" \ + "GPRS NSE Object (ip.access/osmo-bts)\n" \ + "GPRS Cell Object (ip.acecss/osmo-bts)\n" \ + "GPRS NSVC Object (ip.acecss/osmo-bts)\n" \ + "SIEMENSHW Object (Siemens)\n" + + +DEFUN(oml_class_inst, oml_class_inst_cmd, + "bts <0-255> oml class " NM_OBJCLASS_VTY + " instance <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" NM_OBJCLASS_VTY_HELP + "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + 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; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->obj_class = get_string_value(abis_nm_obj_class_names, argv[1]); + oms->obj_inst[0] = atoi(argv[2]); + oms->obj_inst[1] = atoi(argv[3]); + oms->obj_inst[2] = atoi(argv[4]); + + vty->index = oms; + vty->node = OML_NODE; + + return CMD_SUCCESS; + +} + +DEFUN(oml_classnum_inst, oml_classnum_inst_cmd, + "bts <0-255> oml class <0-255> instance <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" "Object Class\n" + "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + 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; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->obj_class = atoi(argv[1]); + oms->obj_inst[0] = atoi(argv[2]); + oms->obj_inst[1] = atoi(argv[3]); + oms->obj_inst[2] = atoi(argv[4]); + + vty->index = oms; + vty->node = OML_NODE; + + return CMD_SUCCESS; +} + +DEFUN(oml_chg_adm_state, oml_chg_adm_state_cmd, + "change-adm-state (locked|unlocked|shutdown|null)", + "Change the Administrative State\n" + "Locked\n" "Unlocked\n" "Shutdown\n" "NULL\n") +{ + struct oml_node_state *oms = vty->index; + enum abis_nm_adm_state state; + + state = get_string_value(abis_nm_adm_state_names, argv[0]); + + abis_nm_chg_adm_state(oms->bts, oms->obj_class, oms->obj_inst[0], + oms->obj_inst[1], oms->obj_inst[2], state); + + return CMD_SUCCESS; +} + +DEFUN(oml_opstart, oml_opstart_cmd, + "opstart", "Send an OPSTART message to the object") +{ + struct oml_node_state *oms = vty->index; + + abis_nm_opstart(oms->bts, oms->obj_class, oms->obj_inst[0], + oms->obj_inst[1], oms->obj_inst[2]); + + return CMD_SUCCESS; +} + +int abis_nm_vty_init(void) +{ + install_element(ENABLE_NODE, &oml_class_inst_cmd); + install_element(ENABLE_NODE, &oml_classnum_inst_cmd); + install_node(&oml_node, dummy_config_write); + + install_element(OML_NODE, &oml_chg_adm_state_cmd); + install_element(OML_NODE, &oml_opstart_cmd); + + return 0; +} diff --git a/src/osmo-bsc/abis_om2000.c b/src/osmo-bsc/abis_om2000.c new file mode 100644 index 000000000..d533ea198 --- /dev/null +++ b/src/osmo-bsc/abis_om2000.c @@ -0,0 +1,2769 @@ +/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface + * implemented based on protocol trace analysis, no formal documentation */ + +/* (C) 2010-2011,2016 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* FIXME: move to libosmocore */ +struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm, + struct osmo_fsm_inst *parent, + uint32_t parent_term_event, + const char *id) +{ + 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); + 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); + + return fi; +} + + +#define OM_ALLOC_SIZE 1024 +#define OM_HEADROOM_SIZE 128 + +#define OM2K_TIMEOUT 10 +#define TRX_FSM_TIMEOUT 60 +#define BTS_FSM_TIMEOUT 60 + +/* use following functions from abis_nm.c: + * om2k_msgb_alloc() + * abis_om2k_sendmsg() + */ + +struct abis_om2k_hdr { + struct abis_om_hdr om; + uint16_t msg_type; + struct abis_om2k_mo mo; + uint8_t data[0]; +} __attribute__ ((packed)); + +enum abis_om2k_msgtype { + OM2K_MSGT_ABORT_SP_CMD = 0x0000, + OM2K_MSGT_ABORT_SP_COMPL = 0x0002, + OM2K_MSGT_ALARM_REP_ACK = 0x0004, + OM2K_MSGT_ALARM_REP_NACK = 0x0005, + OM2K_MSGT_ALARM_REP = 0x0006, + OM2K_MSGT_ALARM_STATUS_REQ = 0x0008, + OM2K_MSGT_ALARM_STATUS_REQ_ACK = 0x000a, + OM2K_MSGT_ALARM_STATUS_REQ_REJ = 0x000b, + OM2K_MSGT_ALARM_STATUS_RES_ACK = 0x000c, + OM2K_MSGT_ALARM_STATUS_RES_NACK = 0x000d, + OM2K_MSGT_ALARM_STATUS_RES = 0x000e, + OM2K_MSGT_CAL_TIME_RESP = 0x0010, + OM2K_MSGT_CAL_TIME_REJ = 0x0011, + OM2K_MSGT_CAL_TIME_REQ = 0x0012, + + OM2K_MSGT_CON_CONF_REQ = 0x0014, + OM2K_MSGT_CON_CONF_REQ_ACK = 0x0016, + OM2K_MSGT_CON_CONF_REQ_REJ = 0x0017, + OM2K_MSGT_CON_CONF_RES_ACK = 0x0018, + OM2K_MSGT_CON_CONF_RES_NACK = 0x0019, + OM2K_MSGT_CON_CONF_RES = 0x001a, + + OM2K_MSGT_CONNECT_CMD = 0x001c, + OM2K_MSGT_CONNECT_COMPL = 0x001e, + OM2K_MSGT_CONNECT_REJ = 0x001f, + + OM2K_MSGT_DISABLE_REQ = 0x0028, + OM2K_MSGT_DISABLE_REQ_ACK = 0x002a, + OM2K_MSGT_DISABLE_REQ_REJ = 0x002b, + OM2K_MSGT_DISABLE_RES_ACK = 0x002c, + OM2K_MSGT_DISABLE_RES_NACK = 0x002d, + OM2K_MSGT_DISABLE_RES = 0x002e, + OM2K_MSGT_DISCONNECT_CMD = 0x0030, + OM2K_MSGT_DISCONNECT_COMPL = 0x0032, + OM2K_MSGT_DISCONNECT_REJ = 0x0033, + OM2K_MSGT_ENABLE_REQ = 0x0034, + OM2K_MSGT_ENABLE_REQ_ACK = 0x0036, + OM2K_MSGT_ENABLE_REQ_REJ = 0x0037, + OM2K_MSGT_ENABLE_RES_ACK = 0x0038, + OM2K_MSGT_ENABLE_RES_NACK = 0x0039, + OM2K_MSGT_ENABLE_RES = 0x003a, + + OM2K_MSGT_FAULT_REP_ACK = 0x0040, + OM2K_MSGT_FAULT_REP_NACK = 0x0041, + OM2K_MSGT_FAULT_REP = 0x0042, + + OM2K_MSGT_IS_CONF_REQ = 0x0060, + OM2K_MSGT_IS_CONF_REQ_ACK = 0x0062, + OM2K_MSGT_IS_CONF_REQ_REJ = 0x0063, + OM2K_MSGT_IS_CONF_RES_ACK = 0x0064, + OM2K_MSGT_IS_CONF_RES_NACK = 0x0065, + OM2K_MSGT_IS_CONF_RES = 0x0066, + + OM2K_MSGT_OP_INFO = 0x0074, + OM2K_MSGT_OP_INFO_ACK = 0x0076, + OM2K_MSGT_OP_INFO_REJ = 0x0077, + OM2K_MSGT_RESET_CMD = 0x0078, + OM2K_MSGT_RESET_COMPL = 0x007a, + OM2K_MSGT_RESET_REJ = 0x007b, + OM2K_MSGT_RX_CONF_REQ = 0x007c, + OM2K_MSGT_RX_CONF_REQ_ACK = 0x007e, + OM2K_MSGT_RX_CONF_REQ_REJ = 0x007f, + OM2K_MSGT_RX_CONF_RES_ACK = 0x0080, + OM2K_MSGT_RX_CONF_RES_NACK = 0x0081, + OM2K_MSGT_RX_CONF_RES = 0x0082, + OM2K_MSGT_START_REQ = 0x0084, + OM2K_MSGT_START_REQ_ACK = 0x0086, + OM2K_MSGT_START_REQ_REJ = 0x0087, + OM2K_MSGT_START_RES_ACK = 0x0088, + OM2K_MSGT_START_RES_NACK = 0x0089, + OM2K_MSGT_START_RES = 0x008a, + OM2K_MSGT_STATUS_REQ = 0x008c, + OM2K_MSGT_STATUS_RESP = 0x008e, + OM2K_MSGT_STATUS_REJ = 0x008f, + + OM2K_MSGT_TEST_REQ = 0x0094, + OM2K_MSGT_TEST_REQ_ACK = 0x0096, + OM2K_MSGT_TEST_REQ_REJ = 0x0097, + OM2K_MSGT_TEST_RES_ACK = 0x0098, + OM2K_MSGT_TEST_RES_NACK = 0x0099, + OM2K_MSGT_TEST_RES = 0x009a, + + OM2K_MSGT_TF_CONF_REQ = 0x00a0, + OM2K_MSGT_TF_CONF_REQ_ACK = 0x00a2, + OM2K_MSGT_TF_CONF_REQ_REJ = 0x00a3, + OM2K_MSGT_TF_CONF_RES_ACK = 0x00a4, + OM2K_MSGT_TF_CONF_RES_NACK = 0x00a5, + OM2K_MSGT_TF_CONF_RES = 0x00a6, + OM2K_MSGT_TS_CONF_REQ = 0x00a8, + OM2K_MSGT_TS_CONF_REQ_ACK = 0x00aa, + OM2K_MSGT_TS_CONF_REQ_REJ = 0x00ab, + OM2K_MSGT_TS_CONF_RES_ACK = 0x00ac, + OM2K_MSGT_TS_CONF_RES_NACK = 0x00ad, + OM2K_MSGT_TS_CONF_RES = 0x00ae, + OM2K_MSGT_TX_CONF_REQ = 0x00b0, + OM2K_MSGT_TX_CONF_REQ_ACK = 0x00b2, + OM2K_MSGT_TX_CONF_REQ_REJ = 0x00b3, + OM2K_MSGT_TX_CONF_RES_ACK = 0x00b4, + OM2K_MSGT_TX_CONF_RES_NACK = 0x00b5, + OM2K_MSGT_TX_CONF_RES = 0x00b6, + + OM2K_MSGT_CAPA_REQ = 0x00e8, + OM2K_MSGT_CAPA_REQ_ACK = 0x00ea, + OM2K_MSGT_CAPA_REQ_REJ = 0x00eb, + OM2K_MSGT_CAPA_RES = 0x00ee, + OM2K_MSGT_CAPA_RES_ACK = 0x00ec, + OM2K_MSGT_CAPA_RES_NACK = 0x00ed, + + OM2K_MSGT_NEGOT_REQ_ACK = 0x0104, + OM2K_MSGT_NEGOT_REQ_NACK = 0x0105, + OM2K_MSGT_NEGOT_REQ = 0x0106, +}; + +enum abis_om2k_dei { + OM2K_DEI_ACCORDANCE_IND = 0x00, + OM2K_DEI_BCC = 0x06, + OM2K_DEI_BS_AG_BKS_RES = 0x07, + OM2K_DEI_BSIC = 0x09, + OM2K_DEI_BA_PA_MFRMS = 0x0a, + OM2K_DEI_CBCH_INDICATOR = 0x0b, + OM2K_DEI_CCCH_OPTIONS = 0x0c, + OM2K_DEI_CAL_TIME = 0x0d, + OM2K_DEI_COMBINATION = 0x0f, + OM2K_DEI_CON_CONN_LIST = 0x10, + OM2K_DEI_DRX_DEV_MAX = 0x12, + OM2K_DEI_END_LIST_NR = 0x13, + OM2K_DEI_EXT_COND_MAP_1 = 0x14, + OM2K_DEI_EXT_COND_MAP_2 = 0x15, + OM2K_DEI_FILLING_MARKER = 0x1c, + OM2K_DEI_FN_OFFSET = 0x1d, + OM2K_DEI_FREQ_LIST = 0x1e, + OM2K_DEI_FREQ_SPEC_RX = 0x1f, + OM2K_DEI_FREQ_SPEC_TX = 0x20, + OM2K_DEI_HSN = 0x21, + OM2K_DEI_ICM_INDICATOR = 0x22, + OM2K_DEI_INT_FAULT_MAP_1A = 0x23, + OM2K_DEI_INT_FAULT_MAP_1B = 0x24, + OM2K_DEI_INT_FAULT_MAP_2A = 0x25, + OM2K_DEI_INT_FAULT_MAP_2A_EXT = 0x26, + OM2K_DEI_IS_CONN_LIST = 0x27, + OM2K_DEI_LIST_NR = 0x28, + OM2K_DEI_LOCAL_ACCESS = 0x2a, + OM2K_DEI_MAIO = 0x2b, + OM2K_DEI_MO_STATE = 0x2c, + OM2K_DEI_NY1 = 0x2d, + OM2K_DEI_OP_INFO = 0x2e, + OM2K_DEI_POWER = 0x2f, + OM2K_DEI_REASON_CODE = 0x32, + OM2K_DEI_RX_DIVERSITY = 0x33, + OM2K_DEI_REPL_UNIT_MAP = 0x34, + OM2K_DEI_RESULT_CODE = 0x35, + OM2K_DEI_T3105 = 0x38, + OM2K_DEI_TF_MODE = 0x3a, + OM2K_DEI_TS_NR = 0x3c, + OM2K_DEI_TSC = 0x3d, + OM2K_DEI_BTS_VERSION = 0x40, + OM2K_DEI_OML_IWD_VERSION = 0x41, + OM2K_DEI_RSL_IWD_VERSION = 0x42, + OM2K_DEI_OML_FUNC_MAP_1 = 0x43, + OM2K_DEI_OML_FUNC_MAP_2 = 0x44, + OM2K_DEI_RSL_FUNC_MAP_1 = 0x45, + OM2K_DEI_RSL_FUNC_MAP_2 = 0x46, + OM2K_DEI_EXT_RANGE = 0x47, + OM2K_DEI_REQ_IND = 0x48, + OM2K_DEI_REPL_UNIT_MAP_EXT = 0x50, + OM2K_DEI_ICM_BOUND_PARAMS = 0x74, + OM2K_DEI_LSC = 0x79, + OM2K_DEI_LSC_FILT_TIME = 0x7a, + OM2K_DEI_CALL_SUPV_TIME = 0x7b, + OM2K_DEI_ICM_CHAN_RATE = 0x7e, + OM2K_DEI_HW_INFO_SIG = 0x84, + OM2K_DEI_TF_SYNC_SRC = 0x86, + OM2K_DEI_TTA = 0x87, + OM2K_DEI_CAPA_SIG = 0x8a, + OM2K_DEI_NEGOT_REC1 = 0x90, + OM2K_DEI_NEGOT_REC2 = 0x91, + OM2K_DEI_ENCR_ALG = 0x92, + OM2K_DEI_INTERF_REJ_COMB = 0x94, + OM2K_DEI_FS_OFFSET = 0x98, + OM2K_DEI_EXT_COND_MAP_2_EXT = 0x9c, + OM2K_DEI_TSS_MO_STATE = 0x9d, +}; + +const struct tlv_definition om2k_att_tlvdef = { + .def = { + [OM2K_DEI_ACCORDANCE_IND] = { TLV_TYPE_TV }, + [OM2K_DEI_BCC] = { TLV_TYPE_TV }, + [OM2K_DEI_BS_AG_BKS_RES] = { TLV_TYPE_TV }, + [OM2K_DEI_BSIC] = { TLV_TYPE_TV }, + [OM2K_DEI_BA_PA_MFRMS] = { TLV_TYPE_TV }, + [OM2K_DEI_CBCH_INDICATOR] = { TLV_TYPE_TV }, + [OM2K_DEI_INT_FAULT_MAP_1A] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_INT_FAULT_MAP_1B] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_INT_FAULT_MAP_2A] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_INT_FAULT_MAP_2A_EXT]={ TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_CCCH_OPTIONS] = { TLV_TYPE_TV }, + [OM2K_DEI_CAL_TIME] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_COMBINATION] = { TLV_TYPE_TV }, + [OM2K_DEI_CON_CONN_LIST] = { TLV_TYPE_TLV }, + [OM2K_DEI_DRX_DEV_MAX] = { TLV_TYPE_TV }, + [OM2K_DEI_END_LIST_NR] = { TLV_TYPE_TV }, + [OM2K_DEI_EXT_COND_MAP_1] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_EXT_COND_MAP_2] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_FILLING_MARKER] = { TLV_TYPE_TV }, + [OM2K_DEI_FN_OFFSET] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_FREQ_LIST] = { TLV_TYPE_TLV }, + [OM2K_DEI_FREQ_SPEC_RX] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_FREQ_SPEC_TX] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_HSN] = { TLV_TYPE_TV }, + [OM2K_DEI_ICM_INDICATOR] = { TLV_TYPE_TV }, + [OM2K_DEI_IS_CONN_LIST] = { TLV_TYPE_TLV }, + [OM2K_DEI_LIST_NR] = { TLV_TYPE_TV }, + [OM2K_DEI_LOCAL_ACCESS] = { TLV_TYPE_TV }, + [OM2K_DEI_MAIO] = { TLV_TYPE_TV }, + [OM2K_DEI_MO_STATE] = { TLV_TYPE_TV }, + [OM2K_DEI_NY1] = { TLV_TYPE_TV }, + [OM2K_DEI_OP_INFO] = { TLV_TYPE_TV }, + [OM2K_DEI_POWER] = { TLV_TYPE_TV }, + [OM2K_DEI_REASON_CODE] = { TLV_TYPE_TV }, + [OM2K_DEI_RX_DIVERSITY] = { TLV_TYPE_TV }, + [OM2K_DEI_RESULT_CODE] = { TLV_TYPE_TV }, + [OM2K_DEI_T3105] = { TLV_TYPE_TV }, + [OM2K_DEI_TF_MODE] = { TLV_TYPE_TV }, + [OM2K_DEI_TS_NR] = { TLV_TYPE_TV }, + [OM2K_DEI_TSC] = { TLV_TYPE_TV }, + [OM2K_DEI_BTS_VERSION] = { TLV_TYPE_FIXED, 12 }, + [OM2K_DEI_OML_IWD_VERSION] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_RSL_IWD_VERSION] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_OML_FUNC_MAP_1] = { TLV_TYPE_TLV }, + [OM2K_DEI_OML_FUNC_MAP_2] = { TLV_TYPE_TLV }, + [OM2K_DEI_RSL_FUNC_MAP_1] = { TLV_TYPE_TLV }, + [OM2K_DEI_RSL_FUNC_MAP_2] = { TLV_TYPE_TLV }, + [OM2K_DEI_EXT_RANGE] = { TLV_TYPE_TV }, + [OM2K_DEI_REQ_IND] = { TLV_TYPE_TV }, + [OM2K_DEI_REPL_UNIT_MAP] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_REPL_UNIT_MAP_EXT] = {TLV_TYPE_FIXED, 6}, + [OM2K_DEI_ICM_BOUND_PARAMS] = { TLV_TYPE_FIXED, 5 }, + [OM2K_DEI_LSC] = { TLV_TYPE_TV }, + [OM2K_DEI_LSC_FILT_TIME] = { TLV_TYPE_TV }, + [OM2K_DEI_CALL_SUPV_TIME] = { TLV_TYPE_TV }, + [OM2K_DEI_ICM_CHAN_RATE] = { TLV_TYPE_TV }, + [OM2K_DEI_HW_INFO_SIG] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_TF_SYNC_SRC] = { TLV_TYPE_TV }, + [OM2K_DEI_TTA] = { TLV_TYPE_TV }, + [OM2K_DEI_CAPA_SIG] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_NEGOT_REC1] = { TLV_TYPE_TLV }, + [OM2K_DEI_NEGOT_REC2] = { TLV_TYPE_TLV }, + [OM2K_DEI_ENCR_ALG] = { TLV_TYPE_TV }, + [OM2K_DEI_INTERF_REJ_COMB] = { TLV_TYPE_TV }, + [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 }, + }, +}; + +static const struct value_string om2k_msgcode_vals[] = { + { 0x0000, "Abort SP Command" }, + { 0x0002, "Abort SP Complete" }, + { 0x0004, "Alarm Report ACK" }, + { 0x0005, "Alarm Report NACK" }, + { 0x0006, "Alarm Report" }, + { 0x0008, "Alarm Status Request" }, + { 0x000a, "Alarm Status Request Accept" }, + { 0x000b, "Alarm Status Request Reject" }, + { 0x000c, "Alarm Status Result ACK" }, + { 0x000d, "Alarm Status Result NACK" }, + { 0x000e, "Alarm Status Result" }, + { 0x0010, "Calendar Time Response" }, + { 0x0011, "Calendar Time Reject" }, + { 0x0012, "Calendar Time Request" }, + { 0x0014, "CON Configuration Request" }, + { 0x0016, "CON Configuration Request Accept" }, + { 0x0017, "CON Configuration Request Reject" }, + { 0x0018, "CON Configuration Result ACK" }, + { 0x0019, "CON Configuration Result NACK" }, + { 0x001a, "CON Configuration Result" }, + { 0x001c, "Connect Command" }, + { 0x001e, "Connect Complete" }, + { 0x001f, "Connect Reject" }, + { 0x0028, "Disable Request" }, + { 0x002a, "Disable Request Accept" }, + { 0x002b, "Disable Request Reject" }, + { 0x002c, "Disable Result ACK" }, + { 0x002d, "Disable Result NACK" }, + { 0x002e, "Disable Result" }, + { 0x0030, "Disconnect Command" }, + { 0x0032, "Disconnect Complete" }, + { 0x0033, "Disconnect Reject" }, + { 0x0034, "Enable Request" }, + { 0x0036, "Enable Request Accept" }, + { 0x0037, "Enable Request Reject" }, + { 0x0038, "Enable Result ACK" }, + { 0x0039, "Enable Result NACK" }, + { 0x003a, "Enable Result" }, + { 0x003c, "Escape Downlink Normal" }, + { 0x003d, "Escape Downlink NACK" }, + { 0x003e, "Escape Uplink Normal" }, + { 0x003f, "Escape Uplink NACK" }, + { 0x0040, "Fault Report ACK" }, + { 0x0041, "Fault Report NACK" }, + { 0x0042, "Fault Report" }, + { 0x0044, "File Package End Command" }, + { 0x0046, "File Package End Result" }, + { 0x0047, "File Package End Reject" }, + { 0x0048, "File Relation Request" }, + { 0x004a, "File Relation Response" }, + { 0x004b, "File Relation Request Reject" }, + { 0x004c, "File Segment Transfer" }, + { 0x004e, "File Segment Transfer Complete" }, + { 0x004f, "File Segment Transfer Reject" }, + { 0x0050, "HW Information Request" }, + { 0x0052, "HW Information Request Accept" }, + { 0x0053, "HW Information Request Reject" }, + { 0x0054, "HW Information Result ACK" }, + { 0x0055, "HW Information Result NACK" }, + { 0x0056, "HW Information Result" }, + { 0x0060, "IS Configuration Request" }, + { 0x0062, "IS Configuration Request Accept" }, + { 0x0063, "IS Configuration Request Reject" }, + { 0x0064, "IS Configuration Result ACK" }, + { 0x0065, "IS Configuration Result NACK" }, + { 0x0066, "IS Configuration Result" }, + { 0x0068, "Load Data End" }, + { 0x006a, "Load Data End Result" }, + { 0x006b, "Load Data End Reject" }, + { 0x006c, "Load Data Init" }, + { 0x006e, "Load Data Init Accept" }, + { 0x006f, "Load Data Init Reject" }, + { 0x0070, "Loop Control Command" }, + { 0x0072, "Loop Control Complete" }, + { 0x0073, "Loop Control Reject" }, + { 0x0074, "Operational Information" }, + { 0x0076, "Operational Information Accept" }, + { 0x0077, "Operational Information Reject" }, + { 0x0078, "Reset Command" }, + { 0x007a, "Reset Complete" }, + { 0x007b, "Reset Reject" }, + { 0x007c, "RX Configuration Request" }, + { 0x007e, "RX Configuration Request Accept" }, + { 0x007f, "RX Configuration Request Reject" }, + { 0x0080, "RX Configuration Result ACK" }, + { 0x0081, "RX Configuration Result NACK" }, + { 0x0082, "RX Configuration Result" }, + { 0x0084, "Start Request" }, + { 0x0086, "Start Request Accept" }, + { 0x0087, "Start Request Reject" }, + { 0x0088, "Start Result ACK" }, + { 0x0089, "Start Result NACK" }, + { 0x008a, "Start Result" }, + { 0x008c, "Status Request" }, + { 0x008e, "Status Response" }, + { 0x008f, "Status Reject" }, + { 0x0094, "Test Request" }, + { 0x0096, "Test Request Accept" }, + { 0x0097, "Test Request Reject" }, + { 0x0098, "Test Result ACK" }, + { 0x0099, "Test Result NACK" }, + { 0x009a, "Test Result" }, + { 0x00a0, "TF Configuration Request" }, + { 0x00a2, "TF Configuration Request Accept" }, + { 0x00a3, "TF Configuration Request Reject" }, + { 0x00a4, "TF Configuration Result ACK" }, + { 0x00a5, "TF Configuration Result NACK" }, + { 0x00a6, "TF Configuration Result" }, + { 0x00a8, "TS Configuration Request" }, + { 0x00aa, "TS Configuration Request Accept" }, + { 0x00ab, "TS Configuration Request Reject" }, + { 0x00ac, "TS Configuration Result ACK" }, + { 0x00ad, "TS Configuration Result NACK" }, + { 0x00ae, "TS Configuration Result" }, + { 0x00b0, "TX Configuration Request" }, + { 0x00b2, "TX Configuration Request Accept" }, + { 0x00b3, "TX Configuration Request Reject" }, + { 0x00b4, "TX Configuration Result ACK" }, + { 0x00b5, "TX Configuration Result NACK" }, + { 0x00b6, "TX Configuration Result" }, + { 0x00bc, "DIP Alarm Report ACK" }, + { 0x00bd, "DIP Alarm Report NACK" }, + { 0x00be, "DIP Alarm Report" }, + { 0x00c0, "DIP Alarm Status Request" }, + { 0x00c2, "DIP Alarm Status Response" }, + { 0x00c3, "DIP Alarm Status Reject" }, + { 0x00c4, "DIP Quality Report I ACK" }, + { 0x00c5, "DIP Quality Report I NACK" }, + { 0x00c6, "DIP Quality Report I" }, + { 0x00c8, "DIP Quality Report II ACK" }, + { 0x00c9, "DIP Quality Report II NACK" }, + { 0x00ca, "DIP Quality Report II" }, + { 0x00dc, "DP Configuration Request" }, + { 0x00de, "DP Configuration Request Accept" }, + { 0x00df, "DP Configuration Request Reject" }, + { 0x00e0, "DP Configuration Result ACK" }, + { 0x00e1, "DP Configuration Result NACK" }, + { 0x00e2, "DP Configuration Result" }, + { 0x00e4, "Capabilities HW Info Report ACK" }, + { 0x00e5, "Capabilities HW Info Report NACK" }, + { 0x00e6, "Capabilities HW Info Report" }, + { 0x00e8, "Capabilities Request" }, + { 0x00ea, "Capabilities Request Accept" }, + { 0x00eb, "Capabilities Request Reject" }, + { 0x00ec, "Capabilities Result ACK" }, + { 0x00ed, "Capabilities Result NACK" }, + { 0x00ee, "Capabilities Result" }, + { 0x00f0, "FM Configuration Request" }, + { 0x00f2, "FM Configuration Request Accept" }, + { 0x00f3, "FM Configuration Request Reject" }, + { 0x00f4, "FM Configuration Result ACK" }, + { 0x00f5, "FM Configuration Result NACK" }, + { 0x00f6, "FM Configuration Result" }, + { 0x00f8, "FM Report Request" }, + { 0x00fa, "FM Report Response" }, + { 0x00fb, "FM Report Reject" }, + { 0x00fc, "FM Start Command" }, + { 0x00fe, "FM Start Complete" }, + { 0x00ff, "FM Start Reject" }, + { 0x0100, "FM Stop Command" }, + { 0x0102, "FM Stop Complete" }, + { 0x0103, "FM Stop Reject" }, + { 0x0104, "Negotiation Request ACK" }, + { 0x0105, "Negotiation Request NACK" }, + { 0x0106, "Negotiation Request" }, + { 0x0108, "BTS Initiated Request ACK" }, + { 0x0109, "BTS Initiated Request NACK" }, + { 0x010a, "BTS Initiated Request" }, + { 0x010c, "Radio Channels Release Command" }, + { 0x010e, "Radio Channels Release Complete" }, + { 0x010f, "Radio Channels Release Reject" }, + { 0x0118, "Feature Control Command" }, + { 0x011a, "Feature Control Complete" }, + { 0x011b, "Feature Control Reject" }, + + { 0, NULL } +}; + +/* TS 12.21 Section 9.4: Attributes */ +static const struct value_string om2k_attr_vals[] = { + { 0x00, "Accordance indication" }, + { 0x01, "Alarm Id" }, + { 0x02, "Alarm Data" }, + { 0x03, "Alarm Severity" }, + { 0x04, "Alarm Status" }, + { 0x05, "Alarm Status Type" }, + { 0x06, "BCC" }, + { 0x07, "BS_AG_BKS_RES" }, + { 0x09, "BSIC" }, + { 0x0a, "BA_PA_MFRMS" }, + { 0x0b, "CBCH Indicator" }, + { 0x0c, "CCCH Options" }, + { 0x0d, "Calendar Time" }, + { 0x0f, "Channel Combination" }, + { 0x10, "CON Connection List" }, + { 0x11, "Data End Indication" }, + { 0x12, "DRX_DEV_MAX" }, + { 0x13, "End List Number" }, + { 0x14, "External Condition Map Class 1" }, + { 0x15, "External Condition Map Class 2" }, + { 0x16, "File Relation Indication" }, + { 0x17, "File Revision" }, + { 0x18, "File Segment Data" }, + { 0x19, "File Segment Length" }, + { 0x1a, "File Segment Sequence Number" }, + { 0x1b, "File Size" }, + { 0x1c, "Filling Marker" }, + { 0x1d, "FN Offset" }, + { 0x1e, "Frequency List" }, + { 0x1f, "Frequency Specifier RX" }, + { 0x20, "Frequency Specifier TX" }, + { 0x21, "HSN" }, + { 0x22, "ICM Indicator" }, + { 0x23, "Internal Fault Map Class 1A" }, + { 0x24, "Internal Fault Map Class 1B" }, + { 0x25, "Internal Fault Map Class 2A" }, + { 0x26, "Internal Fault Map Class 2A Extension" }, + { 0x27, "IS Connection List" }, + { 0x28, "List Number" }, + { 0x29, "File Package State Indication" }, + { 0x2a, "Local Access State" }, + { 0x2b, "MAIO" }, + { 0x2c, "MO State" }, + { 0x2d, "Ny1" }, + { 0x2e, "Operational Information" }, + { 0x2f, "Power" }, + { 0x30, "RU Position Data" }, + { 0x31, "Protocol Error" }, + { 0x32, "Reason Code" }, + { 0x33, "Receiver Diversity" }, + { 0x34, "Replacement Unit Map" }, + { 0x35, "Result Code" }, + { 0x36, "RU Revision Data" }, + { 0x38, "T3105" }, + { 0x39, "Test Loop Setting" }, + { 0x3a, "TF Mode" }, + { 0x3b, "TF Compensation Value" }, + { 0x3c, "Time Slot Number" }, + { 0x3d, "TSC" }, + { 0x3e, "RU Logical Id" }, + { 0x3f, "RU Serial Number Data" }, + { 0x40, "BTS Version" }, + { 0x41, "OML IWD Version" }, + { 0x42, "RWL IWD Version" }, + { 0x43, "OML Function Map 1" }, + { 0x44, "OML Function Map 2" }, + { 0x45, "RSL Function Map 1" }, + { 0x46, "RSL Function Map 2" }, + { 0x47, "Extended Range Indicator" }, + { 0x48, "Request Indicators" }, + { 0x49, "DIP Alarm Condition Map" }, + { 0x4a, "ES Incoming" }, + { 0x4b, "ES Outgoing" }, + { 0x4e, "SES Incoming" }, + { 0x4f, "SES Outgoing" }, + { 0x50, "Replacement Unit Map Extension" }, + { 0x52, "UAS Incoming" }, + { 0x53, "UAS Outgoing" }, + { 0x58, "DF Incoming" }, + { 0x5a, "DF Outgoing" }, + { 0x5c, "SF" }, + { 0x60, "S Bits Setting" }, + { 0x61, "CRC-4 Use Option" }, + { 0x62, "T Parameter" }, + { 0x63, "N Parameter" }, + { 0x64, "N1 Parameter" }, + { 0x65, "N3 Parameter" }, + { 0x66, "N4 Parameter" }, + { 0x67, "P Parameter" }, + { 0x68, "Q Parameter" }, + { 0x69, "BI_Q1" }, + { 0x6a, "BI_Q2" }, + { 0x74, "ICM Boundary Parameters" }, + { 0x77, "AFT" }, + { 0x78, "AFT RAI" }, + { 0x79, "Link Supervision Control" }, + { 0x7a, "Link Supervision Filtering Time" }, + { 0x7b, "Call Supervision Time" }, + { 0x7c, "Interval Length UAS Incoming" }, + { 0x7d, "Interval Length UAS Outgoing" }, + { 0x7e, "ICM Channel Rate" }, + { 0x7f, "Attribute Identifier" }, + { 0x80, "FM Frequency List" }, + { 0x81, "FM Frequency Report" }, + { 0x82, "FM Percentile" }, + { 0x83, "FM Clear Indication" }, + { 0x84, "HW Info Signature" }, + { 0x85, "MO Record" }, + { 0x86, "TF Synchronisation Source" }, + { 0x87, "TTA" }, + { 0x88, "End Segment Number" }, + { 0x89, "Segment Number" }, + { 0x8a, "Capabilities Signature" }, + { 0x8c, "File Relation List" }, + { 0x90, "Negotiation Record I" }, + { 0x91, "Negotiation Record II" }, + { 0x92, "Encryption Algorithm" }, + { 0x94, "Interference Rejection Combining" }, + { 0x95, "Dedication Information" }, + { 0x97, "Feature Code" }, + { 0x98, "FS Offset" }, + { 0x99, "ESB Timeslot" }, + { 0x9a, "Master TG Instance" }, + { 0x9b, "Master TX Chain Delay" }, + { 0x9c, "External Condition Class 2 Extension" }, + { 0x9d, "TSs MO State" }, + { 0, NULL } +}; + +const struct value_string om2k_mo_class_short_vals[] = { + { 0x01, "TRXC" }, + { 0x03, "TS" }, + { 0x04, "TF" }, + { 0x05, "IS" }, + { 0x06, "CON" }, + { 0x07, "DP" }, + { 0x0a, "CF" }, + { 0x0b, "TX" }, + { 0x0c, "RX" }, + { 0, NULL } +}; + +const struct value_string om2k_result_strings[] = { + { 0x02, "Wrong state or out of sequence" }, + { 0x03, "File error" }, + { 0x04, "Fault, unspecified" }, + { 0x05, "Tuning fault" }, + { 0x06, "Protocol error" }, + { 0x07, "MO not connected" }, + { 0x08, "Parameter error" }, + { 0x09, "Optional function not supported" }, + { 0x0a, "Local access state LOCALLY DISCONNECTED" }, + { 0, NULL } +}; + +const struct value_string om2k_accordance_strings[] = { + { 0x00, "Data according to request" }, + { 0x01, "Data not according to request" }, + { 0x02, "Inconsistent MO data" }, + { 0x03, "Capability constraint violation" }, + { 0, NULL } +}; + +const struct value_string om2k_mostate_vals[] = { + { 0x00, "RESET" }, + { 0x01, "STARTED" }, + { 0x02, "ENABLED" }, + { 0x03, "DISABLED" }, + { 0, NULL } +}; + +/* entire decoded OM2K message (header + parsed TLV) */ +struct om2k_decoded_msg { + struct abis_om2k_hdr o2h; + uint16_t msg_type; + struct tlv_parsed tp; +}; + +/* resolve the OM2000 Managed Object by BTS + MO Address */ +static struct om2k_mo * +get_om2k_mo(struct gsm_bts *bts, const struct abis_om2k_mo *abis_mo) +{ + struct om2k_mo *mo = NULL; + struct gsm_bts_trx *trx; + + switch (abis_mo->class) { + case OM2K_MO_CLS_CF: + mo = &bts->rbs2000.cf.om2k_mo; + break; + case OM2K_MO_CLS_CON: + mo = &bts->rbs2000.con.om2k_mo; + break; + case OM2K_MO_CLS_IS: + mo = &bts->rbs2000.is.om2k_mo; + break; + case OM2K_MO_CLS_TF: + mo = &bts->rbs2000.tf.om2k_mo; + break; + + case OM2K_MO_CLS_TRXC: + trx = gsm_bts_trx_num(bts, abis_mo->inst); + if (!trx) + return NULL; + mo = &trx->rbs2000.trxc.om2k_mo; + break; + case OM2K_MO_CLS_TX: + trx = gsm_bts_trx_num(bts, abis_mo->inst); + if (!trx) + return NULL; + mo = &trx->rbs2000.tx.om2k_mo; + break; + case OM2K_MO_CLS_RX: + trx = gsm_bts_trx_num(bts, abis_mo->inst); + if (!trx) + return NULL; + mo = &trx->rbs2000.rx.om2k_mo; + break; + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_num(bts, abis_mo->assoc_so); + if (!trx) + return NULL; + if (abis_mo->inst >= ARRAY_SIZE(trx->ts)) + return NULL; + mo = &trx->ts[abis_mo->inst].rbs2000.om2k_mo; + break; + default: + return NULL; + }; + + return mo; +} + +static struct msgb *om2k_msgb_alloc(void) +{ + return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, + "OM2000"); +} + +static int abis_om2k_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len) +{ + return tlv_parse(tp, &om2k_att_tlvdef, buf, len, 0, 0); +} + +static int abis_om2k_msg_tlv_parse(struct tlv_parsed *tp, struct abis_om2k_hdr *oh) +{ + return abis_om2k_tlv_parse(tp, oh->data, oh->om.length - 6); +} + +/* decode/parse the message */ +static int om2k_decode_msg(struct om2k_decoded_msg *odm, struct msgb *msg) +{ + struct abis_om2k_hdr *o2h = msgb_l2(msg); + odm->msg_type = ntohs(o2h->msg_type); + odm->o2h = *o2h; + return abis_om2k_msg_tlv_parse(&odm->tp, o2h); +} + +static char *om2k_mo_name(const struct abis_om2k_mo *mo) +{ + static char mo_buf[64]; + + memset(mo_buf, 0, sizeof(mo_buf)); + snprintf(mo_buf, sizeof(mo_buf), "%s/%02x/%02x/%02x", + get_value_string(om2k_mo_class_short_vals, mo->class), + mo->bts, mo->assoc_so, mo->inst); + return mo_buf; +} + +/* 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) +{ + struct gsm_bts_trx *trx; + struct gsm_nm_state *nm_state = NULL; + + switch (mo->class) { + case OM2K_MO_CLS_TRXC: + trx = gsm_bts_trx_num(bts, mo->inst); + if (!trx) + return NULL; + nm_state = &trx->mo.nm_state; + break; + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_num(bts, mo->assoc_so); + if (!trx) + return NULL; + if (mo->inst >= ARRAY_SIZE(trx->ts)) + return NULL; + nm_state = &trx->ts[mo->inst].mo.nm_state; + break; + case OM2K_MO_CLS_TF: + nm_state = &bts->rbs2000.tf.mo.nm_state; + break; + case OM2K_MO_CLS_IS: + nm_state = &bts->rbs2000.is.mo.nm_state; + break; + case OM2K_MO_CLS_CON: + nm_state = &bts->rbs2000.con.mo.nm_state; + break; + case OM2K_MO_CLS_DP: + nm_state = &bts->rbs2000.con.mo.nm_state; + break; + case OM2K_MO_CLS_CF: + nm_state = &bts->mo.nm_state; + break; + case OM2K_MO_CLS_TX: + trx = gsm_bts_trx_num(bts, mo->inst); + if (!trx) + return NULL; + /* FIXME */ + break; + case OM2K_MO_CLS_RX: + trx = gsm_bts_trx_num(bts, mo->inst); + if (!trx) + return NULL; + /* FIXME */ + break; + } + + return nm_state; +} + +static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo) +{ + struct gsm_bts_trx *trx; + + switch (mo->class) { + case OM2K_MO_CLS_TX: + case OM2K_MO_CLS_RX: + case OM2K_MO_CLS_TRXC: + return gsm_bts_trx_num(bts, mo->inst); + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_num(bts, mo->assoc_so); + if (!trx) + return NULL; + if (mo->inst >= ARRAY_SIZE(trx->ts)) + return NULL; + return &trx->ts[mo->inst]; + case OM2K_MO_CLS_TF: + case OM2K_MO_CLS_IS: + case OM2K_MO_CLS_CON: + case OM2K_MO_CLS_DP: + case OM2K_MO_CLS_CF: + return bts; + } + + return NULL; +} + +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; + /* NOTICE: 12.21 Availability state values != OM2000 */ + new_state.availability = mo_state; + + memset(&nsd, 0, sizeof(nsd)); + + nsd.bts = bts; + nsd.obj = mo2obj(bts, mo); + nsd.old_state = nm_state; + nsd.new_state = &new_state; + nsd.om2k_mo = mo; + + osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd); + + nm_state->availability = new_state.availability; +} + +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 gsm_nm_state new_state; + + if (!nm_state) + return; + + new_state = *nm_state; + switch (op_state) { + case 1: + new_state.operational = NM_OPSTATE_ENABLED; + break; + case 0: + new_state.operational = NM_OPSTATE_DISABLED; + break; + default: + new_state.operational = NM_OPSTATE_NULL; + break; + } + + nm_state->operational = new_state.operational; +} + +static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om2k_hdr *o2h; + struct gsm_bts_trx *trx; + + msg->l2h = msg->data; + o2h = (struct abis_om2k_hdr *) msg->l2h; + + /* Compute the length in the OML header */ + o2h->om.length = 6 + msgb_l2len(msg)-sizeof(*o2h); + + switch (o2h->mo.class) { + case OM2K_MO_CLS_TRXC: + case OM2K_MO_CLS_TX: + case OM2K_MO_CLS_RX: + /* Route through per-TRX OML Link to the appropriate TRX */ + trx = gsm_bts_trx_by_nr(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)); + return -ENODEV; + } + msg->dst = trx->oml_link; + break; + case OM2K_MO_CLS_TS: + /* Route through per-TRX OML Link to the appropriate TRX */ + trx = gsm_bts_trx_by_nr(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)); + return -ENODEV; + } + msg->dst = trx->oml_link; + break; + default: + /* Route through the IXU/DXU OML Link */ + msg->dst = bts->oml_link; + break; + } + + 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) +{ + o2h->om.mdisc = ABIS_OM_MDISC_FOM; + o2h->om.placement = ABIS_OM_PLACEMENT_ONLY; + o2h->om.sequence = 0; + /* We fill o2h->om.length later during om2k_sendmsg() */ + o2h->msg_type = htons(msg_type); + memcpy(&o2h->mo, mo, sizeof(o2h->mo)); +} + +static int abis_om2k_cal_time_resp(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + time_t tm_t; + 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); + + tm_t = time(NULL); + tm = localtime(&tm_t); + + msgb_put_u8(msg, OM2K_DEI_CAL_TIME); + msgb_put_u8(msg, tm->tm_year % 100); + msgb_put_u8(msg, tm->tm_mon + 1); + msgb_put_u8(msg, tm->tm_mday); + msgb_put_u8(msg, tm->tm_hour); + msgb_put_u8(msg, tm->tm_min); + msgb_put_u8(msg, tm->tm_sec); + + return abis_om2k_sendmsg(bts, msg); +} + +static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t msg_type) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + 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)); + + return abis_om2k_sendmsg(bts, msg); +} + +int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_RESET_CMD); +} + +int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_START_REQ); +} + +int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_STATUS_REQ); +} + +int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CONNECT_CMD); +} + +int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISCONNECT_CMD); +} + +int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_TEST_REQ); +} + +int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_ENABLE_REQ); +} + +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) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, mo, OM2K_MSGT_OP_INFO); + + msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational); + + DEBUGP(DNM, "Tx MO=%s %s\n", 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 */ + update_op_state(bts, mo, operational); + + return abis_om2k_sendmsg(bts, msg); +} + +int abis_om2k_tx_cap_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CAPA_REQ); +} + +static void om2k_fill_is_conn_grp(struct om2k_is_conn_grp *grp, uint16_t icp1, + uint16_t icp2, uint8_t cont_idx) +{ + grp->icp1 = htons(icp1); + grp->icp2 = htons(icp2); + grp->cont_idx = cont_idx; +} + +int abis_om2k_tx_is_conf_req(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct is_conn_group *grp; + unsigned int num_grps = 0, i = 0; + struct om2k_is_conn_grp *cg; + + /* count number of groups in linked list */ + llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list) + num_grps++; + + if (!num_grps) + return -EINVAL; + + /* allocate buffer for oml group array */ + cg = talloc_zero_array(bts, struct om2k_is_conn_grp, num_grps); + + /* fill array with data from linked list */ + llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list) + 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); + + 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); + + talloc_free(cg); + + DEBUGP(DNM, "Tx MO=%s %s\n", + 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); +} + +int abis_om2k_tx_con_conf_req(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct con_group *grp; + unsigned int num_grps = 0; + + /* count number of groups in linked list */ + llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list) + num_grps++; + + if (!num_grps) + return -EINVAL; + + /* first build the value part of the OM2K_DEI_CON_CONN_LIST DEI */ + msgb_put_u8(msg, num_grps); + llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list) { + struct con_path *cp; + unsigned int num_paths = 0; + llist_for_each_entry(cp, &grp->paths, list) + num_paths++; + msgb_put_u8(msg, num_paths); + llist_for_each_entry(cp, &grp->paths, list) { + struct om2k_con_path *om2k_cp; + om2k_cp = (struct om2k_con_path *) msgb_put(msg, sizeof(*om2k_cp)); + om2k_cp->ccp = htons(cp->ccp); + om2k_cp->ci = cp->ci; + om2k_cp->tag = cp->tag; + om2k_cp->tei = cp->tei; + } + } + msgb_push_u8(msg, msgb_length(msg)); + msgb_push_u8(msg, OM2K_DEI_CON_CONN_LIST); + + /* pre-pend the list number DEIs */ + msgb_tv_push(msg, OM2K_DEI_END_LIST_NR, 1); + msgb_tv_push(msg, OM2K_DEI_LIST_NR, 1); + + /* 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); + + DEBUGP(DNM, "Tx MO=%s %s\n", + 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); +} + +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; + mo->bts = 0; + mo->inst = trx->nr; + mo->assoc_so = 255; +} + +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; + mo->inst = ts->nr; + mo->assoc_so = ts->trx->nr; +} + +/* Configure a Receiver MO */ +int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + + om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_RX); + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_RX_CONF_REQ); + + msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn); + msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */ + + return abis_om2k_sendmsg(trx->bts, msg); +} + +/* Configure a Transmitter MO */ +int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + + om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_TX); + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TX_CONF_REQ); + + msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn); + msgb_tv_put(msg, OM2K_DEI_POWER, trx->nominal_power-trx->max_power_red); + msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, 0); /* Filling enabled */ + msgb_tv_put(msg, OM2K_DEI_BCC, trx->bts->bsic & 0x7); + /* Dedication Information is optional */ + + return abis_om2k_sendmsg(trx->bts, msg); +} + +enum abis_om2k_tf_mode { + OM2K_TF_MODE_MASTER = 0x00, + OM2K_TF_MODE_STANDALONE = 0x01, + OM2K_TF_MODE_SLAVE = 0x02, + OM2K_TF_MODE_UNDEFINED = 0xff, +}; + +static const uint8_t fs_offset_undef[5] = { 0xff, 0xff, 0xff, 0xff, 0xff }; + +int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + 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); + + 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); + + DEBUGP(DNM, "Tx MO=%s %s\n", + 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); +} + +static uint8_t pchan2comb(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_CCCH: + return 4; + case GSM_PCHAN_CCCH_SDCCH4: + return 5; + case GSM_PCHAN_SDCCH8_SACCH8C: + return 3; + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_TCH_F_PDCH: + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return 8; + default: + return 0; + } +} + +static uint8_t ts2comb(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case 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)); + /* 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: */ + return 0; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return pchan2comb(GSM_PCHAN_TCH_F); + default: + return pchan2comb(ts->pchan); + } +} + +static int put_freq_list(uint8_t *buf, uint16_t arfcn) +{ + buf[0] = 0x00; /* TX/RX address */ + buf[1] = (arfcn >> 8); + buf[2] = (arfcn & 0xff); + + return 3; +} + +/* Compute a frequency list in OM2000 fomrmat */ +static int om2k_gen_freq_list(uint8_t *list, struct gsm_bts_trx_ts *ts) +{ + uint8_t *cur = list; + int len; + + if (ts->hopping.enabled) { + unsigned int i; + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) + cur += put_freq_list(cur, i); + } + } else + cur += put_freq_list(cur, ts->trx->arfcn); + + len = cur - list; + + return len; +} + +const uint8_t icm_bound_params[] = { 0x02, 0x06, 0x0c, 0x16, 0x06 }; + +int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + uint8_t freq_list[64*3]; /* BA max size: 64 ARFCN */ + int freq_list_len; + + om2k_ts_to_mo(&mo, ts); + + memset(freq_list, 0, sizeof(freq_list)); + freq_list_len = om2k_gen_freq_list(freq_list, ts); + if (freq_list_len < 0) + return freq_list_len; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TS_CONF_REQ); + + msgb_tv_put(msg, OM2K_DEI_COMBINATION, ts2comb(ts)); + msgb_tv_put(msg, OM2K_DEI_TS_NR, ts->nr); + msgb_tlv_put(msg, OM2K_DEI_FREQ_LIST, freq_list_len, freq_list); + 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_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0); + msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */ + /* Optional: Interference Rejection Combining */ + msgb_tv_put(msg, OM2K_DEI_INTERF_REJ_COMB, 0x00); + switch (ts->pchan) { + case GSM_PCHAN_CCCH: + msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06); + msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01); + msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05); + /* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */ + msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01); + break; + case GSM_PCHAN_CCCH_SDCCH4: + msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); + msgb_tv_put(msg, OM2K_DEI_NY1, 35); + msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06); + msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0); + msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); + msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01); + msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); + msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05); + /* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */ + msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01); + msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, + sizeof(icm_bound_params), icm_bound_params); + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); + msgb_tv_put(msg, OM2K_DEI_NY1, 35); + msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0); + msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); + /* Disable RF RESOURCE INDICATION on idle channels */ + msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); + msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, + sizeof(icm_bound_params), icm_bound_params); + break; + default: + msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); + msgb_tv_put(msg, OM2K_DEI_NY1, 35); + msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); + /* Disable RF RESOURCE INDICATION on idle channels */ + msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); + msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, + sizeof(icm_bound_params), icm_bound_params); + msgb_tv_put(msg, OM2K_DEI_TTA, 10); /* Timer for Time Alignment */ + if (ts->pchan == GSM_PCHAN_TCH_H) + msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 1); /* TCH/H */ + else + msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 0); /* TCH/F */ + msgb_tv_put(msg, OM2K_DEI_LSC, 1); /* enabled */ + msgb_tv_put(msg, OM2K_DEI_LSC_FILT_TIME, 10); /* units of 100ms */ + msgb_tv_put(msg, OM2K_DEI_CALL_SUPV_TIME, 8); + msgb_tv_put(msg, OM2K_DEI_ENCR_ALG, 0x00); + /* Not sure what those below mean */ + msgb_tv_put(msg, 0x9e, 0x00); + msgb_tv_put(msg, 0x9f, 0x37); + msgb_tv_put(msg, 0xa0, 0x01); + break; + } + + DEBUGP(DNM, "Tx MO=%s %s\n", + om2k_mo_name(&mo), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_TS_CONF_REQ)); + + return abis_om2k_sendmsg(ts->trx->bts, msg); +} + + +/*********************************************************************** + * OM2000 Managed Object (MO) FSM + ***********************************************************************/ + +#define S(x) (1 << (x)) + +enum om2k_event_name { + OM2K_MO_EVT_START, + OM2K_MO_EVT_RX_CONN_COMPL, + OM2K_MO_EVT_RX_RESET_COMPL, + OM2K_MO_EVT_RX_START_REQ_ACCEPT, + OM2K_MO_EVT_RX_START_RES, + OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, + OM2K_MO_EVT_RX_CFG_RES, + OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, + OM2K_MO_EVT_RX_ENA_RES, + OM2K_MO_EVT_RX_OPINFO_ACC, +}; + +static const struct value_string om2k_event_names[] = { + { OM2K_MO_EVT_START, "START" }, + { 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" }, + { OM2K_MO_EVT_RX_START_RES, "RX-START-RESULT" }, + { OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, "RX-CFG-REQ-ACCEPT" }, + { OM2K_MO_EVT_RX_CFG_RES, "RX-CFG-RESULT" }, + { OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, "RX-ENABLE-REQ-ACCEPT" }, + { OM2K_MO_EVT_RX_ENA_RES, "RX-ENABLE-RESULT" }, + { OM2K_MO_EVT_RX_OPINFO_ACC, "RX-OPINFO-ACCEPT" }, + { 0, NULL } +}; + +enum om2k_mo_fsm_state { + OM2K_ST_INIT, + OM2K_ST_WAIT_CONN_COMPL, + OM2K_ST_WAIT_RES_COMPL, + OM2K_ST_WAIT_START_ACCEPT, + OM2K_ST_WAIT_START_RES, + OM2K_ST_WAIT_CFG_ACCEPT, + OM2K_ST_WAIT_CFG_RES, + OM2K_ST_WAIT_ENABLE_ACCEPT, + OM2K_ST_WAIT_ENABLE_RES, + OM2K_ST_WAIT_OPINFO_ACCEPT, + OM2K_ST_DONE, + OM2K_ST_ERROR, +}; + +struct om2k_mo_fsm_priv { + struct gsm_bts_trx *trx; + struct om2k_mo *mo; + uint8_t ts_nr; +}; + +static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + + OSMO_ASSERT(event == OM2K_MO_EVT_START); + + 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); + 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); + 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); + abis_om2k_tx_connect_cmd(omfp->trx->bts, &omfp->mo->addr); + break; + } +} + +static void om2k_mo_st_wait_conn_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + + switch (omfp->mo->addr.class) { +#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); + 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); + abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr); + break; + } +} + +static void om2k_mo_st_wait_res_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + + 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); +} + +static void om2k_mo_st_wait_start_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_decoded_msg *omd = data; + + switch (omd->msg_type) { + case OM2K_MSGT_START_REQ_ACK: + 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); + break; + } +} + +static void om2k_mo_st_wait_start_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + struct gsm_bts_trx_ts *ts; + + switch (omfp->mo->addr.class) { + 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); + abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1); + return; + case OM2K_MO_CLS_DP: + /* Transition directoy to WAIT_ENABLE_ACCEPT */ + 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); + abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr); + return; +#endif + } + + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_ACCEPT, + OM2K_TIMEOUT, 0); + switch (omfp->mo->addr.class) { + case OM2K_MO_CLS_TF: + abis_om2k_tx_tf_conf_req(omfp->trx->bts); + break; + case OM2K_MO_CLS_IS: + abis_om2k_tx_is_conf_req(omfp->trx->bts); + break; + case OM2K_MO_CLS_CON: + abis_om2k_tx_con_conf_req(omfp->trx->bts); + break; + case OM2K_MO_CLS_TX: + abis_om2k_tx_tx_conf_req(omfp->trx); + break; + case OM2K_MO_CLS_RX: + abis_om2k_tx_rx_conf_req(omfp->trx); + break; + case OM2K_MO_CLS_TS: + ts = mo2obj(omfp->trx->bts, &omfp->mo->addr); + abis_om2k_tx_ts_conf_req(ts); + break; + } +} + +static void om2k_mo_st_wait_cfg_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + uint32_t timeout = OM2K_TIMEOUT; + + if (omfp->mo->addr.class == OM2K_MO_CLS_TF) + timeout = 600; + + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_RES, timeout, 0); +} + +static void om2k_mo_st_wait_cfg_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + struct om2k_decoded_msg *omd = data; + uint8_t accordance; + + if (!TLVP_PRESENT(&omd->tp, OM2K_DEI_ACCORDANCE_IND)) { + osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); + return; + } + accordance = *TLVP_VAL(&omd->tp, OM2K_DEI_ACCORDANCE_IND); + + if (accordance != 0) { + /* accordance not OK */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); + return; + } + + 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); +} + +static void om2k_mo_st_wait_enable_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + struct om2k_decoded_msg *omd = data; + + switch (omd->msg_type) { + case OM2K_MSGT_ENABLE_REQ_REJ: + 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) + 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); + } +} + +static void om2k_mo_st_wait_enable_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + //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); + abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1); +} + +static void om2k_mo_st_wait_opinfo_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + osmo_fsm_inst_state_chg(fi, OM2K_ST_DONE, 0, 0); +} + +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); +} + +static void om2k_mo_s_error_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_ERROR, NULL); +} + +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_ERROR) | + S(OM2K_ST_WAIT_CONN_COMPL) | + S(OM2K_ST_WAIT_START_ACCEPT) | + S(OM2K_ST_WAIT_RES_COMPL), + .action = om2k_mo_st_init, + }, + [OM2K_ST_WAIT_CONN_COMPL] = { + .name = "WAIT-CONN-COMPL", + .in_event_mask = S(OM2K_MO_EVT_RX_CONN_COMPL), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_START_ACCEPT) | + S(OM2K_ST_WAIT_RES_COMPL), + .action = om2k_mo_st_wait_conn_compl, + }, + [OM2K_ST_WAIT_RES_COMPL] = { + .name = "WAIT-RES-COMPL", + .in_event_mask = S(OM2K_MO_EVT_RX_RESET_COMPL), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_START_ACCEPT), + .action = om2k_mo_st_wait_res_compl, + }, + [OM2K_ST_WAIT_START_ACCEPT] = { + .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_ERROR) | + S(OM2K_ST_WAIT_START_RES), + .action =om2k_mo_st_wait_start_accept, + }, + [OM2K_ST_WAIT_START_RES] = { + .name = "WAIT-START-RES", + .in_event_mask = S(OM2K_MO_EVT_RX_START_RES), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_CFG_ACCEPT) | + S(OM2K_ST_WAIT_OPINFO_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_ERROR) | + S(OM2K_ST_WAIT_CFG_RES), + .action = om2k_mo_st_wait_cfg_accept, + }, + [OM2K_ST_WAIT_CFG_RES] = { + .name = "WAIT-CFG-RES", + .in_event_mask = S(OM2K_MO_EVT_RX_CFG_RES), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_ENABLE_ACCEPT), + .action = om2k_mo_st_wait_cfg_res, + }, + [OM2K_ST_WAIT_ENABLE_ACCEPT] = { + .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_ERROR) | + S(OM2K_ST_WAIT_ENABLE_RES), + .action = om2k_mo_st_wait_enable_accept, + }, + [OM2K_ST_WAIT_ENABLE_RES] = { + .name = "WAIT-ENABLE-RES", + .in_event_mask = S(OM2K_MO_EVT_RX_ENA_RES), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_OPINFO_ACCEPT), + .action = om2k_mo_st_wait_enable_res, + }, + [OM2K_ST_WAIT_OPINFO_ACCEPT] = { + .name = "WAIT-OPINFO-ACCEPT", + .in_event_mask = S(OM2K_MO_EVT_RX_OPINFO_ACC), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR), + .action = om2k_mo_st_wait_opinfo_accept, + }, + [OM2K_ST_DONE] = { + .name = "DONE", + .in_event_mask = 0, + .out_state_mask = 0, + .onenter = om2k_mo_s_done_onenter, + }, + [OM2K_ST_ERROR] = { + .name = "ERROR", + .in_event_mask = 0, + .out_state_mask = 0, + .onenter = om2k_mo_s_error_onenter, + }, + +}; + +static int om2k_mo_timer_cb(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); + return 0; +} + +static struct osmo_fsm om2k_mo_fsm = { + .name = "OM2000-MO", + .states = om2k_is_states, + .num_states = ARRAY_SIZE(om2k_is_states), + .log_subsys = DNM, + .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) +{ + struct osmo_fsm_inst *fi; + struct om2k_mo_fsm_priv *omfp; + char idbuf[64]; + + snprintf(idbuf, sizeof(idbuf), "%s-%s", parent->id, + om2k_mo_name(&mo->addr)); + + fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent, + term_event, idbuf); + if (!fi) + return NULL; + + mo->fsm = fi; + omfp = talloc_zero(fi, struct om2k_mo_fsm_priv); + omfp->mo = mo; + omfp->trx = trx; + fi->priv = omfp; + + osmo_fsm_inst_dispatch(fi, OM2K_MO_EVT_START, NULL); + + return fi; +} + +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); + break; + + case OM2K_MSGT_RESET_COMPL: + case OM2K_MSGT_RESET_REJ: + 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); + break; + + case OM2K_MSGT_START_RES: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_START_RES, odm); + break; + + case OM2K_MSGT_CON_CONF_REQ_ACK: + case OM2K_MSGT_IS_CONF_REQ_ACK: + case OM2K_MSGT_RX_CONF_REQ_ACK: + 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); + break; + + case OM2K_MSGT_CON_CONF_RES: + case OM2K_MSGT_IS_CONF_RES: + case OM2K_MSGT_RX_CONF_RES: + 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); + 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); + break; + case OM2K_MSGT_ENABLE_RES: + 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); + break; + default: + return -1; + } + + return 0; +} + +/*********************************************************************** + * OM2000 TRX Finite State Machine, initializes TRXC and all siblings + ***********************************************************************/ + +enum om2k_trx_event { + OM2K_TRX_EVT_START, + OM2K_TRX_EVT_TRXC_DONE, + OM2K_TRX_EVT_TX_DONE, + OM2K_TRX_EVT_RX_DONE, + OM2K_TRX_EVT_TS_DONE, + OM2K_TRX_EVT_STOP, +}; + +static struct value_string om2k_trx_events[] = { + { OM2K_TRX_EVT_START, "START" }, + { OM2K_TRX_EVT_TRXC_DONE, "TRXC-DONE" }, + { OM2K_TRX_EVT_TX_DONE, "TX-DONE" }, + { OM2K_TRX_EVT_RX_DONE, "RX-DONE" }, + { OM2K_TRX_EVT_TS_DONE, "TS-DONE" }, + { OM2K_TRX_EVT_STOP, "STOP" }, + { 0, NULL } +}; + +enum om2k_trx_state { + OM2K_TRX_S_INIT, + OM2K_TRX_S_WAIT_TRXC, + OM2K_TRX_S_WAIT_TX, + OM2K_TRX_S_WAIT_RX, + OM2K_TRX_S_WAIT_TS, + OM2K_TRX_S_DONE, + OM2K_TRX_S_ERROR +}; + +struct om2k_trx_fsm_priv { + struct gsm_bts_trx *trx; + uint8_t next_ts_nr; +}; + +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); +} + +static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + 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); +} + +static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + 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); +} + +static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + 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); + otfp->next_ts_nr = 0; + ts = &otfp->trx->ts[otfp->next_ts_nr++]; + om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx, + &ts->rbs2000.om2k_mo); +} + +static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + struct gsm_bts_trx_ts *ts; + + if (otfp->next_ts_nr < 8) { + /* iterate to the next timeslot */ + ts = &otfp->trx->ts[otfp->next_ts_nr++]; + om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx, + &ts->rbs2000.om2k_mo); + } else { + /* only after all 8 TS */ + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_DONE, 0, 0); + } +} + +static void om2k_trx_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + gsm_bts_trx_set_system_infos(otfp->trx); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +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), + .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), + .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), + .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), + .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_DONE), + .name = "WAIT-TS", + .action = om2k_trx_s_wait_ts, + }, + [OM2K_TRX_S_DONE] = { + .name = "DONE", + .onenter = om2k_trx_s_done_onenter, + }, + [OM2K_TRX_S_ERROR] = { + .name = "ERROR", + }, +}; + +static int om2k_trx_timer_cb(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_ERROR, 0, 0); + return 0; +} + +static struct osmo_fsm om2k_trx_fsm = { + .name = "OM2000-TRX", + .states = om2k_trx_states, + .num_states = ARRAY_SIZE(om2k_trx_states), + .log_subsys = DNM, + .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) +{ + struct osmo_fsm_inst *fi; + struct om2k_trx_fsm_priv *otfp; + char idbuf[32]; + + 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); + if (!fi) + return NULL; + + otfp = talloc_zero(fi, struct om2k_trx_fsm_priv); + otfp->trx = trx; + fi->priv = otfp; + + osmo_fsm_inst_dispatch(fi, OM2K_TRX_EVT_START, NULL); + + return fi; +} + + +/*********************************************************************** + * OM2000 BTS Finite State Machine, initializes CF and all siblings + ***********************************************************************/ + +enum om2k_bts_event { + OM2K_BTS_EVT_START, + OM2K_BTS_EVT_CF_DONE, + OM2K_BTS_EVT_IS_DONE, + OM2K_BTS_EVT_CON_DONE, + OM2K_BTS_EVT_TF_DONE, + OM2K_BTS_EVT_TRX_DONE, + OM2K_BTS_EVT_STOP, +}; + +static const struct value_string om2k_bts_events[] = { + { OM2K_BTS_EVT_START, "START" }, + { OM2K_BTS_EVT_CF_DONE, "CF-DONE" }, + { OM2K_BTS_EVT_IS_DONE, "IS-DONE" }, + { OM2K_BTS_EVT_CON_DONE, "CON-DONE" }, + { OM2K_BTS_EVT_TF_DONE, "TF-DONE" }, + { OM2K_BTS_EVT_TRX_DONE, "TRX-DONE" }, + { OM2K_BTS_EVT_STOP, "STOP" }, + { 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_TRX, + OM2K_BTS_S_DONE, + OM2K_BTS_S_ERROR, +}; + +struct om2k_bts_fsm_priv { + struct gsm_bts *bts; + uint8_t next_trx_nr; +}; + +static void om2k_bts_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + 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); +} + +static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts *bts = obfp->bts; + + 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); +} + +static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts *bts = obfp->bts; + + 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); +} + +static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts *bts = obfp->bts; + + 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); +} + +static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts_trx *trx; + + OSMO_ASSERT(event == OM2K_BTS_EVT_IS_DONE); + + 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); +} + +static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + + OSMO_ASSERT(event == OM2K_BTS_EVT_TRX_DONE); + + 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); + } else { + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_DONE, 0, 0); + } +} + +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 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), + .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), + .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), + .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), + .name = "WAIT-CON", + .action = om2k_bts_s_wait_con, + }, + [OM2K_BTS_S_WAIT_IS] = { + .in_event_mask = S(OM2K_BTS_EVT_IS_DONE), + .out_state_mask = S(OM2K_BTS_S_ERROR) | + S(OM2K_BTS_S_WAIT_TRX), + .name = "WAIT-IS", + .action = om2k_bts_s_wait_is, + }, + [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), + .name = "WAIT-TRX", + .action = om2k_bts_s_wait_trx, + }, + [OM2K_BTS_S_DONE] = { + .name = "DONE", + .onenter = om2k_bts_s_done_onenter, + }, + [OM2K_BTS_S_ERROR] = { + .name = "ERROR", + }, +}; + +static int om2k_bts_timer_cb(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_ERROR, 0, 0); + return 0; +} + +static struct osmo_fsm om2k_bts_fsm = { + .name = "OM2000-BTS", + .states = om2k_bts_states, + .num_states = ARRAY_SIZE(om2k_bts_states), + .log_subsys = DNM, + .event_names = om2k_bts_events, + .timer_cb = om2k_bts_timer_cb, +}; + +struct osmo_fsm_inst * +om2k_bts_fsm_start(struct gsm_bts *bts) +{ + struct osmo_fsm_inst *fi; + struct om2k_bts_fsm_priv *obfp; + char idbuf[16]; + + snprintf(idbuf, sizeof(idbuf), "%u", bts->nr); + + 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; +} + + +/*********************************************************************** + * OM2000 Negotiation + ***********************************************************************/ + +static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t *data, unsigned int len) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, mo, OM2K_MSGT_NEGOT_REQ_ACK); + + msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data); + + DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK)); + + return abis_om2k_sendmsg(bts, msg); +} + +struct iwd_version { + uint8_t gen_char[3+1]; + uint8_t rev_char[3+1]; +}; + +struct iwd_type { + uint8_t num_vers; + struct iwd_version v[8]; +}; + +static int om2k_rx_negot_req(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst; + struct abis_om2k_hdr *o2h = msgb_l2(msg); + struct iwd_type iwd_types[16]; + uint8_t num_iwd_types = o2h->data[2]; + uint8_t *cur = o2h->data+3; + unsigned int i, v; + + uint8_t out_buf[1024]; + uint8_t *out_cur = out_buf+1; + uint8_t out_num_types = 0; + + memset(iwd_types, 0, sizeof(iwd_types)); + + /* Parse the RBS-supported IWD versions into iwd_types array */ + for (i = 0; i < num_iwd_types; i++) { + uint8_t num_versions = *cur++; + uint8_t iwd_type = *cur++; + + iwd_types[iwd_type].num_vers = num_versions; + + for (v = 0; v < num_versions; v++) { + struct iwd_version *iwd_v = &iwd_types[iwd_type].v[v]; + + memcpy(iwd_v->gen_char, cur, 3); + cur += 3; + memcpy(iwd_v->rev_char, cur, 3); + cur += 3; + + DEBUGP(DNM, "\tIWD Type %u Gen %s Rev %s\n", iwd_type, + iwd_v->gen_char, iwd_v->rev_char); + } + } + + /* Select the last version for each IWD type */ + for (i = 0; i < ARRAY_SIZE(iwd_types); i++) { + struct iwd_type *type = &iwd_types[i]; + struct iwd_version *last_v; + + if (type->num_vers == 0) + continue; + + out_num_types++; + + last_v = &type->v[type->num_vers-1]; + + *out_cur++ = i; + memcpy(out_cur, last_v->gen_char, 3); + out_cur += 3; + memcpy(out_cur, last_v->rev_char, 3); + out_cur += 3; + } + + out_buf[0] = out_num_types; + + return abis_om2k_tx_negot_req_ack(sign_link->trx->bts, &o2h->mo, out_buf, out_cur - out_buf); +} + + +/*********************************************************************** + * OM2000 Receive Message Handler + ***********************************************************************/ + +static int om2k_rx_nack(struct msgb *msg) +{ + struct abis_om2k_hdr *o2h = msgb_l2(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), + 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)); + + 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))); + LOGPC(DNM, LOGL_ERROR, "\n"); + + return 0; +} + +static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm) +{ + uint8_t mo_state; + + if (!TLVP_PRESENT(&odm->tp, OM2K_DEI_MO_STATE)) + 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), + get_value_string(om2k_msgcode_vals, odm->msg_type), + get_value_string(om2k_mostate_vals, mo_state)); + + /* Throw error message in case we see an enable rsponse that does + * 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)); + } + + update_mo_state(bts, &odm->o2h.mo, mo_state); + + return 0; +} + +/* Display fault report bits (helper function of display_fault_maps()) */ +static bool display_fault_bits(const uint8_t *vect, uint16_t len, + uint8_t dei, const struct abis_om2k_mo *mo) +{ + uint16_t i; + int k; + bool faults_present = false; + int first = 1; + char string[255]; + + /* Check if errors are present at all */ + for (i = 0; i < len; i++) + if (vect[i]) + faults_present = true; + if (!faults_present) + return false; + + sprintf(string, "Fault Report: %s (", + get_value_string(om2k_attr_vals, dei)); + + for (i = 0; i < len; i++) { + for (k = 0; k < 8; k++) { + if ((vect[i] >> k) & 1) { + if (!first) + sprintf(string + strlen(string), ","); + sprintf(string + strlen(string), "%d", k + i*8); + first = 0; + } + } + } + + sprintf(string + strlen(string), ")\n"); + DEBUGP(DNM, "Rx MO=%s %s", om2k_mo_name(mo), string); + + return true; +} + +/* Display fault report maps */ +static void display_fault_maps(const uint8_t *src, unsigned int src_len, + const struct abis_om2k_mo *mo) +{ + uint8_t tag; + uint16_t tag_len; + const uint8_t *val; + int src_pos = 0; + int rc; + int tlv_count = 0; + uint16_t msg_code; + bool faults_present = false; + + /* Chop off header */ + src+=4; + src_len-=4; + + /* Check message type */ + msg_code = (*src & 0xff) << 8; + src++; + src_len--; + msg_code |= (*src & 0xff); + 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)); + return; + } + + /* Chop off mo-interface */ + src += 4; + src_len -= 4; + + /* Iterate over each TLV element */ + while (1) { + + /* 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)); + return; + } + + /* Parse TLV field */ + 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)); + return; + } + + switch (tag) { + case OM2K_DEI_INT_FAULT_MAP_1A: + case OM2K_DEI_INT_FAULT_MAP_1B: + case OM2K_DEI_INT_FAULT_MAP_2A: + case OM2K_DEI_EXT_COND_MAP_1: + case OM2K_DEI_EXT_COND_MAP_2: + case OM2K_DEI_REPL_UNIT_MAP: + case OM2K_DEI_INT_FAULT_MAP_2A_EXT: + case OM2K_DEI_EXT_COND_MAP_2_EXT: + case OM2K_DEI_REPL_UNIT_MAP_EXT: + faults_present |= display_fault_bits(val, tag_len, + tag, mo); + break; + } + + /* Stop when no further TLV elements can be expected */ + if (src_len - src_pos < 2) + break; + + tlv_count++; + } + + if (!faults_present) { + DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n", + om2k_mo_name(mo)); + } +} + +int abis_om2k_rcvmsg(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct abis_om2k_hdr *o2h = msgb_l2(msg); + struct abis_om_hdr *oh = &o2h->om; + uint16_t msg_type = ntohs(o2h->msg_type); + struct om2k_decoded_msg odm; + struct om2k_mo *mo; + int rc = 0; + + /* Various consistency checks */ + if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { + 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); + 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); + 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))); + + om2k_decode_msg(&odm, msg); + + process_mo_state(bts, &odm); + + switch (msg_type) { + case OM2K_MSGT_CAL_TIME_REQ: + rc = abis_om2k_cal_time_resp(bts); + break; + 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; + case OM2K_MSGT_NEGOT_REQ: + rc = om2k_rx_negot_req(msg); + break; + case OM2K_MSGT_START_RES: + /* common processing here */ + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_RES_ACK); + /* below we dispatch into MO */ + break; + case OM2K_MSGT_IS_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_IS_CONF_RES_ACK); + break; + case OM2K_MSGT_CON_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CON_CONF_RES_ACK); + break; + case OM2K_MSGT_TX_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TX_CONF_RES_ACK); + break; + case OM2K_MSGT_RX_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RX_CONF_RES_ACK); + break; + case OM2K_MSGT_TS_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TS_CONF_RES_ACK); + break; + case OM2K_MSGT_TF_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TF_CONF_RES_ACK); + break; + case OM2K_MSGT_ENABLE_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_ENABLE_RES_ACK); + break; + case OM2K_MSGT_DISABLE_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_DISABLE_RES_ACK); + break; + case OM2K_MSGT_TEST_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TEST_RES_ACK); + break; + case OM2K_MSGT_CAPA_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CAPA_RES_ACK); + break; + /* ERrors */ + case OM2K_MSGT_START_REQ_REJ: + case OM2K_MSGT_CONNECT_REJ: + case OM2K_MSGT_OP_INFO_REJ: + case OM2K_MSGT_DISCONNECT_REJ: + case OM2K_MSGT_TEST_REQ_REJ: + case OM2K_MSGT_CON_CONF_REQ_REJ: + case OM2K_MSGT_IS_CONF_REQ_REJ: + case OM2K_MSGT_TX_CONF_REQ_REJ: + case OM2K_MSGT_RX_CONF_REQ_REJ: + case OM2K_MSGT_TS_CONF_REQ_REJ: + case OM2K_MSGT_TF_CONF_REQ_REJ: + case OM2K_MSGT_ENABLE_REQ_REJ: + case OM2K_MSGT_ALARM_STATUS_REQ_REJ: + case OM2K_MSGT_DISABLE_REQ_REJ: + rc = om2k_rx_nack(msg); + break; + } + + /* Resolve the MO for this message */ + 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; + } + 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; + } + + /* Dispatch message to that MO */ + om2k_mo_fsm_recvmsg(bts, mo, &odm); + + 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) +{ + mo->addr.class = class; + mo->addr.bts = bts_nr; + mo->addr.assoc_so = assoc_so; + mo->addr.inst = inst; +} + +/* initialize the OM2K_MO members of gsm_bts_trx and its timeslots */ +void abis_om2k_trx_init(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + 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); + + 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); + gsm_ts_check_init(ts); + } +} + +/* initialize the OM2K_MO members of gsm_bts */ +void abis_om2k_bts_init(struct gsm_bts *bts) +{ + 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); +} + +static __attribute__((constructor)) void abis_om2k_init(void) +{ + osmo_fsm_register(&om2k_mo_fsm); + osmo_fsm_register(&om2k_bts_fsm); + osmo_fsm_register(&om2k_trx_fsm); +} diff --git a/src/osmo-bsc/abis_om2000_vty.c b/src/osmo-bsc/abis_om2000_vty.c new file mode 100644 index 000000000..faf39c106 --- /dev/null +++ b/src/osmo-bsc/abis_om2000_vty.c @@ -0,0 +1,604 @@ +/* VTY interface for A-bis OM2000 */ + +/* (C) 2010-2018 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static struct cmd_node om2k_node = { + OM2K_NODE, + "%s(om2k)# ", + 1, +}; + +static struct cmd_node om2k_con_group_node = { + OM2K_CON_GROUP_NODE, + "%s(om2k-con-group)# ", + 1, +}; + +struct con_group; + +struct oml_node_state { + struct gsm_bts *bts; + struct abis_om2k_mo mo; + 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|ts|tf|is|con|dp|cf|tx|rx)" +#define OM2K_OBJCLASS_VTY_HELP "TRX Controller\n" \ + "Timeslot\n" \ + "Timing Function\n" \ + "Interface Switch\n" \ + "Abis Concentrator\n" \ + "Digital Path\n" \ + "Central Function\n" \ + "Transmitter\n" \ + "Receiver\n" + +DEFUN(om2k_class_inst, om2k_class_inst_cmd, + "bts <0-255> om2000 class " OM2K_OBJCLASS_VTY + " <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OM2000 managed objects\n" + "Object Class\n" OM2K_OBJCLASS_VTY_HELP + "BTS Number\n" "Associated SO Instance\n" "Instance Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + 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->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% BTS %d not an Ericsson RBS%s", + bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->mo.class = get_string_value(om2k_mo_class_short_vals, argv[1]); + oms->mo.bts = atoi(argv[2]); + oms->mo.assoc_so = atoi(argv[3]); + oms->mo.inst = atoi(argv[4]); + + vty->index = oms; + vty->node = OM2K_NODE; + + return CMD_SUCCESS; + +} + +DEFUN(om2k_classnum_inst, om2k_classnum_inst_cmd, + "bts <0-255> om2000 class <0-255> <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" "Object Class\n" + "BTS Number\n" "Associated SO Instance\n" "Instance Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + 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; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->mo.class = atoi(argv[1]); + oms->mo.bts = atoi(argv[2]); + oms->mo.assoc_so = atoi(argv[3]); + oms->mo.inst = atoi(argv[4]); + + vty->index = oms; + vty->node = OM2K_NODE; + + return CMD_SUCCESS; +} + +DEFUN(om2k_reset, om2k_reset_cmd, + "reset-command", + "Reset the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_reset_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_start, om2k_start_cmd, + "start-request", + "Start the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_start_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_status, om2k_status_cmd, + "status-request", + "Get the MO Status\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_status_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_connect, om2k_connect_cmd, + "connect-command", + "Connect the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_connect_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_disconnect, om2k_disconnect_cmd, + "disconnect-command", + "Disconnect the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_disconnect_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_enable, om2k_enable_cmd, + "enable-request", + "Enable the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_enable_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_disable, om2k_disable_cmd, + "disable-request", + "Disable the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_disable_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_op_info, om2k_op_info_cmd, + "operational-info <0-1>", + "Set operational information\n" + "Set operational info to 0 or 1\n") +{ + struct oml_node_state *oms = vty->index; + int oper = atoi(argv[0]); + + abis_om2k_tx_op_info(oms->bts, &oms->mo, oper); + return CMD_SUCCESS; +} + +DEFUN(om2k_test, om2k_test_cmd, + "test-request", + "Test the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_test_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_cap_req, om2k_cap_req_cmd, + "capabilities-request", + "Request MO capabilities\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_cap_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +static struct con_group *con_group_find_or_create(struct gsm_bts *bts, uint8_t cg) +{ + struct con_group *ent; + + llist_for_each_entry(ent, &bts->rbs2000.con.conn_groups, list) { + if (ent->cg == cg) + return ent; + } + + ent = talloc_zero(bts, struct con_group); + ent->bts = bts; + ent->cg = cg; + INIT_LLIST_HEAD(&ent->paths); + llist_add_tail(&ent->list, &bts->rbs2000.con.conn_groups); + + return ent; +} + +static int con_group_del(struct gsm_bts *bts, uint8_t cg_id) +{ + struct con_group *cg, *cg2; + + llist_for_each_entry_safe(cg, cg2, &bts->rbs2000.con.conn_groups, list) { + if (cg->cg == cg_id) { + llist_del(&cg->list); + talloc_free(cg); + return 0; + }; + } + return -ENOENT; +} + +static void con_group_add_path(struct con_group *cg, uint16_t ccp, + uint8_t ci, uint8_t tag, uint8_t tei) +{ + struct con_path *cp = talloc_zero(cg, struct con_path); + + cp->ccp = ccp; + cp->ci = ci; + cp->tag = tag; + cp->tei = tei; + llist_add(&cp->list, &cg->paths); +} + +static int con_group_del_path(struct con_group *cg, uint16_t ccp, + uint8_t ci, uint8_t tag, uint8_t tei) +{ + struct con_path *cp, *cp2; + llist_for_each_entry_safe(cp, cp2, &cg->paths, list) { + if (cp->ccp == ccp && cp->ci == ci && cp->tag == tag && + cp->tei == tei) { + llist_del(&cp->list); + talloc_free(cp); + return 0; + } + } + return -ENOENT; +} + +DEFUN(cfg_om2k_con_group, cfg_om2k_con_group_cmd, + "con-connection-group <1-31>", + "Configure a CON (Concentrator) Connection Group\n" + "CON Connection Group Number\n") +{ + struct gsm_bts *bts = vty->index; + struct con_group *cg; + uint8_t cgid = atoi(argv[0]); + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% CON MO only exists in RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + cg = con_group_find_or_create(bts, cgid); + if (!cg) { + vty_out(vty, "%% Cannot create CON Group%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vty->node = OM2K_CON_GROUP_NODE; + vty->index = cg; + + return CMD_SUCCESS; +} + +DEFUN(del_om2k_con_group, del_om2k_con_group_cmd, + "del-connection-group <1-31>", + "Delete a CON (Concentrator) Connection Group\n" + "CON Connection Group Number\n") +{ + struct gsm_bts *bts = vty->index; + int rc; + uint8_t cgid = atoi(argv[0]); + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% CON MO only exists in RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + rc = con_group_del(bts, cgid); + if (rc != 0) { + vty_out(vty, "%% Cannot delete CON Group%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +#define CON_PATH_HELP "CON Path (In/Out)\n" \ + "Add CON Path to Concentration Group\n" \ + "Delete CON Path from Concentration Group\n" \ + "CON Conection Point\n" \ + "Contiguity Index\n" \ + +DEFUN(cfg_om2k_con_path_dec, cfg_om2k_con_path_dec_cmd, + "con-path (add|del) <0-2047> <0-255> deconcentrated <0-63>", + CON_PATH_HELP "De-concentrated in/outlet\n" "TEI Value\n") +{ + struct con_group *cg = vty->index; + uint16_t ccp = atoi(argv[1]); + uint8_t ci = atoi(argv[2]); + uint8_t tei = atoi(argv[3]); + + if (!strcmp(argv[0], "add")) + con_group_add_path(cg, ccp, ci, 0, tei); + else { + if (con_group_del_path(cg, ccp, ci, 0, tei) < 0) { + vty_out(vty, "%% No matching CON Path%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_om2k_con_path_conc, cfg_om2k_con_path_conc_cmd, + "con-path (add|del) <0-2047> <0-255> concentrated <1-16>", + CON_PATH_HELP "Concentrated in/outlet\n" "Tag Number\n") +{ + struct con_group *cg = vty->index; + uint16_t ccp = atoi(argv[1]); + uint8_t ci = atoi(argv[2]); + uint8_t tag = atoi(argv[3]); + + if (!strcmp(argv[0], "add")) + con_group_add_path(cg, ccp, ci, tag, 0xff); + else { + if (con_group_del_path(cg, ccp, ci, tag, 0xff) < 0) { + vty_out(vty, "%% No matching CON list entry%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd, + "abis-lower-transport (single-timeslot|super-channel)", + "Configure thee Abis Lower Transport\n" + "Single Timeslot (classic Abis)\n" + "SuperChannel (Packet Abis)\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], "super-channel")) + bts->rbs2000.use_superchannel = 1; + else + bts->rbs2000.use_superchannel = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd, + "is-connection-list (add|del) <0-2047> <0-2047> <0-255>", + "Interface Switch Connection List\n" + "Add to IS list\n" "Delete from IS list\n" + "ICP1\n" "ICP2\n" "Contiguity Index\n") +{ + struct gsm_bts *bts = vty->index; + uint16_t icp1 = atoi(argv[1]); + uint16_t icp2 = atoi(argv[2]); + uint8_t ci = atoi(argv[3]); + struct is_conn_group *grp, *grp2; + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% IS MO only exists in RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "add")) { + grp = talloc_zero(bts, struct is_conn_group); + grp->icp1 = icp1; + grp->icp2 = icp2; + grp->ci = ci; + llist_add_tail(&grp->list, &bts->rbs2000.is.conn_groups); + } else { + llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.is.conn_groups, list) { + if (grp->icp1 == icp1 && grp->icp2 == icp2 + && grp->ci == ci) { + llist_del(&grp->list); + talloc_free(grp); + return CMD_SUCCESS; + } + } + vty_out(vty, "%% No matching IS Conn Group found!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + + +DEFUN(om2k_conf_req, om2k_conf_req_cmd, + "configuration-request", + "Send the configuration request for current MO\n") +{ + struct oml_node_state *oms = vty->index; + struct gsm_bts *bts = oms->bts; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + + switch (oms->mo.class) { + case OM2K_MO_CLS_IS: + abis_om2k_tx_is_conf_req(bts); + break; + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_by_nr(bts, oms->mo.assoc_so); + if (!trx) { + vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr, + oms->mo.assoc_so, VTY_NEWLINE); + return CMD_WARNING; + } + if (oms->mo.inst >= ARRAY_SIZE(trx->ts)) { + vty_out(vty, "%% Timeslot %u out of range%s", + oms->mo.inst, VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[oms->mo.inst]; + abis_om2k_tx_ts_conf_req(ts); + break; + case OM2K_MO_CLS_RX: + case OM2K_MO_CLS_TX: + case OM2K_MO_CLS_TRXC: + trx = gsm_bts_trx_by_nr(bts, oms->mo.inst); + if (!trx) { + vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr, + oms->mo.inst, VTY_NEWLINE); + return CMD_WARNING; + } + switch (oms->mo.class) { + case OM2K_MO_CLS_RX: + abis_om2k_tx_rx_conf_req(trx); + break; + case OM2K_MO_CLS_TX: + abis_om2k_tx_tx_conf_req(trx); + break; + default: + break; + } + break; + case OM2K_MO_CLS_TF: + abis_om2k_tx_tf_conf_req(bts); + break; + default: + vty_out(vty, "%% Don't know how to configure MO%s", + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +static void dump_con_group(struct vty *vty, struct con_group *cg) +{ + struct con_path *cp; + + llist_for_each_entry(cp, &cg->paths, list) { + vty_out(vty, " con-path add %u %u ", cp->ccp, cp->ci); + if (cp->tei == 0xff) { + vty_out(vty, "concentrated %u%s", cp->tag, + VTY_NEWLINE); + } else { + vty_out(vty, "deconcentrated %u%s", cp->tei, + VTY_NEWLINE); + } + } +} + +void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + struct is_conn_group *igrp; + struct con_group *cgrp; + + llist_for_each_entry(igrp, &bts->rbs2000.is.conn_groups, list) + vty_out(vty, " is-connection-list add %u %u %u%s", + igrp->icp1, igrp->icp2, igrp->ci, VTY_NEWLINE); + + llist_for_each_entry(cgrp, &bts->rbs2000.con.conn_groups, list) { + vty_out(vty, " con-connection-group %u%s", cgrp->cg, + VTY_NEWLINE); + dump_con_group(vty, cgrp); + } + if (bts->rbs2000.use_superchannel) + vty_out(vty, " abis-lower-transport super-channel%s", + VTY_NEWLINE); +} + +int abis_om2k_vty_init(void) +{ + install_element(ENABLE_NODE, &om2k_class_inst_cmd); + install_element(ENABLE_NODE, &om2k_classnum_inst_cmd); + install_node(&om2k_node, dummy_config_write); + + install_element(OM2K_NODE, &om2k_reset_cmd); + install_element(OM2K_NODE, &om2k_start_cmd); + install_element(OM2K_NODE, &om2k_status_cmd); + install_element(OM2K_NODE, &om2k_connect_cmd); + install_element(OM2K_NODE, &om2k_disconnect_cmd); + install_element(OM2K_NODE, &om2k_enable_cmd); + install_element(OM2K_NODE, &om2k_disable_cmd); + install_element(OM2K_NODE, &om2k_op_info_cmd); + install_element(OM2K_NODE, &om2k_test_cmd); + install_element(OM2K_NODE, &om2k_cap_req_cmd); + install_element(OM2K_NODE, &om2k_conf_req_cmd); + + install_node(&om2k_con_group_node, dummy_config_write); + install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_dec_cmd); + install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_conc_cmd); + + install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd); + install_element(BTS_NODE, &cfg_bts_alt_mode_cmd); + install_element(BTS_NODE, &cfg_om2k_con_group_cmd); + install_element(BTS_NODE, &del_om2k_con_group_cmd); + + return 0; +} diff --git a/src/osmo-bsc/abis_rsl.c b/src/osmo-bsc/abis_rsl.c new file mode 100644 index 000000000..7bbde47d7 --- /dev/null +++ b/src/osmo-bsc/abis_rsl.c @@ -0,0 +1,3040 @@ +/* GSM Radio Signalling Link messages on the A-bis interface + * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2012 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RSL_ALLOC_SIZE 1024 +#define RSL_ALLOC_HEADROOM 128 + +#define RTP_PT_GSM_FULL 3 +#define RTP_PT_GSM_HALF 96 +#define RTP_PT_GSM_EFR 97 +#define RTP_PT_AMR 98 + +enum sacch_deact { + SACCH_NONE, + SACCH_DEACTIVATE, +}; + +static int rsl_send_imm_assignment(struct gsm_lchan *lchan); +static void error_timeout_cb(void *data); +static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts); +static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc); +static void dyn_ts_switchover_complete(struct gsm_lchan *lchan); + +static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan, + struct gsm_meas_rep *resp) +{ + struct lchan_signal_data sig; + sig.lchan = lchan; + sig.mr = resp; + osmo_signal_dispatch(SS_LCHAN, sig_no, &sig); +} + +static void do_lchan_free(struct gsm_lchan *lchan) +{ + /* We start the error timer to make the channel available again */ + if (lchan->state == LCHAN_S_REL_ERR) { + osmo_timer_setup(&lchan->error_timer, error_timeout_cb, lchan); + osmo_timer_schedule(&lchan->error_timer, + lchan->ts->trx->bts->network->T3111 + 2, 0); + } else { + rsl_lchan_set_state(lchan, LCHAN_S_NONE); + } + lchan_free(lchan); +} + +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) { + case GSM48_CMODE_SPEECH_AMR: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_H]); + break; + case GSM48_CMODE_SPEECH_V1: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_HR]); + break; + default: + break; + } + } else if (lchan->type == GSM_LCHAN_TCH_F) { + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_AMR: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_F]); + break; + case GSM48_CMODE_SPEECH_V1: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_FR]); + break; + case GSM48_CMODE_SPEECH_EFR: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_EFR]); + break; + default: + break; + } + } +} + +static uint8_t mdisc_by_msgtype(uint8_t msg_type) +{ + /* mask off the transparent bit ? */ + msg_type &= 0xfe; + + if ((msg_type & 0xf0) == 0x00) + return ABIS_RSL_MDISC_RLL; + if ((msg_type & 0xf0) == 0x10) { + if (msg_type >= 0x19 && msg_type <= 0x22) + return ABIS_RSL_MDISC_TRX; + else + return ABIS_RSL_MDISC_COM_CHAN; + } + if ((msg_type & 0xe0) == 0x20) + return ABIS_RSL_MDISC_DED_CHAN; + + return ABIS_RSL_MDISC_LOC; +} + +static inline void init_dchan_hdr(struct abis_rsl_dchan_hdr *dh, + uint8_t msg_type) +{ + dh->c.msg_discr = mdisc_by_msgtype(msg_type); + dh->c.msg_type = msg_type; + dh->ie_chan = RSL_IE_CHAN_NR; +} + +/* call rsl_lchan_lookup and set the log context */ +static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, + const char *log_name) +{ + int rc; + struct gsm_lchan *lchan = rsl_lchan_lookup(trx, chan_nr, &rc); + + if (!lchan) { + LOGP(DRSL, LOGL_ERROR, "%sunknown chan_nr=0x%02x\n", + log_name, chan_nr); + return NULL; + } + + if (rc < 0) + LOGP(DRSL, LOGL_ERROR, "%s %smismatching chan_nr=0x%02x\n", + gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr); + + return lchan; +} + +/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */ +uint64_t str_to_imsi(const char *imsi_str) +{ + uint64_t ret; + + ret = strtoull(imsi_str, NULL, 10); + + return ret; +} + +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); + + if (len < GSM_MACBLOCK_LEN) + memset(out+len, 0x2b, GSM_MACBLOCK_LEN - len); +} + +/* Chapter 9.3.7: Encryption Information */ +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; +} + +static void print_rsl_cause(int lvl, const uint8_t *cause_v, uint8_t cause_len) +{ + int i; + + LOGPC(DRSL, lvl, "CAUSE=0x%02x(%s) ", + cause_v[0], rsl_err_name(cause_v[0])); + for (i = 1; i < cause_len-1; i++) + LOGPC(DRSL, lvl, "%02x ", cause_v[i]); +} + +static void lchan_act_tmr_cb(void *data) +{ + struct gsm_lchan *lchan = data; + + rsl_lchan_mark_broken(lchan, "activation timeout"); + lchan_free(lchan); +} + +static void lchan_deact_tmr_cb(void *data) +{ + struct gsm_lchan *lchan = data; + + rsl_lchan_mark_broken(lchan, "de-activation timeout"); + lchan_free(lchan); +} + + +/* 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) +{ + struct abis_rsl_dchan_hdr *dh; + const struct gsm_bts *bts = trx->bts; + struct msgb *msg = rsl_msgb_alloc(); + uint8_t type = osmo_sitype2rsl(si_type); + + if (bts->c0 != trx) + LOGP(DRR, LOGL_ERROR, "Attempting to set BCCH SI%s on wrong BTS%u/TRX%u\n", + get_value_string(osmo_sitype_strs, si_type), bts->nr, trx->nr); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof*dh); + init_dchan_hdr(dh, RSL_MT_BCCH_INFO); + dh->chan_nr = RSL_CHAN_BCCH; + + if (trx->bts->type == GSM_BTS_TYPE_RBS2000 + && type == RSL_SYSTEM_INFO_13) { + /* Ericsson proprietary encoding of SI13 */ + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, RSL_ERIC_SYSTEM_INFO_13); + if (data) + msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data); + msgb_tv_put(msg, RSL_IE_ERIC_BCCH_MAPPING, 0x00); + } else { + /* Normal encoding */ + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + if (data) + msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data); + } + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, + const uint8_t *data, int len) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_TRX; + ch->msg_type = RSL_MT_SACCH_FILL; + + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + if (data) + msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +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); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + if (data) + msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +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); + + db = abs(db); + if (db > 30) + return -EINVAL; + + msg = rsl_msgb_alloc(); + + lchan->bs_power = db/2; + if (fpc) + lchan->bs_power |= 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); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + int ctl_lvl; + + ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm); + if (ctl_lvl < 0) + return ctl_lvl; + + msg = rsl_msgb_alloc(); + + lchan->ms_power = ctl_lvl; + + if (fpc) + lchan->ms_power |= 0x20; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm, + struct gsm_lchan *lchan) +{ + 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; + if (lchan->ts->trx->bts->dtxd) + 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"); + + 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; + break; + case GSM_LCHAN_TCH_H: + 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)); + return -EINVAL; + } + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + cm->chan_rate = 0; + break; + case GSM48_CMODE_SPEECH_V1: + cm->chan_rate = RSL_CMOD_SP_GSM1; + break; + case GSM48_CMODE_SPEECH_EFR: + cm->chan_rate = RSL_CMOD_SP_GSM2; + break; + case GSM48_CMODE_SPEECH_AMR: + cm->chan_rate = RSL_CMOD_SP_GSM3; + break; + 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; + } + break; + default: + LOGP(DRSL, LOGL_ERROR, + "unsupported lchan->tch_mode %u\n", + lchan->tch_mode); + return -EINVAL; + } + + return 0; +} + +static void mr_config_for_bts(struct gsm_lchan *lchan, struct msgb *msg) +{ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + msgb_tlv_put(msg, RSL_IE_MR_CONFIG, lchan->mr_bts_lv[0], + lchan->mr_bts_lv + 1); +} + +static enum gsm_phys_chan_config pchan_for_lchant(enum gsm_chan_t type) +{ + switch (type) { + case GSM_LCHAN_TCH_F: + return GSM_PCHAN_TCH_F; + case GSM_LCHAN_TCH_H: + return GSM_PCHAN_TCH_H; + case GSM_LCHAN_NONE: + case GSM_LCHAN_PDTCH: + /* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only + * used in osmo-bts. Maybe set PDTCH and drop the NONE case + * here. */ + return GSM_PCHAN_PDCH; + default: + return GSM_PCHAN_UNKNOWN; + } +} + +/*! Tx simplified channel activation message for non-standard PDCH type. */ +static int rsl_chan_activate_lchan_as_pdch(struct gsm_lchan *lchan) +{ + struct msgb *msg; + struct abis_rsl_dchan_hdr *dh; + + /* This might be called after release of the second lchan of a TCH/H, + * but PDCH activation must always happen on the first lchan. Make sure + * the calling code passes the correct lchan. */ + OSMO_ASSERT(lchan == lchan->ts->lchan); + + rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + 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_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_PDCH); + + msgb_tv_put(msg, RSL_IE_ACT_TYPE, RSL_ACT_OSMO_PDCH); + + if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_RBS2000 && + lchan->ts->trx->bts->rbs2000.use_superchannel) { + const uint8_t eric_pgsl_tmr[] = { 30, 1 }; + msgb_tv_fixed_put(msg, RSL_IE_ERIC_PGSL_TIMERS, + sizeof(eric_pgsl_tmr), eric_pgsl_tmr); + } + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.1 */ +int rsl_chan_activate_lchan(struct gsm_lchan *lchan, uint8_t act_type, + uint8_t ho_ref) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + uint8_t *len; + uint8_t ta; + + struct rsl_ie_chan_mode cm; + struct gsm48_chan_desc cd; + + /* If a TCH_F/PDCH TS is in PDCH mode, deactivate PDCH first. */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH + && (lchan->ts->flags & TS_F_PDCH_ACTIVE)) { + /* store activation type and handover reference */ + lchan->dyn.act_type = act_type; + lchan->dyn.ho_ref = ho_ref; + return rsl_ipacc_pdch_activate(lchan->ts, 0); + } + + /* + * If necessary, release PDCH on dynamic TS. Note that sending a + * release here is only necessary when in PDCH mode; for TCH types, an + * RSL RF Chan Release is initiated by the BTS when a voice call ends, + * so when we reach this, it will already be released. If a dyn TS is + * in PDCH mode, it is still active and we need to initiate a release + * from the BSC side here. + * + * If pchan_is != pchan_want, the PDCH has already been taken down and + * the switchover now needs to enable the TCH lchan. + * + * To switch a dyn TS between TCH/H and TCH/F, it is sufficient to send + * a chan activ with the new lchan type, because it will already be + * released. + */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is == lchan->ts->dyn.pchan_want) { + enum gsm_phys_chan_config pchan_want; + pchan_want = pchan_for_lchant(lchan->type); + if (lchan->ts->dyn.pchan_is != pchan_want) { + /* + * Make sure to record on lchan[0] so that we'll find + * it after the PDCH release. + */ + struct gsm_lchan *lchan0 = lchan->ts->lchan; + lchan0->dyn.act_type = act_type, + lchan0->dyn.ho_ref = ho_ref; + lchan0->dyn.rqd_ref = lchan->rqd_ref; + lchan0->dyn.rqd_ta = lchan->rqd_ta; + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + DEBUGP(DRSL, "%s saved rqd_ref=%p ta=%u\n", + gsm_lchan_name(lchan0), lchan0->rqd_ref, + lchan0->rqd_ta); + return dyn_ts_switchover_start(lchan->ts, pchan_want); + } + } + + DEBUGP(DRSL, "%s Tx RSL Channel Activate with act_type=%s\n", + gsm_ts_and_pchan_name(lchan->ts), + rsl_act_type_name(act_type)); + + if (act_type == RSL_ACT_OSMO_PDCH) { + if (lchan->ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DRSL, LOGL_ERROR, + "%s PDCH channel activation only allowed on %s\n", + gsm_ts_and_pchan_name(lchan->ts), + gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH)); + return -EINVAL; + } + return rsl_chan_activate_lchan_as_pdch(lchan); + } + + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_want == GSM_PCHAN_PDCH) { + LOGP(DRSL, LOGL_ERROR, + "%s Expected PDCH activation kind\n", + gsm_ts_and_pchan_name(lchan->ts)); + return -EINVAL; + } + + rc = channel_mode_from_lchan(&cm, lchan); + if (rc < 0) { + LOGP(DRSL, LOGL_ERROR, + "%s Cannot find channel mode from lchan type\n", + gsm_ts_and_pchan_name(lchan->ts)); + return rc; + } + + rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + ta = lchan->rqd_ta; + + /* BS11 requires TA shifted by 2 bits */ + if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11) + ta <<= 2; + + memset(&cd, 0, sizeof(cd)); + gsm48_lchan2chan_desc(&cd, lchan); + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV); + + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + dh->chan_nr = gsm_lchan_as_pchan2chan_nr( + lchan, lchan->ts->dyn.pchan_want); + else + dh->chan_nr = gsm_lchan2chan_nr(lchan); + + msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type); + msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), + (uint8_t *) &cm); + + /* + * The Channel Identification is needed for Phase1 phones + * and it contains the GSM48 Channel Description and the + * Mobile Allocation. The GSM 08.58 asks for the Mobile + * Allocation to have a length of zero. We are using the + * msgb_l3len to calculate the length of both messages. + */ + msgb_v_put(msg, RSL_IE_CHAN_IDENT); + len = msgb_put(msg, 1); + msgb_tv_fixed_put(msg, GSM48_IE_CHANDESC_2, sizeof(cd), (const uint8_t *) &cd); + + if (lchan->ts->hopping.enabled) + msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len, + lchan->ts->hopping.ma_data); + else + msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL); + + /* update the calculated size */ + msg->l3h = len + 1; + *len = msgb_l3len(msg); + + if (lchan->encr.alg_id > RSL_ENC_ALG_A5(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); + } + + switch (act_type) { + case RSL_ACT_INTER_ASYNC: + case RSL_ACT_INTER_SYNC: + msgb_tv_put(msg, RSL_IE_HANDO_REF, ho_ref); + break; + default: + 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); + mr_config_for_bts(lchan, msg); + + msg->dst = lchan->ts->trx->rsl_link; + + rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_TOTAL]); + + rc = abis_rsl_sendmsg(msg); + if (!rc) + rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); + return rc; +} + +/* Chapter 8.4.9: Modify channel mode on BTS side */ +int rsl_chan_mode_modify_req(struct gsm_lchan *lchan) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + struct rsl_ie_chan_mode cm; + + rc = channel_mode_from_lchan(&cm, lchan); + if (rc < 0) + return rc; + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_MODE_MODIFY_REQ); + dh->chan_nr = chan_nr; + + msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), + (uint8_t *) &cm); + + if (lchan->encr.alg_id > RSL_ENC_ALG_A5(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); + } + + mr_config_for_bts(lchan, msg); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.6: Send the encryption command with given L3 info */ +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; + + /* First push the L3 IE tag and length */ + msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); + + /* then the link identifier (SAPI0, main sign link) */ + msgb_tv_push(msg, RSL_IE_LINK_IDENT, 0); + + /* then encryption information */ + rc = build_encr_info(encr_info, lchan); + if (rc <= 0) + return rc; + msgb_tlv_push(msg, RSL_IE_ENCR_INFO, rc, encr_info); + + /* and finally the DCHAN header */ + dh = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_ENCR_CMD); + dh->chan_nr = chan_nr; + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.5 / 4.6: Deactivate the SACCH after 04.08 RR CHAN RELEASE */ +int rsl_deact_sacch(struct gsm_lchan *lchan) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + + 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); + + msg->lchan = lchan; + msg->dst = lchan->ts->trx->rsl_link; + + DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan)); + + return abis_rsl_sendmsg(msg); +} + +static bool dyn_ts_should_switch_to_pdch(struct gsm_bts_trx_ts *ts) +{ + int ss; + + if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + return false; + + if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) + return false; + + /* Already in PDCH mode? */ + if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) + return false; + + /* See if all lchans are released. */ + for (ss = 0; ss < ts_subslots(ts); ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->state != LCHAN_S_NONE) { + DEBUGP(DRSL, "%s lchan %u still in use" + " (type=%s,state=%s)\n", + gsm_ts_and_pchan_name(ts), lc->nr, + gsm_lchant_name(lc->type), + gsm_lchans_name(lc->state)); + /* An lchan is still used. */ + return false; + } + } + + /* All channels are released, go to PDCH mode. */ + DEBUGP(DRSL, "%s back to PDCH\n", + gsm_ts_and_pchan_name(ts)); + return true; +} + +static void error_timeout_cb(void *data) +{ + struct gsm_lchan *lchan = data; + if (lchan->state != LCHAN_S_REL_ERR) { + LOGP(DRSL, LOGL_ERROR, "%s error timeout but not in error state: %d\n", + gsm_lchan_name(lchan), lchan->state); + return; + } + + /* go back to the none state */ + LOGP(DRSL, LOGL_INFO, "%s is back in operation.\n", gsm_lchan_name(lchan)); + rsl_lchan_set_state(lchan, LCHAN_S_NONE); + + /* Put PDCH channel back into PDCH mode, if GPRS is enabled */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH + && lchan->ts->trx->bts->gprs.mode != BTS_GPRS_NONE) + rsl_ipacc_pdch_activate(lchan->ts, 1); + + if (dyn_ts_should_switch_to_pdch(lchan->ts)) + dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH); +} + +static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan); + +/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */ +static int rsl_rf_chan_release(struct gsm_lchan *lchan, int error, + enum sacch_deact deact_sacch) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + + /* Stop timers that should lead to a channel release */ + osmo_timer_del(&lchan->T3109); + + if (lchan->state == LCHAN_S_REL_ERR) { + LOGP(DRSL, LOGL_NOTICE, "%s is in error state, not sending release.\n", + gsm_lchan_name(lchan)); + return -1; + } + + 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); + + msg->lchan = lchan; + msg->dst = lchan->ts->trx->rsl_link; + + if (error) + DEBUGP(DRSL, "%s RF Channel Release due to error: %d\n", + gsm_lchan_name(lchan), error); + else + DEBUGP(DRSL, "%s RF Channel Release\n", gsm_lchan_name(lchan)); + + if (error) { + /* + * FIXME: GSM 04.08 gives us two options for the abnormal + * chanel release. This can be either like in the non-existent + * sub-lcuase 3.5.1 or for the main signalling link deactivate + * the SACCH, start timer T3109 and consider the channel as + * released. + * + * This code is doing the later for all raido links and not + * only the main link. Right now all SAPIs are released on the + * local end, the SACCH will be de-activated and right now the + * T3111 will be started. First T3109 should be started and then + * the T3111. + * + * TODO: Move this out of the function. + */ + + /* + * sacch de-activate and "local end release" + */ + if (deact_sacch == SACCH_DEACTIVATE) + rsl_deact_sacch(lchan); + rsl_release_sapis_from(lchan, 0, RSL_REL_LOCAL_END); + + /* + * TODO: start T3109 now. + */ + rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR); + } + + /* Start another timer or assume the BTS sends a ACK/NACK? */ + osmo_timer_setup(&lchan->act_timer, lchan_deact_tmr_cb, lchan); + osmo_timer_schedule(&lchan->act_timer, 4, 0); + + rc = abis_rsl_sendmsg(msg); + + /* BTS will respond by RF CHAN REL ACK */ + return rc; +} + +/* + * Special handling for channel releases in the error case. + */ +static int rsl_rf_chan_release_err(struct gsm_lchan *lchan) +{ + enum sacch_deact sacch_deact; + if (lchan->state != LCHAN_S_ACTIVE) + return 0; + switch (ts_pchan(lchan->ts)) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + sacch_deact = SACCH_DEACTIVATE; + break; + default: + sacch_deact = SACCH_NONE; + break; + } + return rsl_rf_chan_release(lchan, 1, sacch_deact); +} + +static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", gsm_lchan_name(lchan)); + + /* Stop all pending timers */ + osmo_timer_del(&lchan->act_timer); + osmo_timer_del(&lchan->T3111); + + /* + * The BTS didn't respond within the timeout to our channel + * release request and we have marked the channel as broken. + * Now we do receive an ACK and let's be conservative. If it + * is a sysmoBTS we know that only one RF Channel Release ACK + * will be sent. So let's "repair" the channel. + */ + if (lchan->state == LCHAN_S_BROKEN) { + 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); + if (dyn_ts_should_switch_to_pdch(lchan->ts)) + dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH); + return 0; + } + + if (lchan->state != LCHAN_S_REL_REQ && lchan->state != LCHAN_S_REL_ERR) + LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n", + gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + + do_lchan_free(lchan); + + /* + * Check Osmocom RSL CHAN ACT style dynamic TCH/F_TCH/H_PDCH TS for pending + * transitions in these cases: + * + * a) after PDCH was released due to switchover request, activate TCH. + * BSC initiated this switchover, so dyn.pchan_is != pchan_want and + * lchan->type has been set to the desired GSM_LCHAN_*. + * + * b) Voice call ended and a TCH is released. If the TS is now unused, + * switch to PDCH. Here still dyn.pchan_is == dyn.pchan_want because + * we're only just notified and may decide to switch to PDCH now. + */ + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + DEBUGP(DRSL, "%s Rx RSL Channel Release ack for lchan %u\n", + gsm_ts_and_pchan_name(ts), lchan->nr); + + /* (a) */ + if (ts->dyn.pchan_is != ts->dyn.pchan_want) + return dyn_ts_switchover_continue(ts); + + /* (b) */ + if (dyn_ts_should_switch_to_pdch(ts)) + return dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH); + } + + /* + * Put a dynamic TCH/F_PDCH channel back to PDCH mode iff it was + * released successfully. If in error, the PDCH ACT will follow after + * T3111 in error_timeout_cb(). + * + * Any state other than LCHAN_S_REL_ERR became LCHAN_S_NONE after above + * do_lchan_free(). Assert this, because that's what ensures a PDCH ACT + * on a TCH/F_PDCH TS in all cases. + * + * If GPRS is disabled, always skip the PDCH ACT. + */ + OSMO_ASSERT(lchan->state == LCHAN_S_NONE + || lchan->state == LCHAN_S_REL_ERR); + if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) + return 0; + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH + && lchan->state == LCHAN_S_NONE) + return rsl_ipacc_pdch_activate(ts, 1); + return 0; +} + +int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, uint8_t len, + uint8_t *ms_ident, uint8_t chan_needed, bool is_gprs) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + + 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; + + msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group); + msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2); + msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed); + + /* Ericsson wants to have this IE in case a paging message + * relates to packet paging */ + 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; + + return abis_rsl_sendmsg(msg); +} + +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 *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + uint8_t buf[GSM_MACBLOCK_LEN]; + + 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; + + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + msgb_tlv_put(msg, RSL_IE_IMM_ASS_INFO, len, val); + break; + default: + /* If phase 2, construct a FULL_IMM_ASS_INFO */ + pad_macblock(buf, val, len); + msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, GSM_MACBLOCK_LEN, + buf); + break; + } + + msg->dst = bts->c0->rsl_link; + return msg; +} + +/* Chapter 8.5.6 */ +int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val) +{ + struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val); + if (!msg) + return 1; + 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) +{ + struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val); + if (!msg) + return 1; + + /* 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); + + return abis_rsl_sendmsg(msg); +} + +/* 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 abis_rsl_dchan_hdr *dh; + + 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); + 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; + + return abis_rsl_sendmsg(msg); +} + + +/* Send "DATA REQUEST" message with given L3 Info payload */ +/* Chapter 8.3.1 */ +int rsl_data_request(struct msgb *msg, uint8_t link_id) +{ + if (msg->lchan == NULL) { + LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n"); + return -EINVAL; + } + + rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, gsm_lchan2chan_nr(msg->lchan), + link_id, 1); + + msg->dst = msg->lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/* Send "ESTABLISH REQUEST" message with given L3 Info payload */ +/* Chapter 8.3.1 */ +int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id) +{ + struct msgb *msg; + + msg = rsl_rll_simple(RSL_MT_EST_REQ, gsm_lchan2chan_nr(lchan), + link_id, 0); + msg->dst = lchan->ts->trx->rsl_link; + + DEBUGP(DRLL, "%s RSL RLL ESTABLISH REQ (link_id=0x%02x)\n", + gsm_lchan_name(lchan), link_id); + + return abis_rsl_sendmsg(msg); +} + +static void rsl_handle_release(struct gsm_lchan *lchan); + +/* Special work handler to handle missing RSL_MT_REL_CONF message from + * Nokia InSite BTS */ +static void lchan_rel_work_cb(void *data) +{ + struct gsm_lchan *lchan = data; + int sapi; + + for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + if (lchan->sapis[sapi] == LCHAN_SAPI_REL) + lchan->sapis[sapi] = LCHAN_SAPI_UNUSED; + } + rsl_handle_release(lchan); +} + +/* Chapter 8.3.7 Request the release of multiframe mode of RLL connection. + This is what higher layers should call. The BTS then responds with + RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE, + which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls + lchan_free() */ +int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id, + enum rsl_rel_mode release_mode) +{ + + struct msgb *msg; + + msg = rsl_rll_simple(RSL_MT_REL_REQ, gsm_lchan2chan_nr(lchan), + link_id, 0); + /* 0 is normal release, 1 is local end */ + msgb_tv_put(msg, RSL_IE_RELEASE_MODE, release_mode); + + /* FIXME: start some timer in case we don't receive a REL ACK ? */ + + msg->dst = lchan->ts->trx->rsl_link; + + DEBUGP(DRLL, "%s RSL RLL RELEASE REQ (link_id=0x%02x, reason=%u)\n", + gsm_lchan_name(lchan), link_id, release_mode); + + abis_rsl_sendmsg(msg); + + /* Do not wait for Nokia BTS to send the confirm. */ + if (is_nokia_bts(lchan->ts->trx->bts) + && lchan->ts->trx->bts->nokia.no_loc_rel_cnf + && release_mode == RSL_REL_LOCAL_END) { + DEBUGP(DRLL, "Scheduling release, becasuse Nokia InSite BTS does not send a RELease CONFirm.\n"); + lchan->sapis[link_id & 0x7] = LCHAN_SAPI_REL; + osmo_timer_setup(&lchan->rel_work, lchan_rel_work_cb, lchan); + osmo_timer_schedule(&lchan->rel_work, 0, 0); + } + + return 0; +} + +int rsl_lchan_mark_broken(struct gsm_lchan *lchan, const char *reason) +{ + LOGP(DRSL, LOGL_ERROR, "%s %s lchan broken: %s\n", + gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), reason); + rsl_lchan_set_state(lchan, LCHAN_S_BROKEN); + lchan->broken_reason = reason; + return 0; +} + +int rsl_lchan_set_state_with_log(struct gsm_lchan *lchan, enum gsm_lchan_state state, const char *file, unsigned line) +{ + if (lchan->state != state) + LOGPSRC(DRSL, LOGL_DEBUG, file, line, "%s state %s -> %s\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state), gsm_lchans_name(state)); + + lchan->state = state; + return 0; +} + +/* Chapter 8.4.2: Channel Activate Acknowledge */ +static int rsl_rx_chan_act_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts_trx_ts *ts = lchan->ts; + + /* BTS has confirmed channel activation, we now need + * to assign the activated channel to the MS */ + if (rslh->ie_chan != RSL_IE_CHAN_NR) + return -EINVAL; + + osmo_timer_del(&lchan->act_timer); + + if (lchan->state == LCHAN_S_BROKEN) { + int do_release = is_sysmobts_v2(ts->trx->bts); + LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK for broken channel. %s\n", + gsm_lchan_name(lchan), + do_release ? "Releasing it" : "Keeping it broken"); + if (do_release) { + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE); + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + /* + * lchan_act_tmr_cb() already called + * lchan_free() and cleared the lchan->type, so + * calling dyn_ts_switchover_complete() here + * would not have the desired effect of + * mimicking an activated lchan that we can + * release. Instead hack the dyn ts state to + * make sure that rsl_rx_rf_chan_rel_ack() will + * switch back to PDCH, i.e. have pchan_is == + * pchan_want, both != GSM_PCHAN_PDCH: + */ + ts->dyn.pchan_is = GSM_PCHAN_NONE; + ts->dyn.pchan_want = GSM_PCHAN_NONE; + } + rsl_rf_chan_release(msg->lchan, 0, SACCH_NONE); + } + return 0; + } + + if (lchan->state != LCHAN_S_ACT_REQ) + LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n", + gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + rsl_lchan_set_state(lchan, LCHAN_S_ACTIVE); + + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + dyn_ts_switchover_complete(lchan); + + if (lchan->rqd_ref) { + rsl_send_imm_assignment(lchan); + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + } + + send_lchan_signal(S_LCHAN_ACTIVATE_ACK, lchan, NULL); + + /* Update bts attributes inside the PCU */ + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH || + ts->pchan == GSM_PCHAN_TCH_F_PDCH || + ts->pchan == GSM_PCHAN_PDCH) + pcu_info_update(ts->trx->bts); + + return 0; +} + +/* Chapter 8.4.3: Channel Activate NACK */ +static int rsl_rx_chan_act_nack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + + osmo_timer_del(&msg->lchan->act_timer); + + rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]); + + if (msg->lchan->state == LCHAN_S_BROKEN) { + LOGP(DRSL, LOGL_ERROR, + "%s CHANNEL ACTIVATE NACK for broken channel.\n", + gsm_lchan_name(msg->lchan)); + return -1; + } + + LOGP(DRSL, LOGL_ERROR, "%s CHANNEL ACTIVATE NACK ", + gsm_lchan_name(msg->lchan)); + + /* BTS has rejected channel activation ?!? */ + if (dh->ie_chan != RSL_IE_CHAN_NR) + return -EINVAL; + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) { + const uint8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE); + print_rsl_cause(LOGL_ERROR, cause, + TLVP_LEN(&tp, RSL_IE_CAUSE)); + msg->lchan->error_cause = *cause; + if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC) { + rsl_lchan_mark_broken(msg->lchan, "NACK on activation"); + } else + rsl_rf_chan_release(msg->lchan, 1, SACCH_DEACTIVATE); + + } else { + rsl_lchan_mark_broken(msg->lchan, "NACK on activation no IE"); + } + + LOGPC(DRSL, LOGL_ERROR, "\n"); + + send_lchan_signal(S_LCHAN_ACTIVATE_NACK, msg->lchan, NULL); + return 0; +} + +/* Chapter 8.4.4: Connection Failure Indication */ +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 tlv_parsed tp; + uint8_t cause = 0; + + LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL in state %s ", + gsm_lchan_name(msg->lchan), + gsm_lchans_name(msg->lchan->state)); + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) { + print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE), + TLVP_LEN(&tp, RSL_IE_CAUSE)); + cause = *TLVP_VAL(&tp, RSL_IE_CAUSE); + } + + LOGPC(DRSL, LOGL_NOTICE, "\n"); + rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]); + + osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_RSL_CONN_FAIL, &cause); + + return 0; +} + +static void print_meas_rep_uni(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); +} + +static void print_meas_rep(struct gsm_lchan *lchan, struct gsm_meas_rep *mr) +{ + int i; + const char *name = ""; + + if (lchan && lchan->conn) { + if (lchan->conn->bsub) + name = bsc_subscr_name(lchan->conn->bsub); + else + name = lchan->name; + } + + DEBUGP(DMEAS, "[%s] MEASUREMENT RESULT NR=%d ", name, mr->nr); + + if (mr->flags & MEAS_REP_F_DL_DTX) + DEBUGPC(DMEAS, "DTXd "); + + print_meas_rep_uni(&mr->ul, "ul"); + DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power); + + if (mr->flags & MEAS_REP_F_MS_TO) + DEBUGPC(DMEAS, "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); + } + + if (mr->flags & MEAS_REP_F_UL_DTX) + DEBUGPC(DMEAS, "DTXu "); + if (mr->flags & MEAS_REP_F_BA1) + DEBUGPC(DMEAS, "BA1 "); + if (!(mr->flags & MEAS_REP_F_DL_VALID)) + DEBUGPC(DMEAS, "NOT VALID "); + else + print_meas_rep_uni(&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)); + } +} + +static struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan) +{ + struct gsm_meas_rep *meas_rep; + + meas_rep = &lchan->meas_rep[lchan->meas_rep_idx]; + memset(meas_rep, 0, sizeof(*meas_rep)); + meas_rep->lchan = lchan; + lchan->meas_rep_idx = (lchan->meas_rep_idx + 1) + % ARRAY_SIZE(lchan->meas_rep); + + return meas_rep; +} + +static int rsl_rx_meas_res(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + struct gsm_meas_rep *mr = lchan_next_meas_rep(msg->lchan); + uint8_t len; + const uint8_t *val; + int rc; + + /* check if this channel is actually active */ + /* FIXME: maybe this check should be way more generic/centralized */ + if (msg->lchan->state != LCHAN_S_ACTIVE) { + LOGP(DRSL, LOGL_DEBUG, "%s: MEAS RES for inactive channel\n", + gsm_lchan_name(msg->lchan)); + return 0; + } + + memset(mr, 0, sizeof(*mr)); + mr->lchan = msg->lchan; + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) || + !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) || + !TLVP_PRESENT(&tp, RSL_IE_BS_POWER)) { + LOGP(DRSL, LOGL_ERROR, "%s Measurement Report lacks mandatory IEs\n", + gsm_lchan_name(mr->lchan)); + return -EIO; + } + + /* Mandatory Parts */ + mr->nr = *TLVP_VAL(&tp, RSL_IE_MEAS_RES_NR); + + len = TLVP_LEN(&tp, RSL_IE_UPLINK_MEAS); + val = TLVP_VAL(&tp, RSL_IE_UPLINK_MEAS); + if (len >= 3) { + if (val[0] & 0x40) + mr->flags |= MEAS_REP_F_DL_DTX; + mr->ul.full.rx_lev = val[0] & 0x3f; + mr->ul.sub.rx_lev = val[1] & 0x3f; + mr->ul.full.rx_qual = val[2]>>3 & 0x7; + mr->ul.sub.rx_qual = val[2] & 0x7; + } + + mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + + /* Optional Parts */ + if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET)) { + /* According to 3GPP TS 48.058 § MS Timing Offset = Timing Offset field - 63 */ + mr->ms_timing_offset = *TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET) - 63; + mr->flags |= MEAS_REP_F_MS_TO; + } + + if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) { + struct e1inp_sign_link *sign_link = msg->dst; + + val = TLVP_VAL(&tp, RSL_IE_L1_INFO); + mr->flags |= MEAS_REP_F_MS_L1; + mr->ms_l1.pwr = ms_pwr_dbm(sign_link->trx->bts->band, val[0] >> 3); + 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 */ + if (msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11 + || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) + mr->ms_l1.ta >>= 2; + /* store TA for next assignment/handover */ + mr->lchan->rqd_ta = mr->ms_l1.ta; + } + if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { + msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO); + rc = gsm48_parse_meas_rep(mr, msg); + if (rc < 0) + return rc; + } + + mr->lchan->meas_rep_count++; + mr->lchan->meas_rep_last_seen_nr = mr->nr; + LOGP(DRSL, LOGL_DEBUG, "%s: meas_rep_count++=%d meas_rep_last_seen_nr=%u\n", + gsm_lchan_name(mr->lchan), mr->lchan->meas_rep_count, mr->lchan->meas_rep_last_seen_nr); + + print_meas_rep(msg->lchan, mr); + + send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr); + + return 0; +} + +/* Chapter 8.4.7 */ +static int rsl_rx_hando_det(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + + DEBUGP(DRSL, "%s HANDOVER DETECT ", gsm_lchan_name(msg->lchan)); + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY)) + DEBUGPC(DRSL, "access delay = %u\n", + *TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY)); + else + DEBUGPC(DRSL, "\n"); + + send_lchan_signal(S_LCHAN_HANDOVER_DETECT, msg->lchan, NULL); + + return 0; +} + +static bool lchan_may_change_pdch(struct gsm_lchan *lchan, bool pdch_act) +{ + struct gsm_bts_trx_ts *ts; + + OSMO_ASSERT(lchan); + + ts = lchan->ts; + OSMO_ASSERT(ts); + OSMO_ASSERT(ts->trx); + OSMO_ASSERT(ts->trx->bts); + + if (lchan->ts->pchan != GSM_PCHAN_TCH_F_PDCH) { + LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK" + " for channel that is no TCH/F_PDCH\n", + gsm_lchan_name(lchan), + gsm_pchan_name(ts->pchan), + pdch_act? "ACT" : "DEACT"); + return false; + } + + if (lchan->state != LCHAN_S_NONE) { + LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK" + " in unexpected state: %s\n", + gsm_lchan_name(lchan), + gsm_pchan_name(ts->pchan), + pdch_act? "ACT" : "DEACT", + gsm_lchans_name(lchan->state)); + return false; + } + return true; +} + +static int rsl_rx_pdch_act_ack(struct msgb *msg) +{ + if (!lchan_may_change_pdch(msg->lchan, true)) + return -EINVAL; + + msg->lchan->ts->flags |= TS_F_PDCH_ACTIVE; + msg->lchan->ts->flags &= ~TS_F_PDCH_ACT_PENDING; + + return 0; +} + +static int rsl_rx_pdch_deact_ack(struct msgb *msg) +{ + if (!lchan_may_change_pdch(msg->lchan, false)) + return -EINVAL; + + msg->lchan->ts->flags &= ~TS_F_PDCH_ACTIVE; + msg->lchan->ts->flags &= ~TS_F_PDCH_DEACT_PENDING; + + rsl_chan_activate_lchan(msg->lchan, msg->lchan->dyn.act_type, + msg->lchan->dyn.ho_ref); + + return 0; +} + +static int abis_rsl_rx_dchan(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + int rc = 0; + char *ts_name; + struct e1inp_sign_link *sign_link = msg->dst; + + msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr, + "Abis RSL rx DCHAN: "); + if (!msg->lchan) + return -1; + ts_name = gsm_lchan_name(msg->lchan); + + switch (rslh->c.msg_type) { + case RSL_MT_CHAN_ACTIV_ACK: + DEBUGP(DRSL, "%s CHANNEL ACTIVATE ACK\n", ts_name); + rc = rsl_rx_chan_act_ack(msg); + count_codecs(sign_link->trx->bts, msg->lchan); + break; + case RSL_MT_CHAN_ACTIV_NACK: + rc = rsl_rx_chan_act_nack(msg); + break; + case RSL_MT_CONN_FAIL: + rc = rsl_rx_conn_fail(msg); + break; + case RSL_MT_MEAS_RES: + rc = rsl_rx_meas_res(msg); + break; + case RSL_MT_HANDO_DET: + rc = rsl_rx_hando_det(msg); + break; + case RSL_MT_RF_CHAN_REL_ACK: + rc = rsl_rx_rf_chan_rel_ack(msg->lchan); + break; + case RSL_MT_MODE_MODIFY_ACK: + count_codecs(sign_link->trx->bts, msg->lchan); + DEBUGP(DRSL, "%s CHANNEL MODE MODIFY ACK\n", ts_name); + break; + case RSL_MT_MODE_MODIFY_NACK: + LOGP(DRSL, LOGL_ERROR, "%s CHANNEL MODE MODIFY NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_MODE_MODIFY_NACK]); + break; + case RSL_MT_IPAC_PDCH_ACT_ACK: + DEBUGP(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name); + rc = rsl_rx_pdch_act_ack(msg); + break; + case RSL_MT_IPAC_PDCH_ACT_NACK: + LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH ACT NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + break; + case RSL_MT_IPAC_PDCH_DEACT_ACK: + DEBUGP(DRSL, "%s IPAC PDCH DEACT ACK\n", ts_name); + rc = rsl_rx_pdch_deact_ack(msg); + break; + case RSL_MT_IPAC_PDCH_DEACT_NACK: + LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH DEACT NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + 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: + LOGP(DRSL, LOGL_NOTICE, "%s Unimplemented Abis RSL DChan " + "msg 0x%02x\n", ts_name, rslh->c.msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "%s unknown Abis RSL DChan msg 0x%02x\n", + ts_name, rslh->c.msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + return -EINVAL; + } + + return rc; +} + +static int rsl_rx_error_rep(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + struct tlv_parsed tp; + struct e1inp_sign_link *sign_link = msg->dst; + + LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(sign_link->trx)); + + rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh)); + + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) + print_rsl_cause(LOGL_ERROR, TLVP_VAL(&tp, RSL_IE_CAUSE), + TLVP_LEN(&tp, RSL_IE_CAUSE)); + + LOGPC(DRSL, LOGL_ERROR, "\n"); + + return 0; +} + +static int abis_rsl_rx_trx(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + struct e1inp_sign_link *sign_link = msg->dst; + int rc = 0; + + switch (rslh->msg_type) { + case RSL_MT_ERROR_REPORT: + rc = rsl_rx_error_rep(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)); + break; + case RSL_MT_OVERLOAD: + /* indicate CCCH / ACCH / processor overload */ + LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n", + gsm_trx_name(sign_link->trx)); + break; + case 0x42: /* Nokia specific: SI End ACK */ + LOGP(DRSL, LOGL_INFO, "Nokia SI End ACK\n"); + break; + case 0x43: /* Nokia specific: SI End NACK */ + LOGP(DRSL, LOGL_INFO, "Nokia SI End NACK\n"); + break; + 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]); + return -EINVAL; + } + return rc; +} + +/* If T3101 expires, we never received a response to IMMEDIATE ASSIGN */ +static void t3101_expired(void *data) +{ + struct gsm_lchan *lchan = data; + LOGP(DRSL, LOGL_NOTICE, + "%s T3101 expired: no response to IMMEDIATE ASSIGN\n", + gsm_lchan_name(lchan)); + rsl_rf_chan_release(lchan, 1, SACCH_DEACTIVATE); +} + +/* If T3111 expires, we will send the RF Channel Request */ +static void t3111_expired(void *data) +{ + struct gsm_lchan *lchan = data; + LOGP(DRSL, LOGL_NOTICE, + "%s T3111 expired: releasing RF Channel\n", + gsm_lchan_name(lchan)); + rsl_rf_chan_release(lchan, 0, SACCH_NONE); +} + +/* If T3109 expires the MS has not send a UA/UM do the error release */ +static void t3109_expired(void *data) +{ + struct gsm_lchan *lchan = data; + + LOGP(DRSL, LOGL_ERROR, + "%s SACCH deactivation timeout.\n", gsm_lchan_name(lchan)); + rsl_rf_chan_release(lchan, 1, SACCH_NONE); +} + +/* Format an IMM ASS REJ according to 04.08 Chapter 9.1.20 */ +static int rsl_send_imm_ass_rej(struct gsm_bts *bts, + struct gsm48_req_ref *rqd_ref, + uint8_t wait_ind) +{ + uint8_t buf[GSM_MACBLOCK_LEN]; + struct gsm48_imm_ass_rej *iar = (struct gsm48_imm_ass_rej *)buf; + + /* create IMMEDIATE ASSIGN REJECT 04.08 message */ + memset(iar, 0, sizeof(*iar)); + iar->proto_discr = GSM48_PDISC_RR; + iar->msg_type = GSM48_MT_RR_IMM_ASS_REJ; + iar->page_mode = GSM48_PM_SAME; + + /* + * Set all request references and wait indications to the same value. + * 3GPP TS 44.018 v4.5.0 release 4 (section 9.1.20.2) requires that + * we duplicate reference and wait indication to fill the message. + * The BTS will aggregate up to 4 of our ASS REJ messages if possible. + */ + memcpy(&iar->req_ref1, rqd_ref, sizeof(iar->req_ref1)); + iar->wait_ind1 = wait_ind; + memcpy(&iar->req_ref2, rqd_ref, sizeof(iar->req_ref2)); + iar->wait_ind2 = wait_ind; + memcpy(&iar->req_ref3, rqd_ref, sizeof(iar->req_ref3)); + iar->wait_ind3 = wait_ind; + memcpy(&iar->req_ref4, rqd_ref, sizeof(iar->req_ref4)); + iar->wait_ind4 = wait_ind; + + /* we need to subtract 1 byte from sizeof(*iar) since ia includes the l2_plen field */ + iar->l2_plen = GSM48_LEN2PLEN((sizeof(*iar)-1)); + + return rsl_imm_assign_cmd(bts, sizeof(*iar), (uint8_t *) iar); +} + +/* Handle packet channel rach requests */ +static int rsl_rx_pchan_rqd(struct msgb *msg, struct gsm_bts *bts) +{ + struct gsm48_req_ref *rqd_ref; + struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg); + rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1]; + uint8_t ra = rqd_ref->ra; + uint8_t t1, t2, t3; + uint32_t fn; + uint8_t rqd_ta; + uint8_t is_11bit; + + /* Process rach request and forward contained information to PCU */ + if (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",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_hdr->data[sizeof(struct gsm48_req_ref)+2]; + } + + return pcu_tx_rach_ind(bts, rqd_ta, ra, fn, is_11bit, + GSM_L1_BURST_TYPE_ACCESS_0); +} + +/* MS has requested a channel on the RACH */ +static int rsl_rx_chan_rqd(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg); + struct gsm48_req_ref *rqd_ref; + enum gsm_chan_t lctype; + enum gsm_chreq_reason_t chreq_reason; + struct gsm_lchan *lchan; + uint8_t rqd_ta; + + uint16_t arfcn; + uint8_t subch; + + /* parse request reference to be used in immediate assign */ + if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE) + return -EINVAL; + + rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1]; + + /* parse access delay and use as TA */ + if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY) + return -EINVAL; + rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2]; + + /* Determine channel request cause code */ + chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci); + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: reason: %s (ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n", + msg->lchan->ts->trx->bts->nr, + get_value_string(gsm_chreq_descs, chreq_reason), + rqd_ref->ra, bts->network->neci, chreq_reason); + + /* Handle PDCH related rach requests (in case of BSC-co-located-PCU */ + if (chreq_reason == GSM_CHREQ_REASON_PDCH) + return rsl_rx_pchan_rqd(msg, bts); + + /* determine channel type (SDCCH/TCH_F/TCH_H) based on + * request reference RA */ + lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra); + + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL]); + + /* check availability / allocate channel + * + * - First try to allocate SDCCH. + * - If SDCCH is not available, try whatever MS requested, if not SDCCH. + * - If there is still no channel available, reject channel request. + * + * lchan_alloc() possibly tries to allocate larger lchans. + * + * Note: If the MS requests not TCH/H, we don't know if the phone + * supports TCH/H, so we must assign TCH/F or SDCCH. + */ + lchan = lchan_alloc(bts, GSM_LCHAN_SDCCH, 0); + if (!lchan && lctype != GSM_LCHAN_SDCCH) { + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s " + "0x%x, retrying with %s\n", + msg->lchan->ts->trx->bts->nr, + gsm_lchant_name(GSM_LCHAN_SDCCH), rqd_ref->ra, + gsm_lchant_name(lctype)); + lchan = lchan_alloc(bts, lctype, 0); + } + if (!lchan) { + uint8_t wait_ind; + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s 0x%x\n", + msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra); + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL]); + if (bts->T3122) + wait_ind = bts->T3122; + else if (bts->network->T3122) + wait_ind = bts->network->T3122 & 0xff; + else + wait_ind = GSM_T3122_DEFAULT; + /* The BTS will gather multiple CHAN RQD and reject up to 4 MS at the same time. */ + rsl_send_imm_ass_rej(bts, rqd_ref, wait_ind); + return 0; + } + + /* + * Expecting lchan state to be NONE, except for dyn TS in PDCH mode. + * Those are expected to be ACTIVE: the PDCH release will be sent from + * rsl_chan_activate_lchan() below. + */ + if (lchan->state != LCHAN_S_NONE + && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH + && lchan->state == LCHAN_S_ACTIVE)) + LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel " + "in state %s\n", gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + + /* save the RACH data as we need it after the CHAN ACT ACK */ + lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref); + if (!lchan->rqd_ref) { + LOGP(DRSL, LOGL_ERROR, "Failed to allocate gsm48_req_ref.\n"); + lchan_free(lchan); + return -ENOMEM; + } + + memcpy(lchan->rqd_ref, rqd_ref, sizeof(*rqd_ref)); + lchan->rqd_ta = rqd_ta; + + arfcn = lchan->ts->trx->arfcn; + subch = lchan->nr; + + lchan->encr.alg_id = RSL_ENC_ALG_A5(0); /* no encryption */ + lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power); + lchan->bs_power = 0; /* 0dB reduction, output power = Pn */ + lchan->rsl_cmode = RSL_CMOD_SPD_SIGN; + lchan->tch_mode = GSM48_CMODE_SIGN; + + /* Start another timer or assume the BTS sends a ACK/NACK? */ + osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan); + osmo_timer_schedule(&lchan->act_timer, 4, 0); + + DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s " + "r=%s ra=0x%02x ta=%d\n", gsm_lchan_name(lchan), arfcn, subch, + gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason), + rqd_ref->ra, rqd_ta); + + rsl_chan_activate_lchan(lchan, RSL_ACT_INTRA_IMM_ASS, 0); + + return 0; +} + +static int rsl_send_imm_assignment(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + uint8_t buf[GSM_MACBLOCK_LEN]; + struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf; + + /* create IMMEDIATE ASSIGN 04.08 messge */ + memset(ia, 0, sizeof(*ia)); + /* we set ia->l2_plen once we know the length of the MA below */ + 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); + + /* use request reference extracted from CHAN_RQD */ + memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref)); + ia->timing_advance = lchan->rqd_ta; + if (!lchan->ts->hopping.enabled) { + ia->mob_alloc_len = 0; + } else { + ia->mob_alloc_len = lchan->ts->hopping.ma_len; + memcpy(ia->mob_alloc, lchan->ts->hopping.ma_data, ia->mob_alloc_len); + } + /* we need to subtract 1 byte from sizeof(*ia) since ia includes the l2_plen field */ + ia->l2_plen = GSM48_LEN2PLEN((sizeof(*ia)-1) + ia->mob_alloc_len); + + /* Start timer T3101 to wait for GSM48_MT_RR_PAG_RESP */ + osmo_timer_setup(&lchan->T3101, t3101_expired, lchan); + osmo_timer_schedule(&lchan->T3101, bts->network->T3101, 0); + + /* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */ + return rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia); +} + +/* current load on the CCCH */ +static int rsl_rx_ccch_load(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct abis_rsl_dchan_hdr *rslh = msgb_l2(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; + + 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; + } + 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) { + sd.rach_slot_count = rslh->data[2] << 8 | rslh->data[3]; + sd.rach_busy_count = rslh->data[4] << 8 | rslh->data[5]; + sd.rach_access_count = rslh->data[6] << 8 | rslh->data[7]; + osmo_signal_dispatch(SS_CCCH, S_CCCH_RACH_LOAD, &sd); + } + break; + default: + break; + } + + return 0; +} + +/* Ericsson specific: Immediate Assign Sent */ +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; + + LOGP(DRSL, LOGL_INFO, "IMM.ass sent\n"); + msgb_pull(msg, sizeof(*dh)); + + /* FIXME: Move to TLV once we support defining TV types with V having len != 1 byte */ + if(msg->len < 5) + LOGP(DRSL, LOGL_ERROR, "short IMM.ass sent message!\n"); + else if(msg->data[0] != RSL_IE_ERIC_MOBILE_ID) + 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); + } + return 0; +} + +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); + int rc = 0; + + msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr, + "Abis RSL rx CCHAN: "); + + switch (rslh->c.msg_type) { + case RSL_MT_CHAN_RQD: + /* MS has requested a channel on the RACH */ + rc = rsl_rx_chan_rqd(msg); + break; + case RSL_MT_CCCH_LOAD_IND: + /* current load on the CCCH */ + rc = rsl_rx_ccch_load(msg); + break; + case RSL_MT_DELETE_IND: + /* CCCH overloaded, IMM_ASSIGN was dropped */ + case RSL_MT_CBCH_LOAD_IND: + /* current load on the CBCH */ + LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message " + "type %s\n", rsl_msg_name(rslh->c.msg_type)); + break; + case RSL_MT_ERICSSON_IMM_ASS_SENT: + rc = rsl_rx_ericsson_imm_assign_sent(msg); + break; + 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]); + return -EINVAL; + } + + return rc; +} + +static int rsl_rx_rll_err_ind(struct msgb *msg) +{ + struct tlv_parsed tp; + 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 (!TLVP_PRESENT(&tp, RSL_IE_RLM_CAUSE)) { + LOGP(DRLL, LOGL_ERROR, + "%s ERROR INDICATION without mandantory cause.\n", + gsm_lchan_name(msg->lchan)); + return -1; + } + + rlm_cause = *TLVP_VAL(&tp, RSL_IE_RLM_CAUSE); + LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s in state=%s\n", + gsm_lchan_name(msg->lchan), + rsl_rlm_cause_name(rlm_cause), + gsm_lchans_name(msg->lchan->state)); + + rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND); + + if (rlm_cause == RLL_CAUSE_T200_EXPIRED) { + rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR]); + return rsl_rf_chan_release_err(msg->lchan); + } + + return 0; +} + +static void rsl_handle_release(struct gsm_lchan *lchan) +{ + int sapi; + struct gsm_bts *bts; + + /* + * Maybe only one link/SAPI was releasd or the error handling + * was activated. Just return now and let the other code handle + * it. + */ + if (lchan->state != LCHAN_S_REL_REQ) + return; + + for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) + continue; + LOGP(DRSL, LOGL_DEBUG, "%s waiting for SAPI=%d to be released.\n", + gsm_lchan_name(lchan), sapi); + return; + } + + + /* Stop T3109 and wait for T3111 before re-using the channel */ + osmo_timer_del(&lchan->T3109); + osmo_timer_setup(&lchan->T3111, t3111_expired, lchan); + bts = lchan->ts->trx->bts; + osmo_timer_schedule(&lchan->T3111, bts->network->T3111, 0); +} + +/* ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST + 0x02, 0x06, + 0x01, 0x20, + 0x02, 0x00, + 0x0b, 0x00, 0x0f, 0x05, 0x08, ... */ + +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; + char *ts_name; + uint8_t sapi = rllh->link_id & 7; + + msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, + "Abis RSL rx RLL: "); + ts_name = gsm_lchan_name(msg->lchan); + DEBUGP(DRLL, "%s SAPI=%u ", ts_name, sapi); + + switch (rllh->c.msg_type) { + case RSL_MT_DATA_IND: + DEBUGPC(DRLL, "DATA INDICATION\n"); + if (msgb_l2len(msg) > + sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && + rllh->data[0] == RSL_IE_L3_INFO) { + msg->l3h = &rllh->data[3]; + return gsm0408_rcvmsg(msg, rllh->link_id); + } + break; + case RSL_MT_EST_IND: + DEBUGPC(DRLL, "ESTABLISH INDICATION\n"); + /* lchan is established, stop T3101 */ + + /* Note: By definition the first Establish Indication must + * happen first on SAPI 0, once the connection on SAPI 0 is + * made, parallel connections on other SAPIs are permitted */ + if (sapi != 0 && msg->lchan->sapis[0] != LCHAN_SAPI_MS) { + LOGP(DRLL, LOGL_NOTICE, "MS attempted to establish DCCH on SAPI=%d (expected SAPI=0)\n", + rllh->link_id & 0x7); + + /* Note: We do not need to close the channel, + * since we might still get a proper Establish Ind. + * If not, T3101 will close the channel on timeout. */ + break; + } + + /* Note: Check for MF SACCH on SAPI=0 (not permitted). By + * definition we establish a link in multiframe (MF) mode. + * (see also 3GPP TS 48.058, chapter 3.1. However, on SAPI=0 + * SACCH is only allowed in UL mode, not in MF mode. + * (see also 3GPP TS 44.005, figure 5) So we have to drop such + * Establish Indications */ + if (sapi == 0 && (rllh->link_id >> 6 & 0x03) == 1) { + LOGP(DRLL, LOGL_NOTICE, "MS attempted to establish an SACCH in MF mode on SAPI=0 (not permitted)\n"); + + /* Note: We do not need to close the channel, + * since we might still get a proper Establish Ind. + * If not, T3101 will close the channel on timeout. */ + break; + } + + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_MS; + osmo_timer_del(&msg->lchan->T3101); + if (msgb_l2len(msg) > + sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && + rllh->data[0] == RSL_IE_L3_INFO) { + msg->l3h = &rllh->data[3]; + return gsm0408_rcvmsg(msg, rllh->link_id); + } + break; + case RSL_MT_EST_CONF: + DEBUGPC(DRLL, "ESTABLISH CONFIRM\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_NET; + rll_indication(msg->lchan, rllh->link_id, + BSC_RLLR_IND_EST_CONF); + break; + case RSL_MT_REL_IND: + /* BTS informs us of having received DISC from MS */ + DEBUGPC(DRLL, "RELEASE INDICATION\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; + rll_indication(msg->lchan, rllh->link_id, + BSC_RLLR_IND_REL_IND); + rsl_handle_release(msg->lchan); + /* if it was the main signalling link, let the subscr_conn_fsm know */ + if (msg->lchan->conn && sapi == 0 && (rllh->link_id >> 6) == 0) + osmo_fsm_inst_dispatch(msg->lchan->conn->fi, GSCON_EV_RLL_REL_IND, msg); + break; + case RSL_MT_REL_CONF: + /* BTS informs us of having received UA from MS, + * in response to DISC that we've sent earlier */ + DEBUGPC(DRLL, "RELEASE CONFIRMATION\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; + rsl_handle_release(msg->lchan); + break; + case RSL_MT_ERROR_IND: + DEBUGPC(DRLL, "ERROR INDICATION\n"); + rc = rsl_rx_rll_err_ind(msg); + break; + case RSL_MT_UNIT_DATA_IND: + DEBUGPC(DRLL, "UNIT DATA INDICATION\n"); + LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message " + "type 0x%02x\n", rllh->c.msg_type); + break; + default: + DEBUGPC(DRLL, "UNKNOWN\n"); + LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message " + "type 0x%02x\n", rllh->c.msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + } + return rc; +} + +static uint8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan) +{ + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x00; + case GSM_LCHAN_TCH_H: + return 0x03; + default: + break; + } + break; + case GSM48_CMODE_SPEECH_EFR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x01; + /* there's no half-rate EFR */ + default: + break; + } + break; + case GSM48_CMODE_SPEECH_AMR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x02; + case GSM_LCHAN_TCH_H: + return 0x05; + default: + break; + } + break; + default: + break; + } + LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for " + "tch_mode == 0x%02x\n", lchan->tch_mode); + return 0; +} + +static uint8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan) +{ + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return RTP_PT_GSM_FULL; + case GSM_LCHAN_TCH_H: + return RTP_PT_GSM_HALF; + default: + break; + } + break; + case GSM48_CMODE_SPEECH_EFR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return RTP_PT_GSM_EFR; + /* there's no half-rate EFR */ + default: + break; + } + break; + case GSM48_CMODE_SPEECH_AMR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + return RTP_PT_AMR; + default: + break; + } + break; + default: + break; + } + LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for " + "tch_mode == 0x%02x\n & lchan_type == %d", + lchan->tch_mode, lchan->type); + return 0; +} + +/* ip.access specific RSL extensions */ +static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv) +{ + struct in_addr ip; + uint16_t port, conn_id; + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) { + ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_LOCAL_IP); + DEBUGPC(DRSL, "LOCAL_IP=%s ", inet_ntoa(ip)); + lchan->abis_ip.bound_ip = ntohl(ip.s_addr); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_PORT)) { + port = tlvp_val16_unal(tv, RSL_IE_IPAC_LOCAL_PORT); + port = ntohs(port); + DEBUGPC(DRSL, "LOCAL_PORT=%u ", port); + lchan->abis_ip.bound_port = port; + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_CONN_ID)) { + conn_id = tlvp_val16_unal(tv, RSL_IE_IPAC_CONN_ID); + conn_id = ntohs(conn_id); + DEBUGPC(DRSL, "CON_ID=%u ", conn_id); + lchan->abis_ip.conn_id = conn_id; + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_RTP_PAYLOAD2)) { + lchan->abis_ip.rtp_payload2 = + *TLVP_VAL(tv, RSL_IE_IPAC_RTP_PAYLOAD2); + DEBUGPC(DRSL, "RTP_PAYLOAD2=0x%02x ", + lchan->abis_ip.rtp_payload2); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) { + lchan->abis_ip.speech_mode = + *TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE); + DEBUGPC(DRSL, "speech_mode=0x%02x ", + lchan->abis_ip.speech_mode); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) { + ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_REMOTE_IP); + DEBUGPC(DRSL, "REMOTE_IP=%s ", inet_ntoa(ip)); + lchan->abis_ip.connect_ip = ntohl(ip.s_addr); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_PORT)) { + port = tlvp_val16_unal(tv, RSL_IE_IPAC_REMOTE_PORT); + port = ntohs(port); + DEBUGPC(DRSL, "REMOTE_PORT=%u ", port); + lchan->abis_ip.connect_port = port; + } + + DEBUGPC(DRSL, "\n"); +} + +/*! \brief Issue IPA RSL CRCX to configure RTP on BTS side + * \param[in] lchan Logical Channel for which we issue CRCX + */ +int rsl_ipacc_crcx(struct gsm_lchan *lchan) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + + 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); + + /* 0x1- == receive-only, 0x-1 == EFR codec */ + lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan); + lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan); + 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); + + DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n", + gsm_lchan_name(lchan), lchan->abis_ip.speech_mode, + lchan->abis_ip.rtp_payload); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/*! \brief Issue IPA RSL MDCX to configure MGW-side of RTP + * \param[in] lchan Logical Channel for which we issue MDCX + * \param[in] ip Remote (MGW) IP address for RTP + * \param[in] port Remote (MGW) UDP port number for RTP + * \param[in] rtp_payload2 Contents of RTP PAYLOAD 2 IE + */ +int rsl_ipacc_mdcx(struct gsm_lchan *lchan, uint32_t ip, uint16_t port, + uint8_t rtp_payload2) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + uint32_t *att_ip; + struct in_addr ia; + + 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); + + /* we need to store these now as MDCX_ACK does not return them :( */ + lchan->abis_ip.rtp_payload2 = rtp_payload2; + lchan->abis_ip.connect_port = port; + lchan->abis_ip.connect_ip = ip; + + /* 0x0- == both directions, 0x-1 == EFR codec */ + lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan); + lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan); + + ia.s_addr = htonl(ip); + DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d " + "CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan), + inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2, + lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode); + + 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(ip)); + *att_ip = ia.s_addr; + msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port); + 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 (rtp_payload2) + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +static bool check_gprs_enabled(struct gsm_bts_trx_ts *ts) +{ + if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) { + LOGP(DRSL, LOGL_NOTICE, "%s: GPRS mode is 'none': not activating PDCH.\n", + gsm_ts_and_pchan_name(ts)); + return false; + } + return true; +} + +int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + uint8_t msg_type; + + if (ts->flags & TS_F_PDCH_PENDING_MASK) { + LOGP(DRSL, LOGL_ERROR, + "%s PDCH %s requested, but a PDCH%s%s is still pending\n", + gsm_ts_name(ts), + act ? "ACT" : "DEACT", + ts->flags & TS_F_PDCH_ACT_PENDING? " ACT" : "", + ts->flags & TS_F_PDCH_DEACT_PENDING? " DEACT" : ""); + return -EINVAL; + } + + if (act){ + if (!check_gprs_enabled(ts)) + return -ENOTSUP; + + msg_type = RSL_MT_IPAC_PDCH_ACT; + ts->flags |= TS_F_PDCH_ACT_PENDING; + } else { + msg_type = RSL_MT_IPAC_PDCH_DEACT; + ts->flags |= TS_F_PDCH_DEACT_PENDING; + } + /* TODO add timeout to cancel PDCH DE/ACT */ + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, msg_type); + dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; + dh->chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0); + + DEBUGP(DRSL, "%s IPAC PDCH %sACT\n", gsm_ts_name(ts), + act ? "" : "DE"); + + msg->dst = ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tv; + struct gsm_lchan *lchan = msg->lchan; + + /* the BTS has acknowledged a local bind, it now tells us the IP + * 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 (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) || + !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) || + !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) { + LOGP(DRSL, LOGL_NOTICE, "mandatory IE missing"); + return -EINVAL; + } + + ipac_parse_rtp(lchan, &tv); + + osmo_signal_dispatch(SS_ABISIP, S_ABISIP_CRCX_ACK, msg->lchan); + + return 0; +} + +static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tv; + struct gsm_lchan *lchan = msg->lchan; + + /* the BTS has acknowledged a remote connect request and + * 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)); + ipac_parse_rtp(lchan, &tv); + osmo_signal_dispatch(SS_ABISIP, S_ABISIP_MDCX_ACK, msg->lchan); + + return 0; +} + +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 (TLVP_PRESENT(&tv, RSL_IE_CAUSE)) + print_rsl_cause(LOGL_DEBUG, TLVP_VAL(&tv, RSL_IE_CAUSE), + TLVP_LEN(&tv, RSL_IE_CAUSE)); + + osmo_signal_dispatch(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan); + + return 0; +} + +static int abis_rsl_rx_ipacc(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + char *ts_name; + int rc = 0; + + msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, + "Abis RSL rx IPACC: "); + ts_name = gsm_lchan_name(msg->lchan); + + switch (rllh->c.msg_type) { + case RSL_MT_IPAC_CRCX_ACK: + DEBUGP(DRSL, "%s IPAC_CRCX_ACK ", ts_name); + rc = abis_rsl_rx_ipacc_crcx_ack(msg); + break; + case RSL_MT_IPAC_CRCX_NACK: + /* somehow the BTS was unable to bind the lchan to its local + * port?!? */ + LOGP(DRSL, LOGL_ERROR, "%s IPAC_CRCX_NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + break; + case RSL_MT_IPAC_MDCX_ACK: + /* the BTS tells us that a connect operation was successful */ + DEBUGP(DRSL, "%s IPAC_MDCX_ACK ", ts_name); + rc = abis_rsl_rx_ipacc_mdcx_ack(msg); + break; + case RSL_MT_IPAC_MDCX_NACK: + /* somehow the BTS was unable to connect the lchan to a remote + * port */ + LOGP(DRSL, LOGL_ERROR, "%s IPAC_MDCX_NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + break; + case RSL_MT_IPAC_DLCX_IND: + DEBUGP(DRSL, "%s IPAC_DLCX_IND ", ts_name); + rc = abis_rsl_rx_ipacc_dlcx_ind(msg); + break; + default: + LOGP(DRSL, 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]); + break; + } + + return rc; +} + +int dyn_ts_switchover_start(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config to_pchan) +{ + int ss; + int rc = -EIO; + + OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); + + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + LOGP(DRSL, LOGL_ERROR, + "%s: Attempt to switch dynamic channel to %s," + " but is already in switchover.\n", + gsm_ts_and_pchan_name(ts), + gsm_pchan_name(to_pchan)); + return ts->dyn.pchan_want == to_pchan? 0 : -EAGAIN; + } + + if (ts->dyn.pchan_is == to_pchan) { + LOGP(DRSL, LOGL_INFO, + "%s %s Already is in %s mode, cannot switchover.\n", + gsm_ts_name(ts), gsm_pchan_name(ts->pchan), + gsm_pchan_name(to_pchan)); + return -EINVAL; + } + + /* Paranoia: let's make sure all is indeed released. */ + for (ss = 0; ss < ts_subslots(ts); ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->state != LCHAN_S_NONE) { + LOGP(DRSL, LOGL_ERROR, + "%s Attempt to switch dynamic channel to %s," + " but is not fully released.\n", + gsm_ts_and_pchan_name(ts), + gsm_pchan_name(to_pchan)); + return -EAGAIN; + } + } + + if (to_pchan == GSM_PCHAN_PDCH && !check_gprs_enabled(ts)) + return -ENOTSUP; + + DEBUGP(DRSL, "%s starting switchover to %s\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(to_pchan)); + + /* Record that we're busy switching. */ + ts->dyn.pchan_want = to_pchan; + + /* + * To switch from PDCH, we need to initiate the release from the BSC + * side. dyn_ts_switchover_continue() will be called from + * rsl_rx_rf_chan_rel_ack(). PDCH is always on lchan[0]. + */ + if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) { + rsl_lchan_set_state(ts->lchan, LCHAN_S_REL_REQ); + rc = rsl_rf_chan_release(ts->lchan, 0, SACCH_NONE); + if (rc) { + LOGP(DRSL, LOGL_ERROR, + "%s RSL RF Chan Release failed\n", + gsm_ts_and_pchan_name(ts)); + return dyn_ts_switchover_failed(ts, rc); + } + return 0; + } + + /* + * To switch from TCH/F and TCH/H pchans, this has been called from + * rsl_rx_rf_chan_rel_ack(), i.e. release is complete. Go ahead and + * activate as new type. This will always be PDCH. + */ + return dyn_ts_switchover_continue(ts); +} + +static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts) +{ + int rc; + uint8_t act_type; + uint8_t ho_ref; + int ss; + struct gsm_lchan *lchan; + + OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); + DEBUGP(DRSL, "%s switchover: release complete," + " activating new pchan type\n", + gsm_ts_and_pchan_name(ts)); + + if (ts->dyn.pchan_is == ts->dyn.pchan_want) { + LOGP(DRSL, LOGL_ERROR, + "%s Requested to switchover dynamic channel to the" + " same type it is already in.\n", + gsm_ts_and_pchan_name(ts)); + return 0; + } + + for (ss = 0; ss < ts_subslots(ts); ss++) { + lchan = &ts->lchan[ss]; + if (lchan->rqd_ref) { + LOGP(DRSL, LOGL_ERROR, + "%s During dyn TS switchover, expecting no" + " Request Reference to be pending. Discarding!\n", + gsm_lchan_name(lchan)); + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + } + } + + /* + * When switching pchan modes, all lchans are unused. So always + * activate whatever wants to be activated on the first lchan. (We + * wouldn't remember to use lchan[1] across e.g. a PDCH deact anyway) + */ + lchan = ts->lchan; + + /* + * For TCH/x, the lchan->type has been set in lchan_alloc(), but it may + * have been lost during channel release due to dynamic switchover. + * + * For PDCH, the lchan->type will actually remain NONE. + * TODO: set GSM_LCHAN_PDTCH? + */ + switch (ts->dyn.pchan_want) { + case GSM_PCHAN_TCH_F: + lchan->type = GSM_LCHAN_TCH_F; + break; + case GSM_PCHAN_TCH_H: + lchan->type = GSM_LCHAN_TCH_H; + break; + case GSM_PCHAN_PDCH: + lchan->type = GSM_LCHAN_NONE; + break; + default: + LOGP(DRSL, LOGL_ERROR, + "%s Invalid target pchan for dynamic TS\n", + gsm_ts_and_pchan_name(ts)); + } + + act_type = (ts->dyn.pchan_want == GSM_PCHAN_PDCH) + ? RSL_ACT_OSMO_PDCH + : lchan->dyn.act_type; + ho_ref = (ts->dyn.pchan_want == GSM_PCHAN_PDCH) + ? 0 + : lchan->dyn.ho_ref; + + /* Fetch the rqd_ref back from before switchover started. */ + lchan->rqd_ref = lchan->dyn.rqd_ref; + lchan->rqd_ta = lchan->dyn.rqd_ta; + lchan->dyn.rqd_ref = NULL; + lchan->dyn.rqd_ta = 0; + + /* During switchover, we have received a release ack, which means that + * the act_timer has been stopped. Start the timer again so we mark + * this channel broken if the activation ack comes too late. */ + osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan); + osmo_timer_schedule(&lchan->act_timer, 4, 0); + + rc = rsl_chan_activate_lchan(lchan, act_type, ho_ref); + if (rc) { + LOGP(DRSL, LOGL_ERROR, + "%s RSL Chan Activate failed\n", + gsm_ts_and_pchan_name(ts)); + return dyn_ts_switchover_failed(ts, rc); + } + return 0; +} + +static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc) +{ + ts->dyn.pchan_want = ts->dyn.pchan_is; + LOGP(DRSL, LOGL_ERROR, "%s Error %d during dynamic channel switchover." + " Going back to previous pchan.\n", gsm_ts_and_pchan_name(ts), + rc); + return rc; +} + +static void dyn_ts_switchover_complete(struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan_act; + enum gsm_phys_chan_config pchan_was; + struct gsm_bts_trx_ts *ts = lchan->ts; + + OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); + + pchan_act = pchan_for_lchant(lchan->type); + /* + * Paranoia: do the types match? + * In case of errors: we've received an act ack already, so what to do + * about it? Logging the error should suffice for now. + */ + if (pchan_act != ts->dyn.pchan_want) + LOGP(DRSL, LOGL_ERROR, + "%s Requested transition does not match lchan type %s\n", + gsm_ts_and_pchan_name(ts), + gsm_lchant_name(lchan->type)); + + pchan_was = ts->dyn.pchan_is; + ts->dyn.pchan_is = ts->dyn.pchan_want = pchan_act; + + if (pchan_was != ts->dyn.pchan_is) + LOGP(DRSL, LOGL_INFO, "%s switchover from %s complete.\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan_was)); +} + +/* Entry-point where L2 RSL from BTS enters */ +int abis_rsl_rcvmsg(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link; + struct abis_rsl_common_hdr *rslh; + int rc = 0; + + if (!msg) { + DEBUGP(DRSL, "Empty RSL msg?..\n"); + return -1; + } + + if (msgb_l2len(msg) < sizeof(*rslh)) { + DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg)); + msgb_free(msg); + return -1; + } + + sign_link = msg->dst; + rslh = msgb_l2(msg); + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = abis_rsl_rx_rll(msg); + break; + case ABIS_RSL_MDISC_DED_CHAN: + rc = abis_rsl_rx_dchan(msg); + break; + case ABIS_RSL_MDISC_COM_CHAN: + rc = abis_rsl_rx_cchan(msg); + break; + case ABIS_RSL_MDISC_TRX: + rc = abis_rsl_rx_trx(msg); + break; + case ABIS_RSL_MDISC_LOC: + LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL msg disc 0x%02x\n", + rslh->msg_discr); + break; + case ABIS_RSL_MDISC_IPACCESS: + rc = abis_rsl_rx_ipacc(msg); + break; + 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]); + rc = -EINVAL; + } + msgb_free(msg); + return rc; +} + +int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number, + struct rsl_ie_cb_cmd_type cb_command, + const uint8_t *data, int len) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *cb_cmd; + + cb_cmd = rsl_msgb_alloc(); + if (!cb_cmd) + return -1; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(cb_cmd, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_SMS_BC_CMD); + dh->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN; + dh->chan_nr = chan_number; /* TODO: check the chan config */ + + msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, *(uint8_t*)&cb_command); + msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data); + + cb_cmd->dst = bts->c0->rsl_link; + + return abis_rsl_sendmsg(cb_cmd); +} + +int rsl_nokia_si_begin(struct gsm_bts_trx *trx) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_TRX; + ch->msg_type = 0x40; /* Nokia SI Begin */ + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_nokia_si_end(struct gsm_bts_trx *trx) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_TRX; + ch->msg_type = 0x41; /* Nokia SI End */ + + msgb_tv_put(msg, 0xFD, 0x00); /* Nokia Pagemode Info, No paging reorganisation required */ + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduction) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_DED_CHAN; + ch->msg_type = RSL_MT_BS_POWER_CONTROL; + + 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; + + return abis_rsl_sendmsg(msg); +} + +/** + * Release all allocated SAPIs starting from @param start and + * release them with the given release mode. Once the release + * confirmation arrives it will be attempted to release the + * the RF channel. + */ +int rsl_release_sapis_from(struct gsm_lchan *lchan, int start, + enum rsl_rel_mode release_mode) +{ + int no_sapi = 1; + int sapi; + + for (sapi = start; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + uint8_t link_id; + if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) + continue; + + link_id = sapi; + if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H) + link_id |= 0x40; + rsl_release_request(lchan, link_id, release_mode); + no_sapi = 0; + } + + return no_sapi; +} + +int rsl_start_t3109(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + + osmo_timer_setup(&lchan->T3109, t3109_expired, lchan); + osmo_timer_schedule(&lchan->T3109, bts->network->T3109, 0); + return 0; +} + +/** + * \brief directly RF Channel Release the lchan + * + * When no SAPI was allocated, directly release the logical channel. This + * should only be called from chan_alloc.c on channel release handling. In + * case no SAPI was established the RF Channel can be directly released, + */ +int rsl_direct_rf_release(struct gsm_lchan *lchan) +{ + int i; + for (i = 0; i < ARRAY_SIZE(lchan->sapis); ++i) { + if (lchan->sapis[i] != LCHAN_SAPI_UNUSED) { + LOGP(DRSL, LOGL_ERROR, "%s SAPI(%d) still allocated.\n", + gsm_lchan_name(lchan), i); + return -1; + } + } + + /* Now release it */ + return rsl_rf_chan_release(lchan, 0, SACCH_NONE); +} + +/* Initial timeslot actions when a timeslot first comes into operation. */ +bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts) +{ + dyn_ts_init(ts); + return true; +} diff --git a/src/osmo-bsc/acc_ramp.c b/src/osmo-bsc/acc_ramp.c new file mode 100644 index 000000000..ac9f02da1 --- /dev/null +++ b/src/osmo-bsc/acc_ramp.c @@ -0,0 +1,363 @@ +/* (C) 2018 by sysmocom s.f.m.c. GmbH + * + * Author: Stefan Sperling + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * Check if an ACC has been permanently barred for a BTS, + * e.g. with the 'rach access-control-class' VTY command. + */ +static bool acc_is_permanently_barred(struct gsm_bts *bts, unsigned int acc) +{ + OSMO_ASSERT(acc >= 0 && acc <= 9); + if (acc == 8 || acc == 9) + return (bts->si_common.rach_control.t2 & (1 << (acc - 8))); + return (bts->si_common.rach_control.t3 & (1 << (acc))); +} + +static void allow_one_acc(struct acc_ramp *acc_ramp, unsigned int acc) +{ + OSMO_ASSERT(acc >= 0 && acc <= 9); + if (acc_ramp->barred_accs & (1 << acc)) + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: allowing Access Control Class %u\n", acc_ramp->bts->nr, acc); + acc_ramp->barred_accs &= ~(1 << acc); +} + +static void barr_one_acc(struct acc_ramp *acc_ramp, unsigned int acc) +{ + OSMO_ASSERT(acc >= 0 && acc <= 9); + if ((acc_ramp->barred_accs & (1 << acc)) == 0) + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: barring Access Control Class %u\n", acc_ramp->bts->nr, acc); + acc_ramp->barred_accs |= (1 << acc); +} + +static void barr_all_accs(struct acc_ramp *acc_ramp) +{ + unsigned int acc; + for (acc = 0; acc < 10; acc++) { + if (!acc_is_permanently_barred(acc_ramp->bts, acc)) + barr_one_acc(acc_ramp, acc); + } +} + +static void allow_all_accs(struct acc_ramp *acc_ramp) +{ + unsigned int acc; + for (acc = 0; acc < 10; acc++) { + if (!acc_is_permanently_barred(acc_ramp->bts, acc)) + allow_one_acc(acc_ramp, acc); + } +} + +static unsigned int get_next_step_interval(struct acc_ramp *acc_ramp) +{ + struct gsm_bts *bts = acc_ramp->bts; + uint64_t load; + + if (acc_ramp->step_interval_is_fixed) + return acc_ramp->step_interval_sec; + + /* Scale the step interval to current channel load average. */ + load = (bts->chan_load_avg << 8); /* convert to fixed-point */ + acc_ramp->step_interval_sec = ((load * ACC_RAMP_STEP_INTERVAL_MAX) / 100) >> 8; + if (acc_ramp->step_interval_sec < ACC_RAMP_STEP_SIZE_MIN) + acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN; + else if (acc_ramp->step_interval_sec > ACC_RAMP_STEP_INTERVAL_MAX) + acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MAX; + + LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: step interval set to %u seconds based on %u%% channel load average\n", + bts->nr, acc_ramp->step_interval_sec, bts->chan_load_avg); + return acc_ramp->step_interval_sec; +} + +static void do_acc_ramping_step(void *data) +{ + struct acc_ramp *acc_ramp = data; + int i; + + /* Shortcut in case we only do one ramping step. */ + if (acc_ramp->step_size == ACC_RAMP_STEP_SIZE_MAX) { + allow_all_accs(acc_ramp); + gsm_bts_set_system_infos(acc_ramp->bts); + return; + } + + /* Allow 'step_size' ACCs, starting from ACC0. ACC9 will be allowed last. */ + for (i = 0; i < acc_ramp->step_size; i++) { + int idx = ffs(acc_ramp_get_barred_t3(acc_ramp)); + if (idx > 0) { + /* One of ACC0-ACC7 is still bared. */ + unsigned int acc = idx - 1; + if (!acc_is_permanently_barred(acc_ramp->bts, acc)) + allow_one_acc(acc_ramp, acc); + } else { + idx = ffs(acc_ramp_get_barred_t2(acc_ramp)); + if (idx == 1 || idx == 2) { + /* ACC8 or ACC9 is still barred. */ + unsigned int acc = idx - 1 + 8; + if (!acc_is_permanently_barred(acc_ramp->bts, acc)) + allow_one_acc(acc_ramp, acc); + } else { + /* All ACCs are now allowed. */ + break; + } + } + } + + gsm_bts_set_system_infos(acc_ramp->bts); + + /* If we have not allowed all ACCs yet, schedule another ramping step. */ + if (acc_ramp_get_barred_t2(acc_ramp) != 0x00 || + acc_ramp_get_barred_t3(acc_ramp) != 0x00) + osmo_timer_schedule(&acc_ramp->step_timer, get_next_step_interval(acc_ramp), 0); +} + +/* 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) +{ + 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) + return 0; + + if (nsd->obj_class != NM_OC_RADIO_CARRIER) + return 0; + + trx = nsd->obj; + + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: administrative state %s -> %s\n", + acc_ramp->bts->nr, trx->nr, + get_value_string(abis_nm_adm_state_names, nsd->old_state->administrative), + get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative)); + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: operational state %s -> %s\n", + acc_ramp->bts->nr, trx->nr, + 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) + return 0; + + /* RSL must already be up. We cannot send RACH system information to the BTS otherwise. */ + if (trx->rsl_link == NULL) { + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change because RSL link is down\n", + acc_ramp->bts->nr, trx->nr); + 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 + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " + "because TRX is transitioning into operational state '%s'\n", + acc_ramp->bts->nr, trx->nr, + 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 + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " + "because TRX is not usable\n", acc_ramp->bts->nr, trx->nr); + } + break; + case NM_STATE_LOCKED: + case NM_STATE_SHUTDOWN: + abort_ramping = true; + break; + case NM_STATE_NULL: + default: + LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized administrative state '0x%x' " + "reported for TRX 0\n", acc_ramp->bts->nr, 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 + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " + "because TRX is transitioning into administrative state '%s'\n", + acc_ramp->bts->nr, trx->nr, + 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 + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " + "because TRX is in administrative state '%s'\n", + acc_ramp->bts->nr, trx->nr, + 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: + LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized operational state '0x%x' " + "reported for TRX 0\n", acc_ramp->bts->nr, nsd->new_state->administrative); + break; + } + } + + if (trigger_ramping) + acc_ramp_trigger(acc_ramp); + else if (abort_ramping) + acc_ramp_abort(acc_ramp); + + return 0; +} + +/*! + * Initialize an acc_ramp data structure. + * Storage for this structure must be provided by the caller. + * + * By default, ACC ramping is disabled and all ACCs are allowed. + * + * \param[in] acc_ramp Pointer to acc_ramp structure to be initialized. + * \param[in] bts BTS which uses this ACC ramp data structure. + */ +void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts) +{ + acc_ramp->bts = bts; + acc_ramp_set_enabled(acc_ramp, false); + acc_ramp->step_size = ACC_RAMP_STEP_SIZE_DEFAULT; + acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN; + acc_ramp->step_interval_is_fixed = false; + allow_all_accs(acc_ramp); + 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); +} + +/*! + * Change the ramping step size which controls how many ACCs will be allowed per ramping step. + * Returns negative on error (step_size out of range), else zero. + * \param[in] acc_ramp Pointer to acc_ramp structure. + * \param[in] step_size The new step size value. + */ +int acc_ramp_set_step_size(struct acc_ramp *acc_ramp, unsigned int step_size) +{ + if (step_size < ACC_RAMP_STEP_SIZE_MIN || step_size > ACC_RAMP_STEP_SIZE_MAX) + return -ERANGE; + + acc_ramp->step_size = step_size; + LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step size set to %u\n", acc_ramp->bts->nr, step_size); + return 0; +} + +/*! + * Change the ramping step interval to a fixed value. Unless this function is called, + * the interval is automatically scaled to the BTS channel load average. + * \param[in] acc_ramp Pointer to acc_ramp structure. + * \param[in] step_interval The new fixed step interval in seconds. + */ +int acc_ramp_set_step_interval(struct acc_ramp *acc_ramp, unsigned int step_interval) +{ + if (step_interval < ACC_RAMP_STEP_INTERVAL_MIN || step_interval > ACC_RAMP_STEP_INTERVAL_MAX) + return -ERANGE; + + acc_ramp->step_interval_sec = step_interval; + acc_ramp->step_interval_is_fixed = true; + LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to %u seconds\n", + acc_ramp->bts->nr, step_interval); + return 0; +} + +/*! + * Clear a previously set fixed ramping step interval, so that the interval + * is again automatically scaled to the BTS channel load average. + * \param[in] acc_ramp Pointer to acc_ramp structure. + */ +void acc_ramp_set_step_interval_dynamic(struct acc_ramp *acc_ramp) +{ + acc_ramp->step_interval_is_fixed = false; + LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to 'dynamic'\n", + acc_ramp->bts->nr); +} + +/*! + * Determine if ACC ramping should be started according to configuration, and + * begin the ramping process if the necessary conditions are present. + * Perform at least one ramping step to allow 'step_size' ACCs. + * If 'step_size' is ACC_RAMP_STEP_SIZE_MAX, or if ACC ramping is disabled, + * all ACCs will be allowed immediately. + * \param[in] acc_ramp Pointer to acc_ramp structure. + */ +void acc_ramp_trigger(struct acc_ramp *acc_ramp) +{ + /* Abort any previously running ramping process and allow all available ACCs. */ + acc_ramp_abort(acc_ramp); + + if (acc_ramp_is_enabled(acc_ramp)) { + /* Set all available ACCs to barred and start ramping up. */ + barr_all_accs(acc_ramp); + do_acc_ramping_step(acc_ramp); + } +} + +/*! + * Abort the ramping process and allow all available ACCs immediately. + * \param[in] acc_ramp Pointer to acc_ramp structure. + */ +void acc_ramp_abort(struct acc_ramp *acc_ramp) +{ + if (osmo_timer_pending(&acc_ramp->step_timer)) + osmo_timer_del(&acc_ramp->step_timer); + + allow_all_accs(acc_ramp); +} diff --git a/src/osmo-bsc/arfcn_range_encode.c b/src/osmo-bsc/arfcn_range_encode.c new file mode 100644 index 000000000..84f9f635f --- /dev/null +++ b/src/osmo-bsc/arfcn_range_encode.c @@ -0,0 +1,336 @@ +/* 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 . + */ + +#include +#include + +#include + +#include + +#include + +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; + + /* + * 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/bsc_api.c b/src/osmo-bsc/bsc_api.c new file mode 100644 index 000000000..8ae781e96 --- /dev/null +++ b/src/osmo-bsc/bsc_api.c @@ -0,0 +1,841 @@ +/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */ + +/* (C) 2010-2011 by Holger Hans Peter Freyther + * (C) 2010-2011 by On-Waves + * (C) 2009,2017 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define GSM0808_T10_VALUE 6, 0 + +#define HO_DTAP_CACHE_MSGB_CB_LINK_ID 0 +#define HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH 1 + +static void rll_ind_cb(struct gsm_lchan *, uint8_t, void *, enum bsc_rllr_ind); +static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id); +static void handle_release(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); +static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); +static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); + +/*! \brief Determine and apply AMR multi-rate configuration to lchan + * Determine which AMR multi-rate configuration to use and apply it to + * the lchan (so it can be communicated to BTS and MS during channel + * activation. + * \param[in] conn subscriber connection (used to resolve bsc_api) + * \param[out] lchan logical channel to which to apply mr config + * \param[in] full_rate whether to use full-rate (1) or half-rate (0) config + */ +static void handle_mr_config(struct gsm_subscriber_connection *conn, + struct gsm_lchan *lchan, int full_rate) +{ + struct bsc_api *api; + api = conn->network->bsc_api; + struct amr_multirate_conf *mr; + struct gsm48_multi_rate_conf *mr_conf; + + /* BSC api override for this method, used in OsmoBSC mode with + * bsc_mr_config() to use MSC-specific/specified configuration */ + if (api->mr_config) + return api->mr_config(conn, lchan, full_rate); + + /* NITB case: use the BTS-specic multi-rate configuration from + * the vty/configuration file */ + if (full_rate) + mr = &lchan->ts->trx->bts->mr_full; + else + mr = &lchan->ts->trx->bts->mr_half; + + mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie; + mr_conf->ver = 1; + + /* default, if no AMR codec defined */ + if (!mr->gsm48_ie[1]) { + mr_conf->icmi = 1; + mr_conf->m5_90 = 1; + } + /* store encoded MR config IE lchan for both MS (uplink) and BTS + * (downlink) directions */ + gsm48_multirate_config(lchan->mr_ms_lv, mr, mr->ms_mode); + gsm48_multirate_config(lchan->mr_bts_lv, mr, mr->bts_mode); +} + +/* + * Start a new assignment and make sure that it is completed within T10 either + * positively, negatively or by the timeout. + * + * 1.) allocate a new lchan + * 2.) copy the encryption key and other data from the + * old to the new channel. + * 3.) RSL Channel Activate this channel and wait + * + * -> Signal handler for the LCHAN + * 4.) Send GSM 04.08 assignment command to the MS + * + * -> Assignment Complete/Assignment Failure + * 5.) Release the SDCCH, continue signalling on the new link + */ +static int handle_new_assignment(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate) +{ + struct gsm_lchan *new_lchan; + enum gsm_chan_t chan_type; + + chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H; + + new_lchan = lchan_alloc(conn_get_bts(conn), chan_type, 0); + + if (!new_lchan) { + LOGP(DMSC, LOGL_NOTICE, "%s No free channel for %s\n", + bsc_subscr_name(conn->bsub), gsm_lchant_name(chan_type)); + return -1; + } + + /* check if we are on TCH/F and requested TCH/H, but got TCH/F */ + if (conn->lchan->type == new_lchan->type + && chan_type != new_lchan->type) { + LOGPLCHAN(conn->lchan, DHO, LOGL_NOTICE, + "-> %s Will not re-assign to identical channel type, %s was requested\n", + gsm_lchan_name(new_lchan), gsm_lchant_name(chan_type)); + lchan_free(new_lchan); + return -1; + } + + /* copy old data to the new channel */ + memcpy(&new_lchan->encr, &conn->lchan->encr, sizeof(new_lchan->encr)); + new_lchan->ms_power = conn->lchan->ms_power; + new_lchan->bs_power = conn->lchan->bs_power; + new_lchan->rqd_ta = conn->lchan->rqd_ta; + + /* copy new data to it */ + new_lchan->tch_mode = chan_mode; + new_lchan->rsl_cmode = (chan_mode == GSM48_CMODE_SIGN) ? + RSL_CMOD_SPD_SIGN : RSL_CMOD_SPD_SPEECH; + + /* handle AMR correctly */ + if (chan_mode == GSM48_CMODE_SPEECH_AMR) + handle_mr_config(conn, new_lchan, full_rate); + + if (rsl_chan_activate_lchan(new_lchan, 0x1, 0) < 0) { + LOGPLCHAN(new_lchan, DHO, LOGL_ERROR, "could not activate channel\n"); + lchan_free(new_lchan); + return -1; + } + + /* remember that we have the channel */ + conn->secondary_lchan = new_lchan; + new_lchan->conn = conn; + return 0; +} + +static void ho_dtap_cache_add(struct gsm_subscriber_connection *conn, struct msgb *msg, + int link_id, bool allow_sacch) +{ + if (conn->ho_dtap_cache_len >= 23) { + LOGP(DHO, LOGL_ERROR, "%s: Cannot cache more DTAP messages," + " already reached sane maximum of %u cached messages\n", + bsc_subscr_name(conn->bsub), conn->ho_dtap_cache_len); + msgb_free(msg); + return; + } + conn->ho_dtap_cache_len ++; + LOGP(DHO, LOGL_DEBUG, "%s: Caching DTAP message during ho/ass (%u)\n", + bsc_subscr_name(conn->bsub), conn->ho_dtap_cache_len); + msg->cb[HO_DTAP_CACHE_MSGB_CB_LINK_ID] = (unsigned long)link_id; + msg->cb[HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH] = allow_sacch ? 1 : 0; + msgb_enqueue(&conn->ho_dtap_cache, msg); +} + +void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send) +{ + struct msgb *msg; + unsigned int flushed_count = 0; + + if (conn->secondary_lchan || conn->ho) { + LOGP(DHO, LOGL_ERROR, "%s: Cannot send cached DTAP messages, handover/assignment is still ongoing\n", + bsc_subscr_name(conn->bsub)); + send = 0; + } + + while ((msg = msgb_dequeue(&conn->ho_dtap_cache))) { + conn->ho_dtap_cache_len --; + flushed_count ++; + if (send) { + int link_id = (int)msg->cb[HO_DTAP_CACHE_MSGB_CB_LINK_ID]; + bool allow_sacch = !!msg->cb[HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH]; + LOGP(DHO, LOGL_DEBUG, "%s: Sending cached DTAP message after handover/assignment (%u/%u)\n", + bsc_subscr_name(conn->bsub), flushed_count, conn->ho_dtap_cache_len); + gsm0808_submit_dtap(conn, msg, link_id, allow_sacch); + } else + msgb_free(msg); + } +} + +int bsc_api_init(struct gsm_network *network, struct bsc_api *api) +{ + network->bsc_api = api; + return 0; +} + +/*! \brief process incoming 08.08 DTAP from MSC (send via BTS to MS) */ +int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, + struct msgb *msg, int link_id, int allow_sacch) +{ + uint8_t sapi; + + + if (!conn->lchan) { + LOGP(DMSC, LOGL_ERROR, + "%s Called submit dtap without an lchan.\n", + bsc_subscr_name(conn->bsub)); + msgb_free(msg); + return -1; + } + + /* buffer message during assignment / handover */ + if (conn->secondary_lchan || conn->ho) { + ho_dtap_cache_add(conn, msg, link_id, !! allow_sacch); + return 0; + } + + 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) { + if (conn->lchan->type == GSM_LCHAN_TCH_F || conn->lchan->type == GSM_LCHAN_TCH_H) + link_id |= 0x40; + } + + msg->l3h = msg->data; + /* is requested SAPI already up? */ + if (conn->lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) { + /* Establish L2 for additional SAPI */ + OBSC_LINKID_CB(msg) = link_id; + if (rll_establish(msg->lchan, sapi, rll_ind_cb, msg) != 0) { + msgb_free(msg); + send_sapi_reject(conn, link_id); + return -1; + } + return 0; + } else { + /* Directly forward via RLL/RSL to BTS */ + return rsl_data_request(msg, link_id); + } +} + +/* + * \brief Check if the given channel is compatible with the mode/fullrate + */ +static int chan_compat_with_mode(struct gsm_lchan *lchan, int chan_mode, int full_rate) +{ + switch (chan_mode) { + case GSM48_CMODE_SIGN: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_SDCCH: + return 1; + default: + return 0; + } + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_AMR: + case GSM48_CMODE_DATA_3k6: + case GSM48_CMODE_DATA_6k0: + /* these services can all run on TCH/H, but we may have + * an explicit override by the 'full_rate' argument */ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return full_rate ? 1 : 0; + case GSM_LCHAN_TCH_H: + return full_rate ? 0 : 1; + default: + return 0; + } + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_SPEECH_EFR: + /* these services all explicitly require a TCH/F */ + return (lchan->type == GSM_LCHAN_TCH_F) ? 1 : 0; + default: + return 0; + } +} + +/*! Send a GSM08.08 Assignment Request. Right now this does not contain the + * audio codec type or the allowed rates for the config. In case the current + * channel does not allow the selected mode a new one will be allocated. + * \param[out] conn related subscriber connection + * \param[in] chan_mode mode of the channel (see enum gsm48_chan_mode) + * \param[in] full_rate select full rate or half rate channel + * \returns 0 on success, 1 when no operation is neccessary, -1 on failure */ +int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate) +{ + /* TODO: Add multirate configuration, make it work for more than audio. */ + + struct bsc_api *api; + api = conn->network->bsc_api; + + if (!chan_compat_with_mode(conn->lchan, chan_mode, full_rate)) { + if (handle_new_assignment(conn, chan_mode, full_rate) != 0) + goto error; + } else { + /* Check if the channel is already in the requested mode, if + * yes, we skip unnecessary channel mode modify operations. */ + if (conn->lchan->tch_mode == chan_mode) + return 1; + + if (chan_mode == GSM48_CMODE_SPEECH_AMR) + handle_mr_config(conn, conn->lchan, full_rate); + + LOGPLCHAN(conn->lchan, DMSC, LOGL_NOTICE, + "Sending ChanModify for speech: %s\n", + get_value_string(gsm48_chan_mode_names, chan_mode)); + gsm48_lchan_modify(conn->lchan, chan_mode); + } + + /* we expect the caller will manage T10 */ + return 0; + +error: + api->assign_fail(conn, 0, NULL); + return -1; +} + +int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, unsigned int mi_len, + uint8_t *mi, int chan_type) +{ + return rsl_paging_cmd(bts, page_group, mi_len, mi, chan_type, false); +} + +static void handle_ass_compl(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct bsc_api *api = conn->network->bsc_api; + enum gsm48_rr_cause cause; + + /* Expecting gsm48_hdr + cause value */ + if (msgb_l3len(msg) != sizeof(*gh) + 1) { + LOGPLCHAN(msg->lchan, DRR, LOGL_ERROR, + "RR Assignment Complete: length invalid: %u, expected %zu\n", + msgb_l3len(msg), sizeof(*gh) + 1); + return; + } + + cause = gh->data[0]; + + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "ASSIGNMENT COMPLETE cause = %s\n", + rr_cause_name(cause)); + + if (conn->ho) { + struct lchan_signal_data sig = { + .lchan = msg->lchan, + }; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_ASSIGNMENT_COMPL, &sig); + /* FIXME: release old channel */ + + /* send pending messages, if any */ + ho_dtap_cache_flush(conn, 1); + + return; + } + + if (conn->secondary_lchan != msg->lchan) { + LOGPLCHAN(msg->lchan, DRR, LOGL_ERROR, + "RR Assignment Complete does not match conn's secondary lchan.\n"); + return; + } + + /* swap channels */ + osmo_timer_del(&conn->T10); + + lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END); + conn->lchan = conn->secondary_lchan; + conn->secondary_lchan = NULL; + + /* send pending messages, if any */ + ho_dtap_cache_flush(conn, 1); + + if (is_ipaccess_bts(conn_get_bts(conn)) && conn->lchan->tch_mode != GSM48_CMODE_SIGN) + rsl_ipacc_crcx(conn->lchan); + + api->assign_compl(conn, cause); +} + +static void handle_ass_fail(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct bsc_api *api = conn->network->bsc_api; + uint8_t *rr_failure; + struct gsm48_hdr *gh; + + if (conn->ho) { + struct lchan_signal_data sig; + struct gsm48_hdr *gh = msgb_l3(msg); + + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "ASSIGNMENT FAILED cause = %s\n", + rr_cause_name(gh->data[0])); + + sig.lchan = msg->lchan; + sig.mr = NULL; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_ASSIGNMENT_FAIL, &sig); + /* FIXME: release allocated new channel */ + + /* send pending messages, if any */ + ho_dtap_cache_flush(conn, 1); + + return; + } + + if (conn->lchan != msg->lchan) { + LOGPLCHAN(msg->lchan, DMSC, LOGL_ERROR, + "Assignment failure should occur on primary lchan.\n"); + return; + } + + /* stop the timer and release it */ + osmo_timer_del(&conn->T10); + if (conn->secondary_lchan) { + lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); + conn->secondary_lchan = NULL; + } + + /* send pending messages, if any */ + ho_dtap_cache_flush(conn, 1); + + gh = msgb_l3(msg); + if (msgb_l3len(msg) - sizeof(*gh) != 1) { + LOGPLCHAN(conn->lchan, DMSC, LOGL_ERROR, "assignment failure unhandled: %zu\n", + msgb_l3len(msg) - sizeof(*gh)); + rr_failure = NULL; + } else { + rr_failure = &gh->data[0]; + } + + api->assign_fail(conn, + GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE, + rr_failure); +} + +static void handle_classmark_chg(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t cm2_len, cm3_len = 0; + uint8_t *cm2, *cm3 = NULL; + + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "CLASSMARK CHANGE "); + + /* classmark 2 */ + cm2_len = gh->data[0]; + cm2 = &gh->data[1]; + DEBUGPC(DRR, "CM2(len=%u) ", cm2_len); + + if (payload_len > cm2_len + 1) { + /* we must have a classmark3 */ + if (gh->data[cm2_len+1] != 0x20) { + DEBUGPC(DRR, "ERR CM3 TAG\n"); + return; + } + if (cm2_len > 3) { + DEBUGPC(DRR, "CM2 too long!\n"); + return; + } + + cm3_len = gh->data[cm2_len+2]; + cm3 = &gh->data[cm2_len+3]; + if (cm3_len > 14) { + DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len); + return; + } + DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len); + } + api->classmark_chg(conn, cm2, cm2_len, cm3, cm3_len); +} + +/* Chapter 9.1.16 Handover complete */ +static void handle_rr_ho_compl(struct msgb *msg) +{ + struct lchan_signal_data sig; + struct gsm48_hdr *gh = msgb_l3(msg); + + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, + "HANDOVER COMPLETE cause = %s\n", rr_cause_name(gh->data[0])); + + sig.lchan = msg->lchan; + sig.mr = NULL; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig); + /* FIXME: release old channel */ + + /* send pending messages, if any */ + ho_dtap_cache_flush(msg->lchan->conn, 1); +} + +/* Chapter 9.1.17 Handover Failure */ +static void handle_rr_ho_fail(struct msgb *msg) +{ + struct lchan_signal_data sig; + struct gsm48_hdr *gh = msgb_l3(msg); + + /* Log on both RR and HO categories: it is an RR message, but is still quite important when + * filtering on HO. */ + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, + "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0])); + LOGPLCHAN(msg->lchan, DHO, LOGL_DEBUG, + "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0])); + + sig.lchan = msg->lchan; + sig.mr = NULL; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig); + /* FIXME: release allocated new channel */ + + /* send pending messages, if any */ + ho_dtap_cache_flush(msg->lchan->conn, 1); +} + + +static void dispatch_dtap(struct gsm_subscriber_connection *conn, + uint8_t link_id, struct msgb *msg) +{ + struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; + struct gsm48_hdr *gh; + uint8_t pdisc; + uint8_t msg_type; + int rc; + + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DMSC, LOGL_ERROR, "(%s) Message too short for a GSM48 header.\n", + bsc_subscr_name(conn->bsub)); + return; + } + + gh = msgb_l3(msg); + pdisc = gsm48_hdr_pdisc(gh); + msg_type = gsm48_hdr_msg_type(gh); + + /* the idea is to handle all RR messages here, and only hand + * MM/CC/SMS-CP/LCS up to the MSC. Some messages like PAGING + * RESPONSE or CM SERVICE REQUEST will not be covered here, as + * they are only possible in the first L3 message of each L2 + * channel, i.e. 'conn' will not exist and gsm0408_rcvmsg() + * will call api->compl_l3() for it */ + switch (pdisc) { + case GSM48_PDISC_RR: + switch (msg_type) { + case GSM48_MT_RR_GPRS_SUSP_REQ: + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, + "%s\n", gsm48_rr_msg_name(GSM48_MT_RR_GPRS_SUSP_REQ)); + break; + case GSM48_MT_RR_STATUS: + LOGPLCHAN(msg->lchan, DRR, LOGL_NOTICE, + "%s (cause: %s)\n", gsm48_rr_msg_name(GSM48_MT_RR_STATUS), + rr_cause_name(gh->data[0])); + break; + case GSM48_MT_RR_MEAS_REP: + /* This shouldn't actually end up here, as RSL treats + * L3 Info of 08.58 MEASUREMENT REPORT different by calling + * directly into gsm48_parse_meas_rep */ + LOGPLCHAN(msg->lchan, DMEAS, LOGL_ERROR, + "DIRECT GSM48 MEASUREMENT REPORT ?!?\n"); + gsm48_tx_rr_status(conn, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); + break; + case GSM48_MT_RR_HANDO_COMPL: + handle_rr_ho_compl(msg); + break; + case GSM48_MT_RR_HANDO_FAIL: + handle_rr_ho_fail(msg); + break; + case GSM48_MT_RR_CIPH_M_COMPL: + if (api->cipher_mode_compl) + api->cipher_mode_compl(conn, msg, + conn->lchan->encr.alg_id); + break; + case GSM48_MT_RR_ASS_COMPL: + handle_ass_compl(conn, msg); + break; + case GSM48_MT_RR_ASS_FAIL: + handle_ass_fail(conn, msg); + break; + case GSM48_MT_RR_CHAN_MODE_MODIF_ACK: + osmo_timer_del(&conn->T10); + rc = gsm48_rx_rr_modif_ack(msg); + if (rc < 0) { + api->assign_fail(conn, + GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + NULL); + } else if (rc >= 0) { + api->assign_compl(conn, 0); + } + break; + case GSM48_MT_RR_CLSM_CHG: + handle_classmark_chg(conn, msg); + break; + case GSM48_MT_RR_APP_INFO: + /* Passing RR APP INFO to MSC, not quite + * according to spec */ + if (api->dtap) + api->dtap(conn, link_id, msg); + break; + default: + /* Drop unknown RR message */ + LOGPLCHAN(msg->lchan, DRR, LOGL_NOTICE, + "Dropping %s 04.08 RR message\n", gsm48_rr_msg_name(msg_type)); + gsm48_tx_rr_status(conn, GSM48_RR_CAUSE_MSG_TYPE_N); + break; + } + break; + default: + if (api->dtap) + api->dtap(conn, link_id, msg); + break; + } +} + +/*! \brief RSL has received a DATA INDICATION with L3 from MS */ +int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id) +{ + int rc; + struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; + struct gsm_lchan *lchan; + + lchan = msg->lchan; + if (lchan->state != LCHAN_S_ACTIVE) { + LOGPLCHAN(msg->lchan, DRSL, LOGL_INFO, "Got data in non active state, discarding.\n"); + return -1; + } + + + if (lchan->conn) { + /* if we already have a connection, forward via DTAP to + * MSC */ + dispatch_dtap(lchan->conn, link_id, msg); + } else { + /* allocate a new connection */ + rc = BSC_API_CONN_POL_REJECT; + lchan->conn = bsc_subscr_con_allocate(msg->lchan->ts->trx->bts->network); + if (!lchan->conn) { + lchan_release(lchan, 1, RSL_REL_NORMAL); + return -1; + } + lchan->conn->lchan = lchan; + + /* fwd via bsc_api to send COMPLETE L3 INFO to MSC */ + rc = api->compl_l3(lchan->conn, msg, 0); + + if (rc != BSC_API_CONN_POL_ACCEPT) { + //osmo_fsm_inst_dispatch(lchan->conn->fi, FIXME, NULL); + } + } + + return 0; +} + +/*! \brief We received a GSM 08.08 CIPHER MODE from the MSC */ +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); +} + +/* + * Release all occupied RF Channels but stay around for more. + */ +int gsm0808_clear(struct gsm_subscriber_connection *conn) +{ + if (conn->ho) + bsc_clear_handover(conn, 1); + + if (conn->secondary_lchan) + lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); + + if (conn->lchan) + lchan_release(conn->lchan, 1, RSL_REL_NORMAL); + + conn->lchan = NULL; + conn->secondary_lchan = NULL; + + osmo_timer_del(&conn->T10); + + return 0; +} + +static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id) +{ + struct bsc_api *api; + + if (!conn) + return; + + api = conn->network->bsc_api; + if (!api || !api->sapi_n_reject) + return; + + api->sapi_n_reject(conn, link_id); +} + +static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, enum bsc_rllr_ind rllr_ind) +{ + struct msgb *msg = _data; + + /* + * There seems to be a small window that the RLL timer can + * fire after a lchan_release call and before the S_CHALLOC_FREED + * is called. Check if a conn is set before proceeding. + */ + if (!lchan->conn) + return; + + switch (rllr_ind) { + case BSC_RLLR_IND_EST_CONF: + rsl_data_request(msg, OBSC_LINKID_CB(msg)); + break; + case BSC_RLLR_IND_REL_IND: + case BSC_RLLR_IND_ERR_IND: + case BSC_RLLR_IND_TIMEOUT: + send_sapi_reject(lchan->conn, OBSC_LINKID_CB(msg)); + msgb_free(msg); + break; + } +} + +static int bsc_handle_lchan_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct bsc_api *bsc; + struct gsm_lchan *lchan; + struct lchan_signal_data *lchan_data; + + if (subsys != SS_LCHAN) + return 0; + + + lchan_data = signal_data; + if (!lchan_data->lchan || !lchan_data->lchan->conn) + return 0; + + lchan = lchan_data->lchan; + bsc = lchan->ts->trx->bts->network->bsc_api; + if (!bsc) + return 0; + + switch (signal) { + case S_LCHAN_UNEXPECTED_RELEASE: + handle_release(lchan->conn, bsc, lchan); + break; + case S_LCHAN_ACTIVATE_ACK: + handle_chan_ack(lchan->conn, bsc, lchan); + break; + case S_LCHAN_ACTIVATE_NACK: + handle_chan_nack(lchan->conn, bsc, lchan); + break; + } + + return 0; +} + +static void handle_release(struct gsm_subscriber_connection *conn, + struct bsc_api *bsc, struct gsm_lchan *lchan) +{ + if (conn->secondary_lchan == lchan) { + osmo_timer_del(&conn->T10); + conn->secondary_lchan = NULL; + + bsc->assign_fail(conn, + GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, + NULL); + } + + /* clear the connection now */ + if (bsc->clear_request) + bsc->clear_request(conn, 0); + + /* now give up all channels */ + if (conn->lchan == lchan) + conn->lchan = NULL; + if (conn->ho && conn->ho->new_lchan == lchan) + bsc_clear_handover(conn, 0); + lchan->conn = NULL; +} + +static void handle_chan_ack(struct gsm_subscriber_connection *conn, + struct bsc_api *api, struct gsm_lchan *lchan) +{ + if (conn->secondary_lchan != lchan) + return; + + LOGPLCHAN(lchan, DMSC, LOGL_NOTICE, "Sending RR Assignment\n"); + gsm48_send_rr_ass_cmd(conn->lchan, lchan, lchan->ms_power); +} + +static void handle_chan_nack(struct gsm_subscriber_connection *conn, + struct bsc_api *api, struct gsm_lchan *lchan) +{ + if (conn->secondary_lchan != lchan) + return; + + LOGPLCHAN(lchan, DMSC, LOGL_ERROR, "Channel activation failed. Waiting for timeout now\n"); + conn->secondary_lchan->conn = NULL; + conn->secondary_lchan = NULL; +} + +static __attribute__((constructor)) void on_dso_load_bsc(void) +{ + osmo_signal_register_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL); +} diff --git a/src/osmo-bsc/bsc_ctrl_commands.c b/src/osmo-bsc/bsc_ctrl_commands.c new file mode 100644 index 000000000..171feaff0 --- /dev/null +++ b/src/osmo-bsc/bsc_ctrl_commands.c @@ -0,0 +1,500 @@ +/* + * (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 . + * + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); + } + + 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); + 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->bsc_data->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->bsc_data->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 new file mode 100644 index 000000000..38d1ba4ea --- /dev/null +++ b/src/osmo-bsc/bsc_ctrl_lookup.c @@ -0,0 +1,123 @@ +/* SNMP-like status interface. Look-up of BTS/TRX/MSC + * + * (C) 2010-2011 by Daniel Willmann + * (C) 2010-2011 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 General Public License as published by + * the Free Software Foundation; either version 2 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. + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +extern vector ctrl_node_vec; + +/*! \brief control interface lookup function for bsc/bts/msc gsm_data + * \param[in] data Private data passed to controlif_setup() + * \param[in] vline Vector of the line holding the command string + * \param[out] node_type type (CTRL_NODE_) that was determined + * \param[out] node_data private dta of node that was determined + * \param i Current index into vline, up to which it is parsed + */ +static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type, + void **node_data, int *i) +{ + struct gsm_network *net = data; + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + struct bsc_msc_data *msc = NULL; + char *token = vector_slot(vline, *i); + long num; + + /* TODO: We need to make sure that the following chars are digits + * and/or use strtol to check if number conversion was successful + * Right now something like net.bts_stats will not work */ + if (!strcmp(token, "bts")) { + if (*node_type != CTRL_NODE_ROOT || !net) + goto err_missing; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + bts = gsm_bts_num(net, num); + if (!bts) + goto err_missing; + *node_data = bts; + *node_type = CTRL_NODE_BTS; + } else if (!strcmp(token, "trx")) { + if (*node_type != CTRL_NODE_BTS || !*node_data) + goto err_missing; + bts = *node_data; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + trx = gsm_bts_trx_num(bts, num); + if (!trx) + goto err_missing; + *node_data = trx; + *node_type = CTRL_NODE_TRX; + } else if (!strcmp(token, "ts")) { + if (*node_type != CTRL_NODE_TRX || !*node_data) + goto err_missing; + trx = *node_data; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + if ((num >= 0) && (num < TRX_NR_TS)) + ts = &trx->ts[num]; + if (!ts) + goto err_missing; + *node_data = ts; + *node_type = CTRL_NODE_TS; + } else if (!strcmp(token, "msc")) { + if (*node_type != CTRL_NODE_ROOT || !net) + goto err_missing; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + msc = osmo_msc_data_find(net, num); + if (!msc) + goto err_missing; + *node_data = msc; + *node_type = CTRL_NODE_MSC; + } else + return 0; + + return 1; +err_missing: + return -ENODEV; +err_index: + return -ERANGE; +} + +struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net, + const char *bind_addr, uint16_t port) +{ + return ctrl_interface_setup_dynip2(net, bind_addr, port, + bsc_ctrl_node_lookup, + _LAST_CTRL_NODE_BSC); +} diff --git a/src/osmo-bsc/bsc_dyn_ts.c b/src/osmo-bsc/bsc_dyn_ts.c new file mode 100644 index 000000000..ed7caed7f --- /dev/null +++ b/src/osmo-bsc/bsc_dyn_ts.c @@ -0,0 +1,60 @@ +/* Dynamic PDCH initialisation implementation shared across NM and RSL */ + +/* (C) 2016 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 . + * + */ + +#include +#include +#include +#include + +static void tchf_pdch_ts_init(struct gsm_bts_trx_ts *ts) +{ + int rc; + + rc = rsl_ipacc_pdch_activate(ts, 1); + if (rc != 0 && rc != -ENOTSUP) + LOGP(DRSL, LOGL_ERROR, "%s %s: PDCH ACT failed\n", + gsm_ts_name(ts), gsm_pchan_name(ts->pchan)); +} + +static void tchf_tchh_pdch_ts_init(struct gsm_bts_trx_ts *ts) +{ + dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH); +} + +void dyn_ts_init(struct gsm_bts_trx_ts *ts) +{ + /* Clear all TCH/F_PDCH flags */ + ts->flags &= ~(TS_F_PDCH_PENDING_MASK | TS_F_PDCH_ACTIVE); + + /* Clear TCH/F_TCH/H_PDCH state */ + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + ts->dyn.pending_chan_activ = NULL; + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + tchf_pdch_ts_init(ts); + break; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + tchf_tchh_pdch_ts_init(ts); + break; + default: + break; + } +} diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c new file mode 100644 index 000000000..b6bd41025 --- /dev/null +++ b/src/osmo-bsc/bsc_init.c @@ -0,0 +1,288 @@ +/* A hackish minimal BSC (+MSC +HLR) implementation */ + +/* (C) 2008-2018 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +int bsc_shutdown_net(struct gsm_network *net) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + LOGP(DNM, LOGL_NOTICE, "shutting down OML for BTS %u\n", bts->nr); + osmo_signal_dispatch(SS_L_GLOBAL, S_GLOBAL_BTS_CLOSE_OM, bts); + } + + return 0; +} + +unsigned long long bts_uptime(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); + return 0; + } + + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { + LOGP(DNM, LOGL_ERROR, "BTS %u uptime 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); +} + +static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len) +{ + struct gsm_bts *bts = trx->bts; + int rc, j; + + if (si_len) { + DEBUGP(DRR, "SI%s: %s\n", get_value_string(osmo_sitype_strs, i), + osmo_hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN)); + } else + DEBUGP(DRR, "SI%s: OFF\n", get_value_string(osmo_sitype_strs, i)); + + switch (i) { + case SYSINFO_TYPE_5: + case SYSINFO_TYPE_5bis: + case SYSINFO_TYPE_5ter: + case SYSINFO_TYPE_6: + rc = rsl_sacch_filling(trx, osmo_sitype2rsl(i), + si_len ? GSM_BTS_SI(bts, i) : NULL, si_len); + break; + case SYSINFO_TYPE_2quater: + if (si_len == 0) { + rc = rsl_bcch_info(trx, i, NULL, 0); + break; + } + rc = 0; + for (j = 0; j <= bts->si2q_count; j++) + rc = rsl_bcch_info(trx, i, (const uint8_t *)GSM_BTS_SI2Q(bts, j), GSM_MACBLOCK_LEN); + break; + default: + rc = rsl_bcch_info(trx, i, si_len ? GSM_BTS_SI(bts, i) : NULL, si_len); + break; + } + + return rc; +} + +/* set all system information types for a TRX */ +int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx) +{ + int i, rc; + struct gsm_bts *bts = trx->bts; + uint8_t gen_si[_MAX_SYSINFO_TYPE], n_si = 0, n; + int si_len[_MAX_SYSINFO_TYPE]; + + bts->si_common.cell_sel_par.ms_txpwr_max_ccch = + ms_pwr_ctl_lvl(bts->band, bts->ms_max_power); + bts->si_common.cell_sel_par.neci = bts->network->neci; + + /* Zero/forget the state of the dynamically computed SIs, leeping the static ones */ + bts->si_valid = bts->si_mode_static; + + /* First, we determine which of the SI messages we actually need */ + + if (trx == bts->c0) { + /* 1...4 are always present on a C0 TRX */ + gen_si[n_si++] = SYSINFO_TYPE_1; + gen_si[n_si++] = SYSINFO_TYPE_2; + gen_si[n_si++] = SYSINFO_TYPE_2bis; + gen_si[n_si++] = SYSINFO_TYPE_2ter; + gen_si[n_si++] = SYSINFO_TYPE_2quater; + gen_si[n_si++] = SYSINFO_TYPE_3; + gen_si[n_si++] = SYSINFO_TYPE_4; + + /* 13 is always present on a C0 TRX of a GPRS BTS */ + if (bts->gprs.mode != BTS_GPRS_NONE) + gen_si[n_si++] = SYSINFO_TYPE_13; + } + + /* 5 and 6 are always present on every TRX */ + gen_si[n_si++] = SYSINFO_TYPE_5; + gen_si[n_si++] = SYSINFO_TYPE_5bis; + gen_si[n_si++] = SYSINFO_TYPE_5ter; + gen_si[n_si++] = SYSINFO_TYPE_6; + + /* Second, we generate the selected SI via RSL */ + + for (n = 0; n < n_si; n++) { + i = gen_si[n]; + /* Only generate SI if this SI is not in "static" (user-defined) mode */ + if (!(bts->si_mode_static & (1 << i))) { + /* 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); + if (rc < 0) + goto err_out; + si_len[i] = 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; + } + } + + /* Third, we send the selected SI via RSL */ + + for (n = 0; n < n_si; n++) { + i = gen_si[n]; + /* 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)) + rc = rsl_si(trx, i, 0); + else + rc = rsl_si(trx, i, si_len[i]); + if (rc < 0) + return rc; + } + + /* Make sure the PCU is aware (in case anything GPRS related has + * changed in SI */ + pcu_info_update(bts); + + return 0; +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)); + return rc; +} + +/* set all system information types for a BTS */ +int gsm_bts_set_system_infos(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + /* Generate a new ID */ + bts->bcch_change_mark += 1; + bts->bcch_change_mark %= 0x7; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int rc; + + rc = gsm_bts_trx_set_system_infos(trx); + if (rc != 0) + return rc; + } + + return 0; +} + +/* XXX hard-coded for now */ +#define T3122_CHAN_LOAD_SAMPLE_INTERVAL 1 /* in seconds */ + +static void update_t3122_chan_load_timer(void *data) +{ + struct gsm_network *net = data; + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + bts_update_t3122_chan_load(bts); + + /* Keep this timer ticking. */ + osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0); +} + +static struct gsm_network *bsc_network_init(void *ctx) +{ + struct gsm_network *net = gsm_network_init(ctx); + + net->bsc_data = talloc_zero(net, struct osmo_bsc_data); + if (!net->bsc_data) { + talloc_free(net); + return NULL; + } + + /* Init back pointer */ + net->bsc_data->auto_off_timeout = -1; + net->bsc_data->network = net; + INIT_LLIST_HEAD(&net->bsc_data->mscs); + + net->ho = ho_cfg_init(net, NULL); + net->hodec2.congestion_check_interval_s = HO_CFG_CONGESTION_CHECK_DEFAULT; + + /* init statistics */ + net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0); + if (!net->bsc_ctrs) { + talloc_free(net); + return NULL; + } + + gsm_net_update_ctype(net); + + /* + * At present all BTS in the network share one channel load timeout. + * If this becomes a problem for networks with a lot of BTS, this + * code could be refactored to run the timeout individually per BTS. + */ + 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); + + return net; +} + +int bsc_network_alloc(void) +{ + /* initialize our data structures */ + bsc_gsmnet = bsc_network_init(tall_bsc_ctx); + if (!bsc_gsmnet) + return -ENOMEM; + + return 0; +} + +struct gsm_bts *bsc_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, uint8_t bsic) +{ + struct gsm_bts *bts = gsm_bts_alloc_register(net, type, bsic); + + bts->ho = ho_cfg_init(bts, net->ho); + + return bts; +} diff --git a/src/osmo-bsc/bsc_rf_ctrl.c b/src/osmo-bsc/bsc_rf_ctrl.c new file mode 100644 index 000000000..f4a21b53a --- /dev/null +++ b/src/osmo-bsc/bsc_rf_ctrl.c @@ -0,0 +1,534 @@ +/* RF Ctl handling socket */ + +/* (C) 2010 by Harald Welte + * (C) 2010-2014 by Holger Hans Peter Freyther + * (C) 2010-2014 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 . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#define RF_CMD_QUERY '?' +#define RF_CMD_OFF '0' +#define RF_CMD_ON '1' +#define RF_CMD_D_OFF 'd' +#define RF_CMD_ON_G 'g' + +static const struct value_string opstate_names[] = { + { OSMO_BSC_RF_OPSTATE_INOPERATIONAL, "inoperational" }, + { OSMO_BSC_RF_OPSTATE_OPERATIONAL, "operational" }, + { 0, NULL } +}; + +static const struct value_string adminstate_names[] = { + { OSMO_BSC_RF_ADMINSTATE_UNLOCKED, "unlocked" }, + { OSMO_BSC_RF_ADMINSTATE_LOCKED, "locked" }, + { 0, NULL } +}; + +static const struct value_string policy_names[] = { + { OSMO_BSC_RF_POLICY_OFF, "off" }, + { OSMO_BSC_RF_POLICY_ON, "on" }, + { OSMO_BSC_RF_POLICY_GRACE, "grace" }, + { OSMO_BSC_RF_POLICY_UNKNOWN, "unknown" }, + { 0, NULL } +}; + +const char *osmo_bsc_rf_get_opstate_name(enum osmo_bsc_rf_opstate opstate) +{ + return get_value_string(opstate_names, opstate); +} + +const char *osmo_bsc_rf_get_adminstate_name(enum osmo_bsc_rf_adminstate adminstate) +{ + return get_value_string(adminstate_names, adminstate); +} + +const char *osmo_bsc_rf_get_policy_name(enum osmo_bsc_rf_policy policy) +{ + return get_value_string(policy_names, policy); +} + +enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED) + return OSMO_BSC_RF_OPSTATE_OPERATIONAL; + } + + /* No trx were active, so this bts is disabled */ + return OSMO_BSC_RF_OPSTATE_INOPERATIONAL; +} + +enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + return OSMO_BSC_RF_ADMINSTATE_UNLOCKED; + } + + /* All trx administrative states were locked */ + return OSMO_BSC_RF_ADMINSTATE_LOCKED; +} + +enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts) +{ + struct osmo_bsc_data *bsc_data = bts->network->bsc_data; + + if (!bsc_data) + return OSMO_BSC_RF_POLICY_UNKNOWN; + + switch (bsc_data->rf_ctrl->policy) { + case S_RF_ON: + return OSMO_BSC_RF_POLICY_ON; + case S_RF_OFF: + return OSMO_BSC_RF_POLICY_OFF; + case S_RF_GRACE: + return OSMO_BSC_RF_POLICY_GRACE; + default: + return OSMO_BSC_RF_POLICY_UNKNOWN; + } +} + +static int lock_each_trx(struct gsm_network *net, bool lock) +{ + struct gsm_bts *bts; + + 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) { + LOGP(DLINP, LOGL_DEBUG, + "Excluding BTS(%d) from trx lock.\n", bts->nr); + continue; + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + gsm_trx_lock_rf(trx, lock, "ctrl"); + } + } + + return 0; +} + +static void send_resp(struct osmo_bsc_rf_conn *conn, char send) +{ + struct msgb *msg; + + msg = msgb_alloc(10, "RF Query"); + if (!msg) { + LOGP(DLINP, LOGL_ERROR, "Failed to allocate response msg.\n"); + return; + } + + msg->l2h = msgb_put(msg, 1); + msg->l2h[0] = send; + + if (osmo_wqueue_enqueue(&conn->queue, msg) != 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the answer.\n"); + msgb_free(msg); + return; + } + + return; +} + + +/* + * Send a + * 'g' when we are in grace mode + * '1' when one TRX is online, + * '0' otherwise + */ +static void handle_query(struct osmo_bsc_rf_conn *conn) +{ + struct gsm_bts *bts; + char send = RF_CMD_OFF; + + if (conn->rf->policy == S_RF_GRACE) + return send_resp(conn, RF_CMD_ON_G); + + llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) { + struct gsm_bts_trx *trx; + + /* Exclude the BTS from the global lock */ + if (bts->excl_from_rf_lock) { + LOGP(DLINP, LOGL_DEBUG, + "Excluding BTS(%d) from query.\n", bts->nr); + 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) { + send = RF_CMD_ON; + break; + } + } + } + + send_resp(conn, send); +} + +static void rf_check_cb(void *_data) +{ + struct gsm_bts *bts; + struct osmo_bsc_rf *rf = _data; + + llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) { + struct gsm_bts_trx *trx; + + /* don't bother to check a booting or missing BTS */ + if (!bts->oml_link || !is_ipaccess_bts(bts)) + continue; + + /* Exclude the BTS from the global lock */ + if (bts->excl_from_rf_lock) { + LOGP(DLINP, LOGL_DEBUG, + "Excluding BTS(%d) from query.\n", bts->nr); + 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_ENABLED || + trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) { + LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n"); + ipaccess_drop_oml(bts); + break; + } + } + } +} + +static void send_signal(struct osmo_bsc_rf *rf, int val) +{ + struct rf_signal_data sig; + sig.net = rf->gsm_network; + + rf->policy = val; + osmo_signal_dispatch(SS_RF, val, &sig); +} + +static int switch_rf_off(struct osmo_bsc_rf *rf) +{ + lock_each_trx(rf->gsm_network, true); + send_signal(rf, S_RF_OFF); + + return 0; +} + +static void grace_timeout(void *_data) +{ + struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data; + + LOGP(DLINP, LOGL_NOTICE, "Grace timeout. Going to disable all BTS/TRX.\n"); + switch_rf_off(rf); +} + +static int enter_grace(struct osmo_bsc_rf *rf) +{ + if (osmo_timer_pending(&rf->grace_timeout)) { + LOGP(DLINP, LOGL_NOTICE, "RF Grace timer is pending. Not restarting.\n"); + return 0; + } + + osmo_timer_setup(&rf->grace_timeout, grace_timeout, rf); + osmo_timer_schedule(&rf->grace_timeout, rf->gsm_network->bsc_data->mid_call_timeout, 0); + LOGP(DLINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n", + rf->gsm_network->bsc_data->mid_call_timeout); + + send_signal(rf, S_RF_GRACE); + return 0; +} + +static void rf_delay_cmd_cb(void *data) +{ + struct osmo_bsc_rf *rf = data; + + switch (rf->last_request) { + case RF_CMD_D_OFF: + rf->last_state_command = "RF Direct Off"; + osmo_timer_del(&rf->rf_check); + osmo_timer_del(&rf->grace_timeout); + switch_rf_off(rf); + break; + case RF_CMD_ON: + rf->last_state_command = "RF Direct On"; + osmo_timer_del(&rf->grace_timeout); + lock_each_trx(rf->gsm_network, false); + send_signal(rf, S_RF_ON); + osmo_timer_schedule(&rf->rf_check, 3, 0); + break; + case RF_CMD_OFF: + rf->last_state_command = "RF Scheduled Off"; + osmo_timer_del(&rf->rf_check); + enter_grace(rf); + break; + } +} + +static int rf_read_cmd(struct osmo_fd *fd) +{ + struct osmo_bsc_rf_conn *conn = fd->data; + char buf[1]; + int rc; + + rc = read(fd->fd, buf, sizeof(buf)); + if (rc != sizeof(buf)) { + LOGP(DLINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno)); + osmo_fd_unregister(fd); + close(fd->fd); + osmo_wqueue_clear(&conn->queue); + talloc_free(conn); + return -1; + } + + switch (buf[0]) { + case RF_CMD_QUERY: + handle_query(conn); + break; + case RF_CMD_D_OFF: + case RF_CMD_ON: + case RF_CMD_OFF: + osmo_bsc_rf_schedule_lock(conn->rf, buf[0]); + break; + default: + conn->rf->last_state_command = "Unknown command"; + LOGP(DLINP, LOGL_ERROR, "Unknown command %d\n", buf[0]); + break; + } + + return 0; +} + +static int rf_write_cmd(struct osmo_fd *fd, struct msgb *msg) +{ + int rc; + + rc = write(fd->fd, msg->data, msg->len); + if (rc != msg->len) { + LOGP(DLINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno)); + return -1; + } + + return 0; +} + +static int rf_ctrl_accept(struct osmo_fd *bfd, unsigned int what) +{ + struct osmo_bsc_rf_conn *conn; + struct osmo_bsc_rf *rf = bfd->data; + struct sockaddr_un addr; + socklen_t len = sizeof(addr); + int fd; + + fd = accept(bfd->fd, (struct sockaddr *) &addr, &len); + if (fd < 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n", + errno, strerror(errno)); + return -1; + } + + conn = talloc_zero(rf, struct osmo_bsc_rf_conn); + if (!conn) { + LOGP(DLINP, LOGL_ERROR, "Failed to allocate mem.\n"); + close(fd); + return -1; + } + + osmo_wqueue_init(&conn->queue, 10); + conn->queue.bfd.data = conn; + conn->queue.bfd.fd = fd; + conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE; + conn->queue.read_cb = rf_read_cmd; + conn->queue.write_cb = rf_write_cmd; + conn->rf = rf; + + if (osmo_fd_register(&conn->queue.bfd) != 0) { + close(fd); + talloc_free(conn); + return -1; + } + + return 0; +} + +static void rf_auto_off_cb(void *_timer) +{ + struct osmo_bsc_rf *rf = _timer; + + LOGP(DLINP, LOGL_NOTICE, + "Going to switch off RF due lack of a MSC connection.\n"); + osmo_bsc_rf_schedule_lock(rf, RF_CMD_D_OFF); +} + +static int msc_signal_handler(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_network *net; + struct msc_signal_data *msc; + struct osmo_bsc_rf *rf; + + /* check if we want to handle this signal */ + if (subsys != SS_MSC) + return 0; + + net = handler_data; + msc = signal_data; + + /* check if we have the needed information */ + if (!net->bsc_data) + return 0; + if (msc->data->type != MSC_CON_TYPE_NORMAL) + return 0; + + rf = net->bsc_data->rf_ctrl; + switch (signal) { + case S_MSC_LOST: + if (net->bsc_data->auto_off_timeout < 0) + return 0; + if (osmo_timer_pending(&rf->auto_off_timer)) + return 0; + osmo_timer_schedule(&rf->auto_off_timer, + net->bsc_data->auto_off_timeout, 0); + break; + case S_MSC_CONNECTED: + osmo_timer_del(&rf->auto_off_timer); + break; + } + + return 0; +} + +static int rf_create_socket(struct osmo_bsc_rf *rf, const char *path) +{ + unsigned int namelen; + struct sockaddr_un local; + struct osmo_fd *bfd; + int rc; + + bfd = &rf->listen; + bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (bfd->fd < 0) { + LOGP(DLINP, LOGL_ERROR, "Can not create socket. %d/%s\n", + errno, strerror(errno)); + return -1; + } + + local.sun_family = AF_UNIX; + osmo_strlcpy(local.sun_path, path, sizeof(local.sun_path)); + unlink(local.sun_path); + + /* we use the same magic that X11 uses in Xtranssock.c for + * calculating the proper length of the sockaddr */ +#if defined(BSD44SOCKETS) || defined(__UNIXWARE__) + local.sun_len = strlen(local.sun_path); +#endif +#if defined(BSD44SOCKETS) || defined(SUN_LEN) + namelen = SUN_LEN(&local); +#else + namelen = strlen(local.sun_path) + + offsetof(struct sockaddr_un, sun_path); +#endif + + rc = bind(bfd->fd, (struct sockaddr *) &local, namelen); + if (rc != 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n", + local.sun_path, errno, strerror(errno)); + close(bfd->fd); + return -1; + } + + if (listen(bfd->fd, 0) != 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno)); + close(bfd->fd); + return -1; + } + + bfd->when = BSC_FD_READ; + bfd->cb = rf_ctrl_accept; + bfd->data = rf; + + if (osmo_fd_register(bfd) != 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to register bfd.\n"); + close(bfd->fd); + return -1; + } + + return 0; +} + +struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net) +{ + struct osmo_bsc_rf *rf; + + rf = talloc_zero(NULL, struct osmo_bsc_rf); + if (!rf) { + LOGP(DLINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n"); + return NULL; + } + + if (path && rf_create_socket(rf, path) != 0) { + talloc_free(rf); + return NULL; + } + + rf->gsm_network = net; + rf->policy = S_RF_ON; + rf->last_state_command = ""; + rf->last_rf_lock_ctrl_command = talloc_strdup(rf, ""); + + /* check the rf state */ + osmo_timer_setup(&rf->rf_check, rf_check_cb, rf); + + /* delay cmd handling */ + osmo_timer_setup(&rf->delay_cmd, rf_delay_cmd_cb, rf); + + osmo_timer_setup(&rf->auto_off_timer, rf_auto_off_cb, rf); + + /* listen to RF signals */ + osmo_signal_register_handler(SS_MSC, msc_signal_handler, net); + + return rf; +} + +void osmo_bsc_rf_schedule_lock(struct osmo_bsc_rf *rf, char cmd) +{ + rf->last_request = cmd; + if (!osmo_timer_pending(&rf->delay_cmd)) + osmo_timer_schedule(&rf->delay_cmd, 1, 0); +} diff --git a/src/osmo-bsc/bsc_rll.c b/src/osmo-bsc/bsc_rll.c new file mode 100644 index 000000000..ebf9b8856 --- /dev/null +++ b/src/osmo-bsc/bsc_rll.c @@ -0,0 +1,139 @@ +/* GSM BSC Radio Link Layer API + * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct bsc_rll_req { + struct llist_head list; + struct osmo_timer_list timer; + + struct gsm_lchan *lchan; + uint8_t link_id; + + void (*cb)(struct gsm_lchan *lchan, uint8_t link_id, + void *data, enum bsc_rllr_ind); + void *data; +}; + +/* we only compare C1, C2 and SAPI */ +#define LINKID_MASK 0xC7 + +static LLIST_HEAD(bsc_rll_reqs); + +static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type) +{ + llist_del(&rllr->list); + rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type); + talloc_free(rllr); +} + +static void timer_cb(void *_rllr) +{ + struct bsc_rll_req *rllr = _rllr; + + complete_rllr(rllr, BSC_RLLR_IND_TIMEOUT); +} + +/* establish a RLL connection with given SAPI / priority */ +int rll_establish(struct gsm_lchan *lchan, uint8_t sapi, + void (*cb)(struct gsm_lchan *, uint8_t, void *, + enum bsc_rllr_ind), + void *data) +{ + struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req); + uint8_t link_id; + if (!rllr) + return -ENOMEM; + + link_id = sapi; + + /* If we are a TCH and not in signalling mode, we need to + * indicate that the new RLL connection is to be made on the SACCH */ + if ((lchan->type == GSM_LCHAN_TCH_F || + lchan->type == GSM_LCHAN_TCH_H) && sapi != 0) + link_id |= 0x40; + + rllr->lchan = lchan; + rllr->link_id = link_id; + rllr->cb = cb; + rllr->data = data; + + llist_add(&rllr->list, &bsc_rll_reqs); + + osmo_timer_setup(&rllr->timer, timer_cb, rllr); + osmo_timer_schedule(&rllr->timer, 7, 0); + + /* send the RSL RLL ESTablish REQuest */ + return rsl_establish_request(rllr->lchan, rllr->link_id); +} + +/* Called from RSL code in case we have received an indication regarding + * any RLL link */ +void rll_indication(struct gsm_lchan *lchan, uint8_t link_id, uint8_t type) +{ + struct bsc_rll_req *rllr, *rllr2; + + llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) { + if (rllr->lchan == lchan && + (rllr->link_id & LINKID_MASK) == (link_id & LINKID_MASK)) { + osmo_timer_del(&rllr->timer); + complete_rllr(rllr, type); + return; + } + } +} + +static int rll_lchan_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct challoc_signal_data *challoc; + struct bsc_rll_req *rllr, *rllr2; + + if (subsys != SS_CHALLOC || signal != S_CHALLOC_FREED) + return 0; + + challoc = (struct challoc_signal_data *) signal_data; + + llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) { + if (rllr->lchan == challoc->lchan) { + osmo_timer_del(&rllr->timer); + complete_rllr(rllr, BSC_RLLR_IND_ERR_IND); + } + } + + return 0; +} + +static __attribute__((constructor)) void on_dso_load_rll(void) +{ + osmo_signal_register_handler(SS_CHALLOC, rll_lchan_signal, NULL); +} diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c new file mode 100644 index 000000000..bafe14589 --- /dev/null +++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c @@ -0,0 +1,1204 @@ +/* (C) 2017 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define S(x) (1 << (x)) + +#define MGCP_MGW_TIMEOUT 4 /* in seconds */ +#define MGCP_MGW_TIMEOUT_TIMER_NR 1 + +#define MGCP_MGW_HO_TIMEOUT 4 /* in seconds */ +#define MGCP_MGW_HO_TIMEOUT_TIMER_NR 2 + +#define GSM0808_T10_TIMER_NR 10 +#define GSM0808_T10_VALUE 6 + +#define ENDPOINT_ID "rtpbridge/*@mgw" + +enum gscon_fsm_states { + ST_INIT, + /* waiting for CC from MSC */ + ST_WAIT_CC, + /* active connection */ + ST_ACTIVE, + /* during assignment; waiting for ASS_CMPL */ + ST_WAIT_ASS_CMPL, + /* BSSMAP CLEAR has been received */ + ST_CLEARING, + +/* MGW handling */ + /* during assignment; waiting for MGW response to CRCX for BTS */ + ST_WAIT_CRCX_BTS, + /* during assignment; waiting for MGW response to MDCX for BTS */ + ST_WAIT_MDCX_BTS, + /* during assignment; waiting for MGW response to CRCX for MSC */ + ST_WAIT_CRCX_MSC, + +/* MT (inbound) handover */ + /* Wait for Handover Access from MS/BTS */ + ST_WAIT_MT_HO_ACC, + /* Wait for RR Handover Complete from MS/BTS */ + ST_WAIT_MT_HO_COMPL, + +/* MO (outbound) handover */ + /* Wait for Handover Command / Handover Required Reject from MSC */ + ST_WAIT_MO_HO_CMD, + /* Wait for Clear Command from MSC */ + ST_MO_HO_PROCEEDING, + +/* Internal HO handling */ + /* Wait for the handover logic to complete the handover */ + ST_WAIT_HO_COMPL, + /* during handover; waiting for MGW response to MDCX for BTS */ + ST_WAIT_MDCX_BTS_HO, +}; + +static const struct value_string gscon_fsm_event_names[] = { + {GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"}, + {GSCON_EV_A_CONN_REQ, "MO-CONNECT.req"}, + {GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"}, + {GSCON_EV_A_ASSIGNMENT_CMD, "ASSIGNMENT_CMD"}, + {GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"}, + {GSCON_EV_A_DISC_IND, "DISCONNET.ind"}, + {GSCON_EV_A_HO_REQ, "HANDOVER_REQUEST"}, + + {GSCON_EV_RR_ASS_COMPL, "RR_ASSIGN_COMPL"}, + {GSCON_EV_RR_ASS_FAIL, "RR_ASSIGN_FAIL"}, + {GSCON_EV_RLL_REL_IND, "RLL_RELEASE.ind"}, + {GSCON_EV_RSL_CONN_FAIL, "RSL_CONN_FAIL.ind"}, + {GSCON_EV_RSL_CLEAR_COMPL, "RSL_CLEAR_COMPLETE"}, + + {GSCON_EV_MO_DTAP, "MO-DTAP"}, + {GSCON_EV_MT_DTAP, "MT-DTAP"}, + {GSCON_EV_TX_SCCP, "TX_SCCP"}, + + {GSCON_EV_MGW_FAIL_BTS, "MGW_FAILURE_BTS"}, + {GSCON_EV_MGW_FAIL_MSC, "MGW_FAILURE_MSC"}, + {GSCON_EV_MGW_CRCX_RESP_BTS, "MGW_CRCX_RESPONSE_BTS"}, + {GSCON_EV_MGW_MDCX_RESP_BTS, "MGW_MDCX_RESPONSE_BTS"}, + {GSCON_EV_MGW_CRCX_RESP_MSC, "MGW_CRCX_RESPONSE_MSC"}, + {GSCON_EV_MGW_MDCX_RESP_MSC, "MGW_MDCX_RESPONSE_MSC"}, + + {GSCON_EV_HO_START, "HO_START"}, + {GSCON_EV_HO_TIMEOUT, "HO_TIMEOUT"}, + {GSCON_EV_HO_FAIL, "HO_FAIL"}, + {GSCON_EV_HO_COMPL, "HO_COMPL"}, + + {0, NULL} +}; + +/* Send data SCCP message through SCCP connection. All sigtran messages + * that are send from this FSM must use this function. Never use + * osmo_bsc_sigtran_send() directly since this would defeat the checks + * provided by this function. */ +static void sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) +{ + int rc; + + /* Make sure that we only attempt to send SCCP messages if we have + * a life SCCP connection. Otherwise drop the message. */ + if (fi->state == ST_INIT || fi->state == ST_WAIT_CC) { + LOGPFSML(fi, LOGL_ERROR, "No active SCCP connection, dropping message!\n"); + msgb_free(msg); + return; + } + + rc = osmo_bsc_sigtran_send(conn, msg); + if (rc < 0) + LOGPFSML(fi, LOGL_ERROR, "Unable to deliver SCCP message!\n"); +} + + +/* See TS 48.008 3.2.2.11 Channel Type Octet 5 */ +static int bssap_speech_from_lchan(const struct gsm_lchan *lchan) +{ + switch (lchan->type) { + case GSM_LCHAN_TCH_H: + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + return 0x05; + case GSM48_CMODE_SPEECH_AMR: + return 0x25; + default: + return -1; + } + break; + case GSM_LCHAN_TCH_F: + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + return 0x01; + case GSM48_CMODE_SPEECH_EFR: + return 0x11; + case GSM48_CMODE_SPEECH_AMR: + return 0x21; + default: + return -1; + } + break; + default: + return -1; + } +} + +/* GSM 08.08 3.2.2.33 */ +static uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan) +{ + uint8_t channel_mode = 0, channel = 0; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + channel_mode = 0x9; + break; + case GSM48_CMODE_SIGN: + channel_mode = 0x8; + break; + case GSM48_CMODE_DATA_14k5: + channel_mode = 0xe; + break; + case GSM48_CMODE_DATA_12k0: + channel_mode = 0xb; + break; + case GSM48_CMODE_DATA_6k0: + channel_mode = 0xc; + break; + case GSM48_CMODE_DATA_3k6: + channel_mode = 0xd; + break; + } + + switch (lchan->type) { + case GSM_LCHAN_NONE: + channel = 0x0; + break; + case GSM_LCHAN_SDCCH: + channel = 0x1; + break; + case GSM_LCHAN_TCH_F: + channel = 0x8; + break; + case GSM_LCHAN_TCH_H: + channel = 0x9; + break; + case GSM_LCHAN_UNKNOWN: + default: + LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan); + break; + } + + return channel_mode << 4 | channel; +} + +/* Add the LCLS BSS Status IE to a BSSMAP message. We assume this is + * called on a msgb that was returned by gsm0808_create_ass_compl() */ +static void bssmap_add_lcls_status(struct msgb *msg, enum gsm0808_lcls_status status) +{ + OSMO_ASSERT(msg->l3h[0] == BSSAP_MSG_BSS_MANAGEMENT); + OSMO_ASSERT(msg->l3h[2] == BSS_MAP_MSG_ASSIGMENT_COMPLETE || + msg->l3h[2] == BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE || + msg->l3h[2] == BSS_MAP_MSG_HANDOVER_COMPLETE || + msg->l3h[2] == BSS_MAP_MSG_HANDOVER_PERFORMED); + OSMO_ASSERT(msgb_tailroom(msg) >= 2); + + /* append IE to end of message */ + msgb_tv_put(msg, GSM0808_IE_LCLS_BSS_STATUS, status); + /* increment the "length" byte in the BSSAP header */ + msg->l3h[1] += 2; +} + +/* Add (append) the LCLS BSS Status IE to a BSSMAP message, if there is any LCLS + * active on the given \a conn */ +static void bssmap_add_lcls_status_if_needed(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + enum gsm0808_lcls_status status = lcls_get_status(conn); + if (status != 0xff) { + LOGPFSM(conn->fi, "Adding LCLS BSS-Status (%s) to %s\n", + gsm0808_lcls_status_name(status), + gsm0808_bssmap_name(msg->l3h[2])); + bssmap_add_lcls_status(msg, status); + } +} + +/* Generate and send assignment complete message */ +static void send_ass_compl(struct gsm_lchan *lchan, struct osmo_fsm_inst *fi, bool voice) +{ + struct msgb *resp; + struct gsm0808_speech_codec sc; + struct gsm0808_speech_codec *sc_ptr = NULL; + struct gsm_subscriber_connection *conn; + struct sockaddr_storage *addr_local = NULL; + int perm_spch = 0; + + conn = lchan->conn; + OSMO_ASSERT(conn); + + /* apply LCLS configuration (if any) */ + lcls_apply_config(conn); + + LOGPFSML(fi, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp.conn_id); + + /* Generate voice related fields */ + if (voice) { + perm_spch = bssap_speech_from_lchan(lchan); + switch (conn->sccp.msc->a.asp_proto) { + case OSMO_SS7_ASP_PROT_IPA: + /* don't add any AoIP specific fields. CIC allocated by MSC */ + break; + default: + OSMO_ASSERT(lchan->abis_ip.ass_compl.valid); + addr_local = &conn->user_plane.aoip_rtp_addr_local; + + /* Extrapolate speech codec from speech mode */ + gsm0808_speech_codec_from_chan_type(&sc, perm_spch); + sc_ptr = ≻ + break; + } + /* FIXME: AMR codec configuration must be derived from lchan1! */ + } + + /* Generate message */ + resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause, + lchan_to_chosen_channel(lchan), + lchan->encr.alg_id, perm_spch, + addr_local, sc_ptr, NULL); + + if (!resp) { + LOGPFSML(fi, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n", + conn->sccp.conn_id); + } + + /* Add LCLS BSS-Status IE in case there is any LCLS status for this connection */ + bssmap_add_lcls_status_if_needed(conn, resp); + + sigtran_send(conn, resp, fi); +} + +/* forward MT DTAP from BSSAP side to RSL side */ +static void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) +{ + int rc; + struct msgb *resp = NULL; + + OSMO_ASSERT(fi); + OSMO_ASSERT(msg); + OSMO_ASSERT(conn); + + rc = gsm0808_submit_dtap(conn, msg, OBSC_LINKID_CB(msg), 1); + if (rc != 0) { + LOGPFSML(fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } +} + +/* forward MO DTAP from RSL side to BSSAP side */ +static void forward_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) +{ + struct msgb *resp = NULL; + + OSMO_ASSERT(msg); + OSMO_ASSERT(conn); + + resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg)); + sigtran_send(conn, resp, fi); +} + +/* In case there are open MGCP connections, toss + * those connections */ +static void toss_mgcp_conn(struct gsm_subscriber_connection *conn, struct osmo_fsm_inst *fi) +{ + LOGPFSML(fi, LOGL_ERROR, "tossing all MGCP connections...\n"); + + if (conn->user_plane.fi_bts) { + mgcp_conn_delete(conn->user_plane.fi_bts); + conn->user_plane.fi_bts = NULL; + } + + if (conn->user_plane.fi_msc) { + mgcp_conn_delete(conn->user_plane.fi_msc); + conn->user_plane.fi_msc = NULL; + } + + if (conn->user_plane.mgw_endpoint) { + talloc_free(conn->user_plane.mgw_endpoint); + conn->user_plane.mgw_endpoint = NULL; + } +} + +static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct osmo_scu_prim *scu_prim = NULL; + struct msgb *msg = NULL; + int rc; + + switch (event) { + case GSCON_EV_A_CONN_REQ: + /* RLL ESTABLISH IND with initial L3 Message */ + msg = data; + /* FIXME: Extract Mobile ID and update FSM using osmo_fsm_inst_set_id() + * i.e. we will probably extract the mobile identity earlier, where the + * imsi filter code is. Then we could just use it here. + * related: OS#2969 */ + + rc = osmo_bsc_sigtran_open_conn(conn, msg); + if (rc < 0) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + } else { + /* SCCP T(conn est) is 1-2 minutes, way too long. The MS will timeout + * using T3210 (20s), T3220 (5s) or T3230 (10s) */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CC, 20, 993210); + } + break; + case GSCON_EV_A_CONN_IND: + scu_prim = data; + if (!conn->sccp.msc) { + LOGPFSML(fi, LOGL_NOTICE, "N-CONNECT.ind from unknown MSC %s\n", + osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr)); + 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); + } + /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() + * related: OS2969 (same as above) */ + + LOGPFSML(fi, LOGL_NOTICE, "No support for MSC-originated SCCP Connections yet\n"); + 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); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We've sent the CONNECTION.req to the SCCP provider and are waiting for CC from MSC */ +static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case GSCON_EV_A_CONN_CFM: + /* MSC has confirmed the connection, we now change into the + * active state and wait there for further operations */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + /* if there's user payload, forward it just like EV_MT_DTAP */ + /* FIXME: Question: if there's user payload attached to the CC, forward it like EV_MT_DTAP? */ + break; + default: + OSMO_ASSERT(false); + break; + } +} + +static const char *get_mgw_ep_name(struct gsm_subscriber_connection *conn) +{ + static char ep_name[256]; + struct bsc_msc_data *msc = conn->sccp.msc; + + switch (conn->sccp.msc->a.asp_proto) { + case OSMO_SS7_ASP_PROT_IPA: + /* derive endpoint name from CIC on A interface side */ + snprintf(ep_name, sizeof(ep_name), "%x@mgw", + mgcp_port_to_cic(conn->user_plane.rtp_port, msc->rtp_base)); + break; + default: + /* use dynamic RTPBRIDGE endpoint allocation in MGW */ + osmo_strlcpy(ep_name, ENDPOINT_ID, sizeof(ep_name)); + break; + } + return ep_name; +} + +/* We're on an active subscriber connection, passing DTAP back and forth */ +static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + struct mgcp_conn_peer conn_peer; + int rc; + + switch (event) { + case GSCON_EV_A_ASSIGNMENT_CMD: + /* MSC requests us to perform assignment, this code section is + * triggered via signal GSCON_EV_A_ASSIGNMENT_CMD from + * bssmap_handle_assignm_req() in osmo_bsc_bssap.c, which does + * the parsing of incoming assignment requests. */ + + LOGPFSML(fi, LOGL_NOTICE, "Channel assignment: chan_mode=%s, full_rate=%i\n", + get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), + conn->user_plane.full_rate); + + /* FIXME: We need to check if current channel is sufficient. If + * yes, do MODIFY. If not, do assignment (see commented lines below) */ + + switch (conn->user_plane.chan_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + /* A voice channel is requested, so we run down the + * mgcp-ass-mgcp state-chain (see FIXME above) */ + memset(&conn_peer, 0, sizeof(conn_peer)); + conn_peer.call_id = conn->sccp.conn_id; + osmo_strlcpy(conn_peer.endpoint, get_mgw_ep_name(conn), sizeof(conn_peer.endpoint)); + + /* (Pre)Change state and create the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); + conn->user_plane.fi_bts = + mgcp_conn_create(conn->network->mgw.client, fi, GSCON_EV_MGW_FAIL_BTS, + GSCON_EV_MGW_CRCX_RESP_BTS, &conn_peer); + if (!conn->user_plane.fi_bts) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + return; + } + break; + case GSM48_CMODE_SIGN: + /* A signalling channel is requested, so we perform the + * channel assignment directly without performing any + * MGCP actions. ST_WAIT_ASS_CMPL will see by the + * conn->user_plane.chan_mode parameter that this + * assignment is for a signalling channel and will then + * change back to ST_ACTIVE (here) immediately. */ + rc = gsm0808_assign_req(conn, conn->user_plane.chan_mode, + conn->user_plane.full_rate); + + if (rc == 1) { + send_ass_compl(conn->lchan, fi, false); + return; + } else if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + return; + } + + osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR); + break; + default: + /* An unsupported channel is requested, so we have to + * reject this request by sending an assignment failure + * message immediately */ + LOGPFSML(fi, LOGL_ERROR, "Requested channel mode is not supported! chan_mode=%s full_rate=%d\n", + get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), + conn->user_plane.full_rate); + + /* The requested channel mode is not supported */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, NULL); + sigtran_send(conn, resp, fi); + break; + } + break; + case GSCON_EV_HO_START: + rc = bsc_handover_start_gscon(conn); + if (rc) { + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + return; + } + + /* Note: No timeout is set here, T3103 in handover_logic.c + * will generate a GSCON_EV_HO_TIMEOUT event should the + * handover time out, so we do not need another timeout + * here (maybe its worth to think about giving GSCON + * more power over the actual handover process). */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_HO_COMPL, 0, 0); + break; + case GSCON_EV_A_HO_REQ: + /* FIXME: reject any handover requests with HO FAIL until implemented */ + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Before we may start the channel assignment we need to get an IP/Port for the + * RTP connection from the MGW */ +static void gscon_fsm_wait_crcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer *conn_peer = NULL; + struct msgb *resp = NULL; + int rc; + + switch (event) { + case GSCON_EV_MGW_CRCX_RESP_BTS: + conn_peer = data; + + /* Check if the MGW has assigned an enpoint to us, otherwise we + * can not proceed. */ + if (strlen(conn_peer->endpoint) <= 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + + /* Memorize the endpoint name we got assigned from the MGW. + * When the BTS sided connection is done, we need to create + * a second connection on that same endpoint, so we need + * to know its ID */ + if (!conn->user_plane.mgw_endpoint) + conn->user_plane.mgw_endpoint = talloc_zero_size(conn, MGCP_ENDPOINT_MAXLEN); + OSMO_ASSERT(conn->user_plane.mgw_endpoint); + osmo_strlcpy(conn->user_plane.mgw_endpoint, conn_peer->endpoint, MGCP_ENDPOINT_MAXLEN); + + /* Store the IP-Address and the port the MGW assigned to us, + * then start the channel assignment. */ + conn->user_plane.rtp_port = conn_peer->port; + conn->user_plane.rtp_ip = osmo_ntohl(inet_addr(conn_peer->addr)); + rc = gsm0808_assign_req(conn, conn->user_plane.chan_mode, conn->user_plane.full_rate); + if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_SPEECH_VERSION_UNAVAILABLE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + + osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR); + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We're waiting for an ASSIGNMENT COMPLETE from MS */ +static void gscon_fsm_wait_ass_cmpl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct gsm_lchan *lchan = conn->lchan; + struct mgcp_conn_peer conn_peer; + struct in_addr addr; + struct msgb *resp = NULL; + int rc; + + switch (event) { + case GSCON_EV_RR_ASS_COMPL: + switch (conn->user_plane.chan_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + /* FIXME: What if we are using SCCP-Lite? */ + + /* We are dealing with a voice channel, so we can not + * confirm the assignment directly. We must first do + * some final steps on the MGCP side. */ + + /* Prepare parameters with the information we got during the assignment */ + memset(&conn_peer, 0, sizeof(conn_peer)); + addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); + osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr)); + conn_peer.port = lchan->abis_ip.bound_port; + + /* (Pre)Change state and modify the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); + rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); + if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + break; + case GSM48_CMODE_SIGN: + /* Confirm the successful assignment on BSSMAP and + * change back into active state */ + send_ass_compl(lchan, fi, false); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + /* Unsupported modes should have been already filtered + * by gscon_fsm_active(). If we reach the default + * section here anyway than some unsupported mode must + * have made it into the FSM, this would be a bug, so + * we fire an assertion here */ + OSMO_ASSERT(false); + break; + } + + break; + case GSCON_EV_RR_ASS_FAIL: + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We are waiting for the MGW response to the MDCX */ +static void gscon_fsm_wait_mdcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer conn_peer; + struct sockaddr_in *sin = NULL; + struct msgb *resp = NULL; + + switch (event) { + case GSCON_EV_MGW_MDCX_RESP_BTS: + + /* Prepare parameters with the connection information we got + * with the assignment command */ + memset(&conn_peer, 0, sizeof(conn_peer)); + conn_peer.call_id = conn->sccp.conn_id; + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; + conn_peer.port = osmo_ntohs(sin->sin_port); + osmo_strlcpy(conn_peer.addr, inet_ntoa(sin->sin_addr), sizeof(conn_peer.addr)); + + /* Make sure we use the same endpoint where we created the + * BTS connection. */ + osmo_strlcpy(conn_peer.endpoint, conn->user_plane.mgw_endpoint, sizeof(conn_peer.endpoint)); + + switch (conn->sccp.msc->a.asp_proto) { + case OSMO_SS7_ASP_PROT_IPA: + /* Send assignment complete message to the MSC */ + send_ass_compl(conn->lchan, fi, true); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + /* (Pre)Change state and create the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_MSC, MGCP_MGW_TIMEOUT, + MGCP_MGW_TIMEOUT_TIMER_NR); + conn->user_plane.fi_msc = mgcp_conn_create(conn->network->mgw.client, fi, + GSCON_EV_MGW_FAIL_MSC, + GSCON_EV_MGW_CRCX_RESP_MSC, &conn_peer); + if (!conn->user_plane.fi_msc) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + break; + } + + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +static void gscon_fsm_wait_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer *conn_peer = NULL; + struct gsm_lchan *lchan = conn->lchan; + struct sockaddr_in *sin = NULL; + + switch (event) { + case GSCON_EV_MGW_CRCX_RESP_MSC: + conn_peer = data; + + /* Store address information we got in response from the CRCX command. */ + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_local; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = inet_addr(conn_peer->addr); + sin->sin_port = osmo_ntohs(conn_peer->port); + + /* Send assignment complete message to the MSC */ + send_ass_compl(lchan, fi, true); + + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +static void gscon_fsm_clearing(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp; + + switch (event) { + case GSCON_EV_RSL_CLEAR_COMPL: + resp = gsm0808_create_clear_complete(); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Wait for the handover logic to tell us whether the handover completed, + * failed or has timed out */ +static void gscon_fsm_wait_ho_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer conn_peer; + struct gsm_lchan *lchan = conn->lchan; + struct in_addr addr; + struct msgb *resp; + int rc; + + switch (event) { + case GSCON_EV_HO_COMPL: + /* The handover logic informs us that the handover has been + * completet. Now we have to tell the MGW the IP/Port on the + * new BTS so that the uplink RTP traffic can be redirected + * there. */ + + /* Prepare parameters with the information we got during the + * handover procedure (via IPACC) */ + memset(&conn_peer, 0, sizeof(conn_peer)); + addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); + osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr)); + conn_peer.port = lchan->abis_ip.bound_port; + + /* (Pre)Change state and modify the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_HO_TIMEOUT_TIMER_NR); + rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); + if (rc != 0) { + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + return; + } + break; + case GSCON_EV_HO_TIMEOUT: + case GSCON_EV_HO_FAIL: + /* The handover logic informs us that the handover failed for + * some reason. This means the phone stays on the TS/BTS on + * which it currently is. We will change back to the active + * state again as there are no further operations needed */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Wait for the MGW to confirm handover related modification of the connection + * parameters */ +static void gscon_fsm_wait_mdcx_bts_ho(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + switch (event) { + case GSCON_EV_MGW_MDCX_RESP_BTS: + /* The MGW has confirmed the handover MDCX, and the handover + * is now also done on the RTP side. We may now change back + * to the active state. */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +#define EV_TRANSPARENT_SCCP S(GSCON_EV_TX_SCCP) | S(GSCON_EV_MO_DTAP) | S(GSCON_EV_MT_DTAP) + +static const struct osmo_fsm_state gscon_fsm_states[] = { + [ST_INIT] = { + .name = OSMO_STRINGIFY(INIT), + .in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND), + .out_state_mask = S(ST_WAIT_CC), + .action = gscon_fsm_init, + }, + [ST_WAIT_CC] = { + .name = OSMO_STRINGIFY(WAIT_CC), + .in_event_mask = S(GSCON_EV_A_CONN_CFM), + .out_state_mask = S(ST_ACTIVE), + .action = gscon_fsm_wait_cc, + }, + [ST_ACTIVE] = { + .name = OSMO_STRINGIFY(ACTIVE), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_A_ASSIGNMENT_CMD) | + S(GSCON_EV_A_HO_REQ) | S(GSCON_EV_HO_START), + .out_state_mask = S(ST_CLEARING) | S(ST_WAIT_CRCX_BTS) | S(ST_WAIT_ASS_CMPL) | + S(ST_WAIT_MO_HO_CMD) | S(ST_WAIT_HO_COMPL), + .action = gscon_fsm_active, + }, + [ST_WAIT_CRCX_BTS] = { + .name = OSMO_STRINGIFY(WAIT_CRCX_BTS), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_BTS), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_ASS_CMPL), + .action = gscon_fsm_wait_crcx_bts, + }, + [ST_WAIT_ASS_CMPL] = { + .name = OSMO_STRINGIFY(WAIT_ASS_CMPL), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_RR_ASS_COMPL) | S(GSCON_EV_RR_ASS_FAIL), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS), + .action = gscon_fsm_wait_ass_cmpl, + }, + [ST_WAIT_MDCX_BTS] = { + .name = OSMO_STRINGIFY(WAIT_MDCX_BTS), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CRCX_MSC), + .action = gscon_fsm_wait_mdcx_bts, + }, + [ST_WAIT_CRCX_MSC] = { + .name = OSMO_STRINGIFY(WAIT_CRCX_MSC), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_MSC), + .out_state_mask = S(ST_ACTIVE), + .action = gscon_fsm_wait_crcx_msc, + }, + [ST_CLEARING] = { + .name = OSMO_STRINGIFY(CLEARING), + .in_event_mask = S(GSCON_EV_RSL_CLEAR_COMPL), + .action = gscon_fsm_clearing, + }, + + /* TODO: external handover, probably it makes sense to break up the + * program flow in handover_logic.c a bit and handle some of the logic + * here? */ + [ST_WAIT_MT_HO_ACC] = { + .name = OSMO_STRINGIFY(WAIT_MT_HO_ACC), + }, + [ST_WAIT_MT_HO_COMPL] = { + .name = OSMO_STRINGIFY(WAIT_MT_HO_COMPL), + }, + [ST_WAIT_MO_HO_CMD] = { + .name = OSMO_STRINGIFY(WAIT_MO_HO_CMD), + }, + [ST_MO_HO_PROCEEDING] = { + .name = OSMO_STRINGIFY(MO_HO_PROCEEDING), + }, + + /* Internal handover */ + [ST_WAIT_HO_COMPL] = { + .name = OSMO_STRINGIFY(WAIT_HO_COMPL), + .in_event_mask = S(GSCON_EV_HO_COMPL) | S(GSCON_EV_HO_FAIL) | S(GSCON_EV_HO_TIMEOUT), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS_HO) | S(ST_CLEARING), + .action = gscon_fsm_wait_ho_compl, + }, + [ST_WAIT_MDCX_BTS_HO] = { + .name = OSMO_STRINGIFY(WAIT_MDCX_BTS_HO), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS), + .action = gscon_fsm_wait_mdcx_bts_ho, + .out_state_mask = S(ST_ACTIVE), + }, +}; + +static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + + /* When a connection on the MGW fails, make sure that the reference + * in our book-keeping is erased. */ + switch (event) { + case GSCON_EV_MGW_FAIL_BTS: + conn->user_plane.fi_bts = NULL; + break; + case GSCON_EV_MGW_FAIL_MSC: + conn->user_plane.fi_msc = NULL; + break; + } + + /* Regular allstate event processing */ + switch (event) { + case GSCON_EV_MGW_FAIL_BTS: + case GSCON_EV_MGW_FAIL_MSC: + /* Note: An MGW connection die per definition at any time. + * However, if it dies during the assignment we must return + * with an assignment failure */ + OSMO_ASSERT(fi->state != ST_INIT && fi->state != ST_WAIT_CC); + if (fi->state == ST_WAIT_CRCX_BTS || fi->state == ST_WAIT_ASS_CMPL || fi->state == ST_WAIT_MDCX_BTS + || fi->state == ST_WAIT_CRCX_MSC) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + } + break; + case GSCON_EV_A_CLEAR_CMD: + /* MSC tells us to cleanly shut down */ + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + gsm0808_clear(conn); + /* 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 */ + toss_mgcp_conn(conn, fi); + + /* FIXME: Question: Is this a hack to force a clear complete from internel? + * nobody seems to send the event from outside? */ + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RSL_CLEAR_COMPL, NULL); + break; + case GSCON_EV_A_DISC_IND: + /* MSC or SIGTRAN network has hard-released SCCP connection, + * terminate the FSM now. */ + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); + break; + case GSCON_EV_RLL_REL_IND: + /* BTS reports that one of the LAPDm data links was released */ + /* send proper clear request to MSC */ + LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE); + sigtran_send(conn, resp, fi); + break; + case GSCON_EV_RSL_CONN_FAIL: + LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); + sigtran_send(conn, resp, fi); + break; + case GSCON_EV_MGW_MDCX_RESP_MSC: + LOGPFSML(fi, LOGL_DEBUG, "Rx MDCX of MSC side (LCLS?)\n"); + break; + case GSCON_EV_LCLS_FAIL: + break; + default: + OSMO_ASSERT(false); + break; + } +} + +void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send); + +static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + if (conn->ho) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing handover state\n"); + bsc_clear_handover(conn, 1); + conn->ho = NULL; + } + + if (conn->secondary_lchan) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing secondary_lchan\n"); + lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); + conn->secondary_lchan = NULL; + } + if (conn->lchan) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing lchan\n"); + lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END); + conn->lchan = NULL; + } + + if (conn->bsub) { + LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n"); + bsc_subscr_put(conn->bsub); + conn->bsub = NULL; + } + + if (conn->sccp.state != SUBSCR_SCCP_ST_NONE) { + 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); + conn->sccp.state = SUBSCR_SCCP_ST_NONE; + } + + /* drop pending messages */ + ho_dtap_cache_flush(conn, 0); + + penalty_timers_free(&conn->hodec2.penalty_timers); + + llist_del(&conn->entry); + talloc_free(conn); + fi->priv = NULL; +} + +static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* Make sure all possibly still open MGCP connections get closed */ + toss_mgcp_conn(conn, fi); + + if (conn->lcls.fi) { + /* request termination of LCLS FSM */ + osmo_fsm_inst_term(conn->lcls.fi, cause, NULL); + conn->lcls.fi = NULL; + } +} + +static int gscon_timer_cb(struct osmo_fsm_inst *fi) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + + switch (fi->T) { + case 993210: + /* MSC has not responded/confirmed connection with CC, this + * could indicate a bad SCCP connection. We now inform the the + * FSM that controls the BSSMAP reset about the event. Maybe + * a BSSMAP reset is necessary. */ + a_reset_conn_fail(conn->sccp.msc->a.reset_fsm); + + /* Since we could not reach the MSC, we give up and terminate + * the FSM instance now (N-DISCONNET.req is sent in + * gscon_cleanup() above) */ + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + case GSM0808_T10_TIMER_NR: /* Assignment Failed */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case MGCP_MGW_TIMEOUT_TIMER_NR: /* Assignment failed (no response from MGW) */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case MGCP_MGW_HO_TIMEOUT_TIMER_NR: /* Handover failed (no response from MGW) */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + OSMO_ASSERT(false); + } + return 0; +} + +static struct osmo_fsm gscon_fsm = { + .name = "SUBSCR_CONN", + .states = gscon_fsm_states, + .num_states = ARRAY_SIZE(gscon_fsm_states), + .allstate_event_mask = S(GSCON_EV_A_DISC_IND) | S(GSCON_EV_A_CLEAR_CMD) | S(GSCON_EV_RSL_CONN_FAIL) | + S(GSCON_EV_RLL_REL_IND) | S(GSCON_EV_MGW_FAIL_BTS) | S(GSCON_EV_MGW_FAIL_MSC) | + S(GSCON_EV_MGW_MDCX_RESP_MSC) | S(GSCON_EV_LCLS_FAIL), + .allstate_action = gscon_fsm_allstate, + .cleanup = gscon_cleanup, + .pre_term = gscon_pre_term, + .timer_cb = gscon_timer_cb, + .log_subsys = DMSC, + .event_names = gscon_fsm_event_names, +}; + +/* Allocate a subscriber connection and its associated FSM */ +struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) +{ + struct gsm_subscriber_connection *conn; + static bool g_initialized = false; + + if (!g_initialized) { + osmo_fsm_register(&gscon_fsm); + osmo_fsm_register(&lcls_fsm); + g_initialized = true; + } + + conn = talloc_zero(net, struct gsm_subscriber_connection); + if (!conn) + return NULL; + + conn->network = net; + INIT_LLIST_HEAD(&conn->ho_dtap_cache); + /* BTW, penalty timers will be initialized on-demand. */ + conn->sccp.conn_id = -1; + + /* 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 */ + conn->fi = osmo_fsm_inst_alloc(&gscon_fsm, net, conn, LOGL_NOTICE, NULL); + if (!conn->fi) { + talloc_free(conn); + return NULL; + } + + /* initialize to some magic values that indicate "IE not [yet] received" */ + conn->lcls.config = 0xff; + conn->lcls.control = 0xff; + conn->lcls.fi = osmo_fsm_inst_alloc_child(&lcls_fsm, conn->fi, GSCON_EV_LCLS_FAIL); + if (!conn->lcls.fi) { + osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_ERROR, NULL); + return NULL; + } + conn->lcls.fi->priv = conn; + + llist_add_tail(&conn->entry, &net->subscr_conns); + return conn; +} diff --git a/src/osmo-bsc/bsc_subscriber.c b/src/osmo-bsc/bsc_subscriber.c new file mode 100644 index 000000000..d9d90baa9 --- /dev/null +++ b/src/osmo-bsc/bsc_subscriber.c @@ -0,0 +1,168 @@ +/* GSM subscriber details for use in BSC land */ + +/* + * (C) 2016 by sysmocom s.f.m.c. GmbH + * + * Author: Neels Hofmeyr + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include + +#include +#include + +static struct bsc_subscr *bsc_subscr_alloc(struct llist_head *list) +{ + struct bsc_subscr *bsub; + + bsub = talloc_zero(list, struct bsc_subscr); + if (!bsub) + return NULL; + + llist_add_tail(&bsub->entry, list); + bsub->use_count = 1; + + return bsub; +} + +struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list, + const char *imsi) +{ + struct bsc_subscr *bsub; + + if (!imsi || !*imsi) + return NULL; + + llist_for_each_entry(bsub, list, entry) { + if (!strcmp(bsub->imsi, imsi)) + return bsc_subscr_get(bsub); + } + return NULL; +} + +struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list, + uint32_t tmsi) +{ + struct bsc_subscr *bsub; + + if (tmsi == GSM_RESERVED_TMSI) + return NULL; + + llist_for_each_entry(bsub, list, entry) { + if (bsub->tmsi == tmsi) + return bsc_subscr_get(bsub); + } + return NULL; +} + +void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi) +{ + if (!bsub) + return; + osmo_strlcpy(bsub->imsi, imsi, sizeof(bsub->imsi)); +} + +struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list, + const char *imsi) +{ + struct bsc_subscr *bsub; + bsub = bsc_subscr_find_by_imsi(list, imsi); + if (bsub) + return bsub; + bsub = bsc_subscr_alloc(list); + bsc_subscr_set_imsi(bsub, imsi); + return bsub; +} + +struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list, + uint32_t tmsi) +{ + struct bsc_subscr *bsub; + bsub = bsc_subscr_find_by_tmsi(list, tmsi); + if (bsub) + return bsub; + bsub = bsc_subscr_alloc(list); + bsub->tmsi = tmsi; + return bsub; +} + +const char *bsc_subscr_name(struct bsc_subscr *bsub) +{ + static char buf[32]; + if (!bsub) + return "unknown"; + if (bsub->imsi[0]) + snprintf(buf, sizeof(buf), "IMSI:%s", bsub->imsi); + else + snprintf(buf, sizeof(buf), "TMSI:0x%08x", bsub->tmsi); + return buf; +} + +static void bsc_subscr_free(struct bsc_subscr *bsub) +{ + llist_del(&bsub->entry); + talloc_free(bsub); +} + +struct bsc_subscr *_bsc_subscr_get(struct bsc_subscr *bsub, + const char *file, int line) +{ + OSMO_ASSERT(bsub->use_count < INT_MAX); + bsub->use_count++; + LOGPSRC(DREF, LOGL_DEBUG, file, line, + "BSC subscr %s usage increases to: %d\n", + bsc_subscr_name(bsub), bsub->use_count); + return bsub; +} + +struct bsc_subscr *_bsc_subscr_put(struct bsc_subscr *bsub, + const char *file, int line) +{ + bsub->use_count--; + LOGPSRC(DREF, bsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR, + file, line, + "BSC subscr %s usage decreases to: %d\n", + bsc_subscr_name(bsub), bsub->use_count); + if (bsub->use_count <= 0) + bsc_subscr_free(bsub); + return NULL; +} + +void log_set_filter_bsc_subscr(struct log_target *target, + struct bsc_subscr *bsc_subscr) +{ + struct bsc_subscr **fsub = (void*)&target->filter_data[LOG_FLT_BSC_SUBSCR]; + + /* free the old data */ + if (*fsub) { + bsc_subscr_put(*fsub); + *fsub = NULL; + } + + if (bsc_subscr) { + target->filter_map |= (1 << LOG_FLT_BSC_SUBSCR); + *fsub = bsc_subscr_get(bsc_subscr); + } else + target->filter_map &= ~(1 << LOG_FLT_BSC_SUBSCR); +} diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c new file mode 100644 index 000000000..5d0feb663 --- /dev/null +++ b/src/osmo-bsc/bsc_vty.c @@ -0,0 +1,5003 @@ +/* OpenBSC interface to quagga VTY */ +/* (C) 2009-2017 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../bscconfig.h" + +#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" }, + { BTS_LOC_FIX_3D, "fix3d" }, + { 0, NULL } +}; + +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) +{ + /* It can't hurt to force callers to continue to pass the vty instance + * to this function, in case we'd like to retrieve the global + * gsm_network instance from the vty at some point in the future. But + * until then, just return the global pointer, which should have been + * initialized by common_cs_vty_init(). + */ + OSMO_ASSERT(vty_global_gsm_network); + return vty_global_gsm_network; +} + +static int dummy_config_write(struct vty *v) +{ + return CMD_SUCCESS; +} + +static 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), + get_value_string(abis_nm_adm_state_names, nms->administrative), + abis_nm_avail_name(nms->availability), VTY_NEWLINE); +} + +static void dump_pchan_load_vty(struct vty *vty, char *prefix, + const struct pchan_load *pl) +{ + int i; + int dumped = 0; + + for (i = 0; i < ARRAY_SIZE(pl->pchan); i++) { + const struct load_counter *lc = &pl->pchan[i]; + unsigned int percent; + + if (lc->total == 0) + continue; + + percent = (lc->used * 100) / lc->total; + + vty_out(vty, "%s%20s: %3u%% (%u/%u)%s", prefix, + gsm_pchan_name(i), percent, lc->used, lc->total, + VTY_NEWLINE); + dumped ++; + } + if (!dumped) + vty_out(vty, "%s(none)%s", prefix, VTY_NEWLINE); +} + +static void net_dump_vty(struct vty *vty, struct gsm_network *net) +{ + struct pchan_load pl; + int i; + + vty_out(vty, "BSC is on MCC-MNC %s and has %u BTS%s", + osmo_plmn_name(&net->plmn), net->num_bts, VTY_NEWLINE); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " Encryption:"); + for (i = 0; i < 8; i++) { + if (net->a5_encryption_mask & (1 << i)) + vty_out(vty, " A5/%u", i); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " NECI (TCH/H): %u%s", net->neci, + VTY_NEWLINE); + vty_out(vty, " Use TCH for Paging any: %d%s", net->pag_any_tch, + VTY_NEWLINE); + + { + struct gsm_bts *bts; + unsigned int ho_active_count = 0; + unsigned int ho_inactive_count = 0; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (ho_get_ho_active(bts->ho)) + ho_active_count ++; + else + ho_inactive_count ++; + } + + if (ho_active_count && ho_inactive_count) + vty_out(vty, " Handover: On at %u BTS, Off at %u BTS%s", + ho_active_count, ho_inactive_count, VTY_NEWLINE); + else + vty_out(vty, " Handover: %s%s", ho_active_count ? "On" : "Off", + VTY_NEWLINE); + } + + network_chan_load(&pl, net); + vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); + dump_pchan_load_vty(vty, " ", &pl); + + /* show rf */ + if (net->bsc_data) + vty_out(vty, " Last RF Command: %s%s", + net->bsc_data->rf_ctrl->last_state_command, + VTY_NEWLINE); + if (net->bsc_data) + vty_out(vty, " Last RF Lock Command: %s%s", + net->bsc_data->rf_ctrl->last_rf_lock_ctrl_command, + VTY_NEWLINE); +} + +DEFUN(bsc_show_net, bsc_show_net_cmd, "show network", + SHOW_STR "Display information about a GSM NETWORK\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + net_dump_vty(vty, net); + + 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); +} + +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_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 ramping: %senabled%s", + acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "not ", VTY_NEWLINE); + if (acc_ramp_is_enabled(&bts->acc_ramp)) { + if (!acc_ramp_step_interval_is_dynamic(&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); + else + vty_out(vty, " Access Control Class ramping step interval: dynamic%s", 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); + 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); + 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 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); + + 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); + + 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") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int bts_nr; + + if (argc != 0) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); + return CMD_SUCCESS; + } + /* print all BTS's */ + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) + bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); + + 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 != GSM_PCHAN_NONE) + vty_out(vty, " phys_chan_config %s%s", + gsm_pchan_name(ts->pchan), 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.nm_state.administrative == NM_STATE_LOCKED ? 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 11bit_rach_support_for_egprs %u%s", + bts->gprs.supports_egprs_11bit_rach, VTY_NEWLINE); + + 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 in_addr ia; + + ia.s_addr = htonl(nsvc->remote_ip); + 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); + vty_out(vty, " gprs nsvc %u remote udp port %u%s", i, + nsvc->remote_port, VTY_NEWLINE); + vty_out(vty, " gprs nsvc %u remote ip %s%s", i, + inet_ntoa(ia), 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-descrption attach %u%s", + bts->si_common.chan_desc.att, VTY_NEWLINE); + vty_out(vty, " channel-descrption bs-pa-mfrms %u%s", + bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE); + vty_out(vty, " channel-descrption bs-ag-blks-res %u%s", + bts->si_common.chan_desc.bs_ag_blks_res, 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); + vty_out(vty, " %saccess-control-class-ramping%s", acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "no ", VTY_NEWLINE); + if (!acc_ramp_step_interval_is_dynamic(&bts->acc_ramp)) { + vty_out(vty, " access-control-class-ramping-step-interval %u%s", + acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE); + } else { + vty_out(vty, " access-control-class-ramping-step-interval dynamic%s", VTY_NEWLINE); + } + vty_out(vty, " access-control-class-ramping-step-size %u%s", acc_ramp_get_step_size(&bts->acc_ramp), + 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, " ip.access 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, " ip.access rsl-ip %s%s", inet_ntoa(ia), + VTY_NEWLINE); + } + vty_out(vty, " oml ip.access 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); + } + + 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); + + 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<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; +} + +/* small helper macro for conditional dumping of timer */ +#define VTY_OUT_TIMER(number) \ + if (gsmnet->T##number != GSM_T##number##_DEFAULT) \ + vty_out(vty, " timer t"#number" %u%s", gsmnet->T##number, VTY_NEWLINE) + +static int config_write_net(struct vty *vty) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + int i; + + vty_out(vty, "network%s", VTY_NEWLINE); + vty_out(vty, " network country code %s%s", osmo_mcc_name(gsmnet->plmn.mcc), VTY_NEWLINE); + vty_out(vty, " mobile network code %s%s", + osmo_mnc_name(gsmnet->plmn.mnc, gsmnet->plmn.mnc_3_digits), VTY_NEWLINE); + vty_out(vty, " encryption a5"); + for (i = 0; i < 8; i++) { + if (gsmnet->a5_encryption_mask & (1 << i)) + vty_out(vty, " %u", i); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE); + vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE); + + ho_vty_write_net(vty, gsmnet); + + VTY_OUT_TIMER(3101); + VTY_OUT_TIMER(3103); + VTY_OUT_TIMER(3105); + VTY_OUT_TIMER(3107); + VTY_OUT_TIMER(3109); + VTY_OUT_TIMER(3111); + VTY_OUT_TIMER(3113); + VTY_OUT_TIMER(3115); + VTY_OUT_TIMER(3117); + VTY_OUT_TIMER(3119); + VTY_OUT_TIMER(3122); + VTY_OUT_TIMER(3141); + if (!gsmnet->dyn_ts_allow_tch_f) + vty_out(vty, " dyn_ts_allow_tch_f 0%s", VTY_NEWLINE); + if (gsmnet->tz.override != 0) { + if (gsmnet->tz.dst) + vty_out(vty, " timezone %d %d %d%s", + gsmnet->tz.hr, gsmnet->tz.mn, gsmnet->tz.dst, + VTY_NEWLINE); + else + vty_out(vty, " timezone %d %d%s", + gsmnet->tz.hr, gsmnet->tz.mn, VTY_NEWLINE); + } + if (gsmnet->t3212 == 0) + vty_out(vty, " no periodic location update%s", VTY_NEWLINE); + else + vty_out(vty, " periodic location update %u%s", + gsmnet->t3212 * 6, VTY_NEWLINE); + + { + uint16_t meas_port; + char *meas_host; + const char *meas_scenario; + + meas_feed_cfg_get(&meas_host, &meas_port); + meas_scenario = meas_feed_scenario_get(); + + if (meas_port) + vty_out(vty, " meas-feed destination %s %u%s", + meas_host, meas_port, VTY_NEWLINE); + if (strlen(meas_scenario) > 0) + vty_out(vty, " meas-feed scenario %s%s", + meas_scenario, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx) +{ + 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, " NM State: "); + net_dump_nmstate(vty, &trx->mo.nm_state); + 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%s", + trx->rsl_tei, VTY_NEWLINE); + } else { + vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); + e1isl_dump_vty(vty, trx->rsl_link); + } +} + +static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts) +{ + uint8_t trx_nr; + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) + trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); +} + +DEFUN(show_trx, + show_trx_cmd, + "show trx [<0-255>] [<0-255>]", + SHOW_STR "Display information about a TRX\n" + BTS_TRX_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + int bts_nr, trx_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + 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) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); + return CMD_SUCCESS; + } + if (bts) { + /* print all TRX in this BTS */ + print_all_trx(vty, bts); + return CMD_SUCCESS; + } + + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) + print_all_trx(vty, gsm_bts_num(net, bts_nr)); + + return CMD_SUCCESS; +} + + +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, TSC %u", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts)); + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + vty_out(vty, " (%s mode)", + ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F"); + } else if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + vty_out(vty, " (%s mode)", gsm_pchan_name(ts->dyn.pchan_is)); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " NM State: "); + 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>]", + SHOW_STR "Display information about a TS\n" + BTS_TRX_TS_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + int bts_nr, trx_nr, ts_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + 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) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + } + if (argc >= 3) { + ts_nr = atoi(argv[2]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS '%s'%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + /* Fully Specified: print and exit */ + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + return CMD_SUCCESS; + } + + if (bts && trx) { + /* Iterate over all TS in this TRX */ + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } else if (bts) { + /* Iterate over all TRX in this BTS, TS in each TRX */ + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + 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_dump_vty(vty, ts); + } + } + } else { + /* Iterate over all BTS, TRX in each BTS, TS in each TRX */ + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + 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_dump_vty(vty, ts); + } + } + } + } + + return CMD_SUCCESS; +} + +static 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); + if (bsub->tmsi != GSM_RESERVED_TMSI) + vty_out(vty, " TMSI: 0x%08x%s", bsub->tmsi, + VTY_NEWLINE); + vty_out(vty, " Use count: %d%s", 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"); +} + +/* FIXME: move this to libosmogsm */ +static const struct value_string gsm48_cmode_names[] = { + { GSM48_CMODE_SIGN, "signalling" }, + { GSM48_CMODE_SPEECH_V1, "FR or HR" }, + { GSM48_CMODE_SPEECH_EFR, "EFR" }, + { GSM48_CMODE_SPEECH_AMR, "AMR" }, + { GSM48_CMODE_DATA_14k5, "CSD(14k5)" }, + { GSM48_CMODE_DATA_12k0, "CSD(12k0)" }, + { GSM48_CMODE_DATA_6k0, "CSD(6k0)" }, + { GSM48_CMODE_DATA_3k6, "CSD(3k6)" }, + { 0, NULL } +}; + +/* 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) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + vty_out(vty, " as %s", + gsm_pchan_name(ts->dyn.pchan_is)); + else + vty_out(vty, " switching %s -> %s", + gsm_pchan_name(ts->dyn.pchan_is), + gsm_pchan_name(ts->dyn.pchan_want)); + break; + case GSM_PCHAN_TCH_F_PDCH: + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) + vty_out(vty, " as %s", + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + vty_out(vty, " switching %s -> %s", + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F", + (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" + : "TCH/F"); + break; + default: + /* no dyn ts */ + break; + } +} + +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); + /* show dyn TS details, if applicable */ + switch (lchan->ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + vty_out(vty, " Osmocom Dyn TS:"); + vty_out_dyn_ts_status(vty, lchan->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, lchan->ts); + vty_out(vty, VTY_NEWLINE); + break; + default: + /* no dyn ts */ + break; + } + vty_out(vty, " Connection: %u, State: %s%s%s%s", + lchan->conn ? 1: 0, + gsm_lchans_name(lchan->state), + lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "", + lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "", + 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", + get_value_string(gsm48_cmode_names, 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)); + 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), gsm_lchans_name(lchan->state), + 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 *)) +{ + int lchan_nr; + for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) { + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + if ((lchan->type == GSM_LCHAN_NONE) && (lchan->state == LCHAN_S_NONE)) + continue; + dump_cb(vty, lchan); + } + + return CMD_SUCCESS; +} + +static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int ts_nr; + + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + dump_lchan_trx_ts(ts, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int trx_nr; + + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr); + dump_lchan_trx(trx, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +static int lchan_summary(struct vty *vty, int argc, const char **argv, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + struct gsm_lchan *lchan; + int bts_nr, trx_nr, ts_nr, lchan_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + 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 == 1) + return dump_lchan_bts(bts, vty, dump_cb); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX %s%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + + if (argc == 2) + return dump_lchan_trx(trx, vty, dump_cb); + } + if (argc >= 3) { + ts_nr = atoi(argv[2]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS %s%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[ts_nr]; + + if (argc == 3) + return dump_lchan_trx_ts(ts, vty, dump_cb); + } + if (argc >= 4) { + lchan_nr = atoi(argv[3]); + if (lchan_nr >= TS_MAX_LCHAN) { + vty_out(vty, "%% can't find LCHAN %s%s", argv[3], + VTY_NEWLINE); + return CMD_WARNING; + } + lchan = &ts->lchan[lchan_nr]; + dump_cb(vty, lchan); + return CMD_SUCCESS; + } + + + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + dump_lchan_bts(bts, vty, dump_cb); + } + + return CMD_SUCCESS; +} + + +DEFUN(show_lchan, + show_lchan_cmd, + "show lchan [<0-255>] [<0-255>] [<0-7>] [<0-7>]", + SHOW_STR "Display information about a logical channel\n" + BTS_TRX_TS_LCHAN_STR) +{ + return lchan_summary(vty, argc, argv, lchan_dump_full_vty); +} + +DEFUN(show_lchan_summary, + show_lchan_summary_cmd, + "show lchan summary [<0-255>] [<0-255>] [<0-7>] [<0-7>]", + SHOW_STR "Display information about a logical channel\n" + "Short summary\n" + BTS_TRX_TS_LCHAN_STR) +{ + return lchan_summary(vty, argc, argv, lchan_dump_short_vty); +} + +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, mode=%s, mgw_ep=%s%s", + conn->sccp.conn_id, conn->sccp.msc->nr, conn->hodec2.failures, + get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), + conn->user_plane.mgw_endpoint, VTY_NEWLINE); + if (conn->lcls.global_call_ref_len) { + vty_out(vty, " LCLS GCR: %s%s", + osmo_hexdump_nospc(conn->lcls.global_call_ref, conn->lcls.global_call_ref_len), + VTY_NEWLINE); + vty_out(vty, " LCLS Config: 0x%02x, LCLS Control: 0x%02x, LCLS BSS Status: %s%s", + conn->lcls.config, conn->lcls.control, osmo_fsm_inst_state_name(conn->lcls.fi), + VTY_NEWLINE); + } + if (conn->lchan) + lchan_dump_full_vty(vty, conn->lchan); + if (conn->secondary_lchan) + lchan_dump_full_vty(vty, conn->secondary_lchan); +} + +DEFUN(show_subscr_conn, + show_subscr_conn_cmd, + "show conns", + SHOW_STR "Display currently active subscriber connections\n") +{ + 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) + vty_out(vty, "None%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +static int trigger_ho_or_as(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_bts *to_bts) +{ + int rc; + + 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); + rc = bsc_handover_start(HODEC_NONE, from_lchan, to_bts, from_lchan->type); + if (rc) { + vty_out(vty, "bsc_handover_start() returned %d=%s%s", rc, + strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +static int ho_or_as(struct vty *vty, const char *argv[], int argc) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_subscriber_connection *conn; + struct gsm_bts *bts; + struct gsm_bts *new_bts = NULL; + unsigned int bts_nr = atoi(argv[0]); + 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]); + + /* Lookup the BTS where we want to handover to */ + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->nr == bts_nr_new) { + new_bts = bts; + break; + } + } + + if (!new_bts) { + vty_out(vty, "Unable to trigger handover, specified bts #%u does not exist %s", + bts_nr_new, VTY_NEWLINE); + return CMD_WARNING; + } + } + + action = new_bts ? "handover" : "assignment"; + + /* Find the connection/lchan that we want to handover */ + llist_for_each_entry(conn, &net->subscr_conns, entry) { + if (conn_get_bts(conn)->nr == bts_nr && + conn->lchan->ts->trx->nr == trx_nr && + 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); + } + } + + vty_out(vty, "Unable to trigger %s, specified connection (bts=%u,trx=%u,ts=%u,ss=%u) does not exist%s", + action, bts_nr, trx_nr, ts_nr, ss_nr, VTY_NEWLINE); + + return CMD_WARNING; +} + +#define MANUAL_HANDOVER_STR "Manually trigger handover (for debugging)\n" +#define MANUAL_ASSIGNMENT_STR "Manually trigger assignment (for debugging)\n" + +DEFUN(handover_subscr_conn, + handover_subscr_conn_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> handover <0-255>", + BTS_NR_TRX_TS_SS_STR2 + MANUAL_HANDOVER_STR + "New " BTS_NR_STR) +{ + return ho_or_as(vty, argv, argc); +} + +DEFUN(assignment_subscr_conn, + assignment_subscr_conn_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> assignment", + BTS_NR_TRX_TS_SS_STR2 + MANUAL_ASSIGNMENT_STR) +{ + return ho_or_as(vty, argv, argc); +} + +static struct gsm_lchan *find_used_voice_lchan(struct vty *vty) +{ + struct gsm_bts *bts; + struct gsm_network *network = gsmnet_from_vty(vty); + + llist_for_each_entry(bts, &network->bts_list, list) { + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + int j; + int subslots; + + /* skip administratively deactivated timeslots */ + if (!nm_is_running(&ts->mo.nm_state)) + continue; + + subslots = ts_subslots(ts); + for (j = 0; j < subslots; j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + + if (lchan->state == LCHAN_S_ACTIVE + && (lchan->type == GSM_LCHAN_TCH_F + || lchan->type == GSM_LCHAN_TCH_H)) { + + vty_out(vty, "Found voice call: %s%s", + gsm_lchan_name(lchan), VTY_NEWLINE); + lchan_dump_full_vty(vty, lchan); + return lchan; + } + } + } + } + } + + vty_out(vty, "Cannot find any ongoing voice calls%s", VTY_NEWLINE); + return NULL; +} + +static struct gsm_bts *find_other_bts_with_free_slots(struct vty *vty, struct gsm_bts *not_this_bts, + enum gsm_phys_chan_config free_type) +{ + struct gsm_bts *bts; + struct gsm_network *network = gsmnet_from_vty(vty); + + llist_for_each_entry(bts, &network->bts_list, list) { + struct gsm_bts_trx *trx; + + if (bts == not_this_bts) + continue; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + int j; + int subslots; + + /* skip administratively deactivated timeslots */ + if (!nm_is_running(&ts->mo.nm_state)) + continue; + + if (ts->pchan != free_type) + continue; + + subslots = ts_subslots(ts); + for (j = 0; j < subslots; j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + + if (lchan->state == LCHAN_S_NONE) { + vty_out(vty, "Found unused %s slot: %s%s", + gsm_pchan_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); + return NULL; +} + +DEFUN(handover_any, handover_any_cmd, + "handover any", + MANUAL_HANDOVER_STR + "Pick any actively used TCH/F or TCH/H lchan and handover to any other BTS." + " This is likely to fail if not all BTS are guaranteed to be reachable by the MS.\n") +{ + struct gsm_lchan *from_lchan; + struct gsm_bts *to_bts; + + from_lchan = find_used_voice_lchan(vty); + if (!from_lchan) + return CMD_WARNING; + + to_bts = find_other_bts_with_free_slots(vty, from_lchan->ts->trx->bts, + ts_pchan(from_lchan->ts)); + if (!to_bts) + return CMD_WARNING; + + return trigger_ho_or_as(vty, from_lchan, to_bts); +} + +DEFUN(assignment_any, assignment_any_cmd, + "assignment any", + MANUAL_ASSIGNMENT_STR + "Pick any actively used TCH/F or TCH/H lchan and re-assign within the same BTS." + " This will fail if no lchans of the same type are available besides the used one.\n") +{ + struct gsm_lchan *from_lchan; + + from_lchan = find_used_voice_lchan(vty); + if (!from_lchan) + return CMD_WARNING; + + return trigger_ho_or_as(vty, from_lchan, NULL); +} + +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); + bsc_subscr_dump_vty(vty, pag->bsub); +} + +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) + paging_dump_vty(vty, pag); +} + +DEFUN(show_paging, + show_paging_cmd, + "show paging [<0-255>]", + SHOW_STR "Display information about paging reuqests of a BTS\n" + BTS_NR_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + int bts_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + 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); + bts_paging_dump_vty(vty, bts); + + return CMD_SUCCESS; + } + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + bts_paging_dump_vty(vty, bts); + } + + return CMD_SUCCESS; +} + +DEFUN(show_paging_group, + show_paging_group_cmd, + "show paging-group <0-255> IMSI", + SHOW_STR "Display the paging group\n" + BTS_NR_STR "IMSI\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + unsigned int page_group; + int bts_nr = atoi(argv[0]); + + 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 (!bts) { + vty_out(vty, "%% can't find BTS %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc, + str_to_imsi(argv[1])); + vty_out(vty, "%%Paging group for IMSI %" PRIu64 " on BTS #%d is %u%s", + str_to_imsi(argv[1]), bts->nr, + page_group, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_neci, + cfg_net_neci_cmd, + "neci (0|1)", + "New Establish Cause Indication\n" + "Don't set the NECI bit\n" "Set the NECI bit\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->neci = atoi(argv[0]); + gsm_net_update_ctype(gsmnet); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_pag_any_tch, + cfg_net_pag_any_tch_cmd, + "paging any use tch (0|1)", + "Assign a TCH when receiving a Paging Any request\n" + "Any Channel\n" "Use\n" "TCH\n" + "Do not use TCH for Paging Request Any\n" + "Do use TCH for Paging Request Any\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->pag_any_tch = atoi(argv[0]); + gsm_net_update_ctype(gsmnet); + return CMD_SUCCESS; +} + +#define DEFAULT_TIMER(number) GSM_T##number##_DEFAULT +/* Add another expansion so that DEFAULT_TIMER() becomes its value */ +#define EXPAND_AND_STRINGIFY(x) OSMO_STRINGIFY(x) + +#define DECLARE_TIMER(number, doc) \ + DEFUN(cfg_net_T##number, \ + cfg_net_T##number##_cmd, \ + "timer t" #number " (default|<1-65535>)", \ + "Configure GSM Timers\n" \ + doc " (default: " EXPAND_AND_STRINGIFY(DEFAULT_TIMER(number)) " seconds)\n" \ + "Set to default timer value" \ + " (" EXPAND_AND_STRINGIFY(DEFAULT_TIMER(number)) " seconds)\n" \ + "Timer Value in seconds\n") \ +{ \ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); \ + int value; \ + if (strcmp(argv[0], "default") == 0) \ + value = DEFAULT_TIMER(number); \ + else \ + value = atoi(argv[0]); \ + \ + gsmnet->T##number = value; \ + return CMD_SUCCESS; \ +} + +DECLARE_TIMER(3101, "Set the timeout value for IMMEDIATE ASSIGNMENT") +DECLARE_TIMER(3103, "Set the timeout value for HANDOVER") +DECLARE_TIMER(3105, "Set the timer for repetition of PHYSICAL INFORMATION") +DECLARE_TIMER(3107, "Currently not used") +DECLARE_TIMER(3109, "Set the RSL SACCH deactivation timeout") +DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel") +DECLARE_TIMER(3113, "Set the time to try paging a subscriber") +DECLARE_TIMER(3115, "Currently not used") +DECLARE_TIMER(3117, "Currently not used") +DECLARE_TIMER(3119, "Currently not used") +DECLARE_TIMER(3122, "Default waiting time (seconds) after IMM ASS REJECT") +DECLARE_TIMER(3141, "Currently not used") + +DEFUN_DEPRECATED(cfg_net_dtx, + cfg_net_dtx_cmd, + "dtx-used (0|1)", + ".HIDDEN\n""Obsolete\n""Obsolete\n") +{ + vty_out(vty, "%% 'dtx-used' is now deprecated: use dtx * " + "configuration options of BTS instead%s", VTY_NEWLINE); + return CMD_SUCCESS; +} + +/* per-BTS configuration */ +DEFUN(cfg_bts, + cfg_bts_cmd, + "bts <0-255>", + "Select a BTS to configure\n" + BTS_NR_STR) +{ + 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); + /* + * Initalize bts->acc_ramp here. Else we could segfault while + * processing a configuration file with ACC ramping settings. + */ + acc_ramp_init(&bts->acc_ramp, bts); + } 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(cfg_bts_type, + cfg_bts_type_cmd, + "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(cfg_bts_band, + cfg_bts_band_cmd, + "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(cfg_bts_dtxu, cfg_bts_dtxu_cmd, "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(cfg_bts_no_dtxu, cfg_bts_no_dtxu_cmd, "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(cfg_bts_dtxd, cfg_bts_dtxd_cmd, "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(cfg_bts_no_dtxd, cfg_bts_no_dtxd_cmd, "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(cfg_bts_ci, + cfg_bts_ci_cmd, + "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(cfg_bts_lac, + cfg_bts_lac_cmd, + "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(cfg_bts_bsic, + cfg_bts_bsic_cmd, + "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(cfg_bts_unit_id, + cfg_bts_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") +{ + 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(cfg_bts_rsl_ip, + cfg_bts_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") +{ + 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; +} + +#define NOKIA_STR "Nokia *Site related commands\n" + +DEFUN(cfg_bts_nokia_site_skip_reset, + cfg_bts_nokia_site_skip_reset_cmd, + "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(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") +{ + 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(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") +{ + 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(cfg_bts_stream_id, + cfg_bts_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") +{ + 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; +} + +#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n" + +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(cfg_bts_oml_e1_tei, + cfg_bts_oml_e1_tei_cmd, + "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(cfg_bts_challoc, cfg_bts_challoc_cmd, + "channel allocator (ascending|descending)", + "Channnel Allocator\n" "Channel Allocator\n" + "Allocate Timeslots and Transceivers in ascending order\n" + "Allocate Timeslots and Transceivers in descending order\n") +{ + 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(cfg_bts_rach_tx_integer, + cfg_bts_rach_tx_integer_cmd, + "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(cfg_bts_rach_max_trans, + cfg_bts_rach_max_trans_cmd, + "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(cfg_bts_chan_desc_att, + cfg_bts_chan_desc_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") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.chan_desc.att = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_chan_desc_bs_pa_mfrms, + cfg_bts_chan_desc_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") +{ + 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; +} + +DEFUN(cfg_bts_chan_desc_bs_ag_blks_res, + cfg_bts_chan_desc_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") +{ + 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; +} + +#define NM_STR "Network Management\n" + +DEFUN(cfg_bts_rach_nm_b_thresh, + cfg_bts_rach_nm_b_thresh_cmd, + "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") +{ + struct gsm_bts *bts = vty->index; + bts->rach_b_thresh = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rach_nm_ldavg, + cfg_bts_rach_nm_ldavg_cmd, + "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(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd, + "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(cfg_bts_rach_ec_allowed, cfg_bts_rach_ec_allowed_cmd, + "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(cfg_bts_rach_ac_class, cfg_bts_rach_ac_class_cmd, + "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)); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd, + "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") +{ + struct gsm_bts *bts = vty->index; + + bts->ms_max_power = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define CELL_STR "Cell Parameters\n" + +DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd, + "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") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd, + "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)") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_cell_bar_qualify, cfg_bts_cell_bar_qualify_cmd, + "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(cfg_bts_cell_resel_ofs, cfg_bts_cell_resel_ofs_cmd, + "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(cfg_bts_temp_ofs, cfg_bts_temp_ofs_cmd, + "temporary offset <0-60>", + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset in dB") +{ + 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(cfg_bts_temp_ofs_inf, cfg_bts_temp_ofs_inf_cmd, + "temporary offset infinite", + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset\n" + "Sets cell selection temporary negative offset to infinity") +{ + 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(cfg_bts_penalty_time, cfg_bts_penalty_time_cmd, + "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(cfg_bts_penalty_time_rsvd, cfg_bts_penalty_time_rsvd_cmd, + "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)") +{ + 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(cfg_bts_radio_link_timeout, cfg_bts_radio_link_timeout_cmd, + "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(cfg_bts_radio_link_timeout_inf, cfg_bts_radio_link_timeout_inf_cmd, + "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" + +DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd, + "gprs cell bvci <2-65535>", + GPRS_TEXT + "GPRS Cell Settings\n" + "GPRS BSSGP VC Identifier\n" + "GPRS BSSGP VC Identifier") +{ + /* ETSI TS 101 343: values 0 and 1 are reserved for signalling and PTM */ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.cell.bvci = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd, + "gprs nsei <0-65535>", + GPRS_TEXT + "GPRS NS Entity Identifier\n" + "GPRS NS Entity Identifier") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + 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(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd, + "gprs nsvc <0-1> nsvci <0-65535>", + GPRS_TEXT NSVC_TEXT + "NS Virtual Connection Identifier\n" + "GPRS NS VC Identifier") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].nsvci = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd, + "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]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].local_port = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd, + "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]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].remote_port = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd, + "gprs nsvc <0-1> remote ip A.B.C.D", + GPRS_TEXT NSVC_TEXT + "GPRS NS Remote IP Address\n" + "GPRS NS Remote IP Address\n" + "GPRS NS Remote IP Address\n") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + struct in_addr ia; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + inet_aton(argv[1], &ia); + bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr); + + return CMD_SUCCESS; +} + +DEFUN(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") +{ + struct gsm_bts *bts = vty->index; + + bts->paging.free_chans_need = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd, + "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]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + 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(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd, + "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]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer)) + return CMD_WARNING; + + bts->gprs.cell.timer[idx] = val; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd, + "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; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.rac = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_ctrl_ack, cfg_bts_gprs_ctrl_ack_cmd, + "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; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.ctrl_ack_type_use_block = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_bts_gprs_ctrl_ack, cfg_no_bts_gprs_ctrl_ack_cmd, + "no gprs control-ack-type-rach", NO_STR 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; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.ctrl_ack_type_use_block = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_net_ctrl_ord, cfg_bts_gprs_net_ctrl_ord_cmd, + "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; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.net_ctrl_ord = atoi(argv[0] + 2); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd, + "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(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 "11 bit RACH options\n" + "Disable 11 bit RACH for EGPRS\n" + "Enable 11 bit RACH for EGPRS") +{ + struct gsm_bts *bts = vty->index; + + bts->gprs.supports_egprs_11bit_rach = atoi(argv[0]); + + if (bts->gprs.supports_egprs_11bit_rach > 1) { + vty_out(vty, "Error in RACH type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if ((bts->gprs.mode == BTS_GPRS_NONE) && + (bts->gprs.supports_egprs_11bit_rach == 1)) { + vty_out(vty, "Error:gprs mode is none and 11bit rach is" + " enabled%s", VTY_NEWLINE); + return CMD_WARNING; + } + + 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(cfg_bts_si_mode, cfg_bts_si_mode_cmd, + "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(cfg_bts_si_static, cfg_bts_si_static_cmd, + "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(cfg_bts_early_cm, cfg_bts_early_cm_cmd, + "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(cfg_bts_early_cm_3g, cfg_bts_early_cm_3g_cmd, + "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(cfg_bts_neigh_mode, cfg_bts_neigh_mode_cmd, + "neighbor-list mode (automatic|manual|manual-si5)", + "Neighbor List\n" "Mode of Neighbor List generation\n" + "Automatically from all BTS in this OpenBSC\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(cfg_bts_neigh, cfg_bts_neigh_cmd, + "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]); + + if (!bts->neigh_list_manual_mode) { + vty_out(vty, "%% Cannot configure neighbor list in " + "automatic mode%s", 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(cfg_bts_si2quater_neigh_add, cfg_bts_si2quater_neigh_add_cmd, + "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(cfg_bts_si2quater_neigh_del, cfg_bts_si2quater_neigh_del_cmd, + "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(cfg_bts_si2quater_uarfcn_add, cfg_bts_si2quater_uarfcn_add_cmd, + "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(cfg_bts_si2quater_uarfcn_del, cfg_bts_si2quater_uarfcn_del_cmd, + "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(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd, + "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") +{ + 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 (!strcmp(argv[0], "add")) + bitvec_set_bit_pos(bv, arfcn, 1); + else + bitvec_set_bit_pos(bv, arfcn, 0); + + return CMD_SUCCESS; +} + +DEFUN(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") +{ + 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(cfg_bts_acc_ramping, + cfg_bts_acc_ramping_cmd, + "access-control-class-ramping", + "Enable Access Control Class ramping\n") +{ + struct gsm_bts *bts = vty->index; + + if (!acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_set_enabled(&bts->acc_ramp, true); + + /* + * ACC ramping takes effect either when the BTS reconnects RSL, + * or when RF administrative state changes to 'unlocked'. + */ + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_acc_ramping, cfg_bts_no_acc_ramping_cmd, + "no access-control-class-ramping", + NO_STR + "Disable Access Control Class ramping\n") +{ + 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); + gsm_bts_set_system_infos(bts); + } + + return CMD_SUCCESS; +} + +DEFUN(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\n") +{ + struct gsm_bts *bts = vty->index; + bool dynamic = (strcmp(argv[0], "dynamic") == 0); + int error; + + if (dynamic) { + acc_ramp_set_step_interval_dynamic(&bts->acc_ramp); + 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(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") +{ + 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; +} + +#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n" + +DEFUN(cfg_bts_excl_rf_lock, + cfg_bts_excl_rf_lock_cmd, + "rf-lock-exclude", + EXCL_RFLOCK_STR) +{ + struct gsm_bts *bts = vty->index; + bts->excl_from_rf_lock = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_excl_rf_lock, + cfg_bts_no_excl_rf_lock_cmd, + "no rf-lock-exclude", + NO_STR EXCL_RFLOCK_STR) +{ + 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(cfg_bts_force_comb_si, + cfg_bts_force_comb_si_cmd, + "force-combined-si", + FORCE_COMB_SI_STR) +{ + struct gsm_bts *bts = vty->index; + bts->force_combined_si = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_force_comb_si, + cfg_bts_no_force_comb_si_cmd, + "no force-combined-si", + NO_STR FORCE_COMB_SI_STR) +{ + struct gsm_bts *bts = vty->index; + bts->force_combined_si = 0; + 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(cfg_bts_codec0, cfg_bts_codec0_cmd, + "codec-support fr", + "Codec Support settings\nFullrate\n") +{ + _get_codec_from_arg(vty, 0, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_codec1, cfg_bts_codec1_cmd, + "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(cfg_bts_codec2, cfg_bts_codec2_cmd, + "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(cfg_bts_codec3, cfg_bts_codec3_cmd, + "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(cfg_bts_codec4, cfg_bts_codec4_cmd, + "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(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) +{ + 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(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd, + "depeneds-on-bts <0-255>", + NO_STR "This BTS can only be started if another one is up\n" + BTS_NR_STR) +{ + 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 void 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; + + mr->gsm48_ie[1] = 0; + for (i = 0; i < argc; i++) + mr->gsm48_ie[1] |= 1 << atoi(argv[i]); + mr_conf->icmi = 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; + } +} + +#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(cfg_bts_amr_fr_modes1, cfg_bts_amr_fr_modes1_cmd, + "amr tch-f modes" AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR) +{ + get_amr_from_arg(vty, 1, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_modes2, cfg_bts_amr_fr_modes2_cmd, + "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) +{ + get_amr_from_arg(vty, 2, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_modes3, cfg_bts_amr_fr_modes3_cmd, + "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) +{ + get_amr_from_arg(vty, 3, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_modes4, cfg_bts_amr_fr_modes4_cmd, + "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) +{ + get_amr_from_arg(vty, 4, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_start_mode, cfg_bts_amr_fr_start_mode_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_thres1, cfg_bts_amr_fr_thres1_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_thres2, cfg_bts_amr_fr_thres2_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_thres3, cfg_bts_amr_fr_thres3_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_hyst1, cfg_bts_amr_fr_hyst1_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_hyst2, cfg_bts_amr_fr_hyst2_cmd, + "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(cfg_bts_amr_fr_hyst3, cfg_bts_amr_fr_hyst3_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_modes1, cfg_bts_amr_hr_modes1_cmd, + "amr tch-h modes" AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR) +{ + get_amr_from_arg(vty, 1, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_modes2, cfg_bts_amr_hr_modes2_cmd, + "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) +{ + get_amr_from_arg(vty, 2, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_modes3, cfg_bts_amr_hr_modes3_cmd, + "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) +{ + get_amr_from_arg(vty, 3, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_modes4, cfg_bts_amr_hr_modes4_cmd, + "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) +{ + get_amr_from_arg(vty, 4, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_start_mode, cfg_bts_amr_hr_start_mode_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_thres1, cfg_bts_amr_hr_thres1_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_thres2, cfg_bts_amr_hr_thres2_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_thres3, cfg_bts_amr_hr_thres3_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_hyst1, cfg_bts_amr_hr_hyst1_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_hyst2, cfg_bts_amr_hr_hyst2_cmd, + "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 CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_hyst3, cfg_bts_amr_hr_hyst3_cmd, + "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 CMD_SUCCESS; +} + +#define TRX_TEXT "Radio Transceiver\n" + +/* per TRX configuration */ +DEFUN(cfg_trx, + cfg_trx_cmd, + "trx <0-255>", + TRX_TEXT + "Select a TRX to configure") +{ + 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(cfg_trx_arfcn, + cfg_trx_arfcn_cmd, + "arfcn <0-1023>", + "Set the ARFCN for this TRX\n" + "Absolute Radio Frequency Channel Number\n") +{ + int arfcn = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + /* 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(cfg_trx_nominal_power, + cfg_trx_nominal_power_cmd, + "nominal power <0-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(cfg_trx_max_power_red, + cfg_trx_max_power_red_cmd, + "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; +} + +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(cfg_trx_rsl_e1_tei, + cfg_trx_rsl_e1_tei_cmd, + "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(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") +{ + 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(cfg_ts, + cfg_ts_cmd, + "timeslot <0-7>", + "Select a Timeslot to configure\n" + "Timeslot number\n") +{ + 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(cfg_ts_pchan, + cfg_ts_pchan_cmd, + "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 = 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) + return CMD_WARNING; + + ts->pchan = pchanc; + + return CMD_SUCCESS; +} + + + +DEFUN(cfg_ts_tsc, + cfg_ts_tsc_cmd, + "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(cfg_ts_hopping, + cfg_ts_hopping_cmd, + "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 support hopping%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + ts->hopping.enabled = enabled; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_hsn, + cfg_ts_hsn_cmd, + "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(cfg_ts_maio, + cfg_ts_maio_cmd, + "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(cfg_ts_arfcn_add, + cfg_ts_arfcn_add_cmd, + "hopping arfcn add <0-1023>", + HOPPING_STR "Configure hopping ARFCN list\n" + "Add an entry to the hopping ARFCN list\n" "ARFCN\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int arfcn = atoi(argv[0]); + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_arfcn_del, + cfg_ts_arfcn_del_cmd, + "hopping arfcn del <0-1023>", + HOPPING_STR "Configure hopping ARFCN list\n" + "Delete an entry to the hopping ARFCN list\n" "ARFCN\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int arfcn = atoi(argv[0]); + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0); + + return CMD_SUCCESS; +} + +DEFUN(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") +{ + 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; + vty_out(vty, "%25s: %10"PRIu64" %s%s", desc->name, ctr->current, desc->description, VTY_NEWLINE); + return 0; +} + +void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net) +{ + rate_ctr_for_each_counter(net->bsc_ctrs, print_counter, vty); +} + +DEFUN(drop_bts, + drop_bts_cmd, + "drop bts connection <0-65535> (oml|rsl)", + "Debug/Simulation command to drop Abis/IP BTS\n" + "Debug/Simulation command to drop Abis/IP BTS\n" + "Debug/Simulation command to drop Abis/IP BTS\n" + "BTS NR\n" "Drop OML Connection\n" "Drop RSL Connection\n") +{ + struct gsm_network *gsmnet; + struct gsm_bts_trx *trx; + 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 (!is_ipaccess_bts(bts)) { + vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + + /* close all connections */ + if (strcmp(argv[1], "oml") == 0) { + ipaccess_drop_oml(bts); + } else if (strcmp(argv[1], "rsl") == 0) { + /* close all rsl connections */ + llist_for_each_entry(trx, &bts->trx_list, list) { + ipaccess_drop_rsl(trx); + } + } else { + vty_out(vty, "Argument must be 'oml# or 'rsl'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(restart_bts, restart_bts_cmd, + "restart-bts <0-65535>", + "Restart ip.access nanoBTS through OML\n" + BTS_NR_STR) +{ + struct gsm_network *gsmnet; + struct gsm_bts_trx *trx; + 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 (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) { + vty_out(vty, "This command only works for ipaccess nanoBTS.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + /* go from last TRX to c0 */ + llist_for_each_entry_reverse(trx, &bts->trx_list, list) + abis_nm_ipaccess_restart(trx); + + return CMD_SUCCESS; +} + +DEFUN(bts_resend, bts_resend_cmd, + "bts <0-255> resend-system-information", + "BTS Specific Commands\n" BTS_NR_STR + "Re-generate + re-send BCCH SYSTEM INFORMATION\n") +{ + struct gsm_network *gsmnet; + struct gsm_bts_trx *trx; + 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; + } + + llist_for_each_entry_reverse(trx, &bts->trx_list, list) + gsm_bts_trx_set_system_infos(trx); + + return CMD_SUCCESS; +} + + +DEFUN(smscb_cmd, smscb_cmd_cmd, + "bts <0-255> smscb-command <1-4> HEXSTRING", + "BTS related commands\n" BTS_NR_STR + "SMS Cell Broadcast\n" "Last Valid Block\n" + "Hex Encoded SMSCB message (up to 88 octets)\n") +{ + struct gsm_bts *bts; + int bts_nr = atoi(argv[0]); + int last_block = atoi(argv[1]); + struct rsl_ie_cb_cmd_type cb_cmd; + uint8_t buf[88]; + 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; + } + rc = osmo_hexparse(argv[2], buf, sizeof(buf)); + if (rc < 0 || rc > sizeof(buf)) { + vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE); + return CMD_WARNING; + } + + cb_cmd.spare = 0; + cb_cmd.def_bcast = 0; + cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL; + + switch (last_block) { + case 1: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1; + break; + case 2: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2; + break; + case 3: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3; + break; + case 4: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4; + break; + default: + vty_out(vty, "Error parsing LASTBLOCK%s", VTY_NEWLINE); + return CMD_WARNING; + } + + rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, 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 + "Packet Data Channel\n" + "Activate Dynamic PDCH/TCH (-> PDCH mode)\n" + "Deactivate Dynamic PDCH/TCH (-> TCH mode)\n") +{ + struct gsm_bts_trx_ts *ts; + int activate; + + ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); + if (!ts) + return CMD_WARNING; + + if (!is_ipaccess_bts(ts->trx->bts)) { + vty_out(vty, "%% This command only works for ipaccess BTS%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) { + vty_out(vty, "%% Timeslot %u is not in dynamic TCH_F/PDCH " + "mode%s", ts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[3], "activate")) + activate = 1; + else + activate = 0; + + rsl_ipacc_pdch_activate(ts, activate); + + return CMD_SUCCESS; + +} + +/* determine the logical channel type based on the physical channel type */ +static int lchan_type_by_pchan(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_TCH_F: + return GSM_LCHAN_TCH_F; + case GSM_PCHAN_TCH_H: + return GSM_LCHAN_TCH_H; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + return GSM_LCHAN_SDCCH; + default: + return -1; + } +} + +/* configure the lchan for a single AMR mode (as specified) */ +static int lchan_set_single_amr_mode(struct gsm_lchan *lchan, uint8_t amr_mode) +{ + struct amr_multirate_conf mr; + struct gsm48_multi_rate_conf *mr_conf; + mr_conf = (struct gsm48_multi_rate_conf *) &mr.gsm48_ie; + + if (amr_mode > 7) + return -1; + + memset(&mr, 0, sizeof(mr)); + mr_conf->ver = 1; + /* bit-mask of supported modes, only one bit is set. Reflects + * Figure 10.5.2.47a where there are no thershold and only a + * single mode */ + mr.gsm48_ie[1] = 1 << amr_mode; + + mr.ms_mode[0].mode = amr_mode; + mr.bts_mode[0].mode = amr_mode; + + /* encode this configuration into the lchan for both uplink and + * downlink direction */ + gsm48_multirate_config(lchan->mr_ms_lv, &mr, mr.ms_mode); + gsm48_multirate_config(lchan->mr_bts_lv, &mr, mr.bts_mode); + + return 0; +} + +/* 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) [<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" "AMR Mode\n") +{ + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int ss_nr = atoi(argv[3]); + const char *act_str = argv[4]; + const char *codec_str = argv[5]; + int activate; + + ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); + if (!ts) + return CMD_WARNING; + + lchan = &ts->lchan[ss_nr]; + + if (!strcmp(act_str, "activate")) + activate = 1; + else + activate = 0; + + if (ss_nr >= ts_subslots(ts)) { + vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s", + ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE); + return CMD_WARNING; + } + + if (activate) { + int lchan_t; + if (lchan->state != LCHAN_S_NONE) { + vty_out(vty, "%% Cannot activate: Channel busy!%s", VTY_NEWLINE); + return CMD_WARNING; + } + lchan_t = lchan_type_by_pchan(ts->pchan); + if (lchan_t < 0) + return CMD_WARNING; + /* configure the lchan */ + lchan->type = lchan_t; + lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; + if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr")) + lchan->tch_mode = GSM48_CMODE_SPEECH_V1; + else if (!strcmp(codec_str, "efr")) + lchan->tch_mode = GSM48_CMODE_SPEECH_EFR; + else if (!strcmp(codec_str, "amr")) { + int amr_mode; + if (argc < 7) { + vty_out(vty, "%% AMR requires specification of AMR mode%s", VTY_NEWLINE); + return CMD_WARNING; + } + amr_mode = atoi(argv[6]); + lchan->tch_mode = GSM48_CMODE_SPEECH_AMR; + lchan_set_single_amr_mode(lchan, amr_mode); + } + vty_out(vty, "%% activating lchan %s%s", gsm_lchan_name(lchan), VTY_NEWLINE); + rsl_chan_activate_lchan(lchan, RSL_ACT_TYPE_INITIAL, 0); + rsl_ipacc_crcx(lchan); + } else { + rsl_direct_rf_release(lchan); + } + + return CMD_SUCCESS; +} + +DEFUN(lchan_mdcx, lchan_mdcx_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> mdcx A.B.C.D <0-65535>", + BTS_NR_TRX_TS_SS_STR2 + "Modify RTP Connection\n" "MGW IP Address\n" "MGW UDP Port\n") +{ + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int ss_nr = atoi(argv[3]); + int port = atoi(argv[5]); + struct in_addr ia; + inet_aton(argv[4], &ia); + + ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); + if (!ts) + return CMD_WARNING; + + lchan = &ts->lchan[ss_nr]; + + if (ss_nr >= ts_subslots(ts)) { + vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s", + ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "%% connecting RTP of %s to %s:%u%s", gsm_lchan_name(lchan), + inet_ntoa(ia), port, VTY_NEWLINE); + rsl_ipacc_mdcx(lchan, ntohl(ia.s_addr), port, 0); + return CMD_SUCCESS; +} + +DEFUN(ctrl_trap, ctrl_trap_cmd, + "ctrl-interface generate-trap TRAP VALUE", + "Commands related to the CTRL Interface\n" + "Generate a TRAP for test purpose\n" + "Identity/Name of the TRAP variable\n" + "Value of the TRAP variable\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + ctrl_cmd_send_trap(net->ctrl, argv[0], (char *) argv[1]); + return CMD_SUCCESS; +} + +#define NETWORK_STR "Configure the GSM network\n" +#define CODE_CMD_STR "Code commands\n" +#define NAME_CMD_STR "Name Commands\n" +#define NAME_STR "Name to use\n" + +DEFUN(cfg_net, + cfg_net_cmd, + "network", NETWORK_STR) +{ + vty->index = gsmnet_from_vty(vty); + vty->node = GSMNET_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_ncc, + cfg_net_ncc_cmd, + "network country code <1-999>", + "Set the GSM network country code\n" + "Country commands\n" + CODE_CMD_STR + "Network Country Code to use\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + uint16_t mcc; + + if (osmo_mcc_from_str(argv[0], &mcc)) { + vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + gsmnet->plmn.mcc = mcc; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_mnc, + cfg_net_mnc_cmd, + "mobile network code <0-999>", + "Set the GSM mobile network code\n" + "Network Commands\n" + CODE_CMD_STR + "Mobile Network Code to use\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + uint16_t mnc; + bool mnc_3_digits; + + if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) { + vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + gsmnet->plmn.mnc = mnc; + gsmnet->plmn.mnc_3_digits = mnc_3_digits; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_encryption, + cfg_net_encryption_cmd, + "encryption a5 <0-3> [<0-3>] [<0-3>] [<0-3>]", + "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") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + unsigned int i; + + gsmnet->a5_encryption_mask = 0; + for (i = 0; i < argc; i++) + gsmnet->a5_encryption_mask |= (1 << atoi(argv[i])); + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_net_dyn_ts_allow_tch_f, + cfg_net_dyn_ts_allow_tch_f_cmd, + "dyn_ts_allow_tch_f (0|1)", + "Allow or disallow allocating TCH/F on TCH_F_TCH_H_PDCH timeslots\n" + "Disallow TCH/F on TCH_F_TCH_H_PDCH (default)\n" + "Allow TCH/F on TCH_F_TCH_H_PDCH\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->dyn_ts_allow_tch_f = atoi(argv[0]) ? true : false; + vty_out(vty, "%% dyn_ts_allow_tch_f is deprecated, rather use msc/codec-list to pick codecs%s", + VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_timezone, + cfg_net_timezone_cmd, + "timezone <-19-19> (0|15|30|45)", + "Set the Timezone Offset of the network\n" + "Timezone offset (hours)\n" + "Timezone offset (00 minutes)\n" + "Timezone offset (15 minutes)\n" + "Timezone offset (30 minutes)\n" + "Timezone offset (45 minutes)\n" + ) +{ + struct gsm_network *net = vty->index; + int tzhr = atoi(argv[0]); + int tzmn = atoi(argv[1]); + + net->tz.hr = tzhr; + net->tz.mn = tzmn; + net->tz.dst = 0; + net->tz.override = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_timezone_dst, + cfg_net_timezone_dst_cmd, + "timezone <-19-19> (0|15|30|45) <0-2>", + "Set the Timezone Offset of the network\n" + "Timezone offset (hours)\n" + "Timezone offset (00 minutes)\n" + "Timezone offset (15 minutes)\n" + "Timezone offset (30 minutes)\n" + "Timezone offset (45 minutes)\n" + "DST offset (hours)\n" + ) +{ + struct gsm_network *net = vty->index; + int tzhr = atoi(argv[0]); + int tzmn = atoi(argv[1]); + int tzdst = atoi(argv[2]); + + net->tz.hr = tzhr; + net->tz.mn = tzmn; + net->tz.dst = tzdst; + net->tz.override = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_no_timezone, + cfg_net_no_timezone_cmd, + "no timezone", + NO_STR + "Disable network timezone override, use system tz\n") +{ + struct gsm_network *net = vty->index; + + net->tz.override = 0; + + return CMD_SUCCESS; +} + +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") +{ + struct gsm_network *net = vty->index; + + net->t3212 = atoi(argv[0]) / 6; + + return CMD_SUCCESS; +} + +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") +{ + struct gsm_network *net = vty->index; + + net->t3212 = 0; + + return CMD_SUCCESS; +} + +#define MEAS_FEED_STR "Measurement Report export\n" + +DEFUN(cfg_net_meas_feed_dest, cfg_net_meas_feed_dest_cmd, + "meas-feed destination ADDR <0-65535>", + MEAS_FEED_STR "Where to forward Measurement Report feeds\n" "address or hostname\n" "port number\n") +{ + int rc; + const char *host = argv[0]; + uint16_t port = atoi(argv[1]); + + rc = meas_feed_cfg_set(host, port); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_meas_feed_scenario, cfg_net_meas_feed_scenario_cmd, + "meas-feed scenario NAME", + MEAS_FEED_STR "Set a name to include in the Measurement Report feeds\n" "Name string, up to 31 characters\n") +{ + meas_feed_scenario_set(argv[0]); + + return CMD_SUCCESS; +} + +extern int bsc_vty_init_extra(void); + +int bsc_vty_init(struct gsm_network *network) +{ + 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); + + OSMO_ASSERT(vty_global_gsm_network == NULL); + vty_global_gsm_network = network; + + osmo_stats_vty_add_cmds(); + + install_element(CONFIG_NODE, &cfg_net_cmd); + install_node(&net_node, config_write_net); + install_element(GSMNET_NODE, &cfg_net_ncc_cmd); + install_element(GSMNET_NODE, &cfg_net_mnc_cmd); + install_element(GSMNET_NODE, &cfg_net_encryption_cmd); + install_element(GSMNET_NODE, &cfg_net_timezone_cmd); + install_element(GSMNET_NODE, &cfg_net_timezone_dst_cmd); + install_element(GSMNET_NODE, &cfg_net_no_timezone_cmd); + install_element(GSMNET_NODE, &cfg_net_per_loc_upd_cmd); + install_element(GSMNET_NODE, &cfg_net_no_per_loc_upd_cmd); + 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_ve(&bsc_show_net_cmd); + install_element_ve(&show_bts_cmd); + install_element_ve(&show_trx_cmd); + install_element_ve(&show_ts_cmd); + install_element_ve(&show_lchan_cmd); + install_element_ve(&show_lchan_summary_cmd); + + install_element_ve(&show_subscr_conn_cmd); + install_element_ve(&handover_any_cmd); + install_element_ve(&assignment_any_cmd); + + install_element_ve(&show_paging_cmd); + install_element_ve(&show_paging_group_cmd); + + logging_vty_add_cmds(NULL); + osmo_talloc_vty_add_cmds(); + + install_element(GSMNET_NODE, &cfg_net_neci_cmd); + install_element(GSMNET_NODE, &cfg_net_T3101_cmd); + install_element(GSMNET_NODE, &cfg_net_T3103_cmd); + install_element(GSMNET_NODE, &cfg_net_T3105_cmd); + install_element(GSMNET_NODE, &cfg_net_T3107_cmd); + install_element(GSMNET_NODE, &cfg_net_T3109_cmd); + install_element(GSMNET_NODE, &cfg_net_T3111_cmd); + install_element(GSMNET_NODE, &cfg_net_T3113_cmd); + install_element(GSMNET_NODE, &cfg_net_T3115_cmd); + install_element(GSMNET_NODE, &cfg_net_T3117_cmd); + install_element(GSMNET_NODE, &cfg_net_T3119_cmd); + install_element(GSMNET_NODE, &cfg_net_T3122_cmd); + install_element(GSMNET_NODE, &cfg_net_T3141_cmd); + install_element(GSMNET_NODE, &cfg_net_dtx_cmd); + install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd); + /* See also handover commands added on net level from handover_vty.c */ + + 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_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_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_desc_bs_pa_mfrms_cmd); + install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_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_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_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_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); + /* 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_e1_subslot_cmd); + + 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, &pdch_act_cmd); + install_element(ENABLE_NODE, &lchan_act_cmd); + install_element(ENABLE_NODE, &lchan_mdcx_cmd); + install_element(ENABLE_NODE, &handover_subscr_conn_cmd); + install_element(ENABLE_NODE, &assignment_subscr_conn_cmd); + install_element(ENABLE_NODE, &smscb_cmd_cmd); + install_element(ENABLE_NODE, &ctrl_trap_cmd); + + abis_nm_vty_init(); + abis_om2k_vty_init(); + e1inp_vty_init(); + osmo_fsm_vty_add_cmds(); + + ho_vty_init(); + + bsc_vty_init_extra(); + + return 0; +} diff --git a/src/osmo-bsc/bts_ericsson_rbs2000.c b/src/osmo-bsc/bts_ericsson_rbs2000.c new file mode 100644 index 000000000..9c8b90ee2 --- /dev/null +++ b/src/osmo-bsc/bts_ericsson_rbs2000.c @@ -0,0 +1,212 @@ +/* Ericsson RBS-2xxx specific code */ + +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include + +#include +#include +#include +#include +#include +#include + +#include + +static void bootstrap_om_bts(struct gsm_bts *bts) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); + + /* FIXME: this is global init, not bootstrapping */ + abis_om2k_bts_init(bts); + abis_om2k_trx_init(bts->c0); + + /* TODO: Should we wait for a Failure report? */ + om2k_bts_fsm_start(bts); +} + +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 */ +} + +static int shutdown_om(struct gsm_bts *bts) +{ + gsm_bts_mark_all_ts_uninitialized(bts); + + /* FIXME */ + return 0; +} + + +/* Tell LAPD to start start the SAP (send SABM requests) for all signalling + * timeslots in this line */ +static void start_sabm_in_line(struct e1inp_line *line, int start) +{ + struct e1inp_sign_link *link; + int i; + + for (i = 0; i < ARRAY_SIZE(line->ts); i++) { + struct e1inp_ts *ts = &line->ts[i]; + + if (ts->type != E1INP_TS_TYPE_SIGN) + continue; + + llist_for_each_entry(link, &ts->sign.sign_links, list) { + if (!ts->lapd) + continue; + lapd_instance_set_profile(ts->lapd, + &lapd_profile_abis_ericsson); + + if (start) + lapd_sap_start(ts->lapd, link->tei, link->sapi); + else + lapd_sap_stop(ts->lapd, link->tei, link->sapi); + } + } +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int gbl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts *bts; + + if (subsys != SS_L_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_BTS_CLOSE_OM: + bts = signal_data; + if (bts->type == GSM_BTS_TYPE_RBS2000) + shutdown_om(signal_data); + break; + } + + return 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) +{ + struct input_signal_data *isd = signal_data; + struct e1inp_ts *e1i_ts; + + if (subsys != SS_L_INPUT) + return 0; + + LOGP(DNM, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__, + get_value_string(e1inp_signal_names, signal)); + switch (signal) { + case S_L_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000) + break; + if (isd->tei == isd->trx->bts->oml_tei) + bootstrap_om_bts(isd->trx->bts); + else + bootstrap_om_trx(isd->trx); + break; + } + break; + case S_L_INP_TEI_DN: + if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000) + break; + 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); + /* 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); + lapd_sap_start(e1i_ts->lapd, isd->tei, isd->sapi); + break; + case S_L_INP_LINE_INIT: + case S_L_INP_LINE_NOALARM: + if (strcasecmp(isd->line->driver->name, "DAHDI") + && strcasecmp(isd->line->driver->name, "MISDN_LAPD") + && strcasecmp(isd->line->driver->name, "UNIXSOCKET")) + break; + start_sabm_in_line(isd->line, 1); + break; + case S_L_INP_LINE_ALARM: + if (strcasecmp(isd->line->driver->name, "DAHDI") + && strcasecmp(isd->line->driver->name, "MISDN_LAPD") + && strcasecmp(isd->line->driver->name, "UNIXSOCKET")) + break; + start_sabm_in_line(isd->line, 0); + break; + } + + return 0; +} + +static void config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + abis_om2k_config_write_bts(vty, bts); +} + +static int bts_model_rbs2k_start(struct gsm_network *net); + +static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line) +{ + e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); +} + +static bool bts_model_rbs2k_is_ts_ready(const struct gsm_bts_trx_ts *ts) +{ + return ts && ts->mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + +static struct gsm_bts_model model_rbs2k = { + .type = GSM_BTS_TYPE_RBS2000, + .name = "rbs2000", + .start = bts_model_rbs2k_start, + .oml_rcvmsg = &abis_om2k_rcvmsg, + .oml_is_ts_ready = bts_model_rbs2k_is_ts_ready, + .config_write_bts = &config_write_bts, + .e1line_bind_ops = &bts_model_rbs2k_e1line_bind_ops, +}; + +static int bts_model_rbs2k_start(struct gsm_network *net) +{ + model_rbs2k.features.data = &model_rbs2k._features_data[0]; + model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data); + + osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_GPRS); + osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_HOPPING); + 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 new file mode 100644 index 000000000..18f1ed4c8 --- /dev/null +++ b/src/osmo-bsc/bts_init.c @@ -0,0 +1,30 @@ +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include + +int bts_init(void) +{ + bts_model_bs11_init(); + bts_model_rbs2k_init(); + bts_model_nanobts_init(); + bts_model_nokia_site_init(); + bts_model_sysmobts_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 new file mode 100644 index 000000000..843f264ae --- /dev/null +++ b/src/osmo-bsc/bts_ipaccess_nanobts.c @@ -0,0 +1,591 @@ +/* ip.access nanoBTS specific code */ + +/* (C) 2009-2018 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int bts_model_nanobts_start(struct gsm_network *net); +static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line); + +static char *get_oml_status(const struct gsm_bts *bts) +{ + if (bts->oml_link) + return all_trx_rsl_connected_unlocked(bts) ? "connected" : "degraded"; + + return "disconnected"; +} + +static bool oml_is_ts_ready(const struct gsm_bts_trx_ts *ts) +{ + return ts && ts->mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + +struct gsm_bts_model bts_model_nanobts = { + .type = GSM_BTS_TYPE_NANOBTS, + .name = "nanobts", + .start = bts_model_nanobts_start, + .oml_rcvmsg = &abis_nm_rcvmsg, + .oml_status = &get_oml_status, + .oml_is_ts_ready = oml_is_ts_ready, + .e1line_bind_ops = bts_model_nanobts_e1line_bind_ops, + .nm_att_tlvdef = { + .def = { + /* ip.access specifics */ + [NM_ATT_IPACC_DST_IP] = { TLV_TYPE_FIXED, 4 }, + [NM_ATT_IPACC_DST_IP_PORT] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_IPACC_STREAM_ID] = { TLV_TYPE_TV, }, + [NM_ATT_IPACC_SEC_OML_CFG] = { TLV_TYPE_FIXED, 6 }, + [NM_ATT_IPACC_IP_IF_CFG] = { TLV_TYPE_FIXED, 8 }, + [NM_ATT_IPACC_IP_GW_CFG] = { TLV_TYPE_FIXED, 12 }, + [NM_ATT_IPACC_IN_SERV_TIME] = { TLV_TYPE_FIXED, 4 }, + [NM_ATT_IPACC_LOCATION] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_PAGING_CFG] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_IPACC_UNIT_ID] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_UNIT_NAME] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_SNMP_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NV_FLAGS] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_FREQ_CTRL] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_CUR_SW_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_TIMING_BUS] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_CGI] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_RAC] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_OBJ_VERSION] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NSEI] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_BVCI] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NSVCI] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NS_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_BSSGP_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_NS_LINK_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_RLC_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_ALM_THRESH_LIST]= { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_TIB_CONTROL] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_SUPP_FEATURES] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_CODING_SCHEMES] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_RLC_CFG_2] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_HEARTB_TOUT] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_UPTIME] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_RLC_CFG_3] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_SSL_CFG] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_SEC_POSSIBLE] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_IML_SSL_STATE] = { TLV_TYPE_TL16V }, + [NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V }, + }, + }, +}; + + +/* Callback function to be called whenever we get a GSM 12.21 state change event */ +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 *bts; + struct gsm_bts_trx *trx; + 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 */ + + /* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create + * endless loop */ + if (evt != S_NM_STATECHG_OPER) + return 0; + + switch (obj_class) { + case NM_OC_SITE_MANAGER: + bts = container_of(obj, struct gsm_bts, site_mgr); + if ((new_state->operational == NM_OPSTATE_ENABLED && + new_state->availability == NM_AVSTATE_OK) || + (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_OFF_LINE)) + abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff); + break; + case NM_OC_BTS: + bts = obj; + if (new_state->availability == NM_AVSTATE_DEPENDENCY) { + msgb = nanobts_attr_bts_get(bts); + abis_nm_set_bts_attr(bts, msgb->data, msgb->len); + msgb_free(msgb); + abis_nm_chg_adm_state(bts, obj_class, + bts->bts_nr, 0xff, 0xff, + NM_STATE_UNLOCKED); + abis_nm_opstart(bts, obj_class, + bts->bts_nr, 0xff, 0xff); + } + break; + case NM_OC_CHANNEL: + ts = obj; + trx = ts->trx; + if (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_DEPENDENCY) { + enum abis_nm_chan_comb ccomb = + abis_nm_chcomb4pchan(ts->pchan); + if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) { + ipaccess_drop_oml(trx->bts); + return -1; + } + abis_nm_chg_adm_state(trx->bts, obj_class, + trx->bts->bts_nr, trx->nr, ts->nr, + NM_STATE_UNLOCKED); + abis_nm_opstart(trx->bts, obj_class, + trx->bts->bts_nr, trx->nr, ts->nr); + } + break; + case NM_OC_RADIO_CARRIER: + trx = obj; + if (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_OK) + abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr, + trx->nr, 0xff); + 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); + } + 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_opstart(bts, obj_class, bts->bts_nr, + 0, 0xff); + 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); + } + 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 ((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_opstart(bts, obj_class, bts->bts_nr, + nsvc->id, 0xff); + abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr, + nsvc->id, 0xff, + NM_STATE_UNLOCKED); + } + default: + break; + } + return 0; +} + +/* 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); + struct e1inp_sign_link *sign_link = mb->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + + if (!trx) + return -EINVAL; + + if (!is_ipaccess_bts(trx->bts)) + return 0; + + switch (foh->obj_class) { + case NM_OC_BASEB_TRANSC: + abis_nm_chg_adm_state(trx->bts, foh->obj_class, + trx->bts->bts_nr, trx->nr, 0xff, + NM_STATE_UNLOCKED); + abis_nm_opstart(trx->bts, foh->obj_class, + 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); + break; + case NM_OC_RADIO_CARRIER: { + /* + * Locking the radio carrier will make it go + * offline again and we would come here. The + * framework should determine that there was + * no change and avoid recursion. + * + * This code is here to make sure that on start + * a TRX remains locked. + */ + int rc_state = trx->mo.nm_state.administrative; + /* Patch ARFCN into radio attribute */ + struct msgb *msgb = nanobts_attr_radio_get(trx->bts, trx); + abis_nm_set_radio_attr(trx, msgb->data, msgb->len); + msgb_free(msgb); + abis_nm_chg_adm_state(trx->bts, foh->obj_class, + trx->bts->bts_nr, trx->nr, 0xff, + rc_state); + abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr, + trx->nr, 0xff); + break; + } + } + return 0; +} + +static struct gsm_bts_trx_ts *gsm_bts_trx_ts(struct gsm_network *net, + int bts_nr, int trx_nr, int ts_nr) +{ + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + bts = gsm_bts_num(net, bts_nr); + if (!bts) + return NULL; + trx = gsm_bts_trx_by_nr(bts, trx_nr); + if (!trx) + return NULL; + if (ts_nr < 0 || ts_nr > ARRAY_SIZE(trx->ts)) + return NULL; + return &trx->ts[ts_nr]; +} + +static void nm_rx_opstart_ack_chan(struct abis_om_fom_hdr *foh) +{ + struct gsm_bts_trx_ts *ts; + ts = gsm_bts_trx_ts(bsc_gsmnet, foh->obj_inst.bts_nr, foh->obj_inst.trx_nr, foh->obj_inst.ts_nr); + if (!ts) { + LOGP(DNM, LOGL_ERROR, "%s Channel OPSTART ACK for non-existent TS\n", + abis_nm_dump_foh(foh)); + return; + } + + gsm_ts_check_init(ts); +} + +static void nm_rx_opstart_ack(struct abis_om_fom_hdr *foh) +{ + switch (foh->obj_class) { + case NM_OC_CHANNEL: + nm_rx_opstart_ack_chan(foh); + 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) +{ + if (subsys != SS_NM) + return 0; + + switch (signal) { + case S_NM_SW_ACTIV_REP: + return sw_activ_rep(signal_data); + case S_NM_STATECHG_OPER: + case S_NM_STATECHG_ADM: + return nm_statechg_event(signal, signal_data); + case S_NM_OPSTART_ACK: + nm_rx_opstart_ack(signal_data); + return 0; + default: + break; + } + return 0; +} + +static int bts_model_nanobts_start(struct gsm_network *net) +{ + osmo_signal_unregister_handler(SS_NM, bts_ipa_nm_sig_cb, NULL); + osmo_signal_register_handler(SS_NM, bts_ipa_nm_sig_cb, NULL); + return 0; +} + +int bts_model_nanobts_init(void) +{ + bts_model_nanobts.features.data = &bts_model_nanobts._features_data[0]; + bts_model_nanobts.features.data_len = + sizeof(bts_model_nanobts._features_data); + + osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_GPRS); + osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_MULTI_TSC); + + return gsm_bts_model_register(&bts_model_nanobts); +} + +#define OML_UP 0x0001 +#define RSL_UP 0x0002 + +static struct gsm_bts * +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)) + continue; + + if (bts->ip_access.site_id == site_id && + bts->ip_access.bts_id == bts_id) + return bts; + } + return NULL; +} + +/* These are exported because they are used by the VTY interface. */ +void ipaccess_drop_rsl(struct gsm_bts_trx *trx) +{ + if (!trx->rsl_link) + return; + + LOGP(DLINP, LOGL_NOTICE, "(bts=%d,trx=%d) Dropping RSL link.\n", trx->bts->nr, trx->nr); + e1inp_sign_link_destroy(trx->rsl_link); + trx->rsl_link = NULL; + + if (trx->bts->c0 == trx) + paging_flush_bts(trx->bts, NULL); +} + +void ipaccess_drop_oml(struct gsm_bts *bts) +{ + struct gsm_bts *rdep_bts; + struct gsm_bts_trx *trx; + + if (!bts->oml_link) + return; + + LOGP(DLINP, LOGL_NOTICE, "(bts=%d) Dropping OML link.\n", bts->nr); + e1inp_sign_link_destroy(bts->oml_link); + bts->oml_link = NULL; + bts->uptime = 0; + + /* we have issues reconnecting RSL, drop everything. */ + llist_for_each_entry(trx, &bts->trx_list, list) + ipaccess_drop_rsl(trx); + + gsm_bts_mark_all_ts_uninitialized(bts); + + bts->ip_access.flags = 0; + + /* + * 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 + * should be fine in userspace. + * The oml_link is serving as recursion anchor for us and + * it is set to NULL some lines above. + */ + llist_for_each_entry(rdep_bts, &bts->network->bts_list, list) { + if (!bts_depend_is_depedency(rdep_bts, bts)) + continue; + LOGP(DLINP, LOGL_NOTICE, "Dropping BTS(%u) due BTS(%u).\n", + rdep_bts->nr, bts->nr); + ipaccess_drop_oml(rdep_bts); + } +} + +/* This function is called once the OML/RSL link becomes up. */ +static struct e1inp_sign_link * +ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line, + enum e1inp_sign_type type) +{ + struct gsm_bts *bts; + struct ipaccess_unit *dev = unit_data; + struct e1inp_sign_link *sign_link = NULL; + struct timespec tp; + int rc; + + bts = find_bts_by_unitid(bsc_gsmnet, dev->site_id, dev->bts_id); + if (!bts) { + LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for " + " %u/%u/%u, disconnecting\n", dev->site_id, + dev->bts_id, dev->trx_id); + rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_UNKNOWN_UNIT_ID]); + return NULL; + } + DEBUGP(DLINP, "Identified BTS %u/%u/%u\n", + dev->site_id, dev->bts_id, dev->trx_id); + + switch(type) { + case E1INP_SIGN_OML: + /* remove old OML signal link for this BTS. */ + ipaccess_drop_oml(bts); + + if (!bts_depend_check(bts)) { + LOGP(DLINP, LOGL_NOTICE, + "Dependency not full-filled for %u/%u/%u\n", + dev->site_id, dev->bts_id, dev->trx_id); + return NULL; + } + + /* create new OML link. */ + sign_link = bts->oml_link = + e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML - 1], + 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 */ + 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; + } + break; + case E1INP_SIGN_RSL: { + struct e1inp_ts *ts; + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, dev->trx_id); + + /* no OML link set yet? give up. */ + if (!bts->oml_link || !trx) + return NULL; + + /* remove old RSL link for this TRX. */ + ipaccess_drop_rsl(trx); + + /* set new RSL link for this TRX. */ + line = bts->oml_link->ts->line; + ts = &line->ts[E1INP_SIGN_RSL + dev->trx_id - 1]; + e1inp_ts_config_sign(ts, line); + sign_link = trx->rsl_link = + e1inp_sign_link_create(ts, E1INP_SIGN_RSL, + trx, trx->rsl_tei, 0); + trx->rsl_link->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, + sign_link->tei, sign_link->sapi); + sign_link->trx->bts->ip_access.flags |= + (RSL_UP << sign_link->trx->nr); + } + break; + } + default: + break; + } + return sign_link; +} + +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 gsm_bts *bts = NULL; + struct e1inp_sign_link *link; + + llist_for_each_entry(link, &ts->sign.sign_links, list) { + /* Get bts pointer from the first element of the list. */ + if (bts == NULL) + bts = link->trx->bts; + /* Cancel RSL connection timeout in case are still waiting for an RSL connection. */ + if (link->trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + osmo_timer_del(&link->trx->rsl_connect_timeout); + } + if (bts != NULL) + ipaccess_drop_oml(bts); +} + +/* This function is called if we receive one OML/RSL message. */ +static int ipaccess_sign_link(struct msgb *msg) +{ + int ret = 0; + struct e1inp_sign_link *link = msg->dst; + + switch (link->type) { + case E1INP_SIGN_RSL: + ret = abis_rsl_rcvmsg(msg); + break; + case E1INP_SIGN_OML: + ret = abis_nm_rcvmsg(msg); + break; + default: + LOGP(DLINP, LOGL_ERROR, "Unknown signal link type %d\n", + link->type); + msgb_free(msg); + break; + } + return ret; +} + +/* not static, ipaccess-config needs it. */ +struct e1inp_line_ops ipaccess_e1inp_line_ops = { + .cfg = { + .ipa = { + .addr = "0.0.0.0", + .role = E1INP_LINE_R_BSC, + }, + }, + .sign_link_up = ipaccess_sign_link_up, + .sign_link_down = ipaccess_sign_link_down, + .sign_link = ipaccess_sign_link, +}; + +static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line) +{ + e1inp_line_bind_ops(line, &ipaccess_e1inp_line_ops); +} diff --git a/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c new file mode 100644 index 000000000..1a8d9b07b --- /dev/null +++ b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c @@ -0,0 +1,240 @@ +/* ip.access nanoBTS specific code, OML attribute table generator */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +static void patch_16(uint8_t *data, const uint16_t val) +{ + memcpy(data, &val, sizeof(val)); +} + +static void patch_32(uint8_t *data, const uint32_t val) +{ + memcpy(data, &val, sizeof(val)); +} + +struct msgb *nanobts_attr_bts_get(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); + + /* interference avg. period in numbers of SACCH multifr */ + msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, 0x06); + + rlt = gsm_bts_get_radio_link_timeout(bts); + if (rlt == -1) { + /* Osmocom extension: Use infinite radio link timeout */ + buf[0] = 0xFF; + buf[1] = 0x00; + } else { + /* conn fail based on SACCH error rate */ + buf[0] = 0x01; + buf[1] = rlt; + } + msgb_tl16v_put(msgb, NM_ATT_CONN_FAIL_CRIT, 2, buf); + + memcpy(buf, "\x1e\x24\x24\xa8\x34\x21\xa8", 7); + msgb_tv_fixed_put(msgb, NM_ATT_T200, 7, buf); + + msgb_tv_put(msgb, NM_ATT_MAX_TA, 0x3f); + + /* seconds */ + memcpy(buf, "\x00\x01\x0a", 3); + msgb_tv_fixed_put(msgb, NM_ATT_OVERL_PERIOD, 3, buf); + + /* percent */ + msgb_tv_put(msgb, NM_ATT_CCCH_L_T, 10); + + /* seconds */ + msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, 1); + + /* busy threshold in - 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 */ + buf[0] = 0x03; + buf[1] = 0xe8; + if (bts->rach_ldavg_slots != -1) { + buf[0] = (bts->rach_ldavg_slots >> 8) & 0x0f; + buf[1] = bts->rach_ldavg_slots & 0xff; + } + msgb_tv_fixed_put(msgb, NM_ATT_LDAVG_SLOTS, 2, buf); + + /* 10 milliseconds */ + msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, bts->network->T3105 > 0? bts->network->T3105 : 13); + + /* 10 retransmissions of physical config */ + msgb_tv_put(msgb, NM_ATT_NY1, 10); + + buf[0] = (bts->c0->arfcn >> 8) & 0x0f; + buf[1] = bts->c0->arfcn & 0xff; + msgb_tv_fixed_put(msgb, NM_ATT_BCCH_ARFCN, 2, buf); + + msgb_tv_put(msgb, NM_ATT_BSIC, bts->bsic); + + abis_nm_ipaccess_cgi(buf, bts); + msgb_tl16v_put(msgb, NM_ATT_IPACC_CGI, 7, buf); + + return msgb; +} + +struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts) +{ + struct msgb *msgb; + uint8_t buf[256]; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + /* NSEI 925 */ + buf[0] = bts->gprs.nse.nsei >> 8; + buf[1] = bts->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); + + return msgb; +} + +struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts) +{ + struct msgb *msgb; + uint8_t buf[256]; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + /* 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 */ + 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; + 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; + buf[1] = 0x00; + } + 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 *msgb; + uint8_t buf[256]; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + /* 925 */ + buf[0] = bts->gprs.nsvc[0].nsvci >> 8; + buf[1] = bts->gprs.nsvc[0].nsvci & 0xff; + msgb_tl16v_put(msgb, NM_ATT_IPACC_NSVCI, 2, buf); + + /* remote udp port */ + patch_16(&buf[0], htons(bts->gprs.nsvc[0].remote_port)); + /* remote ip address */ + patch_32(&buf[2], htonl(bts->gprs.nsvc[0].remote_ip)); + /* local udp port */ + patch_16(&buf[6], htons(bts->gprs.nsvc[0].local_port)); + msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf); + + return msgb; +} + +struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts, + struct gsm_bts_trx *trx) +{ + struct msgb *msgb; + uint8_t buf[256]; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + /* number of -2dB reduction steps / Pn */ + msgb_tv_put(msgb, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2); + + buf[0] = trx->arfcn >> 8; + buf[1] = trx->arfcn & 0xff; + msgb_tl16v_put(msgb, NM_ATT_ARFCN_LIST, 2, buf); + + return msgb; +} diff --git a/src/osmo-bsc/bts_nokia_site.c b/src/osmo-bsc/bts_nokia_site.c new file mode 100644 index 000000000..4a24c3931 --- /dev/null +++ b/src/osmo-bsc/bts_nokia_site.c @@ -0,0 +1,1744 @@ +/* Nokia XXXsite family specific code */ + +/* (C) 2011 by Dieter Spaar + * + * 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 . + * + */ + +/* + TODO: Attention: There are some static variables used for states during + configuration. Those variables have to be moved to a BTS specific context, + otherwise there will most certainly be problems if more than one Nokia BTS + is used. +*/ + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +/* TODO: put in a separate file ? */ + +extern int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg); +/* was static in system_information.c */ +extern int generate_cell_chan_list(uint8_t * chan_list, struct gsm_bts *bts); + +static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts); +static void reset_timer_cb(void *_bts); +static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref); +static int dump_elements(uint8_t * data, int len) __attribute__((unused)); + +static void bootstrap_om_bts(struct gsm_bts *bts) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); + + gsm_bts_mark_all_ts_uninitialized(bts); + + if (!bts->nokia.skip_reset) { + if (!bts->nokia.did_reset) + abis_nm_reset(bts, 1); + } else + bts->nokia.did_reset = 1; +} + +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); + + gsm_trx_mark_all_ts_uninitialized(trx); +} + +static int shutdown_om(struct gsm_bts *bts) +{ + /* TODO !? */ + return 0; +} + +#define SAPI_OML 62 +#define SAPI_RSL 0 + +/* + + Tell LAPD to start start the SAP (send SABM requests) for all signalling + timeslots in this line + + Attention: this has to be adapted for mISDN +*/ + +static void start_sabm_in_line(struct e1inp_line *line, int start, int sapi) +{ + struct e1inp_sign_link *link; + int i; + + for (i = 0; i < ARRAY_SIZE(line->ts); i++) { + struct e1inp_ts *ts = &line->ts[i]; + + if (ts->type != E1INP_TS_TYPE_SIGN) + continue; + + llist_for_each_entry(link, &ts->sign.sign_links, list) { + if (sapi != -1 && link->sapi != sapi) + continue; + +#if 0 /* debugging */ + printf("sap start/stop (%d): %d tei=%d sapi=%d\n", + start, i + 1, link->tei, link->sapi); +#endif + + if (start) { + ts->lapd->profile.t200_sec = 1; + ts->lapd->profile.t200_usec = 0; + lapd_sap_start(ts->lapd, link->tei, + link->sapi); + } else + lapd_sap_stop(ts->lapd, link->tei, + link->sapi); + } + } +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int gbl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts *bts; + + if (subsys != SS_L_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_BTS_CLOSE_OM: + bts = signal_data; + if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) + shutdown_om(signal_data); + break; + } + + return 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) +{ + struct input_signal_data *isd = signal_data; + + if (subsys != SS_L_INPUT) + return 0; + + switch (signal) { + case S_L_INP_LINE_INIT: + start_sabm_in_line(isd->line, 1, SAPI_OML); /* start only OML */ + break; + case S_L_INP_TEI_DN: + break; + case S_L_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + if (isd->trx->bts->type != GSM_BTS_TYPE_NOKIA_SITE) + break; + + if (isd->tei == isd->trx->bts->oml_tei) + bootstrap_om_bts(isd->trx->bts); + else + bootstrap_om_trx(isd->trx); + break; + } + break; + case S_L_INP_TEI_UNKNOWN: + /* We are receiving LAPD frames with one TEI that we do not + * seem to know, likely that we (the BSC) stopped working + * and lost our local states. However, the BTS is already + * configured, we try to take over the RSL links. */ + start_sabm_in_line(isd->line, 1, SAPI_RSL); + break; + } + + return 0; +} + +static void nm_statechg_evt(unsigned int signal, + struct nm_statechg_signal_data *nsd) +{ + if (nsd->bts->type != GSM_BTS_TYPE_NOKIA_SITE) + return; +} + +static int nm_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + if (subsys != SS_NM) + return 0; + + switch (signal) { + case S_NM_STATECHG_OPER: + case S_NM_STATECHG_ADM: + nm_statechg_evt(signal, signal_data); + break; + default: + break; + } + + return 0; +} + +/* TODO: put in a separate file ? */ + +static const struct value_string nokia_msgt_name[] = { + { 0x80, "NOKIA_BTS_CONF_DATA" }, + { 0x81, "NOKIA_BTS_ACK" }, + { 0x82, "NOKIA_BTS_OMU_STARTED" }, + { 0x83, "NOKIA_BTS_START_DOWNLOAD_REQ" }, + { 0x84, "NOKIA_BTS_MF_REQ" }, + { 0x85, "NOKIA_BTS_AF_REQ" }, + { 0x86, "NOKIA_BTS_RESET_REQ" }, + { 0x87, "NOKIA_reserved" }, + { 0x88, "NOKIA_BTS_CONF_REQ" }, + { 0x89, "NOKIA_BTS_TEST_REQ" }, + { 0x8A, "NOKIA_BTS_TEST_REPORT" }, + { 0x8B, "NOKIA_reserved" }, + { 0x8C, "NOKIA_reserved" }, + { 0x8D, "NOKIA_reserved" }, + { 0x8E, "NOKIA_BTS_CONF_COMPL" }, + { 0x8F, "NOKIA_reserved" }, + { 0x90, "NOKIA_BTS_STM_TEST_REQ" }, + { 0x91, "NOKIA_BTS_STM_TEST_REPORT" }, + { 0x92, "NOKIA_BTS_TRANSMISSION_COMMAND" }, + { 0x93, "NOKIA_BTS_TRANSMISSION_ANSWER" }, + { 0x94, "NOKIA_BTS_HW_DB_UPLOAD_REQ" }, + { 0x95, "NOKIA_BTS_START_HW_DB_DOWNLOAD_REQ" }, + { 0x96, "NOKIA_BTS_HW_DB_SAVE_REQ" }, + { 0x97, "NOKIA_BTS_FLASH_ERASURE_REQ" }, + { 0x98, "NOKIA_BTS_HW_DB_DOWNLOAD_REQ" }, + { 0x99, "NOKIA_BTS_PWR_SUPPLY_CONTROL" }, + { 0x9A, "NOKIA_BTS_ATTRIBUTE_REQ" }, + { 0x9B, "NOKIA_BTS_ATTRIBUTE_REPORT" }, + { 0x9C, "NOKIA_BTS_HW_REQ" }, + { 0x9D, "NOKIA_BTS_HW_REPORT" }, + { 0x9E, "NOKIA_BTS_RTE_TEST_REQ" }, + { 0x9F, "NOKIA_BTS_RTE_TEST_REPORT" }, + { 0xA0, "NOKIA_BTS_HW_DB_VERIFICATION_REQ" }, + { 0xA1, "NOKIA_BTS_CLOCK_REQ" }, + { 0xA2, "NOKIA_AC_CIRCUIT_REQ_NACK" }, + { 0xA3, "NOKIA_AC_INTERRUPTED" }, + { 0xA4, "NOKIA_BTS_NEW_TRE_INFO" }, + { 0xA5, "NOKIA_AC_BSC_CIRCUITS_ALLOCATED" }, + { 0xA6, "NOKIA_BTS_TRE_POLL_LIST" }, + { 0xA7, "NOKIA_AC_CIRCUIT_REQ" }, + { 0xA8, "NOKIA_BTS_BLOCK_CTRL_REQ" }, + { 0xA9, "NOKIA_BTS_GSM_TIME_REQ" }, + { 0xAA, "NOKIA_BTS_GSM_TIME" }, + { 0xAB, "NOKIA_BTS_OUTPUT_CONTROL" }, + { 0xAC, "NOKIA_BTS_STATE_CHANGED" }, + { 0xAD, "NOKIA_BTS_SW_SAVE_REQ" }, + { 0xAE, "NOKIA_BTS_ALARM" }, + { 0xAF, "NOKIA_BTS_CHA_ADM_STATE" }, + { 0xB0, "NOKIA_AC_POOL_SIZE_REPORT" }, + { 0xB1, "NOKIA_AC_POOL_SIZE_INQUIRY" }, + { 0xB2, "NOKIA_BTS_COMMISS_TEST_COMPLETED" }, + { 0xB3, "NOKIA_BTS_COMMISS_TEST_REQ" }, + { 0xB4, "NOKIA_BTS_TRANSP_BTS_TO_BSC" }, + { 0xB5, "NOKIA_BTS_TRANSP_BSC_TO_BTS" }, + { 0xB6, "NOKIA_BTS_LCS_COMMAND" }, + { 0xB7, "NOKIA_BTS_LCS_ANSWER" }, + { 0xB8, "NOKIA_BTS_LMU_FN_OFFSET_COMMAND" }, + { 0xB9, "NOKIA_BTS_LMU_FN_OFFSET_ANSWER" }, + { 0, NULL } +}; + +static const char *get_msg_type_name_string(uint8_t msg_type) +{ + return get_value_string(nokia_msgt_name, msg_type); +} + +static const struct value_string nokia_element_name[] = { + { 0x01, "Ny1" }, + { 0x02, "T3105_F" }, + { 0x03, "Interference band limits" }, + { 0x04, "Interference report timer in secs" }, + { 0x05, "Channel configuration per TS" }, + { 0x06, "BSIC" }, + { 0x07, "RACH report timer in secs" }, + { 0x08, "Hardware database status" }, + { 0x09, "BTS RX level" }, + { 0x0A, "ARFN" }, + { 0x0B, "STM antenna attenuation" }, + { 0x0C, "Cell allocation bitmap" }, + { 0x0D, "Radio definition per TS" }, + { 0x0E, "Frame number" }, + { 0x0F, "Antenna diversity" }, + { 0x10, "T3105_D" }, + { 0x11, "File format" }, + { 0x12, "Last File" }, + { 0x13, "BTS type" }, + { 0x14, "Erasure mode" }, + { 0x15, "Hopping mode" }, + { 0x16, "Floating TRX" }, + { 0x17, "Power supplies" }, + { 0x18, "Reset type" }, + { 0x19, "Averaging period" }, + { 0x1A, "RBER2" }, + { 0x1B, "LAC" }, + { 0x1C, "CI" }, + { 0x1D, "Failure parameters" }, + { 0x1E, "(RF max power reduction)" }, + { 0x1F, "Measured RX_SENS" }, + { 0x20, "Extended cell radius" }, + { 0x21, "reserved" }, + { 0x22, "Success-Failure" }, + { 0x23, "Ack-Nack" }, + { 0x24, "OMU test results" }, + { 0x25, "File identity" }, + { 0x26, "Generation and version code" }, + { 0x27, "SW description" }, + { 0x28, "BCCH LEV" }, + { 0x29, "Test type" }, + { 0x2A, "Subscriber number" }, + { 0x2B, "reserved" }, + { 0x2C, "HSN" }, + { 0x2D, "reserved" }, + { 0x2E, "MS RXLEV" }, + { 0x2F, "MS TXLEV" }, + { 0x30, "RXQUAL" }, + { 0x31, "RX SENS" }, + { 0x32, "Alarm block" }, + { 0x33, "Neighbouring BCCH levels" }, + { 0x34, "STM report type" }, + { 0x35, "MA" }, + { 0x36, "MAIO" }, + { 0x37, "H_FLAG" }, + { 0x38, "TCH_ARFN" }, + { 0x39, "Clock output" }, + { 0x3A, "Transmitted power" }, + { 0x3B, "Clock sync" }, + { 0x3C, "TMS protocol discriminator" }, + { 0x3D, "TMS protocol data" }, + { 0x3E, "FER" }, + { 0x3F, "SWR result" }, + { 0x40, "Object identity" }, + { 0x41, "STM RX Antenna Test" }, + { 0x42, "reserved" }, + { 0x43, "reserved" }, + { 0x44, "Object current state" }, + { 0x45, "reserved" }, + { 0x46, "FU channel configuration" }, + { 0x47, "reserved" }, + { 0x48, "ARFN of a CU" }, + { 0x49, "FU radio definition" }, + { 0x4A, "reserved" }, + { 0x4B, "Severity" }, + { 0x4C, "Diversity selection" }, + { 0x4D, "RX antenna test" }, + { 0x4E, "RX antenna supervision period" }, + { 0x4F, "RX antenna state" }, + { 0x50, "Sector configuration" }, + { 0x51, "Additional info" }, + { 0x52, "SWR parameters" }, + { 0x53, "HW inquiry mode" }, + { 0x54, "reserved" }, + { 0x55, "Availability status" }, + { 0x56, "reserved" }, + { 0x57, "EAC inputs" }, + { 0x58, "EAC outputs" }, + { 0x59, "reserved" }, + { 0x5A, "Position" }, + { 0x5B, "HW unit identity" }, + { 0x5C, "RF test signal attenuation" }, + { 0x5D, "Operational state" }, + { 0x5E, "Logical object identity" }, + { 0x5F, "reserved" }, + { 0x60, "BS_TXPWR_OM" }, + { 0x61, "Loop_Duration" }, + { 0x62, "LNA_Path_Selection" }, + { 0x63, "Serial number" }, + { 0x64, "HW version" }, + { 0x65, "Obj. identity and obj. state" }, + { 0x66, "reserved" }, + { 0x67, "EAC input definition" }, + { 0x68, "EAC id and text" }, + { 0x69, "HW unit status" }, + { 0x6A, "SW release version" }, + { 0x6B, "FW version" }, + { 0x6C, "Bit_Error_Ratio" }, + { 0x6D, "RXLEV_with_Attenuation" }, + { 0x6E, "RXLEV_without_Attenuation" }, + { 0x6F, "reserved" }, + { 0x70, "CU_Results" }, + { 0x71, "reserved" }, + { 0x72, "LNA_Path_Results" }, + { 0x73, "RTE Results" }, + { 0x74, "Real Time" }, + { 0x75, "RX diversity selection" }, + { 0x76, "EAC input config" }, + { 0x77, "Feature support" }, + { 0x78, "File version" }, + { 0x79, "Outputs" }, + { 0x7A, "FU parameters" }, + { 0x7B, "Diagnostic info" }, + { 0x7C, "FU BSIC" }, + { 0x7D, "TRX Configuration" }, + { 0x7E, "Download status" }, + { 0x7F, "RX difference limit" }, + { 0x80, "TRX HW capability" }, + { 0x81, "Common HW config" }, + { 0x82, "Autoconfiguration pool size" }, + { 0x83, "TRE diagnostic info" }, + { 0x84, "TRE object identity" }, + { 0x85, "New TRE Info" }, + { 0x86, "Acknowledgement period" }, + { 0x87, "Synchronization mode" }, + { 0x88, "reserved" }, + { 0x89, "Block Control Data" }, + { 0x8A, "SW load mode" }, + { 0x8B, "Recommended recovery action" }, + { 0x8C, "BSC BCF id" }, + { 0x8D, "Q1 baud rate" }, + { 0x8E, "Allocation status" }, + { 0x8F, "Functional entity number" }, + { 0x90, "Transmission delay" }, + { 0x91, "Loop Duration ms" }, + { 0x92, "Logical channel" }, + { 0x93, "Q1 address" }, + { 0x94, "Alarm detail" }, + { 0x95, "Cabinet type" }, + { 0x96, "HW unit existence" }, + { 0x97, "RF power parameters" }, + { 0x98, "Message scenario" }, + { 0x99, "HW unit max amount" }, + { 0x9A, "Master TRX" }, + { 0x9B, "Transparent data" }, + { 0x9C, "BSC topology info" }, + { 0x9D, "Air i/f modulation" }, + { 0x9E, "LCS Q1 command data" }, + { 0x9F, "Frame number offset" }, + { 0xA0, "Abis TSL" }, + { 0xA1, "Dynamic pool info" }, + { 0xA2, "LCS LLP data" }, + { 0xA3, "LCS Q1 answer data" }, + { 0xA4, "DFCA FU Radio Definition" }, + { 0xA5, "Antenna hopping" }, + { 0xA6, "Field record sequence number" }, + { 0xA7, "Timeslot offslot" }, + { 0xA8, "EPCR capability" }, + { 0xA9, "Connectsite optional element" }, + { 0xAA, "TSC" }, + { 0xAB, "Special TX Power Setting" }, + { 0xAC, "Optional sync settings" }, + { 0xFA, "Abis If parameters" }, + { 0, NULL } +}; + +static const char *get_element_name_string(uint16_t element) +{ + return get_value_string(nokia_element_name, element); +} + +static const struct value_string nokia_bts_types[] = { + { 0x0a, "MetroSite GSM 900" }, + { 0x0b, "MetroSite GSM 1800" }, + { 0x0c, "MetroSite GSM 1900 (PCS)" }, + { 0x0d, "MetroSite GSM 900 & 1800" }, + { 0x0e, "InSite GSM 900" }, + { 0x0f, "InSite GSM 1800" }, + { 0x10, "InSite GSM 1900" }, + { 0x11, "UltraSite GSM 900" }, + { 0x12, "UltraSite GSM 1800" }, + { 0x13, "UltraSite GSM/US-TDMA 1900" }, + { 0x14, "UltraSite GSM 900 & 1800" }, + { 0x16, "UltraSite GSM/US-TDMA 850" }, + { 0x18, "MetroSite GSM/US-TDMA 850" }, + { 0x19, "UltraSite GSM 800/1900" }, + { 0, NULL } +}; + +static const char *get_bts_type_string(uint8_t type) +{ + return get_value_string(nokia_bts_types, type); +} + +static const struct value_string nokia_severity[] = { + { 0, "indeterminate" }, + { 1, "critical" }, + { 2, "major" }, + { 3, "minor" }, + { 4, "warning" }, + { 0, NULL } +}; + +static const char *get_severity_string(uint8_t severity) +{ + return get_value_string(nokia_severity, severity); +} + +/* TODO: put in a separate file ? */ + +/* some message IDs */ + +#define NOKIA_MSG_CONF_DATA 128 +#define NOKIA_MSG_ACK 129 +#define NOKIA_MSG_OMU_STARTED 130 +#define NOKIA_MSG_START_DOWNLOAD_REQ 131 +#define NOKIA_MSG_MF_REQ 132 +#define NOKIA_MSG_RESET_REQ 134 +#define NOKIA_MSG_CONF_REQ 136 +#define NOKIA_MSG_CONF_COMPLETE 142 +#define NOKIA_MSG_BLOCK_CTRL_REQ 168 +#define NOKIA_MSG_STATE_CHANGED 172 +#define NOKIA_MSG_ALARM 174 + +/* some element IDs */ + +#define NOKIA_EI_BTS_TYPE 0x13 +#define NOKIA_EI_ACK 0x23 +#define NOKIA_EI_ADD_INFO 0x51 +#define NOKIA_EI_SEVERITY 0x4B +#define NOKIA_EI_ALARM_DETAIL 0x94 + +#define OM_ALLOC_SIZE 1024 +#define OM_HEADROOM_SIZE 128 + +static uint8_t fu_config_template[] = { + 0x7F, 0x7A, 0x39, + /* ID = 0x7A (FU parameters) ## constructed ## */ + /* length = 57 */ + /* [3] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [6] */ + 0x00, 0x07, 0x01, 0xFF, + + 0x41, 0x02, + /* ID = 0x01 (Ny1) */ + /* length = 2 */ + /* [12] */ + 0x00, 0x05, + + 0x42, 0x02, + /* ID = 0x02 (T3105_F) */ + /* length = 2 */ + /* [16] */ + 0x00, 0x28, /* FIXME: use net->T3105 */ + + 0x50, 0x02, + /* ID = 0x10 (T3105_D) */ + /* length = 2 */ + /* [20] */ + 0x00, 0x28, /* FIXME: use net->T3105 */ + + 0x43, 0x05, + /* ID = 0x03 (Interference band limits) */ + /* length = 5 */ + /* [24] */ + 0x0F, 0x1B, 0x27, 0x33, 0x3F, + + 0x44, 0x02, + /* ID = 0x04 (Interference report timer in secs) */ + /* length = 2 */ + /* [31] */ + 0x00, 0x10, + + 0x47, 0x01, + /* ID = 0x07 (RACH report timer in secs) */ + /* length = 1 */ + /* [35] */ + 0x1E, + + 0x4C, 0x10, + /* ID = 0x0C (Cell allocation bitmap) ####### */ + /* length = 16 */ + /* [38] */ + 0x8F, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x59, 0x01, + /* ID = 0x19 (Averaging period) */ + /* length = 1 */ + /* [56] */ + 0x01, + + 0x5E, 0x01, + /* ID = 0x1E ((RF max power reduction)) */ + /* length = 1 */ + /* [59] */ + 0x00, + + 0x7F, 0x46, 0x11, + /* ID = 0x46 (FU channel configuration) ## constructed ## */ + /* length = 17 */ + /* [63] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [66] */ + 0x00, 0x07, 0x01, 0xFF, + + 0x45, 0x08, + /* ID = 0x05 (Channel configuration per TS) */ + /* length = 8 */ + /* [72] */ + 0x01, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + + 0x7F, 0x65, 0x0B, + /* ID = 0x65 (Obj. identity and obj. state) ## constructed ## */ + /* length = 11 */ + /* [83] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [86] */ + 0x00, 0x04, 0x01, 0xFF, + + 0x5F, 0x44, 0x01, + /* ID = 0x44 (Object current state) */ + /* length = 1 */ + /* [93] */ + 0x03, + + 0x7F, 0x7C, 0x0A, + /* ID = 0x7C (FU BSIC) ## constructed ## */ + /* length = 10 */ + /* [97] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [100] */ + 0x00, 0x07, 0x01, 0xFF, + + 0x46, 0x01, + /* ID = 0x06 (BSIC) */ + /* length = 1 */ + /* [106] */ + 0x00, + + 0x7F, 0x48, 0x0B, + /* ID = 0x48 (ARFN of a CU) ## constructed ## */ + /* length = 11 */ + /* [110] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [113] */ + 0x00, 0x08, 0x01, 0xFF, + + 0x4A, 0x02, + /* ID = 0x0A (ARFN) ####### */ + /* length = 2 */ + /* [119] */ + 0x03, 0x62, + + 0x7F, 0x49, 0x59, + /* ID = 0x49 (FU radio definition) ## constructed ## */ + /* length = 89 */ + /* [124] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [127] */ + 0x00, 0x07, 0x01, 0xFF, + + 0x4D, 0x50, + /* ID = 0x0D (Radio definition per TS) ####### */ + /* length = 80 */ + /* [133] */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* MA */ + 0x03, 0x62, /* HSN, MAIO or ARFCN if no hopping */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, +}; + +/* TODO: put in a separate file ? */ + +/* + build the configuration for each TRX +*/ + +static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id, + uint8_t * fu_config, int *hopping) +{ + int i; + + *hopping = 0; + + memcpy(fu_config, fu_config_template, sizeof(fu_config_template)); + + /* set ID */ + + fu_config[6 + 2] = id; + fu_config[66 + 2] = id; + fu_config[86 + 2] = id; + fu_config[100 + 2] = id; + fu_config[113 + 2] = id; + fu_config[127 + 2] = id; + + /* set ARFCN */ + + uint16_t arfcn = trx->arfcn; + + fu_config[119] = arfcn >> 8; + fu_config[119 + 1] = arfcn & 0xFF; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + + if (ts->hopping.enabled) { + /* reverse order */ + int j; + for (j = 0; j < ts->hopping.ma_len; j++) + fu_config[133 + (i * 10) + (7 - j)] = + ts->hopping.ma_data[j]; + fu_config[133 + 8 + (i * 10)] = ts->hopping.hsn; + fu_config[133 + 8 + 1 + (i * 10)] = ts->hopping.maio; + *hopping = 1; + } else { + fu_config[133 + 8 + (i * 10)] = arfcn >> 8; + fu_config[133 + 8 + 1 + (i * 10)] = arfcn & 0xFF; + } + } + + /* set BSIC */ + + /* + Attention: all TRX except the first one seem to get the TSC + from the CHANNEL ACTIVATION command (in CHANNEL IDENTIFICATION, + GSM 04.08 CHANNEL DESCRIPTION). + There was a bug in rsl_chan_activate_lchan() setting this parameter. + */ + + uint8_t bsic = trx->bts->bsic; + + fu_config[106] = bsic; + + /* set CA */ + + if (generate_cell_chan_list(&fu_config[38], trx->bts) != 0) { + fprintf(stderr, "generate_cell_chan_list failed\n"); + return 0; + } + + /* set channel configuration */ + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + uint8_t chan_config; + + /* + 0 = FCCH + SCH + BCCH + CCCH + 1 = FCCH + SCH + BCCH + CCCH + SDCCH/4 + SACCH/4 + 2 = BCCH + CCCH (This combination is not used in any BTS) + 3 = FCCH + SCH + BCCH + CCCH + SDCCH/4 with SDCCH2 used as CBCH + 4 = SDCCH/8 + SACCH/8 + 5 = SDCCH/8 with SDCCH2 used as CBCH + 6 = TCH/F + FACCH/F + SACCH/F + 7 = E-RACH (Talk family) + 9 = Dual rate (capability for TCH/F and TCH/H) + 10 = reserved for BTS internal use + 11 = PBCCH + PCCCH + PDTCH + PACCH + PTCCH (can be used in GPRS release 2). + 0xFF = spare TS + */ + + if (ts->pchan == GSM_PCHAN_NONE) + chan_config = 0xFF; + else if (ts->pchan == GSM_PCHAN_CCCH) + chan_config = 0; + else if (ts->pchan == GSM_PCHAN_CCCH_SDCCH4) + chan_config = 1; + else if (ts->pchan == GSM_PCHAN_TCH_F) + chan_config = 6; /* 9 should work too */ + else if (ts->pchan == GSM_PCHAN_TCH_H) + chan_config = 9; + else if (ts->pchan == GSM_PCHAN_SDCCH8_SACCH8C) + chan_config = 4; + else if (ts->pchan == GSM_PCHAN_PDCH) + chan_config = 11; + else { + fprintf(stderr, + "unsupported channel config %d for timeslot %d\n", + ts->pchan, i); + return 0; + } + + fu_config[72 + i] = chan_config; + } + return sizeof(fu_config_template); +} + +/* TODO: put in a separate file ? */ + +static uint8_t bts_config_1[] = { + 0x4E, 0x02, + /* ID = 0x0E (Frame number) */ + /* length = 2 */ + /* [2] */ + 0xFF, 0xFF, + + 0x5F, 0x4E, 0x02, + /* ID = 0x4E (RX antenna supervision period) */ + /* length = 2 */ + /* [7] */ + 0xFF, 0xFF, + + 0x5F, 0x50, 0x02, + /* ID = 0x50 (Sector configuration) */ + /* length = 2 */ + /* [12] */ + 0x01, 0x01, +}; + +static uint8_t bts_config_2[] = { + 0x55, 0x02, + /* ID = 0x15 (Hopping mode) */ + /* length = 2 */ + /* [2] */ + 0x01, 0x00, + + 0x5F, 0x75, 0x02, + /* ID = 0x75 (RX diversity selection) */ + /* length = 2 */ + /* [7] */ + 0x01, 0x01, +}; + +static uint8_t bts_config_3[] = { + 0x5F, 0x20, 0x02, + /* ID = 0x20 (Extended cell radius) */ + /* length = 2 */ + /* [3] */ + 0x01, 0x00, +}; + +static uint8_t bts_config_4[] = { + 0x5F, 0x74, 0x09, + /* ID = 0x74 (Real Time) */ + /* length = 9 */ + /* [3] year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */ + 0x07, 0xDB, 0x06, 0x02, 0x0B, 0x20, 0x0C, 0x00, + 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [15] */ + 0x01, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [21] */ + 0x02, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [27] */ + 0x03, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [33] */ + 0x04, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [39] */ + 0x05, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [45] */ + 0x06, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [51] */ + 0x07, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [57] */ + 0x08, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [63] */ + 0x09, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [69] */ + 0x0A, 0x01, 0x00, +}; + +static uint8_t bts_config_insite[] = { + 0x4E, 0x02, + /* ID = 0x0E (Frame number) */ + /* length = 2 */ + /* [2] */ + 0xFF, 0xFF, + + 0x5F, 0x4E, 0x02, + /* ID = 0x4E (RX antenna supervision period) */ + /* length = 2 */ + /* [7] */ + 0xFF, 0xFF, + + 0x5F, 0x50, 0x02, + /* ID = 0x50 (Sector configuration) */ + /* length = 2 */ + /* [12] */ + 0x01, 0x01, + + 0x55, 0x02, + /* ID = 0x15 (Hopping mode) */ + /* length = 2 */ + /* [16] */ + 0x01, 0x00, + + 0x5F, 0x20, 0x02, + /* ID = 0x20 (Extended cell radius) */ + /* length = 2 */ + /* [21] */ + 0x01, 0x00, + + 0x5F, 0x74, 0x09, + /* ID = 0x74 (Real Time) */ + /* length = 9 */ + /* [26] */ + 0x07, 0xDB, 0x07, 0x0A, 0x0F, 0x09, 0x0B, 0x00, + 0x00, +}; + +void set_real_time(uint8_t * real_time) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + + /* year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */ + + real_time[0] = (1900 + tm->tm_year) >> 8; + real_time[1] = (1900 + tm->tm_year) & 0xFF; + real_time[2] = tm->tm_mon + 1; + real_time[3] = tm->tm_mday; + real_time[4] = tm->tm_hour; + real_time[5] = tm->tm_min; + real_time[6] = tm->tm_sec; + real_time[7] = 0; + real_time[8] = 0; +} + +/* TODO: put in a separate file ? */ + +/* + build the configuration data +*/ + +static int make_bts_config(uint8_t bts_type, int n_trx, uint8_t * fu_config, + int need_hopping) +{ + /* is it an InSite BTS ? */ + if (bts_type == 0x0E || bts_type == 0x0F || bts_type == 0x10) { /* TODO */ + if (n_trx != 1) { + fprintf(stderr, "InSite has only one TRX\n"); + return 0; + } + if (need_hopping != 0) { + fprintf(stderr, "InSite does not support hopping\n"); + return 0; + } + memcpy(fu_config, bts_config_insite, sizeof(bts_config_insite)); + set_real_time(&fu_config[26]); + return sizeof(bts_config_insite); + } + + int len = 0; + int i; + + memcpy(fu_config + len, bts_config_1, sizeof(bts_config_1)); + + /* set sector configuration */ + fu_config[len + 12 - 1] = 1 + n_trx; /* len */ + for (i = 0; i < n_trx; i++) + fu_config[len + 12 + 1 + i] = ((i + 1) & 0xFF); + + len += (sizeof(bts_config_1) + (n_trx - 1)); + + memcpy(fu_config + len, bts_config_2, sizeof(bts_config_2)); + /* set hopping mode (Baseband and RF hopping work for the MetroSite) */ + if (need_hopping) + fu_config[len + 2 + 1] = 1; /* 0: no hopping, 1: Baseband hopping, 2: RF hopping */ + len += sizeof(bts_config_2); + + /* set extended cell radius for each TRX */ + for (i = 0; i < n_trx; i++) { + memcpy(fu_config + len, bts_config_3, sizeof(bts_config_3)); + fu_config[len + 3] = ((i + 1) & 0xFF); + len += sizeof(bts_config_3); + } + + memcpy(fu_config + len, bts_config_4, sizeof(bts_config_4)); + set_real_time(&fu_config[len + 3]); + len += sizeof(bts_config_4); + + return len; +} + +/* TODO: put in a separate file ? */ + +static struct msgb *nm_msgb_alloc(void) +{ + return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, "OML"); +} + +/* TODO: put in a separate file ? */ + +struct abis_om_nokia_hdr { + uint8_t msg_type; + uint8_t spare; + uint16_t reference; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define ABIS_OM_NOKIA_HDR_SIZE (sizeof(struct abis_om_hdr) + sizeof(struct abis_om_nokia_hdr)) + +static int abis_nm_send(struct gsm_bts *bts, uint8_t msg_type, uint16_t ref, + uint8_t * data, int len_data) +{ + struct abis_om_hdr *oh; + struct abis_om_nokia_hdr *noh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *)msgb_put(msg, + ABIS_OM_NOKIA_HDR_SIZE + len_data); + + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_ONLY; + oh->sequence = 0; + oh->length = sizeof(struct abis_om_nokia_hdr) + len_data; + + noh = (struct abis_om_nokia_hdr *)oh->data; + + noh->msg_type = msg_type; + noh->spare = 0; + noh->reference = htons(ref); + memcpy(noh->data, data, len_data); + + DEBUGPC(DNM, "Sending %s\n", get_msg_type_name_string(msg_type)); + + return abis_nm_sendmsg(bts, msg); +} + +/* TODO: put in a separate file ? */ + +static uint8_t download_req[] = { + 0x5F, 0x25, 0x0B, + /* ID = 0x25 (File identity) */ + /* length = 11 */ + /* [3] */ + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, + 0x2A, 0x2A, 0x2A, + + 0x5F, 0x78, 0x03, + /* ID = 0x78 (File version) */ + /* length = 3 */ + /* [17] */ + 0x2A, 0x2A, 0x2A, + + 0x5F, 0x81, 0x0A, 0x01, + /* ID = 0x8A (SW load mode) */ + /* length = 1 */ + /* [24] */ + 0x01, + + 0x5F, 0x81, 0x06, 0x01, + /* ID = 0x86 (Acknowledgement period) */ + /* length = 1 */ + /* [29] */ + 0x01, +}; + +static int abis_nm_download_req(struct gsm_bts *bts, uint16_t ref) +{ + uint8_t *data = download_req; + int len_data = sizeof(download_req); + + return abis_nm_send(bts, NOKIA_MSG_START_DOWNLOAD_REQ, ref, data, + len_data); +} + +/* TODO: put in a separate file ? */ + +static uint8_t ack[] = { + 0x5F, 0x23, 0x01, + /* ID = 0x23 (Ack-Nack) */ + /* length = 1 */ + /* [3] */ + 0x01, +}; + +static int abis_nm_ack(struct gsm_bts *bts, uint16_t ref) +{ + uint8_t *data = ack; + int len_data = sizeof(ack); + + return abis_nm_send(bts, NOKIA_MSG_ACK, ref, data, len_data); +} + +/* TODO: put in a separate file ? */ + +static uint8_t reset[] = { + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [3] */ + 0x00, 0x01, 0xFF, 0xFF, +}; + +static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref) +{ + uint8_t *data = reset; + int len_data = sizeof(reset); + LOGP(DLINP, LOGL_INFO, "Nokia BTS reset timer: %d\n", bts->nokia.bts_reset_timer_cnf); + return abis_nm_send(bts, NOKIA_MSG_RESET_REQ, ref, data, len_data); +} + +/* TODO: put in a separate file ? */ + +static int abis_nm_send_multi_segments(struct gsm_bts *bts, uint8_t msg_type, + uint16_t ref, uint8_t * data, int len) +{ + int len_remain, len_to_send, max_send; + int seq = 0; + int ret; + + len_remain = len; + + while (len_remain) { + struct abis_om_hdr *oh; + struct abis_om_nokia_hdr *noh; + struct msgb *msg = nm_msgb_alloc(); + + if (seq == 0) + max_send = 256 - sizeof(struct abis_om_nokia_hdr); + else + max_send = 256; + + if (len_remain > max_send) { + len_to_send = max_send; + + if (seq == 0) { + /* first segment */ + oh = (struct abis_om_hdr *)msgb_put(msg, + ABIS_OM_NOKIA_HDR_SIZE + + + len_to_send); + + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_FIRST; /* first segment of multi-segment message */ + oh->sequence = seq; + oh->length = 0; /* 256 bytes */ + + noh = (struct abis_om_nokia_hdr *)oh->data; + + noh->msg_type = msg_type; + noh->spare = 0; + noh->reference = htons(ref); + memcpy(noh->data, data, len_to_send); + } else { + /* segment in between */ + oh = (struct abis_om_hdr *)msgb_put(msg, + sizeof + (struct + abis_om_hdr) + + + len_to_send); + + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_MIDDLE; /* segment of multi-segment message */ + oh->sequence = seq; + oh->length = 0; /* 256 bytes */ + + memcpy(oh->data, data, len_to_send); + } + } else { + + len_to_send = len_remain; + + /* check if message fits in a single segment */ + + if (seq == 0) + return abis_nm_send(bts, msg_type, ref, data, + len_to_send); + + /* last segment */ + + oh = (struct abis_om_hdr *)msgb_put(msg, + sizeof(struct + abis_om_hdr) + + len_to_send); + + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_LAST; /* last segment of multi-segment message */ + oh->sequence = seq; + oh->length = len_to_send; + + memcpy(oh->data, data, len_to_send); + } + + DEBUGPC(DNM, "Sending multi-segment %d\n", seq); + + ret = abis_nm_sendmsg(bts, msg); + if (ret < 0) + return ret; + + nokia_abis_nm_queue_send_next(bts); + + /* next segment */ + len_remain -= len_to_send; + data += len_to_send; + seq++; + } + + return 0; +} + +/* TODO: put in a separate file ? */ + +static int abis_nm_send_config(struct gsm_bts *bts, uint8_t bts_type) +{ + struct gsm_bts_trx *trx; + uint8_t config[2048]; /* TODO: might be too small if lots of TRX are used */ + int len = 0; + int idx = 0; + int ret; + int hopping = 0; + int need_hopping = 0; + + memset(config, 0, sizeof(config)); + + llist_for_each_entry(trx, &bts->trx_list, list) { +#if 0 /* debugging */ + printf("TRX\n"); + printf(" arfcn: %d\n", trx->arfcn); + printf(" bsic: %d\n", trx->bts->bsic); + uint8_t ca[20]; + memset(ca, 0xFF, sizeof(ca)); + ret = generate_cell_chan_list(ca, trx->bts); + printf(" ca (%d): %s\n", ret, osmo_hexdump(ca, sizeof(ca))); + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + + printf(" pchan %d: %d\n", i, ts->pchan); + } +#endif + ret = make_fu_config(trx, idx + 1, config + len, &hopping); + need_hopping |= hopping; + len += ret; + + idx++; + } + + ret = make_bts_config(bts_type, idx, config + len, need_hopping); + len += ret; + +#if 0 /* debugging */ + dump_elements(config, len); +#endif + + return abis_nm_send_multi_segments(bts, NOKIA_MSG_CONF_DATA, 1, config, + len); +} + +#define GET_NEXT_BYTE if(idx >= len) return 0; \ + ub = data[idx++]; + +static int find_element(uint8_t * data, int len, uint16_t id, uint8_t * value, + int max_value) +{ + uint8_t ub; + int idx = 0; + int found = 0; + int constructed __attribute__((unused)); + uint16_t id_value; + + for (;;) { + + GET_NEXT_BYTE; + + /* encoding bit, construced means that other elements are contained */ + constructed = ((ub & 0x20) ? 1 : 0); + + if ((ub & 0x1F) == 0x1F) { + /* fixed pattern, ID follows */ + GET_NEXT_BYTE; /* ID */ + id_value = ub & 0x7F; + if (ub & 0x80) { + /* extension bit */ + GET_NEXT_BYTE; /* ID low part */ + id_value = (id_value << 7) | (ub & 0x7F); + } + if (id_value == id) + found = 1; + } else { + id_value = (ub & 0x3F); + if (id_value == id) + found = 1; + } + + GET_NEXT_BYTE; /* length */ + + if (found) { + /* get data */ + uint8_t n = ub; + uint8_t i; + for (i = 0; i < n; i++) { + GET_NEXT_BYTE; + if (max_value <= 0) + return -1; /* buffer too small */ + *value = ub; + value++; + max_value--; + } + return n; /* length */ + } else { + /* skip data */ + uint8_t n = ub; + uint8_t i; + for (i = 0; i < n; i++) { + GET_NEXT_BYTE; + } + } + } + return 0; /* not found */ +} + +static int dump_elements(uint8_t * data, int len) +{ + uint8_t ub; + int idx = 0; + int constructed; + uint16_t id_value; + static char indent[100] = ""; /* TODO: move static to BTS context */ + + for (;;) { + + GET_NEXT_BYTE; + + /* encoding bit, construced means that other elements are contained */ + constructed = ((ub & 0x20) ? 1 : 0); + + if ((ub & 0x1F) == 0x1F) { + /* fixed pattern, ID follows */ + GET_NEXT_BYTE; /* ID */ + id_value = ub & 0x7F; + if (ub & 0x80) { + /* extension bit */ + GET_NEXT_BYTE; /* ID low part */ + id_value = (id_value << 7) | (ub & 0x7F); + } + + } else { + id_value = (ub & 0x3F); + } + + GET_NEXT_BYTE; /* length */ + + printf("%s--ID = 0x%02X (%s) %s\n", indent, id_value, + get_element_name_string(id_value), + constructed ? "** constructed **" : ""); + printf("%s length = %d\n", indent, ub); + printf("%s %s\n", indent, osmo_hexdump(data + idx, ub)); + + if (constructed) { + int indent_len = strlen(indent); + strcat(indent, " "); + + dump_elements(data + idx, ub); + + indent[indent_len] = 0; + } + /* skip data */ + uint8_t n = ub; + uint8_t i; + for (i = 0; i < n; i++) { + GET_NEXT_BYTE; + } + } + return 0; +} + +/* TODO: put in a separate file ? */ + +/* taken from abis_nm.c */ + +static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts) +{ + int wait = 0; + struct msgb *msg; + /* the queue is empty */ + while (!llist_empty(&bts->abis_queue)) { + msg = msgb_dequeue(&bts->abis_queue); + wait = OBSC_NM_W_ACK_CB(msg); + abis_sendmsg(msg); + + if (wait) + break; + } + + bts->abis_nm_pend = wait; +} + +/* TODO: put in a separate file ? */ + +/* timer for restarting OML after BTS reset */ + +static void reset_timer_cb(void *_bts) +{ + struct gsm_bts *bts = _bts; + struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; + struct e1inp_line *line; + + bts->nokia.wait_reset = 0; + + /* OML link */ + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to " + "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr); + return; + } + + start_sabm_in_line(line, 0, -1); /* stop all first */ + start_sabm_in_line(line, 1, SAPI_OML); /* start only OML */ +} + +/* TODO: put in a separate file ? */ + +/* + This is how the configuration is done: + - start OML link + - reset BTS + - receive ACK, wait some time and restart OML link + - receive OMU STARTED message, send START DOWNLOAD REQ + - receive CNF REQ message, send CONF DATA + - receive ACK, start RSL link(s) + ACK some other messages received from the BTS. + + Probably its also possible to configure the BTS without a reset, this + has not been tested yet. +*/ + +static int abis_nm_rcvmsg_fom(struct msgb *mb) +{ + struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)mb->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_nokia_hdr *noh = msgb_l3(mb); + uint8_t mt = noh->msg_type; + int ret = 0; + uint16_t ref = ntohs(noh->reference); + uint8_t info[256]; + uint8_t ack = 0xFF; + uint8_t severity = 0xFF; + int str_len; + int len_data; + + if (bts->nokia.wait_reset) { + LOGP(DNM, LOGL_INFO, + "Ignore message while waiting for reset\n"); + return ret; + } + + if (oh->length < sizeof(struct abis_om_nokia_hdr)) { + LOGP(DNM, LOGL_ERROR, "Message too short\n"); + return -EINVAL; + } + + len_data = oh->length - sizeof(struct abis_om_nokia_hdr); + LOGP(DNM, LOGL_INFO, "(0x%02X) %s\n", mt, get_msg_type_name_string(mt)); +#if 0 /* debugging */ + dump_elements(noh->data, len_data); +#endif + + switch (mt) { + case NOKIA_MSG_OMU_STARTED: + if (find_element(noh->data, len_data, + NOKIA_EI_BTS_TYPE, &bts->nokia.bts_type, + sizeof(uint8_t)) == sizeof(uint8_t)) + LOGP(DNM, LOGL_INFO, "BTS type = %d (%s)\n", + bts->nokia.bts_type, + get_bts_type_string(bts->nokia.bts_type)); + else + LOGP(DNM, LOGL_ERROR, "BTS type not found\n"); + /* send START_DOWNLOAD_REQ */ + abis_nm_download_req(bts, ref); + break; + case NOKIA_MSG_MF_REQ: + break; + case NOKIA_MSG_CONF_REQ: + /* send ACK */ + abis_nm_ack(bts, ref); + nokia_abis_nm_queue_send_next(bts); + /* send CONF_DATA */ + abis_nm_send_config(bts, bts->nokia.bts_type); + bts->nokia.configured = 1; + break; + case NOKIA_MSG_ACK: + if (find_element + (noh->data, len_data, NOKIA_EI_ACK, &ack, + sizeof(uint8_t)) == sizeof(uint8_t)) { + LOGP(DNM, LOGL_INFO, "ACK = %d\n", ack); + if (ack != 1) { + LOGP(DNM, LOGL_ERROR, "No ACK received (%d)\n", + ack); + /* TODO: properly handle failures (NACK) */ + } + } else + LOGP(DNM, LOGL_ERROR, "ACK not found\n"); + + /* TODO: the assumption for the following is that no NACK was received */ + + /* ACK for reset message ? */ + if (!bts->nokia.did_reset) { + bts->nokia.did_reset = 1; + + /* + TODO: For the InSite processing the received data is + blocked in the driver during reset. + Otherwise the LAPD module might assert because the InSite + sends garbage on the E1 line during reset. + This is done by looking at "wait_reset" in the driver + (function handle_ts1_read()) and ignoring the received data. + It seems to be necessary for the MetroSite too. + */ + bts->nokia.wait_reset = 1; + + osmo_timer_setup(&bts->nokia.reset_timer, + reset_timer_cb, bts); + osmo_timer_schedule(&bts->nokia.reset_timer, bts->nokia.bts_reset_timer_cnf, 0); + + struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; + struct e1inp_line *line; + /* OML link */ + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, + "BTS %u OML link referring to " + "non-existing E1 line %u\n", bts->nr, + e1_link->e1_nr); + return -ENOMEM; + } + + start_sabm_in_line(line, 0, -1); /* stop all first */ + } + + /* ACK for CONF DATA message ? */ + if (bts->nokia.configured != 0) { + /* start TRX (RSL link) */ + + struct gsm_e1_subslot *e1_link = + &sign_link->trx->rsl_e1_link; + struct e1inp_line *line; + + bts->nokia.configured = 0; + + /* RSL Link */ + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, + "TRX (%u/%u) RSL link referring " + "to non-existing E1 line %u\n", + sign_link->trx->bts->nr, sign_link->trx->nr, + e1_link->e1_nr); + return -ENOMEM; + } + /* start TRX */ + start_sabm_in_line(line, 1, SAPI_RSL); /* start only RSL */ + } + break; + case NOKIA_MSG_STATE_CHANGED: + /* send ACK */ + abis_nm_ack(bts, ref); + break; + case NOKIA_MSG_CONF_COMPLETE: + /* send ACK */ + abis_nm_ack(bts, ref); + break; + case NOKIA_MSG_BLOCK_CTRL_REQ: /* seems to be send when something goes wrong !? */ + /* send ACK (do we have to send an ACK ?) */ + abis_nm_ack(bts, ref); + break; + case NOKIA_MSG_ALARM: + find_element(noh->data, len_data, NOKIA_EI_SEVERITY, &severity, + sizeof(severity)); + /* TODO: there might be alarms with both elements set */ + str_len = + find_element(noh->data, len_data, NOKIA_EI_ADD_INFO, info, + sizeof(info)); + if (str_len > 0) { + info[str_len] = 0; + LOGP(DNM, LOGL_INFO, "ALARM Severity %s (%d) : %s\n", + get_severity_string(severity), severity, info); + } else { /* nothing found, try details */ + str_len = + find_element(noh->data, len_data, + NOKIA_EI_ALARM_DETAIL, info, + sizeof(info)); + if (str_len > 0) { + uint16_t code; + info[str_len] = 0; + code = (info[0] << 8) + info[1]; + LOGP(DNM, LOGL_INFO, + "ALARM Severity %s (%d), code 0x%X : %s\n", + get_severity_string(severity), severity, + code, info + 2); + } + } + /* send ACK */ + abis_nm_ack(bts, ref); + break; + } + + nokia_abis_nm_queue_send_next(bts); + + return ret; +} + +/* TODO: put in a separate file ? */ + +int abis_nokia_rcvmsg(struct msgb *msg) +{ + struct abis_om_hdr *oh = msgb_l2(msg); + int rc = 0; + + /* Various consistency checks */ + if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { + 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); + return -EINVAL; + } + msg->l3h = (unsigned char *)oh + sizeof(*oh); + + switch (oh->mdisc) { + case ABIS_OM_MDISC_FOM: + LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_FOM\n"); + rc = abis_nm_rcvmsg_fom(msg); + break; + case ABIS_OM_MDISC_MANUF: + LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_MANUF\n"); + break; + case ABIS_OM_MDISC_MMI: + case ABIS_OM_MDISC_TRAU: + LOGP(DNM, LOGL_ERROR, + "unimplemented ABIS OML message discriminator 0x%x\n", + oh->mdisc); + break; + default: + LOGP(DNM, LOGL_ERROR, + "unknown ABIS OML message discriminator 0x%x\n", + oh->mdisc); + return -EINVAL; + } + + msgb_free(msg); + return rc; +} + +static int bts_model_nokia_site_start(struct gsm_network *net); + +static void bts_model_nokia_site_e1line_bind_ops(struct e1inp_line *line) +{ + e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); +} + +static struct gsm_bts_model model_nokia_site = { + .type = GSM_BTS_TYPE_NOKIA_SITE, + .name = "nokia_site", + .start = bts_model_nokia_site_start, + .oml_rcvmsg = &abis_nokia_rcvmsg, + .e1line_bind_ops = &bts_model_nokia_site_e1line_bind_ops, +}; + +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); + + my_net = net; + + return 0; +} + +int bts_model_nokia_site_init(void) +{ + return gsm_bts_model_register(&model_nokia_site); +} diff --git a/src/osmo-bsc/bts_siemens_bs11.c b/src/osmo-bsc/bts_siemens_bs11.c new file mode 100644 index 000000000..2d2351702 --- /dev/null +++ b/src/osmo-bsc/bts_siemens_bs11.c @@ -0,0 +1,604 @@ +/* Siemens BS-11 specific code */ + +/* (C) 2009-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include + +#include +#include +#include +#include +#include + +static int bts_model_bs11_start(struct gsm_network *net); + +static void bts_model_bs11_e1line_bind_ops(struct e1inp_line *line) +{ + e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); +} + +static struct gsm_bts_model model_bs11 = { + .type = GSM_BTS_TYPE_BS11, + .name = "bs11", + .start = bts_model_bs11_start, + .oml_rcvmsg = &abis_nm_rcvmsg, + .e1line_bind_ops = bts_model_bs11_e1line_bind_ops, + .nm_att_tlvdef = { + .def = { + [NM_ATT_AVAIL_STATUS] = { TLV_TYPE_TLV }, + /* BS11 specifics */ + [NM_ATT_BS11_ESN_FW_CODE_NO] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_ESN_HW_CODE_NO] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_ESN_PCB_SERIAL] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_BOOT_SW_VERS] = { TLV_TYPE_TLV }, + [0xd5] = { TLV_TYPE_TLV }, + [0xa8] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PASSWORD] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_TXPWR] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_RSSI_OFFS] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LINE_CFG] = { TLV_TYPE_TV }, + [NM_ATT_BS11_L1_PROT_TYPE] = { TLV_TYPE_TV }, + [NM_ATT_BS11_BIT_ERR_THESH] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_BS11_DIVERSITY] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_LOGIN_TIME] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_USER_NAME] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_BTS_STATE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_E1_STATE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PLL_MODE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PLL] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_CCLK_ACCURACY] = { TLV_TYPE_TV }, + [NM_ATT_BS11_CCLK_TYPE] = { TLV_TYPE_TV }, + [0x95] = { TLV_TYPE_FIXED, 2 }, + }, + }, +}; + +/* The following definitions are for OM and NM packets that we cannot yet + * generate by code but we just pass on */ + +// BTS Site Manager, SET ATTRIBUTES + +/* + Object Class: BTS Site Manager + Instance 1: FF + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + sAbisExternalTime: 2007/09/08 14:36:11 + omLAPDRelTimer: 30sec + shortLAPDIntTimer: 5sec + emergencyTimer1: 10 minutes + emergencyTimer2: 0 minutes +*/ + +unsigned char msg_1[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, 0xFF, 0xFF, 0xFF, + NM_ATT_BS11_ABIS_EXT_TIME, 0x07, + 0xD7, 0x09, 0x08, 0x0E, 0x24, 0x0B, 0xCE, + 0x02, + 0x00, 0x1E, + NM_ATT_BS11_SH_LAPD_INT_TIMER, + 0x01, 0x05, + 0x42, 0x02, 0x00, 0x0A, + 0x44, 0x02, 0x00, 0x00 +}; + +// BTS, SET BTS ATTRIBUTES + +/* + Object Class: BTS + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET BTS ATTRIBUTES + bsIdentityCode / BSIC: + PLMN_colour_code: 7h + BS_colour_code: 7h + BTS Air Timer T3105: 4 ,unit 10 ms + btsIsHopping: FALSE + periodCCCHLoadIndication: 1sec + thresholdCCCHLoadIndication: 0% + cellAllocationNumber: 00h = GSM 900 + enableInterferenceClass: 00h = Disabled + fACCHQual: 6 (FACCH stealing flags minus 1) + intaveParameter: 31 SACCH multiframes + interferenceLevelBoundaries: + Interference Boundary 1: 0Ah + Interference Boundary 2: 0Fh + Interference Boundary 3: 14h + Interference Boundary 4: 19h + Interference Boundary 5: 1Eh + mSTxPwrMax: 11 + GSM range: 2=39dBm, 15=13dBm, stepsize 2 dBm + DCS1800 range: 0=30dBm, 15=0dBm, stepsize 2 dBm + PCS1900 range: 0=30dBm, 15=0dBm, stepsize 2 dBm + 30=33dBm, 31=32dBm + ny1: + Maximum number of repetitions for PHYSICAL INFORMATION message (GSM 04.08): 20 + powerOutputThresholds: + Out Power Fault Threshold: -10 dB + Red Out Power Threshold: - 6 dB + Excessive Out Power Threshold: 5 dB + rACHBusyThreshold: -127 dBm + rACHLoadAveragingSlots: 250 ,number of RACH burst periods + rfResourceIndicationPeriod: 125 SACCH multiframes + T200: + SDCCH: 044 in 5 ms + FACCH/Full rate: 031 in 5 ms + FACCH/Half rate: 041 in 5 ms + SACCH with TCH SAPI0: 090 in 10 ms + SACCH with SDCCH: 090 in 10 ms + SDCCH with SAPI3: 090 in 5 ms + SACCH with TCH SAPI3: 135 in 10 ms + tSync: 9000 units of 10 msec + tTrau: 9000 units of 10 msec + enableUmLoopTest: 00h = disabled + enableExcessiveDistance: 00h = Disabled + excessiveDistance: 64km + hoppingMode: 00h = baseband hopping + cellType: 00h = Standard Cell + BCCH ARFCN / bCCHFrequency: 1 +*/ + +static unsigned char bs11_attr_bts[] = +{ + NM_ATT_BSIC, HARDCODED_BSIC, + NM_ATT_BTS_AIR_TIMER, 0x04, + NM_ATT_BS11_BTSLS_HOPPING, 0x00, + NM_ATT_CCCH_L_I_P, 0x01, + NM_ATT_CCCH_L_T, 0x00, + NM_ATT_BS11_CELL_ALLOC_NR, NM_BS11_CANR_GSM, + NM_ATT_BS11_ENA_INTERF_CLASS, 0x01, + NM_ATT_BS11_FACCH_QUAL, 0x06, + /* interference avg. period in numbers of SACCH multifr */ + NM_ATT_INTAVE_PARAM, 0x1F, + NM_ATT_INTERF_BOUND, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x7B, + NM_ATT_CCCH_L_T, 0x23, + NM_ATT_GSM_TIME, 0x28, 0x00, + NM_ATT_ADM_STATE, 0x03, + NM_ATT_RACH_B_THRESH, 0x7F, + NM_ATT_LDAVG_SLOTS, 0x00, 0xFA, + NM_ATT_BS11_RF_RES_IND_PER, 0x7D, + NM_ATT_T200, 0x2C, 0x1F, 0x29, 0x5A, 0x5A, 0x5A, 0x87, + NM_ATT_BS11_TSYNC, 0x23, 0x28, + NM_ATT_BS11_TTRAU, 0x23, 0x28, + NM_ATT_TEST_DUR, 0x01, 0x00, + NM_ATT_OUTST_ALARM, 0x01, 0x00, + NM_ATT_BS11_EXCESSIVE_DISTANCE, 0x01, 0x40, + NM_ATT_BS11_HOPPING_MODE, 0x01, 0x00, + NM_ATT_BS11_PLL, 0x01, 0x00, + NM_ATT_BCCH_ARFCN, 0x00, HARDCODED_ARFCN/*0x01*/, +}; + +// Handover Recognition, SET ATTRIBUTES + +/* +Illegal Contents GSM Formatted O&M Msg + Object Class: Handover Recognition + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + enableDelayPowerBudgetHO: 00h = Disabled + enableDistanceHO: 00h = Disabled + enableInternalInterCellHandover: 00h = Disabled + enableInternalIntraCellHandover: 00h = Disabled + enablePowerBudgetHO: 00h = Disabled + enableRXLEVHO: 00h = Disabled + enableRXQUALHO: 00h = Disabled + hoAveragingDistance: 8 SACCH multiframes + hoAveragingLev: + A_LEV_HO: 8 SACCH multiframes + W_LEV_HO: 1 SACCH multiframes + hoAveragingPowerBudget: 16 SACCH multiframes + hoAveragingQual: + A_QUAL_HO: 8 SACCH multiframes + W_QUAL_HO: 2 SACCH multiframes + hoLowerThresholdLevDL: (10 - 110) dBm + hoLowerThresholdLevUL: (5 - 110) dBm + hoLowerThresholdQualDL: 06h = 6.4% < BER < 12.8% + hoLowerThresholdQualUL: 06h = 6.4% < BER < 12.8% + hoThresholdLevDLintra : (20 - 110) dBm + hoThresholdLevULintra: (20 - 110) dBm + hoThresholdMsRangeMax: 20 km + nCell: 06h + timerHORequest: 3 ,unit 2 SACCH multiframes +*/ + +unsigned char msg_3[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_BS11_HANDOVER, 0x00, 0xFF, 0xFF, + 0xD0, 0x00, /* enableDelayPowerBudgetHO */ + 0x64, 0x00, /* enableDistanceHO */ + 0x67, 0x00, /* enableInternalInterCellHandover */ + 0x68, 0x00, /* enableInternalInterCellHandover */ + 0x6A, 0x00, /* enablePowerBudgetHO */ + 0x6C, 0x00, /* enableRXLEVHO */ + 0x6D, 0x00, /* enableRXQUALHO */ + 0x6F, 0x08, /* hoAveragingDistance */ + 0x70, 0x08, 0x01, /* hoAveragingLev */ + 0x71, 0x10, 0x10, 0x10, + 0x72, 0x08, 0x02, /* hoAveragingQual */ + 0x73, 0x0A, /* hoLowerThresholdLevDL */ + 0x74, 0x05, /* hoLowerThresholdLevUL */ + 0x75, 0x06, /* hoLowerThresholdQualDL */ + 0x76, 0x06, /* hoLowerThresholdQualUL */ + 0x78, 0x14, /* hoThresholdLevDLintra */ + 0x79, 0x14, /* hoThresholdLevULintra */ + 0x7A, 0x14, /* hoThresholdMsRangeMax */ + 0x7D, 0x06, /* nCell */ + NM_ATT_BS11_TIMER_HO_REQUEST, 0x03, + 0x20, 0x01, 0x00, + 0x45, 0x01, 0x00, + 0x48, 0x01, 0x00, + 0x5A, 0x01, 0x00, + 0x5B, 0x01, 0x05, + 0x5E, 0x01, 0x1A, + 0x5F, 0x01, 0x20, + 0x9D, 0x01, 0x00, + 0x47, 0x01, 0x00, + 0x5C, 0x01, 0x64, + 0x5D, 0x01, 0x1E, + 0x97, 0x01, 0x20, + 0xF7, 0x01, 0x3C, +}; + +// Power Control, SET ATTRIBUTES + +/* + Object Class: Power Control + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + enableMsPowerControl: 00h = Disabled + enablePowerControlRLFW: 00h = Disabled + pcAveragingLev: + A_LEV_PC: 4 SACCH multiframes + W_LEV_PC: 1 SACCH multiframes + pcAveragingQual: + A_QUAL_PC: 4 SACCH multiframes + W_QUAL_PC: 2 SACCH multiframes + pcLowerThresholdLevDL: 0Fh + pcLowerThresholdLevUL: 0Ah + pcLowerThresholdQualDL: 05h = 3.2% < BER < 6.4% + pcLowerThresholdQualUL: 05h = 3.2% < BER < 6.4% + pcRLFThreshold: 0Ch + pcUpperThresholdLevDL: 14h + pcUpperThresholdLevUL: 0Fh + pcUpperThresholdQualDL: 04h = 1.6% < BER < 3.2% + pcUpperThresholdQualUL: 04h = 1.6% < BER < 3.2% + powerConfirm: 2 ,unit 2 SACCH multiframes + powerControlInterval: 2 ,unit 2 SACCH multiframes + powerIncrStepSize: 02h = 4 dB + powerRedStepSize: 01h = 2 dB + radioLinkTimeoutBs: 64 SACCH multiframes + enableBSPowerControl: 00h = disabled +*/ + +unsigned char msg_4[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_BS11_PWR_CTRL, 0x00, 0xFF, 0xFF, + NM_ATT_BS11_ENA_MS_PWR_CTRL, 0x00, + NM_ATT_BS11_ENA_PWR_CTRL_RLFW, 0x00, + 0x7E, 0x04, 0x01, /* pcAveragingLev */ + 0x7F, 0x04, 0x02, /* pcAveragingQual */ + 0x80, 0x0F, /* pcLowerThresholdLevDL */ + 0x81, 0x0A, /* pcLowerThresholdLevUL */ + 0x82, 0x05, /* pcLowerThresholdQualDL */ + 0x83, 0x05, /* pcLowerThresholdQualUL */ + 0x84, 0x0C, /* pcRLFThreshold */ + 0x85, 0x14, /* pcUpperThresholdLevDL */ + 0x86, 0x0F, /* pcUpperThresholdLevUL */ + 0x87, 0x04, /* pcUpperThresholdQualDL */ + 0x88, 0x04, /* pcUpperThresholdQualUL */ + 0x89, 0x02, /* powerConfirm */ + 0x8A, 0x02, /* powerConfirmInterval */ + 0x8B, 0x02, /* powerIncrStepSize */ + 0x8C, 0x01, /* powerRedStepSize */ + 0x8D, 0x40, /* radioLinkTimeoutBs */ + 0x65, 0x01, 0x00 // set to 0x01 to enable BSPowerControl +}; + + +// Transceiver, SET TRX ATTRIBUTES (TRX 0) + +/* + Object Class: Transceiver + BTS relat. Number: 0 + Tranceiver number: 0 + Instance 3: FF +SET TRX ATTRIBUTES + aRFCNList (HEX): 0001 + txPwrMaxReduction: 00h = 30dB + radioMeasGran: 254 SACCH multiframes + radioMeasRep: 01h = enabled + memberOfEmergencyConfig: 01h = TRUE + trxArea: 00h = TRX doesn't belong to a concentric cell +*/ + +static unsigned char bs11_attr_radio[] = +{ + NM_ATT_ARFCN_LIST, 0x01, 0x00, HARDCODED_ARFCN /*0x01*/, + NM_ATT_RF_MAXPOWR_R, 0x00, + NM_ATT_BS11_RADIO_MEAS_GRAN, 0x01, 0x05, + NM_ATT_BS11_RADIO_MEAS_REP, 0x01, 0x01, + NM_ATT_BS11_EMRG_CFG_MEMBER, 0x01, 0x01, + NM_ATT_BS11_TRX_AREA, 0x01, 0x00, +}; + +/* + * Patch the various SYSTEM INFORMATION tables to update + * the LAI + */ +static void patch_nm_tables(struct gsm_bts *bts) +{ + uint8_t arfcn_low = bts->c0->arfcn & 0xff; + uint8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f; + + /* T3105 attribute in units of 10ms */ + bs11_attr_bts[2] = bts->network->T3105 / 10; + + /* patch ARFCN into BTS Attributes */ + bs11_attr_bts[69] &= 0xf0; + bs11_attr_bts[69] |= arfcn_high; + bs11_attr_bts[70] = arfcn_low; + + /* patch ARFCN into TRX Attributes */ + bs11_attr_radio[2] &= 0xf0; + bs11_attr_radio[2] |= arfcn_high; + bs11_attr_radio[3] = arfcn_low; + + /* patch the RACH attributes */ + if (bts->rach_b_thresh != -1) + bs11_attr_bts[33] = bts->rach_b_thresh & 0xff; + + if (bts->rach_ldavg_slots != -1) { + uint8_t avg_high = bts->rach_ldavg_slots & 0xff; + uint8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f; + + bs11_attr_bts[35] = avg_high; + bs11_attr_bts[36] = avg_low; + } + + /* patch BSIC */ + bs11_attr_bts[1] = bts->bsic; + + /* patch the power reduction */ + bs11_attr_radio[5] = bts->c0->max_power_red / 2; +} + + +static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts) +{ + enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan); + struct gsm_e1_subslot *e1l = &ts->e1_link; + + abis_nm_set_channel_attr(ts, ccomb); + + if (is_ipaccess_bts(ts->trx->bts)) + return; + + if (ts_is_tch(ts)) + abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts, + e1l->e1_ts_ss); +} + +static void nm_reconfig_trx(struct gsm_bts_trx *trx) +{ + struct gsm_e1_subslot *e1l = &trx->rsl_e1_link; + int i; + + patch_nm_tables(trx->bts); + + switch (trx->bts->type) { + case GSM_BTS_TYPE_BS11: + /* FIXME: discover this by fetching an attribute */ +#if 0 + trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */ +#else + 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; + } + + for (i = 0; i < TRX_NR_TS; i++) + nm_reconfig_ts(&trx->ts[i]); +} + +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; + } + + llist_for_each_entry(trx, &bts->trx_list, list) + nm_reconfig_trx(trx); +} + + +static void bootstrap_om_bs11(struct gsm_bts *bts) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); + + /* stop sending event reports */ + abis_nm_event_reports(bts, 0); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* reconfigure BTS with all TRX and all TS */ + nm_reconfig_bts(bts); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + /* restart sending event reports */ + abis_nm_event_reports(bts, 1); +} + +static int shutdown_om(struct gsm_bts *bts) +{ + /* stop sending event reports */ + abis_nm_event_reports(bts, 0); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + gsm_bts_mark_all_ts_uninitialized(bts); + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int gbl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts *bts; + + if (subsys != SS_L_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_BTS_CLOSE_OM: + bts = signal_data; + if (bts->type == GSM_BTS_TYPE_BS11) + shutdown_om(signal_data); + break; + } + + return 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) +{ + struct input_signal_data *isd = signal_data; + + if (subsys != SS_L_INPUT) + return 0; + + switch (signal) { + case S_L_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + if (isd->trx->bts->type == GSM_BTS_TYPE_BS11) + bootstrap_om_bs11(isd->trx->bts); + break; + } + } + + return 0; +} + +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); + + return 0; +} + +int bts_model_bs11_init(void) +{ + return gsm_bts_model_register(&model_bs11); +} diff --git a/src/osmo-bsc/bts_sysmobts.c b/src/osmo-bsc/bts_sysmobts.c new file mode 100644 index 000000000..91d1118c5 --- /dev/null +++ b/src/osmo-bsc/bts_sysmobts.c @@ -0,0 +1,60 @@ +/* sysmocom sysmoBTS specific code */ + +/* (C) 2010-2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + + 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, sizeof(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); + + return gsm_bts_model_register(&model_sysmobts); +} diff --git a/src/osmo-bsc/bts_unknown.c b/src/osmo-bsc/bts_unknown.c new file mode 100644 index 000000000..5ecf875c1 --- /dev/null +++ b/src/osmo-bsc/bts_unknown.c @@ -0,0 +1,40 @@ +/* Generic BTS - VTY code tries to allocate this BTS before type is known */ + +/* (C) 2010 by Daniel Willmann + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include + +static struct gsm_bts_model model_unknown = { + .type = GSM_BTS_TYPE_UNKNOWN, + .name = "unknown", + .oml_rcvmsg = &abis_nm_rcvmsg, + .nm_att_tlvdef = { + .def = { + }, + }, +}; + +int bts_model_unknown_init(void) +{ + return gsm_bts_model_register(&model_unknown); +} diff --git a/src/osmo-bsc/chan_alloc.c b/src/osmo-bsc/chan_alloc.c new file mode 100644 index 000000000..a24fbea94 --- /dev/null +++ b/src/osmo-bsc/chan_alloc.c @@ -0,0 +1,734 @@ +/* GSM Channel allocation routines + * + * (C) 2008 by Harald Welte + * (C) 2008, 2009 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +bool ts_is_usable(const struct gsm_bts_trx_ts *ts) +{ + if (!trx_is_usable(ts->trx)) { + LOGP(DRLL, LOGL_DEBUG, "%s not usable\n", gsm_trx_name(ts->trx)); + return false; + } + + /* If a TCH/F_PDCH TS is busy changing, it is already taken or not + * yet available. */ + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + if (ts->flags & TS_F_PDCH_PENDING_MASK) { + LOGP(DRLL, LOGL_DEBUG, "%s in switchover, not available\n", + gsm_ts_and_pchan_name(ts)); + return false; + } + } + + /* If a dynamic channel is busy changing, it is already taken or not + * yet available. */ + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + LOGP(DRLL, LOGL_DEBUG, "%s in switchover, not available\n", + gsm_ts_and_pchan_name(ts)); + return false; + } + } + + return true; +} + +static int trx_count_free_ts(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan) +{ + struct gsm_bts_trx_ts *ts; + int j, ss; + int count = 0; + + if (!trx_is_usable(trx)) + return 0; + + for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { + enum gsm_phys_chan_config ts_pchan_is; + ts = &trx->ts[j]; + if (!ts_is_usable(ts)) + continue; + + ts_pchan_is = ts_pchan(ts); + + if (ts_pchan_is == GSM_PCHAN_PDCH) { + /* Dynamic timeslots in PDCH mode will become TCH if needed. */ + switch (ts->pchan) { + 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; + /* check if all sub-slots are allocated yet */ + for (ss = 0; ss < ts_subslots(ts); ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->type == GSM_LCHAN_NONE && + lc->state == LCHAN_S_NONE) + count++; + } + } + + return count; +} + +/* Count number of free TS of given pchan type */ +int bts_count_free_ts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) +{ + struct gsm_bts_trx *trx; + int count = 0; + + llist_for_each_entry(trx, &bts->trx_list, list) + count += trx_count_free_ts(trx, pchan); + + return count; +} + +static bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_PENDING_MASK) { + /* currently being switched over. Not usable. */ + return false; + } + switch (as_pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + /* continue to check below. */ + break; + default: + return false; + } + break; + + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + /* currently being switched over. Not usable. */ + return false; + } + switch (as_pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_PDCH: + /* continue to check below. */ + break; + default: + return false; + } + break; + + default: + /* static timeslots never switch. */ + return ts->pchan == as_pchan; + } + + /* Dynamic timeslots -- Checks depending on the current actual pchan mode: */ + switch (ts_pchan(ts)) { + case GSM_PCHAN_NONE: + /* Not initialized, possibly because GPRS was disabled. We may switch. */ + return true; + + case GSM_PCHAN_PDCH: + /* This slot is in PDCH mode and available to switch pchan mode. But check for + * error states: */ + if (ts->lchan->state != LCHAN_S_NONE && ts->lchan->state != LCHAN_S_ACTIVE) + return false; + return true; + + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + /* No need to switch at all? */ + if (ts_pchan(ts) == as_pchan) + return true; + + /* If any lchan is in use, we can't change the pchan kind */ + { + int ss; + int subslots = ts_subslots(ts); + for (ss = 0; ss < subslots; ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->type != GSM_LCHAN_NONE || lc->state != LCHAN_S_NONE) + return false; + } + } + return true; + + default: + /* Not implemented. */ + return false; + } +} + +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 gsm_bts_trx_ts *ts; + int j, start, stop, dir, ss; + int check_subslots; + +#define LOGPLCHANALLOC(fmt, args...) \ + LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s as %s: " fmt, \ + gsm_pchan_name(pchan), gsm_pchan_name(as_pchan), ## args) + + if (!trx_is_usable(trx)) { + LOGPLCHANALLOC("%s trx not usable\n", gsm_trx_name(trx)); + return NULL; + } + + 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... */ + if (ts->pchan != 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)); + continue; + } + /* If we need to switch it, after above check we are also allowed to switch it, and we + * will always use the first lchan after the switch. Return that lchan and rely on the + * caller to perform the pchan switchover. */ + if (ts_pchan(ts) != as_pchan) { + LOGPLCHANALLOC("%s is a match, will switch to %s\n", gsm_ts_and_pchan_name(ts), + gsm_pchan_name(as_pchan)); + return ts->lchan; + } + + /* TS is in desired pchan mode. Go ahead and check for an available lchan. */ + check_subslots = ts_subslots(ts); + for (ss = 0; ss < check_subslots; ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->type == GSM_LCHAN_NONE && + lc->state == LCHAN_S_NONE) { + LOGPLCHANALLOC("%s ss=%d is available\n", gsm_ts_and_pchan_name(ts), + lc->nr); + return lc; + } + LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n", + gsm_ts_and_pchan_name(ts), lc->nr, gsm_lchant_name(lc->type), + gsm_lchans_name(lc->state)); + } + } + + return NULL; +#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) +{ + struct gsm_bts_trx *trx; + struct gsm_lchan *lc; + + 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; + } + } + + return NULL; +} + +static struct gsm_lchan * +_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) +{ + return _lc_dyn_find_bts(bts, pchan, pchan); +} + +/* Allocate a logical channel. + * + * Dynamic channel types: we always prefer a dedicated TS, and only pick + + * switch a dynamic TS if no pure TS of the requested PCHAN is available. + * + * TCH_F/PDCH: if we pick a PDCH ACT style dynamic TS as TCH/F channel, PDCH + * will be disabled in rsl_chan_activate_lchan(); there is no need to check + * whether PDCH mode is currently active, here. + */ +struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, + int allow_bigger) +{ + struct gsm_lchan *lchan = NULL; + enum gsm_phys_chan_config first, first_cbch, second, second_cbch; + + LOGP(DRLL, LOGL_DEBUG, "(bts=%d) lchan_alloc(%s)\n", bts->nr, gsm_lchant_name(type)); + + switch (type) { + case GSM_LCHAN_SDCCH: + if (bts->chan_alloc_reverse) { + first = GSM_PCHAN_SDCCH8_SACCH8C; + first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH; + second = GSM_PCHAN_CCCH_SDCCH4; + second_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH; + } else { + first = GSM_PCHAN_CCCH_SDCCH4; + first_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH; + second = GSM_PCHAN_SDCCH8_SACCH8C; + second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH; + } + + lchan = _lc_find_bts(bts, first); + if (lchan == NULL) + lchan = _lc_find_bts(bts, first_cbch); + if (lchan == NULL) + lchan = _lc_find_bts(bts, second); + if (lchan == NULL) + lchan = _lc_find_bts(bts, second_cbch); + + /* allow to assign bigger channels */ + if (allow_bigger) { + if (lchan == NULL) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + if (lchan) + type = GSM_LCHAN_TCH_H; + } + + if (lchan == NULL) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + if (lchan) + type = GSM_LCHAN_TCH_F; + } + + /* try dynamic TCH/F_PDCH */ + if (lchan == NULL) { + lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH, + GSM_PCHAN_TCH_F); + /* TCH/F_PDCH will be used as TCH/F */ + if (lchan) + type = GSM_LCHAN_TCH_F; + } + + /* try fully dynamic TCH/F_TCH/H_PDCH */ + if (lchan == NULL) { + lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_TCH_H_PDCH, + GSM_PCHAN_TCH_H); + if (lchan) + type = GSM_LCHAN_TCH_H; + } + /* + * No need to check fully dynamic channels for TCH/F: + * if no TCH/H was available, neither will be TCH/F. + */ + } + break; + case GSM_LCHAN_TCH_F: + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + /* If we don't have TCH/F available, fall-back to TCH/H */ + if (!lchan) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + if (lchan) + type = GSM_LCHAN_TCH_H; + } + /* If we don't have TCH/H either, 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; + } + + /* 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; + } + /* ...and as TCH/H. */ + 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; + } + break; + case GSM_LCHAN_TCH_H: + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + /* If we don't have TCH/H available, fall-back to TCH/F */ + if (!lchan) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + if (lchan) + type = GSM_LCHAN_TCH_F; + } + /* 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; + } + /* + * No need to check TCH/F_TCH/H_PDCH channels for TCH/F: + * if no TCH/H was available, neither will be TCH/F. + */ + /* If we don't have TCH/F either, try dynamic TCH/F_PDCH */ + if (!lchan) { + lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH, + GSM_PCHAN_TCH_F); + if (lchan) + type = GSM_LCHAN_TCH_F; + } + break; + default: + LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type); + } + + if (lchan) { + lchan->type = type; + + LOGP(DRLL, LOGL_INFO, "%s Allocating lchan=%u as %s\n", + gsm_ts_and_pchan_name(lchan->ts), + lchan->nr, gsm_lchant_name(lchan->type)); + + /* reset measurement report counter and index */ + lchan->meas_rep_count = 0; + lchan->meas_rep_idx = 0; + lchan->meas_rep_last_seen_nr = 255; + + /* clear sapis */ + memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis)); + + /* clear multi rate config */ + memset(&lchan->mr_ms_lv, 0, sizeof(lchan->mr_ms_lv)); + memset(&lchan->mr_bts_lv, 0, sizeof(lchan->mr_bts_lv)); + lchan->broken_reason = ""; + } else { + struct challoc_signal_data sig; + + LOGP(DRLL, LOGL_ERROR, "(bts=%d) Failed to allocate %s channel\n", + bts->nr, gsm_lchant_name(type)); + + sig.bts = bts; + sig.type = type; + osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig); + } + + return lchan; +} + +/* Free a logical channel */ +void lchan_free(struct gsm_lchan *lchan) +{ + struct challoc_signal_data sig; + int i; + + sig.type = lchan->type; + lchan->type = GSM_LCHAN_NONE; + + + if (lchan->conn + && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is != lchan->ts->dyn.pchan_want)) { + struct lchan_signal_data sig; + + /* We might kill an active channel... */ + sig.lchan = lchan; + sig.mr = NULL; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig); + } + + /* stop the timer */ + osmo_timer_del(&lchan->T3101); + + /* clear cached measuement reports */ + lchan->meas_rep_idx = 0; + for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) { + lchan->meas_rep[i].flags = 0; + lchan->meas_rep[i].nr = 0; + } + for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) + lchan->neigh_meas[i].arfcn = 0; + + if (lchan->rqd_ref) { + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + } + + sig.lchan = lchan; + sig.bts = lchan->ts->trx->bts; + osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_FREED, &sig); + + if (lchan->conn + && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is != lchan->ts->dyn.pchan_want)) { + LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n"); + lchan->conn = NULL; + } + + /* FIXME: ts_free() the timeslot, if we're the last logical + * channel using it */ +} + +/* + * There was an error with the TRX and we need to forget + * any state so that a lchan can be allocated again after + * the trx is fully usable. + * + * This should be called after lchan_free to force a channel + * be available for allocation again. This means that this + * method will stop the "delay after error"-timer and set the + * state to LCHAN_S_NONE. + */ +void lchan_reset(struct gsm_lchan *lchan) +{ + osmo_timer_del(&lchan->T3101); + osmo_timer_del(&lchan->T3109); + osmo_timer_del(&lchan->T3111); + osmo_timer_del(&lchan->error_timer); + + lchan->type = GSM_LCHAN_NONE; + rsl_lchan_set_state(lchan, LCHAN_S_NONE); +} + +/* Drive the release process of the lchan */ +static void _lchan_handle_release(struct gsm_lchan *lchan, + int sacch_deact, int mode) +{ + /* Release all SAPIs on the local end and continue */ + rsl_release_sapis_from(lchan, 1, RSL_REL_LOCAL_END); + + /* + * Shall we send a RR Release, start T3109 and wait for the + * release indication from the BTS or just take it down (e.g. + * on assignment requests) + */ + if (sacch_deact) { + gsm48_send_rr_release(lchan); + + /* Deactivate the SACCH on the BTS side */ + rsl_deact_sacch(lchan); + rsl_start_t3109(lchan); + } else if (lchan->sapis[0] == LCHAN_SAPI_UNUSED) { + rsl_direct_rf_release(lchan); + } else { + rsl_release_request(lchan, 0, mode); + } +} + +/* Consider releasing the channel now */ +int lchan_release(struct gsm_lchan *lchan, int sacch_deact, enum rsl_rel_mode mode) +{ + DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan)); + rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ); + + lchan->conn = NULL; + _lchan_handle_release(lchan, sacch_deact, mode); + return 1; +} + +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) { + int i; + + /* skip administratively deactivated tranxsceivers */ + if (!nm_is_running(&trx->mo.nm_state) || + !nm_is_running(&trx->bb_transc.mo.nm_state)) + continue; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + struct load_counter *pl = &cl->pchan[ts->pchan]; + int j; + int subslots; + + /* skip administratively deactivated timeslots */ + if (!nm_is_running(&ts->mo.nm_state)) + continue; + + subslots = ts_subslots(ts); + for (j = 0; j < subslots; j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + + pl->total++; + + switch (lchan->state) { + case LCHAN_S_NONE: + break; + default: + pl->used++; + break; + } + } + } + } +} + +void network_chan_load(struct pchan_load *pl, struct gsm_network *net) +{ + struct gsm_bts *bts; + + memset(pl, 0, sizeof(*pl)); + + llist_for_each_entry(bts, &net->bts_list, list) + bts_chan_load(pl, bts); +} + +/* Update T3122 wait indicator based on samples of BTS channel load. */ +void +bts_update_t3122_chan_load(struct gsm_bts *bts) +{ + struct pchan_load pl; + uint64_t used = 0; + uint32_t total = 0; + uint64_t load; + uint64_t wait_ind; + static const uint8_t min_wait_ind = GSM_T3122_DEFAULT; + static const uint8_t max_wait_ind = 128; /* max wait ~2 minutes */ + int i; + + /* Ignore BTS that are not in operation, in order to not flood the log with "bogus channel load" + * messages */ + if (!trx_is_usable(bts->c0)) + return; + + /* Sum up current load across all channels. */ + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + for (i = 0; i < ARRAY_SIZE(pl.pchan); i++) { + struct load_counter *lc = &pl.pchan[i]; + + /* Ignore samples too large for fixed-point calculations (shouldn't happen). */ + if (lc->used > UINT16_MAX || lc->total > UINT16_MAX) { + LOGP(DRLL, LOGL_NOTICE, "(bts=%d) numbers in channel load sample " + "too large (used=%u / total=%u)\n", bts->nr, lc->used, lc->total); + continue; + } + + used += lc->used; + total += lc->total; + } + + /* Check for invalid samples (shouldn't happen). */ + if (total == 0 || used > total) { + LOGP(DRLL, LOGL_NOTICE, "(bts=%d) bogus channel load sample (used=%"PRIu64" / total=%"PRIu32")\n", + bts->nr, used, total); + bts->T3122 = 0; /* disable override of network-wide default value */ + bts->chan_load_samples_idx = 0; /* invalidate other samples collected so far */ + return; + } + + /* If we haven't got enough samples yet, store measurement for later use. */ + if (bts->chan_load_samples_idx < ARRAY_SIZE(bts->chan_load_samples)) { + struct load_counter *sample = &bts->chan_load_samples[bts->chan_load_samples_idx++]; + sample->total = (unsigned int)total; + sample->used = (unsigned int)used; + return; + } + + /* We have enough samples and will overwrite our current samples later. */ + bts->chan_load_samples_idx = 0; + + /* Add all previous samples to the current sample. */ + for (i = 0; i < ARRAY_SIZE(bts->chan_load_samples); i++) { + struct load_counter *sample = &bts->chan_load_samples[i]; + total += sample->total; + used += sample->used; + } + + used <<= 8; /* convert to fixed-point */ + + /* Log channel load average. */ + load = ((used / total) * 100); + LOGP(DRLL, LOGL_DEBUG, "(bts=%d) channel load average is %"PRIu64".%.2"PRIu64"%%\n", + bts->nr, (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); + + /* Calculate new T3122 wait indicator. */ + wait_ind = ((used / total) * max_wait_ind); + wait_ind >>= 8; /* convert from fixed-point to integer */ + if (wait_ind < min_wait_ind) + wait_ind = min_wait_ind; + else if (wait_ind > max_wait_ind) + wait_ind = max_wait_ind; + + LOGP(DRLL, LOGL_DEBUG, "(bts=%d) T3122 wait indicator set to %"PRIu64" seconds\n", bts->nr, wait_ind); + bts->T3122 = (uint8_t)wait_ind; + osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_T3122], wait_ind); +} diff --git a/src/osmo-bsc/e1_config.c b/src/osmo-bsc/e1_config.c new file mode 100644 index 000000000..e7398ed9c --- /dev/null +++ b/src/osmo-bsc/e1_config.c @@ -0,0 +1,299 @@ +/* OpenBSC E1 Input code */ + +/* (C) 2008-2010 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define SAPI_L2ML 0 +#define SAPI_OML 62 +#define SAPI_RSL 0 /* 63 ? */ + +/* The e1_reconfig_*() functions below take the configuration present in the + * bts/trx/ts data structures and ensure the E1 configuration reflects the + * timeslot/subslot/TEI configuration */ + +int e1_reconfig_ts(struct gsm_bts_trx_ts *ts) +{ + struct gsm_e1_subslot *e1_link = &ts->e1_link; + struct e1inp_line *line; + + DEBUGP(DLMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr); + + if (!e1_link->e1_ts) { + LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) without E1 timeslot?\n", + ts->nr, ts->trx->nr, ts->trx->bts->nr); + return 0; + } + + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) referring to " + "non-existing E1 line %u\n", ts->nr, ts->trx->nr, + ts->trx->bts->nr, e1_link->e1_nr); + return -ENOMEM; + } + + return 0; +} + +int e1_reconfig_trx(struct gsm_bts_trx *trx) +{ + struct gsm_e1_subslot *e1_link = &trx->rsl_e1_link; + struct e1inp_ts *sign_ts; + struct e1inp_line *line; + struct e1inp_sign_link *rsl_link; + int i; + + if (!e1_link->e1_ts) { + LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link without " + "timeslot?\n", trx->bts->nr, trx->nr); + return -EINVAL; + } + + /* RSL Link */ + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link referring " + "to non-existing E1 line %u\n", trx->bts->nr, + trx->nr, e1_link->e1_nr); + return -ENOMEM; + } + sign_ts = &line->ts[e1_link->e1_ts-1]; + e1inp_ts_config_sign(sign_ts, line); + /* Ericsson RBS have a per-TRX OML link in parallel to RSL */ + 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); + if (!oml_link) { + LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) OML link creation " + "failed\n", trx->bts->nr, trx->nr); + return -ENOMEM; + } + if (trx->oml_link) + e1inp_sign_link_destroy(trx->oml_link); + trx->oml_link = oml_link; + } + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, trx->rsl_tei, SAPI_RSL); + if (!rsl_link) { + LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link creation " + "failed\n", trx->bts->nr, trx->nr); + return -ENOMEM; + } + if (trx->rsl_link) + e1inp_sign_link_destroy(trx->rsl_link); + trx->rsl_link = rsl_link; + + for (i = 0; i < TRX_NR_TS; i++) + e1_reconfig_ts(&trx->ts[i]); + + return 0; +} + +/* this is the generic callback for all ISDN-based BTS. */ +static int bts_isdn_sign_link(struct msgb *msg) +{ + int ret = -EINVAL; + struct e1inp_sign_link *link = msg->dst; + struct gsm_bts *bts; + + switch (link->type) { + case E1INP_SIGN_OML: + bts = link->trx->bts; + ret = bts->model->oml_rcvmsg(msg); + break; + case E1INP_SIGN_RSL: + if (link->trx->mo.nm_state.administrative == NM_STATE_LOCKED) { + LOGP(DLMI, LOGL_ERROR, "(bts=%d/trx=%d) discarding RSL message received " + "in locked administrative state\n", link->trx->bts->nr, link->trx->nr); + msgb_free(msg); + break; + } + ret = abis_rsl_rcvmsg(msg); + break; + default: + LOGP(DLMI, LOGL_ERROR, "unknown link type %u\n", link->type); + msgb_free(msg); + break; + } + return ret; +} + +struct e1inp_line_ops bts_isdn_e1inp_line_ops = { + .sign_link = bts_isdn_sign_link, +}; + +int e1_reconfig_bts(struct gsm_bts *bts) +{ + struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; + struct e1inp_ts *sign_ts; + struct e1inp_line *line; + struct e1inp_sign_link *oml_link; + struct gsm_bts_trx *trx; + struct timespec tp; + int rc; + + DEBUGP(DLMI, "e1_reconfig_bts(%u)\n", bts->nr); + + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to " + "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr); + return -ENOMEM; + } + + if (!bts->model->e1line_bind_ops) { + LOGP(DLINP, LOGL_ERROR, "no callback to bind E1 line operations\n"); + return -EINVAL; + } + if (!line->ops) + bts->model->e1line_bind_ops(line); + + /* skip signal link initialization, this is done later for these BTS. */ + if (bts->type == GSM_BTS_TYPE_NANOBTS || + bts->type == GSM_BTS_TYPE_OSMOBTS) + return e1inp_line_update(line); + + /* OML link */ + if (!e1_link->e1_ts) { + LOGP(DLINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n", + bts->nr); + return -EINVAL; + } + + sign_ts = &line->ts[e1_link->e1_ts-1]; + e1inp_ts_config_sign(sign_ts, line); + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, + bts->c0, bts->oml_tei, SAPI_OML); + if (!oml_link) { + LOGP(DLINP, LOGL_ERROR, "BTS %u OML link creation failed\n", + bts->nr); + return -ENOMEM; + } + 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 */ + + llist_for_each_entry(trx, &bts->trx_list, list) + e1_reconfig_trx(trx); + + /* notify E1 input something has changed */ + return e1inp_line_update(line); +} + +#if 0 +/* do some compiled-in configuration for our BTS/E1 setup */ +int e1_config(struct gsm_bts *bts, int cardnr, int release_l2) +{ + struct e1inp_line *line; + struct e1inp_ts *sign_ts; + struct e1inp_sign_link *oml_link, *rsl_link; + struct gsm_bts_trx *trx = bts->c0; + int base_ts; + + switch (bts->nr) { + case 0: + /* First BTS uses E1 TS 01,02,03,04,05 */ + base_ts = HARDCODED_BTS0_TS - 1; + break; + case 1: + /* Second BTS uses E1 TS 06,07,08,09,10 */ + base_ts = HARDCODED_BTS1_TS - 1; + break; + case 2: + /* Third BTS uses E1 TS 11,12,13,14,15 */ + base_ts = HARDCODED_BTS2_TS - 1; + default: + return -EINVAL; + } + + line = talloc_zero(tall_bsc_ctx, struct e1inp_line); + if (!line) + return -ENOMEM; + + /* create E1 timeslots for signalling and TRAU frames */ + e1inp_ts_config(&line->ts[base_ts+1-1], line, E1INP_TS_TYPE_SIGN); + e1inp_ts_config(&line->ts[base_ts+2-1], line, E1INP_TS_TYPE_TRAU); + e1inp_ts_config(&line->ts[base_ts+3-1], line, E1INP_TS_TYPE_TRAU); + + /* create signalling links for TS1 */ + sign_ts = &line->ts[base_ts+1-1]; + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, + trx, TEI_OML, SAPI_OML); + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, TEI_RSL, SAPI_RSL); + + /* create back-links from bts/trx */ + bts->oml_link = oml_link; + trx->rsl_link = rsl_link; + + /* enable subchannel demuxer on TS2 */ + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 3); + + /* enable subchannel demuxer on TS3 */ + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 3); + + trx = gsm_bts_trx_num(bts, 1); + if (trx) { + /* create E1 timeslots for TRAU frames of TRX1 */ + e1inp_ts_config(&line->ts[base_ts+4-1], line, E1INP_TS_TYPE_TRAU); + e1inp_ts_config(&line->ts[base_ts+5-1], line, E1INP_TS_TYPE_TRAU); + + /* create RSL signalling link for TRX1 */ + sign_ts = &line->ts[base_ts+1-1]; + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, TEI_RSL+1, SAPI_RSL); + /* create back-links from trx */ + trx->rsl_link = rsl_link; + + /* enable subchannel demuxer on TS2 */ + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 3); + + /* enable subchannel demuxer on TS3 */ + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 3); + } + + return mi_setup(cardnr, line, release_l2); +} +#endif diff --git a/src/osmo-bsc/gsm_04_08_utils.c b/src/osmo-bsc/gsm_04_08_utils.c new file mode 100644 index 000000000..5bfdf97ff --- /dev/null +++ b/src/osmo-bsc/gsm_04_08_utils.c @@ -0,0 +1,705 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 + * utility functions + */ + +/* (C) 2008-2009 by Harald Welte + * (C) 2008, 2009 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* should ip.access BTS use direct RTP streams between each other (1), + * or should OpenBSC always act as RTP relay/proxy in between (0) ? */ +int ipacc_rtp_direct = 1; + +static int gsm48_sendmsg(struct msgb *msg) +{ + if (msg->lchan) + msg->dst = msg->lchan->ts->trx->rsl_link; + + msg->l3h = msg->data; + return rsl_data_request(msg, 0); +} + +/* Section 9.1.8 / Table 9.9 */ +struct chreq { + uint8_t val; + uint8_t mask; + enum chreq_type type; +}; + +/* If SYSTEM INFORMATION TYPE 4 NECI bit == 1 */ +static const struct chreq chreq_type_neci1[] = { + { 0xa0, 0xe0, CHREQ_T_EMERG_CALL }, + { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_F }, + { 0x68, 0xfc, CHREQ_T_CALL_REEST_TCH_H }, + { 0x6c, 0xfc, CHREQ_T_CALL_REEST_TCH_H_DBL }, + { 0xe0, 0xe0, CHREQ_T_TCH_F }, + { 0x40, 0xf0, CHREQ_T_VOICE_CALL_TCH_H }, + { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H }, + { 0x00, 0xf0, CHREQ_T_LOCATION_UPD }, + { 0x10, 0xf0, CHREQ_T_SDCCH }, + { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI1 }, + { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F }, + { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH }, + { 0x67, 0xff, CHREQ_T_LMU }, + { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH }, + { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH }, + { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH }, + { 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE }, + { 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE }, + { 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE }, + { 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE }, + { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE }, +}; + +/* If SYSTEM INFORMATION TYPE 4 NECI bit == 0 */ +static const struct chreq chreq_type_neci0[] = { + { 0xa0, 0xe0, CHREQ_T_EMERG_CALL }, + { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_H }, + { 0xe0, 0xe0, CHREQ_T_TCH_F }, + { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H }, + { 0x00, 0xe0, CHREQ_T_LOCATION_UPD }, + { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI0 }, + { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F }, + { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH }, + { 0x67, 0xff, CHREQ_T_LMU }, + { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH }, + { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH }, + { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH }, + { 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE }, + { 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE }, + { 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE }, + { 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE }, + { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE }, +}; + +static const enum gsm_chan_t ctype_by_chreq[] = { + [CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_F, + [CHREQ_T_CALL_REEST_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_CALL_REEST_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_LCHAN_TCH_H, + [CHREQ_T_SDCCH] = GSM_LCHAN_SDCCH, + [CHREQ_T_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_VOICE_CALL_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_DATA_CALL_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_LOCATION_UPD] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_PAG_R_TCH_FH] = GSM_LCHAN_TCH_H, + [CHREQ_T_LMU] = GSM_LCHAN_SDCCH, + [CHREQ_T_RESERVED_SDCCH] = GSM_LCHAN_SDCCH, + [CHREQ_T_PDCH_ONE_PHASE] = GSM_LCHAN_PDTCH, + [CHREQ_T_PDCH_TWO_PHASE] = GSM_LCHAN_PDTCH, + [CHREQ_T_RESERVED_IGNORE] = GSM_LCHAN_UNKNOWN, +}; + +static const enum gsm_chreq_reason_t reason_by_chreq[] = { + [CHREQ_T_EMERG_CALL] = GSM_CHREQ_REASON_EMERG, + [CHREQ_T_CALL_REEST_TCH_F] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_CALL_REEST_TCH_H] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_SDCCH] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_TCH_F] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_VOICE_CALL_TCH_H] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_DATA_CALL_TCH_H] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_LOCATION_UPD] = GSM_CHREQ_REASON_LOCATION_UPD, + [CHREQ_T_PAG_R_ANY_NECI1] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_ANY_NECI0] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_TCH_F] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_TCH_FH] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_LMU] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_PDCH_ONE_PHASE] = GSM_CHREQ_REASON_PDCH, + [CHREQ_T_PDCH_TWO_PHASE] = GSM_CHREQ_REASON_PDCH, + [CHREQ_T_RESERVED_SDCCH] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_RESERVED_IGNORE] = GSM_CHREQ_REASON_OTHER, +}; + +/* verify that the two tables match */ +osmo_static_assert(sizeof(ctype_by_chreq) == + sizeof(((struct gsm_network *) NULL)->ctype_by_chreq), assert_size); + +/* + * Update channel types for request based on policy. E.g. in the + * case of a TCH/H network/bsc use TCH/H for the emergency calls, + * for early assignment assign a SDCCH and some other options. + */ +void gsm_net_update_ctype(struct gsm_network *network) +{ + /* copy over the data */ + memcpy(network->ctype_by_chreq, ctype_by_chreq, sizeof(ctype_by_chreq)); + + /* + * Use TCH/H for emergency calls when this cell allows TCH/H. Maybe it + * is better to iterate over the BTS/TRX and check if no TCH/F is available + * and then set it to TCH/H. + */ + if (network->neci) + network->ctype_by_chreq[CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_H; + + if (network->pag_any_tch) { + if (network->neci) { + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_H; + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_H; + } else { + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_F; + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_F; + } + } +} + +enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, uint8_t ra) +{ + int i; + int length; + const struct chreq *chreq; + + if (network->neci) { + chreq = chreq_type_neci1; + length = ARRAY_SIZE(chreq_type_neci1); + } else { + chreq = chreq_type_neci0; + length = ARRAY_SIZE(chreq_type_neci0); + } + + + for (i = 0; i < length; i++) { + const struct chreq *chr = &chreq[i]; + if ((ra & chr->mask) == chr->val) + return network->ctype_by_chreq[chr->type]; + } + LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra); + return GSM_LCHAN_SDCCH; +} + +int get_reason_by_chreq(uint8_t ra, int neci) +{ + int i; + int length; + const struct chreq *chreq; + + if (neci) { + chreq = chreq_type_neci1; + length = ARRAY_SIZE(chreq_type_neci1); + } else { + chreq = chreq_type_neci0; + length = ARRAY_SIZE(chreq_type_neci0); + } + + for (i = 0; i < length; i++) { + const struct chreq *chr = &chreq[i]; + if ((ra & chr->mask) == chr->val) + return reason_by_chreq[chr->type]; + } + LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST REASON 0x%02x\n", ra); + return GSM_CHREQ_REASON_OTHER; +} + +static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg) +{ + 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); +} + +/* 7.1.7 and 9.1.7: RR CHANnel RELease */ +int gsm48_send_rr_release(struct gsm_lchan *lchan) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + uint8_t *cause; + + msg->lchan = lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CHAN_REL; + + cause = msgb_put(msg, 1); + cause[0] = GSM48_RR_CAUSE_NORMAL; + + DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n", + lchan->nr, lchan->type); + + /* Send actual release request to MS */ + return gsm48_sendmsg(msg); +} + +int send_siemens_mrpci(struct gsm_lchan *lchan, + uint8_t *classmark2_lv) +{ + struct rsl_mrpci mrpci; + + if (classmark2_lv[0] < 2) + return -EINVAL; + + mrpci.power_class = classmark2_lv[1] & 0x7; + mrpci.vgcs_capable = classmark2_lv[2] & (1 << 1); + mrpci.vbs_capable = classmark2_lv[2] & (1 <<2); + mrpci.gsm_phase = (classmark2_lv[1]) >> 5 & 0x3; + + return rsl_siemens_mrpci(lchan, &mrpci); +} + +/* Chapter 9.1.9: Ciphering Mode Command */ +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; + + msg->lchan = lchan; + + DEBUGP(DRR, "TX CIPHERING MODE CMD\n"); + + 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; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CIPH_M_CMD; + gh->data[0] = (want_imeisv & 0x1) << 4 | (ciph_mod_set & 0xf); + + return rsl_encryption_cmd(msg); +} + +static void gsm48_cell_desc(struct gsm48_cell_desc *cd, + const struct gsm_bts *bts) +{ + cd->ncc = (bts->bsic >> 3 & 0x7); + cd->bcc = (bts->bsic & 0x7); + cd->arfcn_hi = bts->c0->arfcn >> 8; + cd->arfcn_lo = bts->c0->arfcn & 0xff; +} + +/*! \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[in] mr multi-rate configuration to encode + * \param[in] modes array describing the AMR modes + * \returns 0 on success */ +int gsm48_multirate_config(uint8_t *lv, const struct amr_multirate_conf *mr, const struct amr_mode *modes) +{ + int num = 0, i; + + for (i = 0; i < 8; i++) { + if (((mr->gsm48_ie[1] >> i) & 1)) + num++; + } + if (num > 4) { + LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with too " + "many modes in config.\n"); + num = 4; + } + if (num < 1) { + LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with no " + "mode in config.\n"); + num = 1; + } + + lv[0] = (num == 1) ? 2 : (num + 2); + memcpy(lv + 1, mr->gsm48_ie, 2); + if (num == 1) + return 0; + + lv[3] = modes[0].threshold & 0x3f; + lv[4] = modes[0].hysteresis << 4; + if (num == 2) + return 0; + lv[4] |= (modes[1].threshold & 0x3f) >> 2; + lv[5] = modes[1].threshold << 6; + lv[5] |= (modes[1].hysteresis & 0x0f) << 2; + if (num == 3) + return 0; + lv[5] |= (modes[2].threshold & 0x3f) >> 4; + lv[6] = modes[2].threshold << 4; + lv[6] |= modes[2].hysteresis & 0x0f; + + return 0; +} + +#define GSM48_HOCMD_CCHDESC_LEN 16 + +/* Chapter 9.1.15: Handover Command */ +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_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)); + + msg->lchan = old_lchan; + 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); + ho->ho_ref = ho_ref; + ho->power_command = power_command; + + if (new_lchan->ts->hopping.enabled) { + struct gsm_bts *bts = new_lchan->ts->trx->bts; + struct gsm48_system_information_type_1 *si1; + uint8_t *cur; + + si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1); + /* Copy the Cell Chan Desc (ARFCNS in this cell) */ + msgb_put_u8(msg, GSM48_IE_CELL_CH_DESC); + cur = msgb_put(msg, GSM48_HOCMD_CCHDESC_LEN); + memcpy(cur, si1->cell_channel_description, + GSM48_HOCMD_CCHDESC_LEN); + /* Copy the Mobile Allocation */ + msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, + new_lchan->ts->hopping.ma_len, + new_lchan->ts->hopping.ma_data); + } + /* FIXME: optional bits for type of synchronization? */ + + msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->tch_mode); + + /* 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); + + 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) +{ + 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)); + + DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode); + + msg->lchan = dest_lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_ASS_CMD; + + /* + * fill the channel information element, this code + * should probably be shared with rsl_rx_chan_rqd(), + * gsm48_lchan_modify(). But beware that 10.5.2.5 + * 10.5.2.5.a have slightly different semantic for + * 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); + ass->power_command = power_command; + + /* optional: cell channel description */ + + msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode); + + /* mobile allocation in case of hopping */ + if (lchan->ts->hopping.enabled) { + msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, lchan->ts->hopping.ma_len, + lchan->ts->hopping.ma_data); + } + + /* in case of multi rate we need to attach a config */ + mr_config_for_ms(lchan, msg); + + return gsm48_sendmsg(msg); +} + +/* 9.1.5 Channel mode modify: Modify the mode on the MS side */ +int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t mode) +{ + 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)); + + DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", 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); + cmm->mode = mode; + + /* in case of multi rate we need to attach a config */ + mr_config_for_ms(lchan, msg); + + return gsm48_sendmsg(msg); +} + +int gsm48_rx_rr_modif_ack(struct msgb *msg) +{ + int rc; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_chan_mode_modify *mod = + (struct gsm48_chan_mode_modify *) gh->data; + + DEBUGP(DRR, "CHANNEL MODE MODIFY ACK\n"); + + if (mod->mode != msg->lchan->tch_mode) { + LOGP(DRR, LOGL_ERROR, "CHANNEL MODE change failed. Wanted: %d Got: %d\n", + msg->lchan->tch_mode, 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; + } + + /* We've successfully modified the MS side of the channel, + * now go on to modify the BTS side of the channel */ + rc = rsl_chan_mode_modify_req(msg->lchan); + + /* FIXME: we not only need to do this after mode modify, but + * also after channel activation */ + if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN) + rsl_ipacc_crcx(msg->lchan); + return rc; +} + +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 gsm_meas_rep_cell *mrc; + + if (gh->msg_type != GSM48_MT_RR_MEAS_REP) + return -EINVAL; + + if (data[0] & 0x80) + rep->flags |= MEAS_REP_F_BA1; + if (data[0] & 0x40) + rep->flags |= MEAS_REP_F_UL_DTX; + if ((data[1] & 0x40) == 0x00) + rep->flags |= MEAS_REP_F_DL_VALID; + + rep->dl.full.rx_lev = data[0] & 0x3f; + rep->dl.sub.rx_lev = data[1] & 0x3f; + rep->dl.full.rx_qual = (data[2] >> 4) & 0x7; + rep->dl.sub.rx_qual = (data[2] >> 1) & 0x7; + + rep->num_cell = ((data[3] >> 6) & 0x3) | ((data[2] & 0x01) << 2); + if (rep->num_cell < 1 || rep->num_cell > 6) { + /* There are no neighbor cell reports present. */ + rep->num_cell = 0; + return 0; + } + + /* 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); + mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5); + if (rep->num_cell < 2) + return 0; + + 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); + mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4); + if (rep->num_cell < 3) + return 0; + + 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); + mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3); + if (rep->num_cell < 4) + return 0; + + 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); + mrc->bsic = data[11] >> 2; + if (rep->num_cell < 5) + return 0; + + 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); + mrc->bsic = (data[13] >> 1) & 0x3f; + if (rep->num_cell < 6) + return 0; + + 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); + mrc->bsic = data[15] & 0x3f; + + return 0; +} + +/* 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"); + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +/* 9.2.6 CM service reject */ +int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, + enum gsm48_reject_value value) +{ + struct msgb *msg; + + msg = gsm48_create_mm_serv_rej(value); + if (!msg) { + LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n"); + return -1; + } + + DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value); + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +/* 9.1.29 RR Status */ +struct msgb *gsm48_create_rr_status(uint8_t cause) +{ + struct msgb *msg; + struct gsm48_hdr *gh; + + msg = gsm48_msgb_alloc_name("GSM 04.08 RR STATUS"); + if (!msg) + return NULL; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_STATUS; + gh->data[0] = cause; + + return msg; +} + +/* 9.1.29 RR Status */ +int gsm48_tx_rr_status(struct gsm_subscriber_connection *conn, uint8_t cause) +{ + struct msgb *msg = gsm48_create_rr_status(cause); + if (!msg) + return -1; + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value) +{ + struct msgb *msg; + struct gsm48_hdr *gh; + + msg = gsm48_msgb_alloc_name("GSM 04.08 SERV REJ"); + if (!msg) + return NULL; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_CM_SERV_REJ; + gh->data[0] = value; + + return msg; +} + +struct msgb *gsm48_create_loc_upd_rej(uint8_t cause) +{ + struct gsm48_hdr *gh; + struct msgb *msg; + + msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD REJ"); + if (!msg) + return NULL; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_LOC_UPD_REJECT; + gh->data[0] = cause; + return msg; +} + +int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type) +{ + /* Check the size for the classmark */ + if (length < 1 + *classmark2_lv) + return -1; + + uint8_t *mi_lv = classmark2_lv + *classmark2_lv + 1; + if (length < 2 + *classmark2_lv + mi_lv[0]) + return -2; + + *mi_type = mi_lv[1] & GSM_MI_TYPE_MASK; + return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv); +} + +int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length, + char *mi_string, uint8_t *mi_type) +{ + static const uint32_t classmark_offset = + offsetof(struct gsm48_pag_resp, classmark2); + uint8_t *classmark2_lv = (uint8_t *) &resp->classmark2; + return gsm48_extract_mi(classmark2_lv, length - classmark_offset, + mi_string, mi_type); +} diff --git a/src/osmo-bsc/gsm_04_80_utils.c b/src/osmo-bsc/gsm_04_80_utils.c new file mode 100644 index 000000000..d67f3c568 --- /dev/null +++ b/src/osmo-bsc/gsm_04_80_utils.c @@ -0,0 +1,40 @@ +/* OpenBSC utility functions for 3GPP TS 04.80 */ + +/* (C) 2016 by sysmocom s.m.f.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 . + * + */ + +#include +#include + +int bsc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, + const char *text) +{ + struct msgb *msg = gsm0480_create_ussd_notify(level, text); + if (!msg) + return -1; + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn) +{ + struct msgb *msg = gsm0480_create_ussd_release_complete(); + if (!msg) + return -1; + return gsm0808_submit_dtap(conn, msg, 0, 0); +} diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c new file mode 100644 index 000000000..0f062d25a --- /dev/null +++ b/src/osmo-bsc/gsm_data.c @@ -0,0 +1,1305 @@ +/* (C) 2008-2018 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +void *tall_bsc_ctx = NULL; + +static LLIST_HEAD(bts_models); + +void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr, + uint8_t e1_ts, uint8_t e1_ts_ss) +{ + ts->e1_link.e1_nr = e1_nr; + ts->e1_link.e1_ts = e1_ts; + ts->e1_link.e1_ts_ss = e1_ts_ss; +} + +static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type) +{ + struct gsm_bts_model *model; + + llist_for_each_entry(model, &bts_models, list) { + if (model->type == type) + return model; + } + + return NULL; +} + +int gsm_bts_model_register(struct gsm_bts_model *model) +{ + if (bts_model_find(model->type)) + return -EEXIST; + + tlv_def_patch(&model->nm_att_tlvdef, &abis_nm_att_tlvdef); + llist_add_tail(&model->list, &bts_models); + return 0; +} + +const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = { + { GSM_BTS_TYPE_UNKNOWN, "Unknown BTS Type" }, + { GSM_BTS_TYPE_BS11, "Siemens BTS (BS-11 or compatible)" }, + { 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" }, + { 0, NULL } +}; + +struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr == nr) + return trx; + } + return NULL; +} + +/* Search for a BTS in the given Location Area; optionally start searching + * with start_bts (for continuing to search after the first result) */ +struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac, + struct gsm_bts *start_bts) +{ + int i; + struct gsm_bts *bts; + int skip = 0; + + if (start_bts) + skip = 1; + + for (i = 0; i < net->num_bts; i++) { + bts = gsm_bts_num(net, i); + + if (skip) { + if (start_bts == bts) + skip = 0; + continue; + } + + if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac) + return bts; + } + return NULL; +} + +static const struct value_string bts_gprs_mode_names[] = { + { BTS_GPRS_NONE, "none" }, + { BTS_GPRS_GPRS, "gprs" }, + { BTS_GPRS_EGPRS, "egprs" }, + { 0, NULL } +}; + +enum bts_gprs_mode bts_gprs_mode_parse(const char *arg, int *valid) +{ + int rc; + + rc = get_string_value(bts_gprs_mode_names, arg); + if (valid) + *valid = rc != -EINVAL; + return rc; +} + +const char *bts_gprs_mode_name(enum bts_gprs_mode mode) +{ + return get_value_string(bts_gprs_mode_names, mode); +} + +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)) { + return 0; + } + if (mode == BTS_GPRS_EGPRS && + !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_EGPRS)) { + return 0; + } + + return 1; +} + +int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type) +{ + struct gsm_bts_model *model; + + model = bts_model_find(type); + if (!model) + return -EINVAL; + + bts->type = type; + bts->model = model; + + if (model->start && !model->started) { + int ret = model->start(bts->network); + if (ret < 0) + return ret; + + model->started = true; + } + + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + /* Set the default OML Stream ID to 0xff */ + bts->oml_tei = 0xff; + bts->c0->nominal_power = 23; + break; + case GSM_BTS_TYPE_RBS2000: + INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups); + INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups); + break; + case GSM_BTS_TYPE_BS11: + case GSM_BTS_TYPE_UNKNOWN: + case GSM_BTS_TYPE_NOKIA_SITE: + /* Set default BTS reset timer */ + bts->nokia.bts_reset_timer_cnf = 15; + case _NUM_GSM_BTS_TYPE: + break; + } + + return 0; +} + +struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, + uint8_t bsic) +{ + struct gsm_bts_model *model = bts_model_find(type); + struct gsm_bts *bts; + + if (!model && type != GSM_BTS_TYPE_UNKNOWN) + return NULL; + + bts = gsm_bts_alloc(net, net->num_bts); + if (!bts) + return NULL; + + net->num_bts++; + + bts->type = type; + bts->model = model; + bts->bsic = bsic; + + llist_add_tail(&bts->list, &net->bts_list); + + return bts; +} + +void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts) +{ + *raid = (struct gprs_ra_id){ + .mcc = bts->network->plmn.mcc, + .mnc = bts->network->plmn.mnc, + .mnc_3_digits = bts->network->plmn.mnc_3_digits, + .lac = bts->location_area_code, + .rac = bts->gprs.rac, + }; +} + +void gsm48_ra_id_by_bts(struct gsm48_ra_id *buf, struct gsm_bts *bts) +{ + struct gprs_ra_id raid; + + gprs_ra_id_by_bts(&raid, bts); + gsm48_encode_ra(buf, &raid); +} + +int gsm_parse_reg(void *ctx, regex_t *reg, char **str, int argc, const char **argv) +{ + int ret; + + ret = 0; + if (*str) { + talloc_free(*str); + *str = NULL; + } + regfree(reg); + + if (argc > 0) { + *str = talloc_strdup(ctx, argv[0]); + ret = regcomp(reg, argv[0], 0); + + /* handle compilation failures */ + if (ret != 0) { + talloc_free(*str); + *str = NULL; + } + } + + return ret; +} + +/* Assume there are only 256 possible bts */ +osmo_static_assert(sizeof(((struct gsm_bts *) 0)->nr) == 1, _bts_nr_is_256); +static void depends_calc_index_bit(int bts_nr, int *idx, int *bit) +{ + *idx = bts_nr / (8 * 4); + *bit = bts_nr % (8 * 4); +} + +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; +} + +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); +} + +int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other) +{ + int idx, bit; + depends_calc_index_bit(other->nr, &idx, &bit); + + /* Check if there is a depends bit */ + return (base->depends_on[idx] & (1 << bit)) > 0; +} + +static int bts_is_online(struct gsm_bts *bts) +{ + /* TODO: support E1 BTS too */ + if (!is_ipaccess_bts(bts)) + return 1; + + if (!bts->oml_link) + return 0; + + return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + +int bts_depend_check(struct gsm_bts *bts) +{ + struct gsm_bts *other_bts; + + llist_for_each_entry(other_bts, &bts->network->bts_list, list) { + if (!bts_depend_is_depedency(bts, other_bts)) + continue; + if (bts_is_online(other_bts)) + continue; + return 0; + } + return 1; +} + +/* get the radio link timeout (based on SACCH decode errors, according + * to algorithm specified in TS 05.08 section 5.2. A value of -1 + * indicates we should use an infinitely long timeout, which only works + * with OsmoBTS as the BTS implementation */ +int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts) +{ + const struct gsm48_cell_options *cell_options = &bts->si_common.cell_options; + + if (bts->infinite_radio_link_timeout) + return -1; + else { + /* Encoding as per Table 10.5.21 of TS 04.08 */ + return (cell_options->radio_link_timeout + 1) << 2; + } +} + +/* set the radio link timeout (based on SACCH decode errors, according + * to algorithm specified in TS 05.08 Section 5.2. A value of -1 + * indicates we should use an infinitely long timeout, which only works + * with OsmoBTS as the BTS implementation */ +void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value) +{ + struct gsm48_cell_options *cell_options = &bts->si_common.cell_options; + + if (value < 0) + bts->infinite_radio_link_timeout = true; + else { + bts->infinite_radio_link_timeout = false; + /* Encoding as per Table 10.5.21 of TS 04.08 */ + if (value < 4) + value = 4; + if (value > 64) + value = 64; + cell_options->radio_link_timeout = (value >> 2) - 1; + } +} + +bool classmark_is_r99(struct gsm_classmark *cm) +{ + int rev_lev = 0; + if (cm->classmark1_set) + rev_lev = cm->classmark1.rev_lev; + else if (cm->classmark2_len > 0) + rev_lev = (cm->classmark2[0] >> 5) & 0x3; + return rev_lev >= 2; +} + +static const struct osmo_stat_item_desc bts_stat_desc[] = { + { "chanloadavg", "Channel load average.", "%", 16, 0 }, + { "T3122", "T3122 IMMEDIATE ASSIGNMENT REJECT wait indicator.", "s", 16, GSM_T3122_DEFAULT }, +}; + +static const struct osmo_stat_item_group_desc bts_statg_desc = { + .group_name_prefix = "bts", + .group_description = "base transceiver station", + .class_id = OSMO_STATS_CLASS_GLOBAL, + .num_items = ARRAY_SIZE(bts_stat_desc), + .item_desc = bts_stat_desc, +}; + +void gsm_abis_mo_reset(struct gsm_abis_mo *mo) +{ + mo->nm_state.operational = NM_OPSTATE_NULL; + mo->nm_state.availability = NM_AVSTATE_POWER_OFF; +} + +static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts, + uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3) +{ + mo->bts = bts; + mo->obj_class = obj_class; + mo->obj_inst.bts_nr = p1; + mo->obj_inst.trx_nr = p2; + mo->obj_inst.ts_nr = p3; + gsm_abis_mo_reset(mo); +} + +const struct value_string bts_attribute_names[] = { + OSMO_VALUE_STRING(BTS_TYPE_VARIANT), + OSMO_VALUE_STRING(BTS_SUB_MODEL), + OSMO_VALUE_STRING(TRX_PHY_VERSION), + { 0, NULL } +}; + +enum bts_attribute str2btsattr(const char *s) +{ + return get_string_value(bts_attribute_names, s); +} + +const char *btsatttr2str(enum bts_attribute v) +{ + return get_value_string(bts_attribute_names, v); +} + +const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = { + { BTS_UNKNOWN, "unknown" }, + { BTS_OSMO_LITECELL15, "osmo-bts-lc15" }, + { BTS_OSMO_OCTPHY, "osmo-bts-octphy" }, + { BTS_OSMO_SYSMO, "osmo-bts-sysmo" }, + { BTS_OSMO_TRX, "omso-bts-trx" }, + { 0, NULL } +}; + +enum gsm_bts_type_variant str2btsvariant(const char *arg) +{ + return get_string_value(osmo_bts_variant_names, arg); +} + +const char *btsvariant2str(enum gsm_bts_type_variant v) +{ + return get_value_string(osmo_bts_variant_names, v); +} + +const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = { + { GSM_BTS_TYPE_UNKNOWN, "unknown" }, + { GSM_BTS_TYPE_BS11, "bs11" }, + { GSM_BTS_TYPE_NANOBTS, "nanobts" }, + { GSM_BTS_TYPE_RBS2000, "rbs2000" }, + { GSM_BTS_TYPE_NOKIA_SITE, "nokia_site" }, + { GSM_BTS_TYPE_OSMOBTS, "sysmobts" }, + { 0, NULL } +}; + +enum gsm_bts_type str2btstype(const char *arg) +{ + return get_string_value(bts_type_names, arg); +} + +const char *btstype2str(enum gsm_bts_type type) +{ + return get_value_string(bts_type_names, type); +} + +const struct value_string gsm_chreq_descs[] = { + { GSM_CHREQ_REASON_EMERG, "emergency call" }, + { GSM_CHREQ_REASON_PAG, "answer to paging" }, + { GSM_CHREQ_REASON_CALL, "call re-establishment" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" }, + { GSM_CHREQ_REASON_PDCH, "one phase packet access" }, + { GSM_CHREQ_REASON_OTHER, "other" }, + { 0, NULL } +}; + +const struct value_string gsm_pchant_names[13] = { + { GSM_PCHAN_NONE, "NONE" }, + { GSM_PCHAN_CCCH, "CCCH" }, + { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" }, + { GSM_PCHAN_TCH_F, "TCH/F" }, + { GSM_PCHAN_TCH_H, "TCH/H" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" }, + { GSM_PCHAN_PDCH, "PDCH" }, + { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" }, + { GSM_PCHAN_UNKNOWN, "UNKNOWN" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" }, + { 0, NULL } +}; + +const struct value_string gsm_pchant_descs[13] = { + { GSM_PCHAN_NONE, "Physical Channel not configured" }, + { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" }, + { GSM_PCHAN_CCCH_SDCCH4, + "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" }, + { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" }, + { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" }, + { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" }, + { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" }, + { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" }, + { 0, NULL } +}; + +const char *gsm_pchan_name(enum gsm_phys_chan_config c) +{ + return get_value_string(gsm_pchant_names, c); +} + +enum gsm_phys_chan_config gsm_pchan_parse(const char *name) +{ + return get_string_value(gsm_pchant_names, name); +} + +/* TODO: move to libosmocore, next to gsm_chan_t_names? */ +const char *gsm_lchant_name(enum gsm_chan_t c) +{ + return get_value_string(gsm_chan_t_names, c); +} + +static const struct value_string lchan_s_names[] = { + { LCHAN_S_NONE, "NONE" }, + { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" }, + { LCHAN_S_ACTIVE, "ACTIVE" }, + { LCHAN_S_INACTIVE, "INACTIVE" }, + { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, + { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, + { LCHAN_S_BROKEN, "BROKEN UNUSABLE" }, + { 0, NULL } +}; + +const char *gsm_lchans_name(enum gsm_lchan_state s) +{ + return get_value_string(lchan_s_names, s); +} + +static const struct value_string chreq_names[] = { + { GSM_CHREQ_REASON_EMERG, "EMERGENCY" }, + { GSM_CHREQ_REASON_PAG, "PAGING" }, + { GSM_CHREQ_REASON_CALL, "CALL" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" }, + { GSM_CHREQ_REASON_OTHER, "OTHER" }, + { 0, NULL } +}; + +const char *gsm_chreq_name(enum gsm_chreq_reason_t c) +{ + return get_value_string(chreq_names, c); +} + +struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) +{ + struct gsm_bts *bts; + + if (num >= net->num_bts) + return NULL; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->nr == num) + return bts; + } + + return NULL; +} + +struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx); + int k; + + if (!trx) + return NULL; + + trx->bts = bts; + trx->nr = bts->num_trx++; + trx->mo.nm_state.administrative = NM_STATE_UNLOCKED; + + gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER, + bts->nr, trx->nr, 0xff); + gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC, + bts->nr, trx->nr, 0xff); + + for (k = 0; k < TRX_NR_TS; k++) { + struct gsm_bts_trx_ts *ts = &trx->ts[k]; + int l; + + ts->trx = trx; + ts->nr = k; + ts->pchan = GSM_PCHAN_NONE; + ts->dyn.pchan_is = GSM_PCHAN_NONE; + ts->dyn.pchan_want = GSM_PCHAN_NONE; + ts->tsc = -1; + + gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL, + bts->nr, trx->nr, ts->nr); + + ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data); + ts->hopping.arfcns.data = ts->hopping.arfcns_data; + ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data); + 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); + } + } + + if (trx->nr != 0) + trx->nominal_power = bts->c0->nominal_power; + + llist_add_tail(&trx->list, &bts->trx_list); + + return trx; +} + + +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 struct gprs_rlc_cfg rlc_cfg_default = { + .parameter = { + [RLC_T3142] = 20, + [RLC_T3169] = 5, + [RLC_T3191] = 5, + [RLC_T3193] = 160, /* 10ms */ + [RLC_T3195] = 5, + [RLC_N3101] = 10, + [RLC_N3103] = 4, + [RLC_N3105] = 8, + [CV_COUNTDOWN] = 15, + [T_DL_TBF_EXT] = 250 * 10, /* ms */ + [T_UL_TBF_EXT] = 250 * 10, /* ms */ + }, + .paging = { + .repeat_time = 5 * 50, /* ms */ + .repeat_count = 3, + }, + .cs_mask = 0x1fff, + .initial_cs = 2, + .initial_mcs = 6, +}; + +/* Initialize those parts that don't require osmo-bsc specific dependencies. + * 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 *bts = talloc_zero(net, struct gsm_bts); + int i; + + if (!bts) + return NULL; + + bts->nr = bts_num; + bts->num_trx = 0; + INIT_LLIST_HEAD(&bts->trx_list); + bts->network = net; + + bts->ms_max_power = 15; /* dBm */ + + gsm_mo_init(&bts->mo, bts, NM_OC_BTS, + bts->nr, 0xff, 0xff); + gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER, + 0xff, 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); + 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)); + + /* init statistics */ + bts->bts_ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr); + if (!bts->bts_ctrs) { + talloc_free(bts); + return NULL; + } + bts->bts_statg = osmo_stat_item_group_alloc(bts, &bts_statg_desc, 0); + + /* create our primary TRX */ + bts->c0 = gsm_bts_trx_alloc(bts); + if (!bts->c0) { + rate_ctr_group_free(bts->bts_ctrs); + osmo_stat_item_group_free(bts->bts_statg); + talloc_free(bts); + return NULL; + } + bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4; + + bts->rach_b_thresh = -1; + bts->rach_ldavg_slots = -1; + + bts->paging.free_chans_need = -1; + INIT_LLIST_HEAD(&bts->paging.pending_requests); + + bts->features.data = &bts->_features_data[0]; + bts->features.data_len = sizeof(bts->_features_data); + + /* si handling */ + bts->bcch_change_mark = 1; + bts->chan_load_avg = 0; + + /* timer overrides */ + bts->T3122 = 0; /* not overriden by default */ + + bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED; + bts->dtxd = false; + bts->gprs.ctrl_ack_type_use_block = true; /* use RLC/MAC control block */ + bts->neigh_list_manual_mode = 0; + bts->early_classmark_allowed_3g = true; /* 3g Early Classmark Sending controlled by bts->early_classmark_allowed param */ + 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; + bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list; + bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST; + bts->si_common.si2quater_neigh_list.thresh_hi = 0; + osmo_earfcn_init(&bts->si_common.si2quater_neigh_list); + bts->si_common.neigh_list.data = bts->si_common.data.neigh_list; + bts->si_common.neigh_list.data_len = + sizeof(bts->si_common.data.neigh_list); + bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list; + bts->si_common.si5_neigh_list.data_len = + sizeof(bts->si_common.data.si5_neigh_list); + bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc; + bts->si_common.cell_alloc.data_len = + sizeof(bts->si_common.data.cell_alloc); + bts->si_common.rach_control.re = 1; /* no re-establishment */ + 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.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 = net->t3212; /* Use network's current value */ + 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); + + return bts; +} + +/* reset the state of all MO in the BTS */ +void gsm_bts_mo_reset(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + 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) { + gsm_abis_mo_reset(&trx->mo); + gsm_abis_mo_reset(&trx->bb_transc.mo); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + gsm_abis_mo_reset(&ts->mo); + } + } +} + +struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num) +{ + struct gsm_bts_trx *trx; + + if (num >= bts->num_trx) + return NULL; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr == num) + return trx; + } + + return NULL; +} + +static char ts2str[255]; + +char *gsm_trx_name(const struct gsm_bts_trx *trx) +{ + if (!trx) + snprintf(ts2str, sizeof(ts2str), "(trx=NULL)"); + else + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)", + trx->bts->nr, trx->nr); + + return ts2str; +} + + +char *gsm_ts_name(const struct gsm_bts_trx_ts *ts) +{ + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr); + + return ts2str; +} + +/*! Log timeslot number with full pchan information */ +char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is)); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s" + " switching %s -> %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is), + gsm_pchan_name(ts->dyn.pchan_want)); + break; + case GSM_PCHAN_TCH_F_PDCH: + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s" + " switching %s -> %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F", + (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" + : "TCH/F"); + break; + default: + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan)); + break; + } + + return ts2str; +} + +char *gsm_lchan_name_compute(const struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr); + + return ts2str; +} + +/* obtain the MO structure for a given object instance */ +static inline struct gsm_abis_mo * +gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_bts_trx *trx; + struct gsm_abis_mo *mo = NULL; + + switch (obj_class) { + case NM_OC_BTS: + mo = &bts->mo; + break; + case NM_OC_RADIO_CARRIER: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->mo; + break; + case NM_OC_BASEB_TRANSC: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->bb_transc.mo; + break; + case NM_OC_CHANNEL: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + if (obj_inst->ts_nr >= TRX_NR_TS) + return NULL; + mo = &trx->ts[obj_inst->ts_nr].mo; + break; + case NM_OC_SITE_MANAGER: + mo = &bts->site_mgr.mo; + break; + case NM_OC_BS11: + switch (obj_inst->bts_nr) { + case BS11_OBJ_CCLK: + mo = &bts->bs11.cclk.mo; + break; + case BS11_OBJ_BBSIG: + if (obj_inst->ts_nr > bts->num_trx) + return NULL; + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->bs11.bbsig.mo; + break; + case BS11_OBJ_PA: + if (obj_inst->ts_nr > bts->num_trx) + return NULL; + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->bs11.pa.mo; + break; + default: + return NULL; + } + break; + case NM_OC_BS11_RACK: + mo = &bts->bs11.rack.mo; + break; + case NM_OC_BS11_ENVABTSE: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse)) + return NULL; + mo = &bts->bs11.envabtse[obj_inst->trx_nr].mo; + break; + case NM_OC_GPRS_NSE: + mo = &bts->gprs.nse.mo; + break; + case NM_OC_GPRS_CELL: + mo = &bts->gprs.cell.mo; + break; + case NM_OC_GPRS_NSVC: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + return NULL; + mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo; + break; + } + return mo; +} + +/* obtain the gsm_nm_state data structure for a given object instance */ +struct gsm_nm_state * +gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_abis_mo *mo; + + mo = gsm_objclass2mo(bts, obj_class, obj_inst); + if (!mo) + return NULL; + + return &mo->nm_state; +} + +/* obtain the in-memory data structure of a given object instance */ +void * +gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_bts_trx *trx; + void *obj = NULL; + + switch (obj_class) { + case NM_OC_BTS: + obj = bts; + break; + case NM_OC_RADIO_CARRIER: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + obj = trx; + break; + case NM_OC_BASEB_TRANSC: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + obj = &trx->bb_transc; + break; + case NM_OC_CHANNEL: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + if (obj_inst->ts_nr >= TRX_NR_TS) + return NULL; + obj = &trx->ts[obj_inst->ts_nr]; + break; + case NM_OC_SITE_MANAGER: + obj = &bts->site_mgr; + break; + case NM_OC_GPRS_NSE: + obj = &bts->gprs.nse; + break; + case NM_OC_GPRS_CELL: + obj = &bts->gprs.cell; + break; + case NM_OC_GPRS_NSVC: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + return NULL; + obj = &bts->gprs.nsvc[obj_inst->trx_nr]; + break; + } + return obj; +} + +/* See Table 10.5.25 of GSM04.08 */ +uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, + uint8_t ts_nr, uint8_t lchan_nr) +{ + uint8_t cbits, chan_nr; + + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_F_PDCH: + OSMO_ASSERT(lchan_nr == 0); + cbits = 0x01; + break; + case GSM_PCHAN_PDCH: + OSMO_ASSERT(lchan_nr == 0); + cbits = RSL_CHAN_OSMO_PDCH >> 3; + break; + case GSM_PCHAN_TCH_H: + OSMO_ASSERT(lchan_nr < 2); + cbits = 0x02; + cbits += lchan_nr; + break; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* + * As a special hack for BCCH, lchan_nr == 4 may be passed + * here. This should never be sent in an RSL message. + * See osmo-bts-xxx/oml.c:opstart_compl(). + */ + if (lchan_nr == CCCH_LCHAN) + chan_nr = 0; + else + OSMO_ASSERT(lchan_nr < 4); + cbits = 0x04; + cbits += lchan_nr; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + OSMO_ASSERT(lchan_nr < 8); + cbits = 0x08; + cbits += lchan_nr; + break; + default: + case GSM_PCHAN_CCCH: + OSMO_ASSERT(lchan_nr == 0); + cbits = 0x10; + break; + } + + chan_nr = (cbits << 3) | (ts_nr & 0x7); + + return chan_nr; +} + +uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + return gsm_lchan_as_pchan2chan_nr(lchan, + lchan->ts->dyn.pchan_is); + return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr); +} + +uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan, + enum gsm_phys_chan_config as_pchan) +{ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && as_pchan == GSM_PCHAN_PDCH) + return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK); + return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr); +} + +/* 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; + + if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + lchan = &trx->ts[0].lchan[2]; + else { + int i; + for (i = 0; i < 8; i++) { + if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) { + lchan = &trx->ts[i].lchan[2]; + break; + } + } + } + + return lchan; +} + +/* determine logical channel based on TRX and channel number IE */ +struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, + int *rc) +{ + uint8_t ts_nr = chan_nr & 0x07; + uint8_t cbits = chan_nr >> 3; + uint8_t lch_idx; + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + bool ok = true; + + if (rc) + *rc = -EINVAL; + + if (cbits == 0x01) { + lch_idx = 0; /* TCH/F */ + if (ts->pchan != GSM_PCHAN_TCH_F && + ts->pchan != GSM_PCHAN_PDCH && + ts->pchan != GSM_PCHAN_TCH_F_PDCH + && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && (ts->dyn.pchan_is == GSM_PCHAN_TCH_F + || ts->dyn.pchan_want == GSM_PCHAN_TCH_F))) + ok = false; + } else if ((cbits & 0x1e) == 0x02) { + lch_idx = cbits & 0x1; /* TCH/H */ + if (ts->pchan != GSM_PCHAN_TCH_H + && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && (ts->dyn.pchan_is == GSM_PCHAN_TCH_H + || ts->dyn.pchan_want == GSM_PCHAN_TCH_H))) + ok = false; + } else if ((cbits & 0x1c) == 0x04) { + lch_idx = cbits & 0x3; /* SDCCH/4 */ + if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) + ok = false; + } else if ((cbits & 0x18) == 0x08) { + lch_idx = cbits & 0x7; /* SDCCH/8 */ + if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C && + ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH) + ok = false; + } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_CCCH && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) + ok = false; + /* FIXME: we should not return first sdcch4 !!! */ + } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + ok = false; + } else + return NULL; + + if (rc && ok) + *rc = 0; + + return &ts->lchan[lch_idx]; +} + +static const uint8_t subslots_per_pchan[] = { + [GSM_PCHAN_NONE] = 0, + [GSM_PCHAN_CCCH] = 0, + [GSM_PCHAN_PDCH] = 0, + [GSM_PCHAN_CCCH_SDCCH4] = 4, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 2, + [GSM_PCHAN_SDCCH8_SACCH8C] = 8, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, those TS are handled according to their dynamic state. + */ +}; + +/*! Return the actual pchan type, also heeding dynamic TS. */ +enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + else + return GSM_PCHAN_TCH_F; + default: + return ts->pchan; + } +} + +/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of + * logical channels available in the timeslot. */ +uint8_t ts_subslots(struct gsm_bts_trx_ts *ts) +{ + return subslots_per_pchan[ts_pchan(ts)]; +} + +static bool pchan_is_tch(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + return true; + default: + return false; + } +} + +bool ts_is_tch(struct gsm_bts_trx_ts *ts) +{ + return pchan_is_tch(ts_pchan(ts)); +} + +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 (!nm_is_running(&trx->mo.nm_state) || + !nm_is_running(&trx->bb_transc.mo.nm_state)) + return false; + } + + return true; +} + +void gsm_trx_mark_all_ts_uninitialized(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]; + ts->initialized = false; + } +} + +void gsm_bts_mark_all_ts_uninitialized(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) + gsm_trx_mark_all_ts_uninitialized(trx); +} + +/* Trigger initial timeslot actions iff both OML and RSL are setup. */ +void gsm_ts_check_init(struct gsm_bts_trx_ts *ts) +{ + struct gsm_bts *bts = ts->trx->bts; + if (bts->model->oml_is_ts_ready + && !bts->model->oml_is_ts_ready(ts)) + return; + if (!ts->trx->rsl_link) + return; + if (ts->initialized) + return; + ts->initialized = on_gsm_ts_init(ts); +} + +void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, + const struct gsm_lchan *lchan) +{ + uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff; + + cd->chan_nr = gsm_lchan2chan_nr(lchan); + if (!lchan->ts->hopping.enabled) { + cd->h0.tsc = gsm_ts_tsc(lchan->ts); + cd->h0.h = 0; + cd->h0.arfcn_high = arfcn >> 8; + cd->h0.arfcn_low = arfcn & 0xff; + } else { + cd->h1.tsc = gsm_ts_tsc(lchan->ts); + cd->h1.h = 1; + cd->h1.maio_high = lchan->ts->hopping.maio >> 2; + cd->h1.maio_low = lchan->ts->hopping.maio & 0x03; + cd->h1.hsn = lchan->ts->hopping.hsn; + } +} + +bool nm_is_running(const struct gsm_nm_state *s) { + return (s->operational == NM_OPSTATE_ENABLED) && ( + (s->availability == NM_AVSTATE_OK) || + (s->availability == 0xff) + ); +} diff --git a/src/osmo-bsc/handover_cfg.c b/src/osmo-bsc/handover_cfg.c new file mode 100644 index 000000000..14f5d8e84 --- /dev/null +++ b/src/osmo-bsc/handover_cfg.c @@ -0,0 +1,86 @@ +/* OsmoBSC handover configuration implementation */ +/* (C) 2009-2010 by Andreas Eversberg + * (C) 2009-2010 by Harald Welte + * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Andreas Eversberg + * Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include + +struct handover_cfg { + struct handover_cfg *higher_level_cfg; + +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ + TYPE NAME; \ + bool has_##NAME; + + HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER +}; + +struct handover_cfg *ho_cfg_init(void *ctx, struct handover_cfg *higher_level_cfg) +{ + struct handover_cfg *ho = talloc_zero(ctx, struct handover_cfg); + OSMO_ASSERT(ho); + ho->higher_level_cfg = higher_level_cfg; + return ho; +} + +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY_ARG_EVAL, VTY4, VTY5, VTY6) \ +TYPE ho_get_##NAME(struct handover_cfg *ho) \ +{ \ + if (ho->has_##NAME) \ + return ho->NAME; \ + if (ho->higher_level_cfg) \ + return ho_get_##NAME(ho->higher_level_cfg); \ + return VTY_ARG_EVAL(#DEFAULT_VAL); \ +} \ +\ +void ho_set_##NAME(struct handover_cfg *ho, TYPE value) \ +{ \ + ho->NAME = value; \ + ho->has_##NAME = true; \ +} \ +\ +bool ho_isset_##NAME(struct handover_cfg *ho) \ +{ \ + return ho->has_##NAME; \ +} \ +\ +void ho_clear_##NAME(struct handover_cfg *ho) \ +{ \ + ho->has_##NAME = false; \ +} \ +\ +bool ho_isset_on_parent_##NAME(struct handover_cfg *ho) \ +{ \ + return ho->higher_level_cfg \ + && (ho_isset_##NAME(ho->higher_level_cfg) \ + || ho_isset_on_parent_##NAME(ho->higher_level_cfg)); \ +} + +HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER diff --git a/src/osmo-bsc/handover_decision.c b/src/osmo-bsc/handover_decision.c new file mode 100644 index 000000000..887c2993f --- /dev/null +++ b/src/osmo-bsc/handover_decision.c @@ -0,0 +1,343 @@ +/* Handover Decision making for Inter-BTS (Intra-BSC) Handover. This + * only implements the handover algorithm/decision, but not execution + * of it */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Find BTS by ARFCN and BSIC */ +struct gsm_bts *bts_by_arfcn_bsic(const struct gsm_network *net, + uint16_t arfcn, uint8_t bsic) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->c0->arfcn == arfcn && + bts->bsic == bsic) + return bts; + } + + return NULL; +} + + +/* issue handover to a cell identified by ARFCN and BSIC */ +static int handover_to_arfcn_bsic(struct gsm_lchan *lchan, + uint16_t arfcn, uint8_t bsic) +{ + struct gsm_bts *new_bts; + + /* resolve the gsm_bts structure for the best neighbor */ + /* FIXME: use some better heuristics here to determine which cell + * using this ARFCN really is closest to the target cell. For + * now we simply assume that each ARFCN will only be used by one + * cell */ + new_bts = bts_by_arfcn_bsic(lchan->ts->trx->bts->network, arfcn, bsic); + if (!new_bts) { + LOGP(DHODEC, LOGL_NOTICE, "unable to determine neighbor BTS " + "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic); + return -EINVAL; + } + + /* and actually try to handover to that cell */ + return bsc_handover_start(HODEC1, lchan, new_bts, lchan->type); +} + +/* did we get a RXLEV for a given cell in the given report? */ +static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr, + uint16_t arfcn, uint8_t bsic) +{ + int i; + + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + + /* search for matching report */ + if (!(mrc->arfcn == arfcn && mrc->bsic == bsic)) + continue; + + mrc->flags |= MRC_F_PROCESSED; + return mrc->rxlev; + } + return -ENODEV; +} + +/* obtain averaged rxlev for given neighbor */ +static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) +{ + unsigned int i, idx; + int avg = 0; + + /* reduce window to the actual number of existing measurements */ + if (window > nmp->rxlev_cnt) + window = nmp->rxlev_cnt; + /* this should never happen */ + if (window <= 0) { + LOGP(DHODEC, LOGL_ERROR, "Requested Neighbor RxLev for invalid window size of %d\n", window); + return 0; + } + + idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), + nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), + window); + + for (i = 0; i < window; i++) { + int j = (idx+i) % ARRAY_SIZE(nmp->rxlev); + + avg += nmp->rxlev[j]; + } + + return avg / window; +} + +/* find empty or evict bad neighbor */ +static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan) +{ + int j, worst = 999999; + struct neigh_meas_proc *nmp_worst = NULL; + + /* first try to find an empty/unused slot */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + if (!nmp->arfcn) + return nmp; + } + + /* no empty slot found. evict worst neighbor from list */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG); + if (!nmp_worst || avg < worst) { + worst = avg; + nmp_worst = nmp; + } + } + + return nmp_worst; +} + +/* process neighbor cell measurement reports */ +static void process_meas_neigh(struct gsm_meas_rep *mr) +{ + int i, j, idx; + + /* for each reported cell, try to update global state */ + for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j]; + unsigned int idx; + int rxlev; + + /* skip unused entries */ + if (!nmp->arfcn) + continue; + + rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic); + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + if (rxlev >= 0) { + nmp->rxlev[idx] = rxlev; + nmp->last_seen_nr = mr->nr; + } else + nmp->rxlev[idx] = 0; + nmp->rxlev_cnt++; + } + + /* iterate over list of reported cells, check if we did not + * process all of them */ + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + struct neigh_meas_proc *nmp; + + if (mrc->flags & MRC_F_PROCESSED) + continue; + + nmp = find_evict_neigh(mr->lchan); + + nmp->arfcn = mrc->arfcn; + nmp->bsic = mrc->bsic; + + nmp->rxlev_cnt = 0; + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + nmp->rxlev[idx] = mrc->rxlev; + nmp->rxlev_cnt++; + nmp->last_seen_nr = mr->nr; + + mrc->flags |= MRC_F_PROCESSED; + } +} + +/* attempt to do a handover */ +static int attempt_handover(struct gsm_meas_rep *mr) +{ + struct gsm_bts *bts = mr->lchan->ts->trx->bts; + struct neigh_meas_proc *best_cell = NULL; + unsigned int best_better_db = 0; + int i, rc; + + /* find the best cell in this report that is at least RXLEV_HYST + * better than the current serving cell */ + + for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i]; + int avg, better; + + /* skip empty slots */ + if (nmp->arfcn == 0) + continue; + + /* caculate average rxlev for this cell over the window */ + avg = neigh_meas_avg(nmp, ho_get_hodec1_rxlev_neigh_avg_win(bts->ho)); + + /* check if hysteresis is fulfilled */ + if (avg < mr->dl.full.rx_lev + ho_get_hodec1_pwr_hysteresis(bts->ho)) + continue; + + better = avg - mr->dl.full.rx_lev; + if (better > best_better_db) { + best_cell = nmp; + best_better_db = better; + } + } + + if (!best_cell) + return 0; + + LOGP(DHODEC, LOGL_INFO, "%s: Cell on ARFCN %u is better: ", + gsm_ts_name(mr->lchan->ts), best_cell->arfcn); + if (!ho_get_ho_active(bts->ho)) { + LOGPC(DHODEC, LOGL_INFO, "Skipping, Handover disabled\n"); + return 0; + } + + rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic); + switch (rc) { + case 0: + LOGPC(DHODEC, LOGL_INFO, "Starting handover: meas report number %d \n", mr->nr); + break; + case -ENOSPC: + LOGPC(DHODEC, LOGL_INFO, "No channel available\n"); + break; + case -EBUSY: + LOGPC(DHODEC, LOGL_INFO, "Handover already active\n"); + break; + default: + LOGPC(DHODEC, LOGL_ERROR, "Unknown error\n"); + } + return rc; +} + +/* process an already parsed measurement report and decide if we want to + * attempt a handover */ +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; + + /* If this cell does not use handover algorithm 1, then we're not responsible. */ + if (ho_get_algorithm(bts->ho) != 1) + return; + + /* we currently only do handover for TCH channels */ + switch (mr->lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + break; + default: + 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, + 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)) { + LOGPC(DHO, LOGL_INFO, "HO cause: Interference HO av_rxlev=%d dBm\n", + rxlev2dbm(av_rxlev)); + attempt_handover(mr); + return; + } + + /* Bad Quality */ + if (meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5)) { + LOGPC(DHO, LOGL_INFO, "HO cause: Bad Quality av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev)); + attempt_handover(mr); + return; + } + + /* Low Level */ + if (rxlev2dbm(av_rxlev) <= -110) { + LOGPC(DHO, LOGL_INFO, "HO cause: Low Level av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev)); + attempt_handover(mr); + return; + } + + /* Distance */ + if (mr->ms_l1.ta > ho_get_hodec1_max_distance(bts->ho)) { + LOGPC(DHO, LOGL_INFO, "HO cause: Distance av_rxlev=%d dBm ta=%d \n", + rxlev2dbm(av_rxlev), mr->ms_l1.ta); + attempt_handover(mr); + return; + } + + /* Power Budget AKA Better Cell */ + pwr_interval = ho_get_hodec1_pwr_interval(bts->ho); + /* handover_cfg.h defines pwr_interval as [1..99], but since we're using it in a modulo below, + * assert non-zero to clarify. */ + OSMO_ASSERT(pwr_interval); + if ((mr->nr % pwr_interval) == pwr_interval - 1) + attempt_handover(mr); +} + +struct handover_decision_callbacks hodec1_callbacks = { + .hodec_id = HODEC1, + .on_measurement_report = on_measurement_report, +}; + +void handover_decision_1_init(void) +{ + handover_decision_callbacks_register(&hodec1_callbacks); +} diff --git a/src/osmo-bsc/handover_decision_2.c b/src/osmo-bsc/handover_decision_2.c new file mode 100644 index 000000000..7ac54df95 --- /dev/null +++ b/src/osmo-bsc/handover_decision_2.c @@ -0,0 +1,1830 @@ +/* Handover Decision Algorithm 2 for intra-BSC (inter-BTS) handover, public API for OsmoBSC. */ + +/* (C) 2009 by Andreas Eversberg + * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Andreas Eversberg + * Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOGPHOBTS(bts, level, fmt, args...) \ + LOGP(DHODEC, level, "(BTS %u) " fmt, bts->nr, ## args) + +#define LOGPHOLCHAN(lchan, level, fmt, args...) \ + LOGP(DHODEC, level, "(lchan %u.%u%u%u %s) (subscr %s) " fmt, \ + lchan->ts->trx->bts->nr, \ + lchan->ts->trx->nr, \ + lchan->ts->nr, \ + lchan->nr, \ + gsm_pchan_name(lchan->ts->pchan), \ + bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ + ## args) + +#define LOGPHOLCHANTOBTS(lchan, new_bts, level, fmt, args...) \ + LOGP(DHODEC, level, "(lchan %u.%u%u%u %s)->(BTS %u) (subscr %s) " fmt, \ + lchan->ts->trx->bts->nr, \ + lchan->ts->trx->nr, \ + lchan->ts->nr, \ + lchan->nr, \ + gsm_pchan_name(lchan->ts->pchan), \ + new_bts->nr, \ + bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ + ## args) + +#define REQUIREMENT_A_TCHF 0x01 +#define REQUIREMENT_B_TCHF 0x02 +#define REQUIREMENT_C_TCHF 0x04 +#define REQUIREMENT_A_TCHH 0x10 +#define REQUIREMENT_B_TCHH 0x20 +#define REQUIREMENT_C_TCHH 0x40 +#define REQUIREMENT_TCHF_MASK (REQUIREMENT_A_TCHF | REQUIREMENT_B_TCHF | REQUIREMENT_C_TCHF) +#define REQUIREMENT_TCHH_MASK (REQUIREMENT_A_TCHH | REQUIREMENT_B_TCHH | REQUIREMENT_C_TCHH) +#define REQUIREMENT_A_MASK (REQUIREMENT_A_TCHF | REQUIREMENT_A_TCHH) +#define REQUIREMENT_B_MASK (REQUIREMENT_B_TCHF | REQUIREMENT_B_TCHH) +#define REQUIREMENT_C_MASK (REQUIREMENT_C_TCHF | REQUIREMENT_C_TCHH) + +struct ho_candidate { + struct gsm_lchan *lchan; /* candidate for whom */ + struct gsm_bts *bts; /* target BTS */ + uint8_t requirements; /* what is fulfilled */ + int avg; /* average RX level */ +}; + +enum ho_reason { + HO_REASON_INTERFERENCE, + HO_REASON_BAD_QUALITY, + HO_REASON_LOW_RXLEVEL, + HO_REASON_MAX_DISTANCE, + HO_REASON_BETTER_CELL, + HO_REASON_CONGESTION, +}; + +static const struct value_string ho_reason_names[] = { + { HO_REASON_INTERFERENCE, "interference (bad quality)" }, + { HO_REASON_BAD_QUALITY, "bad quality" }, + { HO_REASON_LOW_RXLEVEL, "low rxlevel" }, + { HO_REASON_MAX_DISTANCE, "maximum allowed distance" }, + { HO_REASON_BETTER_CELL, "better cell" }, + { HO_REASON_CONGESTION, "congestion" }, + {0, NULL} +}; + +static const char *ho_reason_name(int value) +{ + return get_value_string(ho_reason_names, value); +} + + +static bool hodec2_initialized = false; +static enum ho_reason global_ho_reason; + +static void congestion_check_cb(void *arg); + +/* 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) +{ + int congestion_check_interval_s; + bool was_active; + + /* Don't setup timers from VTY config parsing before the main program has actually initialized + * the data structures. */ + if (!hodec2_initialized) + return; + + was_active = net->hodec2.congestion_check_timer.active; + if (was_active) + osmo_timer_del(&net->hodec2.congestion_check_timer); + + congestion_check_interval_s = net->hodec2.congestion_check_interval_s; + if (congestion_check_interval_s < 1) { + if (was_active) + LOGP(DHODEC, LOGL_NOTICE, "HO algorithm 2: Disabling congestion check\n"); + return; + } + + LOGP(DHODEC, LOGL_DEBUG, "HO algorithm 2: next periodical congestion check in %u seconds\n", + congestion_check_interval_s); + + osmo_timer_setup(&net->hodec2.congestion_check_timer, + congestion_check_cb, net); + osmo_timer_schedule(&net->hodec2.congestion_check_timer, + congestion_check_interval_s, 0); +} + +void hodec2_on_change_congestion_check_interval(struct gsm_network *net, unsigned int new_interval) +{ + net->hodec2.congestion_check_interval_s = new_interval; + reinit_congestion_timer(net); +} + +static void conn_penalty_time_add(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, + int penalty_time) +{ + 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, bts, penalty_time); +} + +static unsigned int conn_penalty_time_remaining(struct gsm_subscriber_connection *conn, + struct gsm_bts *bts) +{ + if (!conn->hodec2.penalty_timers) + return 0; + return penalty_timers_remaining(conn->hodec2.penalty_timers, bts); +} + +/* 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) +{ + int i; + + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + + if (mrc->arfcn != arfcn) + continue; + if (mrc->bsic != bsic) + continue; + + return mrc; + } + return NULL; +} + +/* obtain averaged rxlev for given neighbor */ +static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) +{ + unsigned int i, idx; + int avg = 0; + + /* reduce window to the actual number of existing measurements */ + if (window > nmp->rxlev_cnt) + window = nmp->rxlev_cnt; + /* this should never happen */ + if (window <= 0) + return 0; + + idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), + nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), + window); + + for (i = 0; i < window; i++) { + int j = (idx+i) % ARRAY_SIZE(nmp->rxlev); + + avg += nmp->rxlev[j]; + } + + return avg / window; +} + +/* Find empty slot or the worst neighbor. */ +static struct neigh_meas_proc *find_unused_or_worst_neigh(struct gsm_lchan *lchan) +{ + struct neigh_meas_proc *nmp_worst = NULL; + int worst; + int j; + + /* First try to find an empty/unused slot. */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + if (!nmp->arfcn) + return nmp; + } + + /* No empty slot found. Return worst neighbor to be evicted. */ + worst = 0; /* (overwritten on first loop, but avoid compiler warning) */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG); + if (nmp_worst && avg >= worst) + continue; + worst = avg; + nmp_worst = nmp; + } + + return nmp_worst; +} + +/* process neighbor cell measurement reports */ +static void process_meas_neigh(struct gsm_meas_rep *mr) +{ + int i, j, idx; + + /* For each reported cell, try to update measurements we already have from previous reports. */ + for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j]; + unsigned int idx; + struct gsm_meas_rep_cell *mrc; + + /* skip unused entries */ + if (!nmp->arfcn) + continue; + + mrc = cell_in_rep(mr, nmp->arfcn, nmp->bsic); + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + if (mrc) { + nmp->rxlev[idx] = mrc->rxlev; + nmp->last_seen_nr = mr->nr; + LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u rxlev=%d last_seen_nr=%u\n", + nmp->arfcn, mrc->rxlev, nmp->last_seen_nr); + mrc->flags |= MRC_F_PROCESSED; + } else { + nmp->rxlev[idx] = 0; + LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u not in report (last_seen_nr=%u)\n", + nmp->arfcn, nmp->last_seen_nr); + } + nmp->rxlev_cnt++; + } + + /* Add cells that we don't know about yet, if necessary overwriting previous records that reflect + * cells with worse receive levels */ + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + struct neigh_meas_proc *nmp; + + if (mrc->flags & MRC_F_PROCESSED) + continue; + + nmp = find_unused_or_worst_neigh(mr->lchan); + + nmp->arfcn = mrc->arfcn; + nmp->bsic = mrc->bsic; + + nmp->rxlev_cnt = 0; + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + nmp->rxlev[idx] = mrc->rxlev; + nmp->rxlev_cnt++; + nmp->last_seen_nr = mr->nr; + LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u new in report rxlev=%d last_seen_nr=%u\n", + nmp->arfcn, mrc->rxlev, nmp->last_seen_nr); + + mrc->flags |= MRC_F_PROCESSED; + } +} + +static bool codec_type_is_supported(struct gsm_subscriber_connection *conn, + enum gsm0808_speech_codec_type type) +{ + int i; + struct gsm0808_speech_codec_list *clist = &conn->codec_list; + + if (!conn->codec_list_present) { + /* We don't have a list of supported codecs. This should never happen. */ + LOGPHOLCHAN(conn->lchan, LOGL_ERROR, + "No Speech Codec List present, accepting all codecs\n"); + return true; + } + + for (i = 0; i < clist->len; i++) { + if (clist->codec[i].type == type) { + LOGPHOLCHAN(conn->lchan, LOGL_DEBUG, "%s supported\n", + gsm0808_speech_codec_type_name(type)); + return true; + } + } + LOGPHOLCHAN(conn->lchan, LOGL_DEBUG, "Codec not supported by MS or not allowed by MSC: %s\n", + gsm0808_speech_codec_type_name(type)); + return false; +} + +/* + * Check what requirements the given cell fulfills. + * A bit mask of fulfilled requirements is returned. + * + * Target cell requirement A -- ability to service the call + * + * In order to successfully handover/assign to a better cell, the target cell + * must be able to continue the current call. Therefore the cell must fulfill + * the following criteria: + * + * * The handover must be enabled for the target cell, if it differs from the + * originating cell. + * * The assignment must be enabled for the cell, if it equals the current + * cell. + * * The handover penalty timer must not run for the cell. + * * If FR, EFR or HR codec is used, the cell must support this codec. + * * If FR or EFR codec is used, the cell must have a TCH/F slot type + * available. + * * If HR codec is used, the cell must have a TCH/H slot type available. + * * If AMR codec is used, the cell must have a TCH/F slot available, if AFS + * is supported by mobile and BTS. + * * If AMR codec is used, the cell must have a TCH/H slot available, if AHS + * is supported by mobile and BTS. + * * osmo-nitb with built-in MNCC application: + * o If AMR codec is used, the cell must support AMR codec with equal codec + * rate or rates. (not meaning TCH types) + * * If defined, the number of maximum unsynchronized handovers to this cell + * may not be exceeded. (This limits processing load for random access + * bursts.) + * + * + * Target cell requirement B -- avoid congestion + * + * In order to prevent congestion of a target cell, the cell must fulfill the + * requirement A, but also: + * + * * The minimum free channels, that are defined for that cell must be + * maintained after handover/assignment. + * * The minimum free channels are defined for TCH/F and TCH/H slot types + * individually. + * + * + * Target cell requirement C -- balance congestion + * + * In order to balance congested cells, the target cell must fulfill the + * requirement A, but also: + * + * * The target cell (which is congested also) must have more or equal free + * slots after handover/assignment. + * * 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) +{ + int count; + uint8_t requirement = 0; + unsigned int penalty_time; + struct gsm_bts *current_bts = lchan->ts->trx->bts; + + /* 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; + } + } else { + if (!ho_get_ho_active(bts->ho)) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "not a candidate, handover is disabled in target BTS\n"); + return 0; + } + } + + /* the handover penalty timer must not run for this bts */ + penalty_time = conn_penalty_time_remaining(lchan->conn, bts); + if (penalty_time) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, target BTS still in penalty time" + " (%u seconds left)\n", penalty_time); + return 0; + } + + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "tch_mode='%s' type='%s'\n", + get_value_string(gsm48_chan_mode_names, lchan->tch_mode), + gsm_lchant_name(lchan->type)); + + /* compatibility check for codecs. + * if so, the candidates for full rate and half rate are selected */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: /* mandatory */ + requirement |= REQUIREMENT_A_TCHF; + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "tch_mode='%s' type='%s' supported\n", + get_value_string(gsm48_chan_mode_names, lchan->tch_mode), + gsm_lchant_name(lchan->type)); + break; + case GSM_LCHAN_TCH_H: + if (!bts->codec.hr) { + LOGPHOLCHANTOBTS(lchan, 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)); + break; + } + if (codec_type_is_supported(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; + } + break; + case GSM48_CMODE_SPEECH_EFR: + if (!bts->codec.efr) { + LOGPHOBTS(bts, LOGL_DEBUG, "EFR not supported\n"); + break; + } + if (codec_type_is_supported(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"); + break; + } + if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3)) + requirement |= REQUIREMENT_A_TCHF; + if (codec_type_is_supported(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"); + return 0; + } + + /* 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; + } + + /* remove slot types that are not available */ + if (!tchf_count && requirement & REQUIREMENT_A_TCHF) { + LOGPHOLCHANTOBTS(lchan, 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, + "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; + } + + /* omit same channel type on same BTS (will not change anything) */ + if (bts == current_bts) { + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "removing TCH/F, already on TCH/F in this cell\n"); + requirement &= ~(REQUIREMENT_A_TCHF); + break; + case GSM_LCHAN_TCH_H: + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "removing TCH/H, already on TCH/H in this cell\n"); + requirement &= ~(REQUIREMENT_A_TCHH); + break; + default: + break; + } + + if (!requirement) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, + "Reassignment within cell not an option, no differing channel types available\n"); + return 0; + } + } + +#ifdef LEGACY + // 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) { + case GSM_LCHAN_TCH_F: + if ((requirement & REQUIREMENT_A_TCHF) + && !!memcmp(¤t_bts->mr_full, &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, + 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, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHF); + if ((requirement & REQUIREMENT_A_TCHH) + && !!memcmp(¤t_bts->mr_half, &bts->mr_half, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHH); + break; + default: + break; + } + + if (!requirement) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "not a candidate, cannot provide identical codec rate\n"); + return 0; + } + } +#endif + + /* the maximum number of unsynchonized handovers must no be exceeded */ + if (current_bts != bts + && bsc_ho_count(bts, true) >= ho_get_hodec2_ho_max(bts->ho)) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "not a candidate, number of allowed handovers (%d) would be exceeded\n", + ho_get_hodec2_ho_max(bts->ho)); + return 0; + } + + /* Requirement B */ + + /* 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)) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/F would not be congested after HO\n"); + requirement |= REQUIREMENT_B_TCHF; + } else { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/F would be congested after HO\n"); + } + } + if (requirement & REQUIREMENT_A_TCHH) { + if (tchh_count - 1 >= ho_get_hodec2_tchh_min_slots(bts->ho)) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/H would not be congested after HO\n"); + requirement |= REQUIREMENT_B_TCHH; + } else { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/H would be congested after HO\n"); + } + } + + /* 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); + if (requirement & REQUIREMENT_A_TCHF) { + if (tchf_count - 1 >= count + 1) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/F would be less congested in target than source cell after HO\n"); + requirement |= REQUIREMENT_C_TCHF; + } else { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/F would not be less congested in target than source cell after HO\n"); + } + } + if (requirement & REQUIREMENT_A_TCHH) { + if (tchh_count - 1 >= count + 1) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/H would be less congested in target than source cell after HO\n"); + requirement |= REQUIREMENT_C_TCHH; + } else { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/H would not be less congested in target than source cell after HO\n"); + } + } + + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "requirements=0x%x\n", requirement); + + /* return mask of fulfilled requirements */ + return requirement; +} + +/* Trigger handover or assignment depending on the target BTS */ +static int trigger_handover_or_assignment(struct gsm_lchan *lchan, struct gsm_bts *new_bts, uint8_t requirements) +{ + struct gsm_bts *current_bts = lchan->ts->trx->bts; + int afs_bias = 0; + bool full_rate = false; + + if (current_bts == new_bts) + LOGPHOLCHAN(lchan, LOGL_NOTICE, "Triggering Assignment\n"); + else + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_NOTICE, "Triggering Handover\n"); + + /* 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); + + /* select TCH rate, prefer TCH/F if AFS is improved */ + switch (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; + } + full_rate = true; + break; + } + /* change to half rate */ + if (!(requirements & REQUIREMENT_TCHH_MASK)) { + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, + "neither TCH/F nor TCH/H requested, aborting ho/as\n"); + return -EINVAL; + } + break; + case GSM_LCHAN_TCH_H: + /* change to full rate if AFS is improved and a candidate */ + if (afs_bias > 0 && (requirements & REQUIREMENT_TCHF_MASK)) { + full_rate = true; + LOGPHOLCHAN(lchan, LOGL_DEBUG, "[Improve AHS->AFS]\n"); + break; + } + /* change to full rate if the only candidate */ + if ((requirements & REQUIREMENT_TCHF_MASK) + && !(requirements & REQUIREMENT_TCHH_MASK)) { + full_rate = true; + break; + } + /* keep on half rate */ + if (!(requirements & REQUIREMENT_TCHH_MASK)) { + LOGPHOLCHANTOBTS(lchan, new_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; + } + break; + default: + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, "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", + full_rate ? "TCH/F" : "TCH/H", + ho_reason_name(global_ho_reason)); + else + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_NOTICE, + "Triggering handover to %s, due to %s\n", + full_rate ? "TCH/F" : "TCH/H", + ho_reason_name(global_ho_reason)); + + return bsc_handover_start(HODEC2, lchan, current_bts == new_bts? NULL : new_bts, + full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H); +} + +/* debug collected candidates */ +static inline void debug_candidate(struct ho_candidate *candidate, + int neighbor, int8_t rxlev, int tchf_count, int tchh_count) +{ + if (neighbor) + LOGP(DHODEC, LOGL_DEBUG, " - neighbor BTS %d, RX level " + "%d -> %d\n", candidate->bts->nr, rxlev2dbm(rxlev), + rxlev2dbm(candidate->avg)); + else + LOGP(DHODEC, LOGL_DEBUG, " - current BTS %d, RX level %d\n", + candidate->bts->nr, rxlev2dbm(candidate->avg)); + + LOGP(DHODEC, LOGL_DEBUG, " o free TCH/F slots %d, minimum required " + "%d\n", tchf_count, ho_get_hodec2_tchf_min_slots(candidate->bts->ho)); + LOGP(DHODEC, LOGL_DEBUG, " o free TCH/H slots %d, minimum required " + "%d\n", tchh_count, ho_get_hodec2_tchh_min_slots(candidate->bts->ho)); + + if ((candidate->requirements & REQUIREMENT_TCHF_MASK)) + LOGP(DHODEC, LOGL_DEBUG, " o requirement "); + else + LOGP(DHODEC, LOGL_DEBUG, " o no requirement "); + if ((candidate->requirements & REQUIREMENT_A_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "A "); + if ((candidate->requirements & REQUIREMENT_B_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "B "); + if ((candidate->requirements & REQUIREMENT_C_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "C "); + LOGPC(DHODEC, LOGL_DEBUG, "fulfilled for TCHF"); + if (!(candidate->requirements & REQUIREMENT_TCHF_MASK)) /* nothing */ + LOGPC(DHODEC, LOGL_DEBUG, " (no %s possible)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_TCHF_MASK) + == REQUIREMENT_A_TCHF) /* only A */ + LOGPC(DHODEC, LOGL_DEBUG, " (more congestion after %s)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_B_TCHF)) /* B incl. */ + LOGPC(DHODEC, LOGL_DEBUG, " (not congested after %s)\n", + (neighbor) ? "handover" : "assignment"); + else /* so it must include C */ + LOGPC(DHODEC, LOGL_DEBUG, " (less or equally congested after " + "%s)\n", (neighbor) ? "handover" : "assignment"); + + if ((candidate->requirements & REQUIREMENT_TCHH_MASK)) + LOGP(DHODEC, LOGL_DEBUG, " o requirement "); + else + LOGP(DHODEC, LOGL_DEBUG, " o no requirement "); + if ((candidate->requirements & REQUIREMENT_A_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "A "); + if ((candidate->requirements & REQUIREMENT_B_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "B "); + if ((candidate->requirements & REQUIREMENT_C_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "C "); + LOGPC(DHODEC, LOGL_DEBUG, "fulfilled for TCHH"); + if (!(candidate->requirements & REQUIREMENT_TCHH_MASK)) /* nothing */ + LOGPC(DHODEC, LOGL_DEBUG, " (no %s possible)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_TCHH_MASK) + == REQUIREMENT_A_TCHH) /* only A */ + LOGPC(DHODEC, LOGL_DEBUG, " (more congestion after %s)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_B_TCHH)) /* B incl. */ + LOGPC(DHODEC, LOGL_DEBUG, " (not congested after %s)\n", + (neighbor) ? "handover" : "assignment"); + else /* so it must include C */ + LOGPC(DHODEC, LOGL_DEBUG, " (less or equally congested after " + "%s)\n", (neighbor) ? "handover" : "assignment"); +} + +/* 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) +{ + 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 = &clist[*candidates]; + c->lchan = lchan; + c->bts = bts; + c->requirements = check_requirements(lchan, bts, tchf_count, tchh_count); + c->avg = av_rxlev; + debug_candidate(c, 0, 0, tchf_count, tchh_count); + (*candidates)++; +} + +/* 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, + int *neighbors_count) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + int tchf_count, tchh_count; + struct gsm_bts *neighbor_bts; + int avg; + struct ho_candidate *c; + int min_rxlev; + + /* skip empty slots */ + if (nmp->arfcn == 0) + return; + + if (neighbors_count) + (*neighbors_count)++; + + /* skip if measurement report is old */ + if (nmp->last_seen_nr != lchan->meas_rep_last_seen_nr) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "neighbor ARFCN %u measurement report is old" + " (nmp->last_seen_nr=%u lchan->meas_rep_last_seen_nr=%u)\n", + nmp->arfcn, nmp->last_seen_nr, lchan->meas_rep_last_seen_nr); + return; + } + + neighbor_bts = bts_by_arfcn_bsic(bts->network, nmp->arfcn, nmp->bsic); + if (!neighbor_bts) { + LOGPHOBTS(bts, LOGL_DEBUG, "neighbor ARFCN %u does not belong to this network\n", + nmp->arfcn); + return; + } + + /* in case we have measurements of our bts, due to misconfiguration */ + if (neighbor_bts == bts) { + LOGPHOBTS(bts, LOGL_ERROR, "Configuration error: this BTS appears as its own neighbor\n"); + return; + } + + /* caculate average rxlev for this cell over the window */ + avg = neigh_meas_avg(nmp, ho_get_hodec2_rxlev_neigh_avg_win(bts->ho)); + + /* 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)) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, + "BTS %d is not a candidate, because RX level (%d) is lower" + " or equal than current RX level (%d) + hysteresis (%d)\n", + neighbor_bts->nr, rxlev2dbm(avg), rxlev2dbm(av_rxlev), pwr_hyst); + return; + } + } + + /* if the minimum level is not reached */ + min_rxlev = ho_get_hodec2_min_rxlev(neighbor_bts->ho); + if (rxlev2dbm(avg) < min_rxlev) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, + "BTS %d is not a candidate, because RX level (%d) is lower" + " than its minimum required RX level (%d)\n", + neighbor_bts->nr, rxlev2dbm(avg), min_rxlev); + return; + } + + 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 = &clist[*candidates]; + c->lchan = lchan; + c->bts = neighbor_bts; + c->requirements = check_requirements(lchan, neighbor_bts, tchf_count, + tchh_count); + c->avg = avg; + debug_candidate(c, 1, av_rxlev, tchf_count, tchh_count); + (*candidates)++; +} + +static void collect_candidates_for_lchan(struct gsm_lchan *lchan, + struct ho_candidate *clist, unsigned int *candidates, + int *_av_rxlev, bool include_weaker_rxlev) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + int av_rxlev; + unsigned int candidates_was; + bool assignment; + bool handover; + int neighbors_count = 0; + unsigned int rxlev_avg_win = ho_get_hodec2_rxlev_avg_win(bts->ho); + + OSMO_ASSERT(candidates); + candidates_was = *candidates; + + /* caculate 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; + + /* in case there is no measurment report (yet) */ + if (av_rxlev < 0) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not collecting candidates, not enough measurements" + " (got %d, want %u)\n", + lchan->meas_rep_count, rxlev_avg_win); + return; + } + + assignment = ho_get_hodec2_as_active(bts->ho); + handover = ho_get_ho_active(bts->ho); + + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Collecting candidates for%s%s%s\n", + assignment ? " Assignment" : "", + assignment && handover ? " and" : "", + handover ? " Handover" : ""); + + if (assignment) + collect_assignment_candidate(lchan, clist, candidates, av_rxlev); + + 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); + } + } + + LOGPHOLCHAN(lchan, LOGL_DEBUG, "adding %u candidates from %u neighbors, total %u\n", + *candidates - candidates_was, neighbors_count, *candidates); +} + +/* + * Search for a alternative / better cell. + * + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. If no AFS improvement offset is given, try to + * maintain the same TCH rate, if available. + * Do not perform this process, if handover and assignment are disabled for + * the current cell. + * Do not perform handover, if the minimum acceptable RX level + * is not reched for this cell. + * + * If one or more 'better cells' are available, check the current and neighbor + * cell measurements in descending order of their RX levels (down-link): + * + * * Select the best candidate that fulfills requirement B (no congestion + * after handover/assignment) and trigger handover or assignment. + * * If no candidate fulfills requirement B, select the best candidate that + * fulfills requirement C (less or equally congested cells after handover) + * and trigger handover or assignment. + * * If no candidate fulfills requirement C, do not perform handover nor + * assignment. + * + * If the RX level (down-link) or RX quality (down-link) of the current cell is + * below minimum acceptable level, or if the maximum allowed timing advance is + * reached or exceeded, check the RX levels (down-link) of the current and + * neighbor cells in descending order of their levels: (bad BTS case) + * + * * Select the best candidate that fulfills requirement B (no congestion after + * handover/assignment) and trigger handover or assignment. + * * If no candidate fulfills requirement B, select the best candidate that + * fulfills requirement C (less or equally congested cells after handover) + * and trigger handover or assignment. + * * If no candidate fulfills requirement C, select the best candidate that + * fulfills requirement A (ignore congestion after handover or assignment) + * and trigger handover or assignment. + * * If no candidate fulfills requirement A, do not perform handover nor + * assignment. + * + * RX levels (down-link) of current and neighbor cells: + * + * * The RX levels of the current cell and neighbor cells are improved by a + * given offset, if AFS (AMR on TCH/F) is used or is a candidate for + * handover/assignment. + * * If AMR is used, the requirement for handover is checked for TCH/F and + * TCH/H. Both results (if any) are used as a candidate. + * * If AMR is used, the requirement for assignment to a different TCH slot + * rate is checked. The result (if available) is used as a candidate. + * + * 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) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + int ahs = (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && lchan->type == GSM_LCHAN_TCH_H); + int av_rxlev; + struct ho_candidate clist[1 + ARRAY_SIZE(lchan->neigh_meas)]; + unsigned int candidates = 0; + int i; + struct ho_candidate *best_cand = NULL; + unsigned int best_better_db; + bool best_applied_afs_bias = false; + int better; + + /* check for disabled handover/assignment at the current cell */ + if (!ho_get_hodec2_as_active(bts->ho) + && !ho_get_ho_active(bts->ho)) { + LOGP(DHODEC, LOGL_INFO, "Skipping, Handover and Assignment both disabled in this cell\n"); + return 0; + } + + collect_candidates_for_lchan(lchan, clist, &candidates, &av_rxlev, 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. */ + if (!candidates) + goto no_candidates; + + /* select best candidate that fulfills requirement B: no congestion after HO */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + int afs_bias; + if (!(clist[i].requirements & REQUIREMENT_B_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* Apply AFS bias? */ + afs_bias = 0; + if (ahs && (clist[i].requirements & REQUIREMENT_B_TCHF)) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + better += afs_bias; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + best_applied_afs_bias = afs_bias? true : false; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d%s\n", + rxlev2dbm(best_cand->avg), + best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_B_MASK); + } + + /* select best candidate that fulfills requirement C: less or equal congestion after HO */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + int afs_bias; + if (!(clist[i].requirements & REQUIREMENT_C_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* Apply AFS bias? */ + afs_bias = 0; + if (ahs && (clist[i].requirements & REQUIREMENT_C_TCHF)) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + better += afs_bias; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + best_applied_afs_bias = afs_bias? true : false; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d%s\n", + rxlev2dbm(best_cand->avg), + best_applied_afs_bias? " (applied AHS -> AFS rxlev bias)" : ""); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_C_MASK); + } + + /* we are done in case the MS RXLEV/RXQUAL/TA aren't critical and we're avoiding congestion. */ + if (!include_weaker_rxlev) + goto no_candidates; + + /* Select best candidate that fulfills requirement A: can service the call. + * From above we know that there are no options that avoid congestion. Here we're trying to find + * *any* free lchan that has no critically low RXLEV and is able to handle the MS. */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + int afs_bias; + if (!(clist[i].requirements & REQUIREMENT_A_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* Apply AFS bias? */ + afs_bias = 0; + if (ahs && (clist[i].requirements & REQUIREMENT_A_TCHF)) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + better += afs_bias; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + best_applied_afs_bias = afs_bias? true : false; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d" + " with greater congestion found%s\n", + rxlev2dbm(best_cand->avg), + best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_A_MASK); + } + + /* Damn, all is congested, has too low RXLEV or cannot service the voice call due to codec + * restrictions or because all lchans are taken. */ + +no_candidates: + if (include_weaker_rxlev) + LOGPHOLCHAN(lchan, LOGL_INFO, "No alternative lchan found\n"); + else + LOGPHOLCHAN(lchan, LOGL_INFO, "No better/less congested neighbor cell found\n"); + + return 0; +} + +/* + * Handover/assignment check, if measurement report is received + * + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. + * + * In case of handover triggered because maximum allowed timing advance is + * exceeded, the handover penalty timer is started for the originating cell. + * + */ +static void on_measurement_report(struct gsm_meas_rep *mr) +{ + struct gsm_lchan *lchan = mr->lchan; + struct gsm_bts *bts = lchan->ts->trx->bts; + int av_rxlev = -EINVAL, av_rxqual = -EINVAL; + unsigned int pwr_interval; + + /* we currently only do handover for TCH channels */ + switch (mr->lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + break; + default: + return; + } + + if (log_check_level(DHODEC, LOGL_DEBUG)) { + int i; + LOGPHOLCHAN(lchan, LOGL_DEBUG, "MEASUREMENT REPORT (%d neighbors)\n", + mr->num_cell); + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + LOGPHOLCHAN(lchan, LOGL_DEBUG, + " %d: arfcn=%u bsic=%u neigh_idx=%u rxlev=%u flags=%x\n", + i, mrc->arfcn, mrc->bsic, mrc->neigh_idx, mrc->rxlev, mrc->flags); + } + } + + /* parse actual neighbor cell info */ + if (mr->num_cell > 0 && mr->num_cell < 7) + process_meas_neigh(mr); + + /* check for ongoing handover/assignment */ + if (!lchan->conn) { + LOGPHOLCHAN(lchan, LOGL_ERROR, "Skipping, No subscriber connection???\n"); + return; + } + if (lchan->conn->secondary_lchan) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Initial Assignment is still ongoing\n"); + return; + } + if (lchan->conn->ho) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Handover already triggered\n"); + return; + } + + LOGPHOLCHAN(lchan, LOGL_DEBUG, "HODEC2: evaluating measurement report\n"); + + /* get average levels. if not enought 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)); + if (av_rxlev < 0 && av_rxqual < 0) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Not enough recent measurements\n"); + return; + } + if (av_rxlev >= 0) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Measurement report: average RX level = %d\n", + rxlev2dbm(av_rxlev)); + } + if (av_rxqual >= 0) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Measurement report: average RX quality = %d\n", + av_rxqual); + } + + /* improve levels in case of AFS, if defined */ + if (lchan->type == GSM_LCHAN_TCH_F + && lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + int rxlev_bias = ho_get_hodec2_afs_bias_rxlev(bts->ho); + int rxqual_bias = ho_get_hodec2_afs_bias_rxqual(bts->ho); + if (av_rxlev >= 0 && rxlev_bias) { + int imp = av_rxlev + rxlev_bias; + LOGPHOLCHAN(lchan, LOGL_INFO, "Virtually improving RX level from %d to %d," + " due to AFS bias\n", rxlev2dbm(av_rxlev), rxlev2dbm(imp)); + av_rxlev = imp; + } + if (av_rxqual >= 0 && rxqual_bias) { + int imp = av_rxqual - rxqual_bias; + if (imp < 0) + imp = 0; + LOGPHOLCHAN(lchan, LOGL_INFO, "Virtually improving RX quality from %d to %d," + " due to AFS bias\n", rxlev2dbm(av_rxqual), rxlev2dbm(imp)); + av_rxqual = imp; + } + } + + /* Bad Quality */ + if (av_rxqual >= 0 && av_rxqual > ho_get_hodec2_min_rxqual(bts->ho)) { + if (rxlev2dbm(av_rxlev) > -85) { + global_ho_reason = HO_REASON_INTERFERENCE; + LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment" + " due to interference (bad quality)\n"); + } else { + global_ho_reason = HO_REASON_BAD_QUALITY; + LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment due to bad quality\n"); + } + find_alternative_lchan(lchan, true); + return; + } + + /* Low Level */ + if (av_rxlev >= 0 && rxlev2dbm(av_rxlev) < ho_get_hodec2_min_rxlev(bts->ho)) { + global_ho_reason = HO_REASON_LOW_RXLEVEL; + LOGPHOLCHAN(lchan, LOGL_INFO, "Attempting handover/assignment due to low rxlev\n"); + find_alternative_lchan(lchan, true); + return; + } + + /* Max Distance */ + if (lchan->meas_rep_count > 0 + && lchan->rqd_ta > ho_get_hodec2_max_distance(bts->ho)) { + global_ho_reason = HO_REASON_MAX_DISTANCE; + LOGPHOLCHAN(lchan, LOGL_INFO, "Attempting handover due to high TA\n"); + /* start penalty timer to prevent comming back too + * early. it must be started before selecting a better cell, + * so there is no assignment selected, due to running + * penalty timer. */ + conn_penalty_time_add(lchan->conn, bts, ho_get_hodec2_penalty_max_dist(bts->ho)); + find_alternative_lchan(lchan, true); + return; + } + + /* pwr_interval's range is 1-99, clarifying that no div-zero shall happen in modulo below: */ + pwr_interval = ho_get_hodec2_pwr_interval(bts->ho); + OSMO_ASSERT(pwr_interval); + + /* try handover to a better cell */ + if (av_rxlev >= 0 && (mr->nr % pwr_interval) == 0) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Looking whether a cell has better RXLEV\n"); + global_ho_reason = HO_REASON_BETTER_CELL; + find_alternative_lchan(lchan, false); + } +} + +/* + * Handover/assignment check after timer timeout: + * + * Even if handover process tries to prevent a congestion, a cell might get + * congested due to new call setups or handovers to prevent loss of radio link. + * A cell is congested, if not the minimum number of free slots are available. + * The minimum number can be defined for TCH/F and TCH/H individually. + * + * Do not perform congestion check, if no minimum free slots are defined for + * a cell. + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. If no AFS improvement offset is given, try to + * maintain the same TCH rate, if available. + * Do not perform this process, if handover and assignment are disabled for + * the current cell. + * Do not perform handover, if the minimum acceptable RX level + * is not reched for this cell. + * Only check candidates that will solve/reduce congestion. + * + * If a cell is congested, all slots are checked for all their RX levels + * (down-link) of the current and neighbor cell measurements in descending + * order of their RX levels: + * + * * Select the best candidate that fulfills requirement B (no congestion after + * handover/assignment), trigger handover or assignment. Candidates that will + * cause an assignment from AHS (AMR on TCH/H) to AFS (AMR on TCH/F) are + * omitted. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the worst candidate that fulfills requirement B, trigger + * assignment. Note that only assignment candidates for changing from AHS to + * AFS are left. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the best candidates that fulfill requirement C (less or equally + * congested cells after handover/assignment), trigger handover or + * assignment. Candidates that will cause an assignment from AHS (AMR on + * TCH/H) to AFS (AMR on TCH/F) are omitted. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the worst candidate that fulfills requirement C, trigger + * assignment. Note that only assignment candidates for changing from AHS to + * AFS are left. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. + */ +static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int tchh_congestion) +{ + struct gsm_lchan *lc; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + 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; + int rc = 0; + int any_ho = 0; + int is_improved = 0; + + if (tchf_congestion < 0) + tchf_congestion = 0; + if (tchh_congestion < 0) + tchh_congestion = 0; + + LOGPHOBTS(bts, LOGL_INFO, "congested: %d TCH/F and %d TCH/H should be moved\n", + tchf_congestion, tchh_congestion); + + /* allocate array of all bts */ + clist = talloc_zero_array(tall_bsc_ctx, struct ho_candidate, + bts->num_trx * 8 * 2 * (1 + ARRAY_SIZE(lc->neigh_meas))); + if (!clist) + return 0; + + candidates = 0; + + /* loop through all active lchan and collect candidates */ + llist_for_each_entry(trx, &bts->trx_list, list) { + if (!trx_is_usable(trx)) + continue; + + for (i = 0; i < 8; i++) { + ts = &trx->ts[i]; + if (!ts_is_usable(ts)) + continue; + + /* (Do not consider dynamic TS that are in PDCH mode) */ + switch (ts_pchan(ts)) { + case GSM_PCHAN_TCH_F: + lc = &ts->lchan[0]; + /* omit if channel not active */ + if (lc->type != GSM_LCHAN_TCH_F + || lc->state != LCHAN_S_ACTIVE) + break; + /* omit if there is an ongoing ho/as */ + if (!lc->conn || lc->conn->secondary_lchan + || lc->conn->ho) + break; + /* We desperately want to resolve congestion, ignore rxlev when + * collecting candidates by passing include_weaker_rxlev=true. */ + collect_candidates_for_lchan(lc, clist, &candidates, NULL, true); + break; + case GSM_PCHAN_TCH_H: + for (j = 0; j < 2; j++) { + lc = &ts->lchan[j]; + /* omit if channel not active */ + if (lc->type != GSM_LCHAN_TCH_H + || lc->state != LCHAN_S_ACTIVE) + continue; + /* omit of there is an ongoing ho/as */ + if (!lc->conn + || lc->conn->secondary_lchan + || lc->conn->ho) + continue; + /* We desperately want to resolve congestion, ignore rxlev when + * collecting candidates by passing include_weaker_rxlev=true. */ + collect_candidates_for_lchan(lc, clist, &candidates, NULL, true); + } + break; + default: + break; + } + } + } + + if (!candidates) { + LOGPHOBTS(bts, LOGL_DEBUG, "No neighbor cells qualify to solve congestion\n"); + goto exit; + } + if (log_check_level(DHODEC, LOGL_DEBUG)) { + LOGPHOBTS(bts, LOGL_DEBUG, "Considering %u candidates to solve congestion:\n", candidates); + for (i = 0; i < candidates; i++) { + LOGPHOLCHANTOBTS(clist[i].lchan, clist[i].bts, LOGL_DEBUG, + "#%d: req=0x%x avg-rxlev=%d\n", + i, clist[i].requirements, clist[i].avg); + } + } + +#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; + + 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; + LOGP(DHODEC, 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 */ + if (best_cand) { + any_ho = 1; + LOGPHOLCHAN(best_cand->lchan, LOGL_INFO, + "Best candidate BTS %u (RX level %d) without congestion found\n", + best_cand->bts->nr, rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(best_cand->lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_B_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + if (best_cand->lchan->type == GSM_LCHAN_TCH_H) + tchh_congestion--; + else + tchf_congestion--; + if (tchf_congestion > 0 || tchh_congestion > 0) { + delete_lchan = best_cand->lchan; + best_cand = NULL; + goto next_b1; + } +#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 best candidate that fulfills requirement B" + " (omitting change from AHS to AFS)\n"); + +#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; + + 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; + 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; + LOGP(DHODEC, LOGL_INFO, "Worst candidate for assignment " + "(RX level %d) from TCH/H -> TCH/F without congestion " + "found\n", rxlev2dbm(worst_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(worst_cand->lchan, + worst_cand->bts, + 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; + } + + LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a worst candidate that fulfills requirement B," + " selecting candidates that change from AHS to AFS only\n"); + +#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; + + 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; + LOGP(DHODEC, 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 */ + if (best_cand) { + any_ho = 1; + LOGP(DHODEC, LOGL_INFO, "Best candidate BTS %d (RX level %d) " + "with less or equal congestion found\n", + best_cand->bts->nr, rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(best_cand->lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_C_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + if (best_cand->lchan->type == GSM_LCHAN_TCH_H) + tchh_congestion--; + else + tchf_congestion--; + if (tchf_congestion > 0 || tchh_congestion > 0) { + delete_lchan = best_cand->lchan; + best_cand = NULL; + goto next_c1; + } +#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 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; + + 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; + LOGP(DHODEC, LOGL_INFO, "Worst candidate for assignment " + "(RX level %d) from TCH/H -> TCH/F with less or equal " + "congestion found\n", rxlev2dbm(worst_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(worst_cand->lchan, + worst_cand->bts, + 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"); + + +exit: + /* free array */ + talloc_free(clist); + + if (tchf_congestion <= 0 && tchh_congestion <= 0) + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d solved!\n", + bts->nr); + else if (any_ho) + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d reduced!\n", + bts->nr); + else + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d can't be reduced/solved!\n", bts->nr); + + return rc; +} + +static void bts_congestion_check(struct gsm_bts *bts) +{ + int min_free_tchf, min_free_tchh; + int tchf_count, tchh_count; + + global_ho_reason = HO_REASON_CONGESTION; + + /* only check BTS if TRX 0 is usable */ + if (!trx_is_usable(bts->c0)) { + LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: TRX 0 not usable\n"); + return; + } + + /* only check BTS if handover or assignment is enabled */ + if (!ho_get_hodec2_as_active(bts->ho) + && !ho_get_ho_active(bts->ho)) { + LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: Assignment and Handover both disabled\n"); + return; + } + + min_free_tchf = ho_get_hodec2_tchf_min_slots(bts->ho); + min_free_tchh = ho_get_hodec2_tchh_min_slots(bts->ho); + + /* only check BTS with congestion level set */ + if (!min_free_tchf && !min_free_tchh) { + LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: no minimum for free TCH/F nor TCH/H set\n"); + return; + } + + tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F); + tchh_count = bts_count_free_ts(bts, GSM_PCHAN_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); + + /* only check BTS if congested */ + if (tchf_count >= min_free_tchf && tchh_count >= 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); +} + +void hodec2_congestion_check(struct gsm_network *net) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + bts_congestion_check(bts); +} + +static void congestion_check_cb(void *arg) +{ + struct gsm_network *net = arg; + hodec2_congestion_check(net); + reinit_congestion_timer(net); +} + +void on_ho_chan_activ_nack(struct bsc_handover *ho) +{ + struct gsm_bts *new_bts = ho->new_lchan->ts->trx->bts; + + LOGPHO(ho, LOGL_ERROR, "Channel Activate Nack for %s, starting penalty timer\n", ho->inter_cell? "Handover" : "Assignment"); + + /* if channel failed, wait 10 seconds before allowing to retry handover */ + conn_penalty_time_add(ho->old_lchan->conn, new_bts, 10); /* FIXME configurable */ +} + +void on_ho_failure(struct bsc_handover *ho) +{ + struct gsm_bts *old_bts = ho->old_lchan->ts->trx->bts; + struct gsm_bts *new_bts = ho->new_lchan->ts->trx->bts; + struct gsm_subscriber_connection *conn = ho->old_lchan->conn; + + if (!conn) { + LOGPHO(ho, LOGL_ERROR, "HO failure, but no conn"); + return; + } + + if (conn->hodec2.failures >= ho_get_hodec2_retries(old_bts->ho)) { + int penalty = ho->inter_cell + ? ho_get_hodec2_penalty_failed_ho(old_bts->ho) + : ho_get_hodec2_penalty_failed_as(old_bts->ho); + LOGPHO(ho, LOGL_NOTICE, "%s failed, starting penalty timer (%d s)\n", + ho->inter_cell ? "Handover" : "Assignment", + penalty); + conn->hodec2.failures = 0; + conn_penalty_time_add(conn, new_bts, penalty); + } else { + conn->hodec2.failures++; + LOGPHO(ho, LOGL_NOTICE, "%s failed, allowing handover decision to try again" + " (%d/%d attempts)\n", + ho->inter_cell ? "Handover" : "Assignment", + conn->hodec2.failures, ho_get_hodec2_retries(old_bts->ho)); + } +} + +struct handover_decision_callbacks hodec2_callbacks = { + .hodec_id = 2, + .on_measurement_report = on_measurement_report, + .on_ho_chan_activ_nack = on_ho_chan_activ_nack, + .on_ho_failure = on_ho_failure, +}; + +void hodec2_init(struct gsm_network *net) +{ + handover_decision_callbacks_register(&hodec2_callbacks); + hodec2_initialized = true; + reinit_congestion_timer(net); +} diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c new file mode 100644 index 000000000..960bf6993 --- /dev/null +++ b/src/osmo-bsc/handover_logic.c @@ -0,0 +1,473 @@ +/* Handover Logic for Inter-BTS (Intra-BSC) Handover. This does not + * actually implement the handover algorithm/decision, but executes a + * handover decision */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static LLIST_HEAD(bsc_handovers); +static LLIST_HEAD(handover_decision_callbacks); + +static void handover_free(struct bsc_handover *ho) +{ + osmo_timer_del(&ho->T3103); + llist_del(&ho->list); + talloc_free(ho); +} + +static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + llist_for_each_entry(ho, &bsc_handovers, list) { + if (ho->new_lchan == new_lchan) + return ho; + } + + return NULL; +} + +static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan) +{ + struct bsc_handover *ho; + + llist_for_each_entry(ho, &bsc_handovers, list) { + if (ho->old_lchan == old_lchan) + return ho; + } + + return NULL; +} + +/*! Hand over the specified logical channel to the specified new BTS and possibly change the lchan type. + * This is the main entry point for the actual handover algorithm, after the decision whether to initiate + * HO to a specific BTS. To not change the lchan type, pass old_lchan->type. */ +int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts, + enum gsm_chan_t new_lchan_type) +{ + struct gsm_subscriber_connection *conn; + struct bsc_handover *ho; + static uint8_t ho_ref = 0; + bool do_assignment; + + OSMO_ASSERT(old_lchan); + + /* don't attempt multiple handovers for the same lchan at + * the same time */ + if (bsc_ho_by_old_lchan(old_lchan)) + return -EBUSY; + + conn = old_lchan->conn; + if (!conn) { + LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n"); + return -ENOSPC; + } + + if (!new_bts) + new_bts = old_lchan->ts->trx->bts; + OSMO_ASSERT(new_bts); + + do_assignment = (new_bts == old_lchan->ts->trx->bts); + + ho = talloc_zero(conn, struct bsc_handover); + if (!ho) { + LOGP(DHO, LOGL_FATAL, "Out of Memory\n"); + return -ENOMEM; + } + ho->from_hodec_id = from_hodec_id; + ho->old_lchan = old_lchan; + ho->new_bts = new_bts; + ho->new_lchan_type = new_lchan_type; + ho->ho_ref = ho_ref++; + ho->inter_cell = !do_assignment; + ho->async = true; + llist_add(&ho->list, &bsc_handovers); + + conn->ho = ho; + + DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Initiating %s...\n", + old_lchan->ts->trx->bts->nr, + old_lchan->ts->trx->nr, + old_lchan->ts->nr, + old_lchan->nr, + gsm_pchan_name(old_lchan->ts->pchan), + new_bts->nr, + gsm_lchant_name(new_lchan_type), + do_assignment ? "Assignment" : "Handover"); + + return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HO_START, NULL); +} + +/*! Start actual handover. Call bsc_handover_start() instead; The only legal caller is the GSCON FSM in + * bsc_subscr_conn_fsm.c. */ +int bsc_handover_start_gscon(struct gsm_subscriber_connection *conn) +{ + int rc; + struct gsm_network *network = conn->network; + struct bsc_handover *ho = conn->ho; + struct gsm_lchan *old_lchan; + struct gsm_lchan *new_lchan; + + if (!ho) { + LOGP(DHO, LOGL_ERROR, "%s: Requested to start handover, but conn->ho is NULL\n", + bsc_subscr_name(conn->bsub)); + return -EINVAL; + } + + OSMO_ASSERT(ho->old_lchan && ho->new_bts); + + if (ho->old_lchan->conn != conn) { + LOGP(DHO, LOGL_ERROR, + "%s: Requested to start handover, but the lchan does not belong to this conn\n", + bsc_subscr_name(conn->bsub)); + return -EINVAL; + } + + rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]); + + ho->new_lchan = lchan_alloc(ho->new_bts, ho->new_lchan_type, 0); + if (!ho->new_lchan) { + LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(ho->new_lchan_type)); + rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]); + return -ENOSPC; + } + + LOGPHO(ho, LOGL_INFO, "Triggering %s\n", ho->inter_cell? "Handover" : "Assignment"); + + /* copy some parameters from old lchan */ + old_lchan = ho->old_lchan; + new_lchan = ho->new_lchan; + memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr)); + if (!ho->inter_cell) { + new_lchan->ms_power = old_lchan->ms_power; + new_lchan->rqd_ta = old_lchan->rqd_ta; + } else { + new_lchan->ms_power = + ms_pwr_ctl_lvl(ho->new_bts->band, ho->new_bts->ms_max_power); + /* FIXME: do we have a better idea of the timing advance? */ + //new_lchan->rqd_ta = old_lchan->rqd_ta; + } + new_lchan->bs_power = old_lchan->bs_power; + new_lchan->rsl_cmode = old_lchan->rsl_cmode; + new_lchan->tch_mode = old_lchan->tch_mode; + memcpy(&new_lchan->mr_ms_lv, &old_lchan->mr_ms_lv, sizeof(new_lchan->mr_ms_lv)); + memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, sizeof(new_lchan->mr_bts_lv)); + + new_lchan->conn = conn; + + rc = rsl_chan_activate_lchan(new_lchan, + ho->async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC, + ho->ho_ref); + if (rc < 0) { + LOGPHO(ho, LOGL_INFO, "%s Failure: activate lchan rc = %d\n", + ho->inter_cell? "Handover" : "Assignment", rc); + lchan_free(new_lchan); + ho->new_lchan = NULL; + bsc_clear_handover(conn, 0); + return rc; + } + + rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ); + /* we continue in the SS_LCHAN handler / ho_chan_activ_ack */ + + return 0; +} + +/* clear any operation for this connection */ +void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan) +{ + struct bsc_handover *ho = conn->ho; + + if (!ho) + return; + + if (ho->new_lchan) { + ho->new_lchan->conn = NULL; + if (free_lchan) + lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END); + ho->new_lchan = NULL; + } + + handover_free(ho); + conn->ho = NULL; +} + +/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */ +static void ho_T3103_cb(void *_ho) +{ + struct bsc_handover *ho = _ho; + struct gsm_network *net = ho->new_lchan->ts->trx->bts->network; + + DEBUGP(DHO, "HO T3103 expired\n"); + rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]); + + /* Inform the GSCON FSM about the timed out handover */ + osmo_fsm_inst_dispatch(ho->old_lchan->conn->fi, GSCON_EV_HO_TIMEOUT, NULL); + + bsc_clear_handover(ho->old_lchan->conn, 1); +} + +/* RSL has acknowledged activation of the new lchan */ +static int ho_chan_activ_ack(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + /* we need to check if this channel activation is related to + * a handover at all (and if, which particular handover) */ + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) + return -ENODEV; + + LOGPHO(ho, LOGL_INFO, "Channel Activate Ack, send %s COMMAND\n", ho->inter_cell? "HANDOVER" : "ASSIGNMENT"); + + /* we can now send the 04.08 HANDOVER COMMAND to the MS + * using the old lchan */ + + gsm48_send_ho_cmd(ho->old_lchan, new_lchan, new_lchan->ms_power, ho->ho_ref); + + /* start T3103. We can continue either with T3103 expiration, + * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */ + osmo_timer_setup(&ho->T3103, ho_T3103_cb, ho); + osmo_timer_schedule(&ho->T3103, 10, 0); + + /* create a RTP connection */ + if (is_ipaccess_bts(new_lchan->ts->trx->bts)) + rsl_ipacc_crcx(new_lchan); + + return 0; +} + +/* RSL has not acknowledged activation of the new lchan */ +static int ho_chan_activ_nack(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + struct handover_decision_callbacks *hdc; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + /* This lchan is not involved in a handover. */ + return 0; + } + + hdc = handover_decision_callbacks_get(ho->from_hodec_id); + if (hdc && hdc->on_ho_chan_activ_nack) + hdc->on_ho_chan_activ_nack(ho); + + bsc_clear_handover(new_lchan->conn, 0); + return 0; +} + +/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */ +static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) +{ + struct gsm_network *net; + struct bsc_handover *ho; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + net = new_lchan->ts->trx->bts->network; + + LOGPHO(ho, LOGL_INFO, "%s Complete\n", ho->inter_cell ? "Handover" : "Assignment"); + + rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED]); + + osmo_timer_del(&ho->T3103); + + /* Replace the ho lchan with the primary one */ + if (ho->old_lchan != new_lchan->conn->lchan) + LOGPHO(ho, LOGL_ERROR, "Primary lchan changed during handover.\n"); + + if (new_lchan->conn->ho != ho) + LOGPHO(ho, LOGL_ERROR, "Handover channel changed during this handover.\n"); + + new_lchan->conn->lchan = new_lchan; + ho->old_lchan->conn = NULL; + + lchan_release(ho->old_lchan, 0, RSL_REL_LOCAL_END); + + handover_free(ho); + new_lchan->conn->ho = NULL; + + /* Inform the GSCON FSM that the handover is complete */ + osmo_fsm_inst_dispatch(new_lchan->conn->fi, GSCON_EV_HO_COMPL, NULL); + return 0; +} + +/* GSM 04.08 HANDOVER FAIL has been received */ +static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) +{ + struct gsm_network *net = old_lchan->ts->trx->bts->network; + struct bsc_handover *ho; + struct handover_decision_callbacks *hdc; + + ho = bsc_ho_by_old_lchan(old_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + hdc = handover_decision_callbacks_get(ho->from_hodec_id); + if (hdc && hdc->on_ho_failure) + hdc->on_ho_failure(ho); + + rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]); + + bsc_clear_handover(ho->new_lchan->conn, 1); + + /* Inform the GSCON FSM that the handover failed */ + osmo_fsm_inst_dispatch(old_lchan->conn->fi, GSCON_EV_HO_FAIL, NULL); + return 0; +} + +/* GSM 08.58 HANDOVER DETECT has been received */ +static int ho_rsl_detect(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + LOGPHO(ho, LOGL_DEBUG, "Handover RACH detected\n"); + + /* This is just for logging on the DHO category. The actual MGCP switchover happens in + * osmo_bsc_mgcp.c by receiving the same S_LCHAN_HANDOVER_DETECT signal. + * (Calling mgcp_handover() directly currently breaks linking in utils/...) */ + + return 0; +} + +static int ho_meas_rep(struct gsm_meas_rep *mr) +{ + struct handover_decision_callbacks *hdc; + enum hodec_id hodec_id = ho_get_algorithm(mr->lchan->ts->trx->bts->ho); + + hdc = handover_decision_callbacks_get(hodec_id); + if (!hdc || !hdc->on_measurement_report) + return 0; + hdc->on_measurement_report(mr); + return 0; +} + +static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct lchan_signal_data *lchan_data; + struct gsm_lchan *lchan; + + lchan_data = signal_data; + switch (subsys) { + case SS_LCHAN: + lchan = lchan_data->lchan; + switch (signal) { + case S_LCHAN_ACTIVATE_ACK: + return ho_chan_activ_ack(lchan); + case S_LCHAN_ACTIVATE_NACK: + return ho_chan_activ_nack(lchan); + case S_LCHAN_HANDOVER_DETECT: + return ho_rsl_detect(lchan); + case S_LCHAN_HANDOVER_COMPL: + return ho_gsm48_ho_compl(lchan); + case S_LCHAN_HANDOVER_FAIL: + return ho_gsm48_ho_fail(lchan); + case S_LCHAN_MEAS_REP: + return ho_meas_rep(lchan_data->mr); + } + break; + default: + break; + } + + return 0; +} + +/* Return the old lchan or NULL. This is meant for audio handling */ +struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) + return NULL; + return ho->old_lchan; +} + +static __attribute__((constructor)) void on_dso_load_ho_logic(void) +{ + osmo_signal_register_handler(SS_LCHAN, ho_logic_sig_cb, NULL); +} + +/* Count number of currently ongoing handovers + * inter_cell: if true, count only handovers between two cells. If false, count only handovers within one + * cell. */ +int bsc_ho_count(struct gsm_bts *bts, bool inter_cell) +{ + struct bsc_handover *ho; + int count = 0; + + llist_for_each_entry(ho, &bsc_handovers, list) { + if (ho->inter_cell != inter_cell) + continue; + if (ho->new_lchan->ts->trx->bts == bts) + count++; + } + + return count; +} + +void handover_decision_callbacks_register(struct handover_decision_callbacks *hdc) +{ + llist_add_tail(&hdc->entry, &handover_decision_callbacks); +} + +struct handover_decision_callbacks *handover_decision_callbacks_get(int hodec_id) +{ + struct handover_decision_callbacks *hdc; + llist_for_each_entry(hdc, &handover_decision_callbacks, entry) { + if (hdc->hodec_id == hodec_id) + return hdc; + } + return NULL; +} diff --git a/src/osmo-bsc/handover_vty.c b/src/osmo-bsc/handover_vty.c new file mode 100644 index 000000000..51e448e03 --- /dev/null +++ b/src/osmo-bsc/handover_vty.c @@ -0,0 +1,177 @@ +/* OsmoBSC interface to quagga VTY for handover parameters */ +/* (C) 2009-2010 by Andreas Eversberg + * (C) 2009-2010 by Harald Welte + * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Andreas Eversberg + * Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +static struct handover_cfg *ho_cfg_from_vty(struct vty *vty) +{ + switch (vty->node) { + case GSMNET_NODE: + return gsmnet_from_vty(vty)->ho; + case BTS_NODE: + OSMO_ASSERT(vty->index); + return ((struct gsm_bts *)vty->index)->ho; + default: + OSMO_ASSERT(false); + } +} + + +#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, \ + VTY_DOC) \ +DEFUN(cfg_ho_##NAME, cfg_ho_##NAME##_cmd, \ + VTY_CMD_PREFIX VTY_CMD " (" VTY_CMD_ARG "|default)", \ + VTY_DOC \ + "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n") \ +{ \ + struct handover_cfg *ho = ho_cfg_from_vty(vty); \ + const char *val = argv[0]; \ + if (!strcmp(val, "default")) { \ + const char *msg; \ + if (ho_isset_##NAME(ho)) {\ + ho_clear_##NAME(ho); \ + msg = "setting removed, now is"; \ + } else \ + msg = "already was unset, still is"; \ + vty_out(vty, "%% '" VTY_CMD_PREFIX VTY_CMD "' %s " VTY_WRITE_FMT "%s%s", \ + msg, VTY_WRITE_CONV( ho_get_##NAME(ho) ), \ + ho_isset_on_parent_##NAME(ho)? " (set on higher level node)" : "", \ + VTY_NEWLINE); \ + } \ + else \ + ho_set_##NAME(ho, VTY_ARG_EVAL(val)); \ + return CMD_SUCCESS; \ +} + +HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER + + +/* Aliases of 'handover' for 'handover1' for backwards compat */ +#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, \ + VTY_DOC) \ +ALIAS_DEPRECATED(cfg_ho_##NAME, cfg_ho_##NAME##_cmd_alias, \ + "handover " VTY_CMD " (" VTY_CMD_ARG "|default)", \ + "Legacy alias for 'handover1': " VTY_DOC \ + "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n"); + +HODEC1_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER + +static inline const int a2congestion_check_interval(const char *arg) +{ + if (!strcmp(arg, "disabled")) + return 0; + return atoi(arg); +} + +static inline const char *congestion_check_interval2a(int val) +{ + static char str[9]; + if (val < 1 + || snprintf(str, sizeof(str), "%d", val) >= sizeof(str)) + return "disabled"; + return str; +} + +DEFUN(cfg_net_ho_congestion_check_interval, cfg_net_ho_congestion_check_interval_cmd, + "handover2 congestion-check (disabled|<1-999>|now)", + HO_CFG_STR_HANDOVER2 + "Configure congestion check interval" HO_CFG_STR_2 + "Disable congestion checking, do not handover based on cell overload\n" + "Congestion check interval in seconds (default " + OSMO_STRINGIFY_VAL(HO_CFG_CONGESTION_CHECK_DEFAULT) ")\n" + "Manually trigger a congestion check to run right now\n") +{ + if (!strcmp(argv[0], "now")) { + hodec2_congestion_check(gsmnet_from_vty(vty)); + return CMD_SUCCESS; + } + + hodec2_on_change_congestion_check_interval(gsmnet_from_vty(vty), + a2congestion_check_interval(argv[0])); + return CMD_SUCCESS; +} + +static void ho_vty_write(struct vty *vty, const char *indent, struct handover_cfg *ho) +{ +#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, \ + VTY_DOC) \ + if (ho_isset_##NAME(ho)) \ + vty_out(vty, "%s" VTY_CMD_PREFIX VTY_CMD " " VTY_WRITE_FMT "%s", indent, \ + VTY_WRITE_CONV( ho_get_##NAME(ho) ), VTY_NEWLINE); + + HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER +} + +void ho_vty_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + ho_vty_write(vty, " ", bts->ho); +} + +void ho_vty_write_net(struct vty *vty, struct gsm_network *net) +{ + ho_vty_write(vty, " ", net->ho); + + if (net->hodec2.congestion_check_interval_s != HO_CFG_CONGESTION_CHECK_DEFAULT) + vty_out(vty, " handover2 congestion-check %s%s", + congestion_check_interval2a(net->hodec2.congestion_check_interval_s), + VTY_NEWLINE); +} + +static void ho_vty_init_cmds(int parent_node) +{ +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ + install_element(parent_node, &cfg_ho_##NAME##_cmd); + + HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER + + /* Aliases of 'handover' for 'handover1' for backwards compat */ +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ + install_element(parent_node, &cfg_ho_##NAME##_cmd_alias); + +HODEC1_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER +} + +void ho_vty_init() +{ + ho_vty_init_cmds(GSMNET_NODE); + install_element(GSMNET_NODE, &cfg_net_ho_congestion_check_interval_cmd); + + ho_vty_init_cmds(BTS_NODE); +} + diff --git a/src/osmo-bsc/meas_feed.c b/src/osmo-bsc/meas_feed.c new file mode 100644 index 000000000..2e80754d4 --- /dev/null +++ b/src/osmo-bsc/meas_feed.c @@ -0,0 +1,185 @@ +/* UDP-Feed of measurement reports */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +struct meas_feed_state { + struct osmo_wqueue wqueue; + char scenario[31+1]; + char *dst_host; + uint16_t dst_port; +}; + +static struct meas_feed_state g_mfs = {}; + +static int process_meas_rep(struct gsm_meas_rep *mr) +{ + struct msgb *msg; + struct meas_feed_meas *mfm; + struct bsc_subscr *bsub; + + /* 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"); + return 0; + } + if (!mr->lchan->conn) { + LOGP(DMEAS, LOGL_DEBUG, "meas_feed: lchan without conn, not sending report\n"); + return 0; + } + + bsub = mr->lchan->conn->bsub; + + msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed"); + if (!msg) + return 0; + + /* fill in the header */ + mfm = (struct meas_feed_meas *) msgb_put(msg, sizeof(*mfm)); + mfm->hdr.msg_type = MEAS_FEED_MEAS; + mfm->hdr.version = MEAS_FEED_VERSION; + + /* fill in MEAS_FEED_MEAS specific header */ + if (bsub) + osmo_strlcpy(mfm->imsi, bsub->imsi, sizeof(mfm->imsi)); + /* This used to be a human readable meaningful name set in the old osmo-nitb's subscriber + * database. Now we're several layers away from that (and possibly don't even have a name in + * osmo-hlr either), hence this is a legacy item now that we should leave empty ... *but*: + * here in the BSC we often don't know the subscriber's full identity information. For example, + * we might only know the TMSI, and hence would pass an empty IMSI above. So after all, feed + * bsc_subscr_name(), which possibly will feed the IMSI again, but in case only the TMSI is known + * would add that to the information set as "TMSI:0x12345678". */ + osmo_strlcpy(mfm->name, bsc_subscr_name(bsub), sizeof(mfm->name)); + osmo_strlcpy(mfm->scenario, g_mfs.scenario, sizeof(mfm->scenario)); + + /* copy the entire measurement report */ + memcpy(&mfm->mr, mr, sizeof(mfm->mr)); + + /* copy channel information */ + /* we assume that the measurement report always belong to some timeslot */ + mfm->lchan_type = (uint8_t)mr->lchan->type; + mfm->pchan_type = (uint8_t)mr->lchan->ts->pchan; + mfm->bts_nr = mr->lchan->ts->trx->bts->nr; + mfm->trx_nr = mr->lchan->ts->trx->nr; + mfm->ts_nr = mr->lchan->ts->nr; + mfm->ss_nr = mr->lchan->nr; + + /* and send it to the socket */ + if (osmo_wqueue_enqueue(&g_mfs.wqueue, msg) != 0) { + LOGP(DMEAS, LOGL_ERROR, "meas_feed %s: sending measurement report failed\n", + gsm_lchan_name(mr->lchan)); + msgb_free(msg); + } else + LOGP(DMEAS, LOGL_DEBUG, "meas_feed %s: sent measurement report\n", + gsm_lchan_name(mr->lchan)); + + return 0; +} + +static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct lchan_signal_data *sdata = signal_data; + + if (subsys != SS_LCHAN) + return 0; + + if (signal == S_LCHAN_MEAS_REP) + process_meas_rep(sdata->mr); + + return 0; +} + +static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + return write(ofd->fd, msgb_data(msg), msgb_length(msg)); +} + +static int feed_read_cb(struct osmo_fd *ofd) +{ + int rc; + char buf[256]; + + rc = read(ofd->fd, buf, sizeof(buf)); + ofd->fd &= ~BSC_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"); + } + + 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_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM, + IPPROTO_UDP, dst_host, dst_port, + OSMO_SOCK_F_CONNECT); + if (rc < 0) + return rc; + + g_mfs.wqueue.bfd.when &= ~BSC_FD_READ; + + if (g_mfs.dst_host) + talloc_free(g_mfs.dst_host); + g_mfs.dst_host = talloc_strdup(NULL, dst_host); + g_mfs.dst_port = dst_port; + + return 0; +} + +void meas_feed_cfg_get(char **host, uint16_t *port) +{ + *port = g_mfs.dst_port; + *host = g_mfs.dst_host; +} + +void meas_feed_scenario_set(const char *name) +{ + osmo_strlcpy(g_mfs.scenario, name, sizeof(g_mfs.scenario)); +} + +const char *meas_feed_scenario_get(void) +{ + return g_mfs.scenario; +} diff --git a/src/osmo-bsc/meas_rep.c b/src/osmo-bsc/meas_rep.c new file mode 100644 index 000000000..73d9a1f21 --- /dev/null +++ b/src/osmo-bsc/meas_rep.c @@ -0,0 +1,134 @@ +/* Measurement Report Processing */ + +/* (C) 2009 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include + +static int get_field(const struct gsm_meas_rep *rep, + enum meas_rep_field field) +{ + switch (field) { + case MEAS_REP_DL_RXLEV_FULL: + if (!(rep->flags & MEAS_REP_F_DL_VALID)) + return -EINVAL; + return rep->dl.full.rx_lev; + case MEAS_REP_DL_RXLEV_SUB: + if (!(rep->flags & MEAS_REP_F_DL_VALID)) + return -EINVAL; + return rep->dl.sub.rx_lev; + case MEAS_REP_DL_RXQUAL_FULL: + if (!(rep->flags & MEAS_REP_F_DL_VALID)) + return -EINVAL; + return rep->dl.full.rx_qual; + case MEAS_REP_DL_RXQUAL_SUB: + if (!(rep->flags & MEAS_REP_F_DL_VALID)) + return -EINVAL; + return rep->dl.sub.rx_qual; + case MEAS_REP_UL_RXLEV_FULL: + return rep->ul.full.rx_lev; + case MEAS_REP_UL_RXLEV_SUB: + return rep->ul.sub.rx_lev; + case MEAS_REP_UL_RXQUAL_FULL: + return rep->ul.full.rx_qual; + case MEAS_REP_UL_RXQUAL_SUB: + return rep->ul.sub.rx_qual; + } + + return 0; +} + + +unsigned int calc_initial_idx(unsigned int array_size, + unsigned int meas_rep_idx, + unsigned int num_values) +{ + int offs, idx; + + /* from which element do we need to start if we're interested + * in an average of 'num' elements */ + offs = meas_rep_idx - num_values; + + if (offs < 0) + idx = array_size + offs; + else + idx = offs; + + return idx; +} + +/* obtain an average over the last 'num' fields in the meas reps */ +int get_meas_rep_avg(const struct gsm_lchan *lchan, + enum meas_rep_field field, unsigned int num) +{ + unsigned int i, idx; + int avg = 0, valid_num = 0; + + if (num < 1) + return -EINVAL; + + if (num > lchan->meas_rep_count) + return -EINVAL; + + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, num); + + for (i = 0; i < num; i++) { + int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep); + int val = get_field(&lchan->meas_rep[j], field); + + if (val >= 0) { + avg += val; + valid_num++; + } + } + + if (valid_num == 0) + return -EINVAL; + + return avg / valid_num; +} + +/* 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) +{ + unsigned int i, idx; + int count = 0; + + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, m); + + for (i = 0; i < m; i++) { + int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep); + int val = get_field(&lchan->meas_rep[j], field); + + if (val >= be) /* implies that val < 0 will not count */ + count++; + + if (count >= n) + return 1; + } + + return 0; +} diff --git a/src/osmo-bsc/net_init.c b/src/osmo-bsc/net_init.c new file mode 100644 index 000000000..db84e2a53 --- /dev/null +++ b/src/osmo-bsc/net_init.c @@ -0,0 +1,69 @@ +/* (C) 2008-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +/* Initialize the bare minimum of struct gsm_network, minimizing required dependencies. + * 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_network_init()). */ +struct gsm_network *gsm_network_init(void *ctx) +{ + struct gsm_network *net = talloc_zero(ctx, struct gsm_network); + if (!net) + return NULL; + + net->plmn = (struct osmo_plmn_id){ + .mcc = 1, + .mnc = 1, + }; + + net->dyn_ts_allow_tch_f = true; + + /* Permit a compile-time default of A5/3 and A5/1 */ + net->a5_encryption_mask = (1 << 3) | (1 << 1); + + /* Use 30 min periodic update interval as sane default */ + net->t3212 = 5; + + INIT_LLIST_HEAD(&net->subscr_conns); + + net->bsc_subscribers = talloc_zero(net, struct llist_head); + INIT_LLIST_HEAD(net->bsc_subscribers); + + INIT_LLIST_HEAD(&net->bts_list); + net->num_bts = 0; + net->T3101 = GSM_T3101_DEFAULT; + net->T3103 = GSM_T3103_DEFAULT; + net->T3105 = GSM_T3105_DEFAULT; + net->T3107 = GSM_T3107_DEFAULT; + net->T3109 = GSM_T3109_DEFAULT; + net->T3111 = GSM_T3111_DEFAULT; + net->T3113 = GSM_T3113_DEFAULT; + net->T3115 = GSM_T3115_DEFAULT; + net->T3117 = GSM_T3117_DEFAULT; + net->T3119 = GSM_T3119_DEFAULT; + net->T3122 = GSM_T3122_DEFAULT; + net->T3141 = GSM_T3141_DEFAULT; + + return net; +} diff --git a/src/osmo-bsc/osmo_bsc_lcls.c b/src/osmo-bsc/osmo_bsc_lcls.c new file mode 100644 index 000000000..c2b076090 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_lcls.c @@ -0,0 +1,766 @@ +/* (C) 2018 by Harald Welte + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct value_string lcls_event_names[] = { + { LCLS_EV_UPDATE_CFG_CSC, "UPDATE_CFG_CSC" }, + { LCLS_EV_APPLY_CFG_CSC, "APPLY_CFG_CSC" }, + { LCLS_EV_CORRELATED, "CORRELATED" }, + { LCLS_EV_OTHER_ENABLED, "OTHER_ENABLED" }, + { LCLS_EV_OTHER_BREAK, "OTHER_BREAK" }, + { LCLS_EV_OTHER_DEAD, "OTHER_DEAD" }, + { 0, NULL } +}; + + +/*********************************************************************** + * Utility functions + ***********************************************************************/ + +enum gsm0808_lcls_status lcls_get_status(struct gsm_subscriber_connection *conn) +{ + if (!conn->lcls.fi) + return 0xff; + + switch (conn->lcls.fi->state) { + case ST_NO_LCLS: + return 0xff; + case ST_NOT_YET_LS: + return GSM0808_LCLS_STS_NOT_YET_LS; + case ST_NOT_POSSIBLE_LS: + return GSM0808_LCLS_STS_NOT_POSSIBLE_LS; + case ST_NO_LONGER_LS: + return GSM0808_LCLS_STS_NO_LONGER_LS; + case ST_REQ_LCLS_NOT_SUPP: + return GSM0808_LCLS_STS_REQ_LCLS_NOT_SUPP; + case ST_LOCALLY_SWITCHED: + case ST_LOCALLY_SWITCHED_WAIT_BREAK: + case ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK: + return GSM0808_LCLS_STS_LOCALLY_SWITCHED; + } + OSMO_ASSERT(0); +} + +static void lcls_send_notify(struct gsm_subscriber_connection *conn) +{ + enum gsm0808_lcls_status status = lcls_get_status(conn); + struct msgb *msg; + + if (status == 0xff) + return; + + LOGPFSM(conn->lcls.fi, "Sending BSSMAP LCLS NOTIFICATION (%s)\n", + gsm0808_lcls_status_name(status)); + msg = gsm0808_create_lcls_notification(status, false); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, msg); +} + +static struct gsm_subscriber_connection * +find_conn_with_same_gcr(struct gsm_subscriber_connection *conn_local) +{ + struct gsm_network *net = conn_local->network; + struct gsm_subscriber_connection *conn_other; + + llist_for_each_entry(conn_other, &net->subscr_conns, entry) { + /* don't report back the same connection */ + if (conn_other == conn_local) + continue; + /* don't consider any conn where GCR length is not the same as before */ + if (conn_other->lcls.global_call_ref_len != conn_local->lcls.global_call_ref_len) + continue; + if (!memcmp(conn_other->lcls.global_call_ref, conn_local->lcls.global_call_ref, + conn_local->lcls.global_call_ref_len)) + return conn_other; + } + return NULL; +} + +static bool lcls_is_supported_config(enum gsm0808_lcls_config cfg) +{ + /* this is the only configuration that we support for now */ + if (cfg == GSM0808_LCLS_CFG_BOTH_WAY) + return true; + else + return false; +} + +/* LCLS Call Leg Correlation as per 23.284 4.3 / 48.008 3.1.33.2.1 */ +static int lcls_perform_correlation(struct gsm_subscriber_connection *conn_local) +{ + struct gsm_subscriber_connection *conn_other; + + /* We can only correlate if a GCR is present */ + OSMO_ASSERT(conn_local->lcls.global_call_ref_len); + /* We can only correlate if we're not in active LS */ + OSMO_ASSERT(conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED && + conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_BREAK && + conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK); + + conn_other = conn_local->lcls.other; + if (conn_other) { + LOGPFSM(conn_local->lcls.fi, "Breaking previous correlation with %s\n", + osmo_fsm_inst_name(conn_other->lcls.fi)); + OSMO_ASSERT(conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED && + conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_BREAK && + conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK); + conn_local->lcls.other->lcls.other = NULL; + conn_local->lcls.other = NULL; + } + + conn_other = find_conn_with_same_gcr(conn_local); + if (!conn_other) { + /* we found no other call with same GCR: not possible */ + LOGPFSM(conn_local->lcls.fi, "Unsuccessful correlation\n"); + return -ENODEV; + } + + /* store pointer to "other" in "local" */ + conn_local->lcls.other = conn_other; + + LOGPFSM(conn_local->lcls.fi, "Successfully correlated with %s\n", + osmo_fsm_inst_name(conn_other->lcls.fi)); + + /* notify other conn about our correlation */ + osmo_fsm_inst_dispatch(conn_other->lcls.fi, LCLS_EV_CORRELATED, conn_local); + + return 0; +} + + +struct lcls_cfg_csc { + enum gsm0808_lcls_config config; + enum gsm0808_lcls_control control; +}; + +/* Update the connections LCLS configuration and return old/previous configuration. + * \returns (staticallly allocated) old configuration; NULL if new config not supported */ +static struct lcls_cfg_csc *update_lcls_cfg_csc(struct gsm_subscriber_connection *conn, + struct lcls_cfg_csc *new_cfg_csc) +{ + static struct lcls_cfg_csc old_cfg_csc; + old_cfg_csc.config = conn->lcls.config; + old_cfg_csc.control = conn->lcls.control; + + if (new_cfg_csc->config != 0xff) { + if (!lcls_is_supported_config(new_cfg_csc->config)) + return NULL; + if (conn->lcls.config != new_cfg_csc->config) { + /* TODO: logging */ + conn->lcls.config = new_cfg_csc->config; + } + } + if (new_cfg_csc->control != 0xff) { + if (conn->lcls.control != new_cfg_csc->control) { + /* TODO: logging */ + conn->lcls.control = new_cfg_csc->control; + } + } + + return &old_cfg_csc; +} + +/* Attempt to update conn->lcls with the new config/csc provided. If new config is + * unsupported, change into LCLS NOT SUPPORTED state and return -EINVAL. */ +static int lcls_handle_cfg_update(struct gsm_subscriber_connection *conn, void *data) +{ + struct lcls_cfg_csc *new_cfg_csc, *old_cfg_csc; + + new_cfg_csc = (struct lcls_cfg_csc *) data; + old_cfg_csc = update_lcls_cfg_csc(conn, new_cfg_csc); + if (!old_cfg_csc) { + osmo_fsm_inst_state_chg(conn->lcls.fi, ST_REQ_LCLS_NOT_SUPP, 0, 0); + return -EINVAL; + } + return 0; +} + +/* notify the LCLS FSM about new LCLS Config and/or CSC */ +void lcls_update_config(struct gsm_subscriber_connection *conn, + const uint8_t *config, const uint8_t *control) +{ + struct lcls_cfg_csc new_cfg = { + .config = 0xff, + .control = 0xff, + }; + /* nothing to update, skip it */ + if (!config && !control) + return; + if (config) + new_cfg.config = *config; + if (control) + new_cfg.control = *control; + osmo_fsm_inst_dispatch(conn->lcls.fi, LCLS_EV_UPDATE_CFG_CSC, &new_cfg); +} + +/* apply the configuration, may be changed before by lcls_update_config */ +void lcls_apply_config(struct gsm_subscriber_connection *conn) +{ + osmo_fsm_inst_dispatch(conn->lcls.fi, LCLS_EV_APPLY_CFG_CSC, NULL); +} + +static void lcls_break_local_switching(struct gsm_subscriber_connection *conn) +{ + struct mgcp_conn_peer peer; + struct sockaddr_in *sin; + + LOGPFSM(conn->lcls.fi, "=== HERE IS WHERE WE DISABLE LCLS\n"); + if (!conn->user_plane.fi_msc) { + /* the MGCP FSM has died, e.g. due to some MGCP/SDP parsing error */ + LOGPFSML(conn->lcls.fi, LOGL_NOTICE, "Cannot disable LCLS without MSC-side MGCP FSM\n"); + return; + } + + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; + OSMO_ASSERT(sin->sin_family == AF_INET); + + memset(&peer, 0, sizeof(peer)); + peer.port = htons(sin->sin_port); + osmo_strlcpy(peer.addr, inet_ntoa(sin->sin_addr), sizeof(peer.addr)); + mgcp_conn_modify(conn->user_plane.fi_msc, 0, &peer); +} + +static bool lcls_enable_possible(struct gsm_subscriber_connection *conn) +{ + struct gsm_subscriber_connection *other_conn = conn->lcls.other; + OSMO_ASSERT(other_conn); + + if (!lcls_is_supported_config(conn->lcls.config)) { + LOGPFSM(conn->lcls.fi, "Not enabling LS due to unsupported local config\n"); + return false; + } + + if (!lcls_is_supported_config(other_conn->lcls.config)) { + LOGPFSM(conn->lcls.fi, "Not enabling LS due to unsupported other config\n"); + return false; + } + + if (conn->lcls.control != GSM0808_LCLS_CSC_CONNECT) { + LOGPFSM(conn->lcls.fi, "Not enabling LS due to insufficient local control\n"); + return false; + } + + if (other_conn->lcls.control != GSM0808_LCLS_CSC_CONNECT) { + LOGPFSM(conn->lcls.fi, "Not enabling LS due to insufficient other control\n"); + return false; + } + + return true; +} + +/*********************************************************************** + * State callback functions + ***********************************************************************/ + +static void lcls_no_lcls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* we're just starting and cannot yet have a correlated call */ + OSMO_ASSERT(conn->lcls.other == NULL); + + if (conn->sccp.msc->lcls_mode == BSC_LCLS_MODE_DISABLED) { + LOGPFSML(fi, LOGL_DEBUG, "LCLS disabled for this MSC, ignoring %s\n", + osmo_fsm_event_name(fi->fsm, event)); + return; + } + + /* If there's no GCR set, we can never leave this state */ + if (conn->lcls.global_call_ref_len == 0) { + LOGPFSML(fi, LOGL_NOTICE, "No GCR set, ignoring %s\n", + osmo_fsm_event_name(fi->fsm, event)); + return; + } + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + return; + case LCLS_EV_APPLY_CFG_CSC: + if (conn->lcls.config == 0xff) + return; + if (lcls_perform_correlation(conn) != 0) { + /* Correlation leads to no result: Not Possible to LS */ + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + return; + } + /* we now have two correlated calls */ + OSMO_ASSERT(conn->lcls.other); + if (lcls_enable_possible(conn)) { + /* Local Switching now active */ + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); + } else { + /* Couldn't be enabled: Not yet LS */ + osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); + } + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void lcls_not_yet_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* not yet locally switched means that we have correlation but no instruction + * to actually connect them yet */ + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + return; + case LCLS_EV_APPLY_CFG_CSC: + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); + } + break; + case LCLS_EV_OTHER_ENABLED: + OSMO_ASSERT(conn->lcls.other == data); + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + } else { + /* we couldn't enable our side, so ask other side to break */ + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); + } + break; + case LCLS_EV_CORRELATED: + /* other call informs us that he correlated with us */ + conn->lcls.other = data; + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void lcls_not_possible_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other == NULL); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + return; + case LCLS_EV_APPLY_CFG_CSC: + if (lcls_perform_correlation(conn) != 0) { + /* no correlation result: Remain in NOT_POSSIBLE_LS */ + return; + } + /* we now have two correlated calls */ + OSMO_ASSERT(conn->lcls.other); + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); + } else { + osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); + } + break; + case LCLS_EV_CORRELATED: + /* other call informs us that he correlated with us */ + conn->lcls.other = data; + osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); + /* Send NOTIFY about the fact that correlation happened */ + lcls_send_notify(conn); + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void lcls_no_longer_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); + } + break; + case LCLS_EV_OTHER_ENABLED: + OSMO_ASSERT(conn->lcls.other == data); + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + } else { + /* we couldn't enable our side, so ask other side to break */ + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); + } + break; + case LCLS_EV_CORRELATED: + /* other call informs us that he correlated with us */ + conn->lcls.other = data; + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void lcls_req_lcls_not_supp_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* we could have a correlated other call or not */ + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + //FIXME osmo_fsm_inst_state_chg(fi, + return; + case LCLS_EV_APPLY_CFG_CSC: + if (lcls_perform_correlation(conn) != 0) { + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + return; + } + /* we now have two correlated calls */ + OSMO_ASSERT(conn->lcls.other); + if (!lcls_is_supported_config(conn->lcls.config)) + return; + if (lcls_enable_possible(conn)) + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + else + osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); + break; + case LCLS_EV_CORRELATED: + /* other call informs us that he correlated with us */ + conn->lcls.other = data; + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + break; + default: + OSMO_ASSERT(0); + break; + } + +} + +static void lcls_locally_switched_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) { + lcls_break_local_switching(conn); + return; + } + break; + case LCLS_EV_APPLY_CFG_CSC: + if (conn->lcls.control == GSM0808_LCLS_CSC_RELEASE_LCLS) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); + /* FIXME: what if there's a new config included? */ + return; + } + /* TODO: Handle any changes of "config" once we support bi-casting etc. */ + break; + case LCLS_EV_OTHER_BREAK: + OSMO_ASSERT(conn->lcls.other == data); + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED_WAIT_BREAK, 0, 0); + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + break; + default: + OSMO_ASSERT(0); + break; + } +} + + +static void lcls_locally_switched_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct gsm_subscriber_connection *conn_other = conn->lcls.other; + struct mgcp_conn_peer peer; + struct sockaddr_in *sin; + + OSMO_ASSERT(conn_other); + + LOGPFSM(fi, "=== HERE IS WHERE WE ENABLE LCLS\n"); + if (!conn->user_plane.fi_msc) { + LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without MSC-side MGCP FSM. FIXME\n"); + return; + } + + sin = (struct sockaddr_in *)&conn_other->user_plane.aoip_rtp_addr_local; + OSMO_ASSERT(sin->sin_family == AF_INET); + + memset(&peer, 0, sizeof(peer)); + peer.port = htons(sin->sin_port); + osmo_strlcpy(peer.addr, inet_ntoa(sin->sin_addr), sizeof(peer.addr)); + mgcp_conn_modify(conn->user_plane.fi_msc, 0, &peer); + +} + +static void lcls_locally_switched_wait_break_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) { + lcls_break_local_switching(conn); + return; + } + break; + case LCLS_EV_APPLY_CFG_CSC: + if (conn->lcls.control == GSM0808_LCLS_CSC_RELEASE_LCLS) { + lcls_break_local_switching(conn); + osmo_fsm_inst_state_chg(fi, ST_NO_LONGER_LS, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); + /* no NOTIFY here, as the caller will be returning status in LCLS-CTRL-ACK */ + /* FIXME: what if there's a new config included? */ + return; + } + /* TODO: Handle any changes of "config" once we support bi-casting etc. */ + break; + case LCLS_EV_OTHER_BREAK: + /* we simply ignore it, must be a re-transmission */ + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + break; + default: + lcls_locally_switched_fn(fi, event, data); + break; + } +} + +static void lcls_locally_switched_wait_other_break_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) { + lcls_break_local_switching(conn); + return; + } + /* TODO: Handle any changes of "config" once we support bi-casting etc. */ + break; + case LCLS_EV_OTHER_BREAK: + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + lcls_break_local_switching(conn); + osmo_fsm_inst_state_chg(fi, ST_NO_LONGER_LS, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + break; + default: + lcls_locally_switched_fn(fi, event, data); + break; + } +} + +static void lcls_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + if (conn->lcls.other) { + /* inform the "other" side that we're dead, so it can disabe LS and send NOTIFY */ + if (conn->lcls.other->fi) + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_DEAD, conn); + conn->lcls.other = NULL; + } +} + + +/*********************************************************************** + * FSM Definition + ***********************************************************************/ + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state lcls_fsm_states[] = { + [ST_NO_LCLS] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC), + .out_state_mask = S(ST_NO_LCLS) | + S(ST_NOT_YET_LS) | + S(ST_NOT_POSSIBLE_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "NO_LCLS", + .action = lcls_no_lcls_fn, + }, + [ST_NOT_YET_LS] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_CORRELATED) | + S(LCLS_EV_OTHER_ENABLED) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NOT_YET_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "NOT_YET_LS", + .action = lcls_not_yet_ls_fn, + }, + [ST_NOT_POSSIBLE_LS] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_CORRELATED), + .out_state_mask = S(ST_NOT_YET_LS) | + S(ST_NOT_POSSIBLE_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "NOT_POSSIBLE_LS", + .action = lcls_not_possible_ls_fn, + }, + [ST_NO_LONGER_LS] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_CORRELATED) | + S(LCLS_EV_OTHER_ENABLED) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NO_LONGER_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "NO_LONGER_LS", + .action = lcls_no_longer_ls_fn, + }, + [ST_REQ_LCLS_NOT_SUPP] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_CORRELATED) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NOT_YET_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "REQ_LCLS_NOT_SUPP", + .action = lcls_req_lcls_not_supp_fn, + }, + [ST_LOCALLY_SWITCHED] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_OTHER_BREAK) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NO_LONGER_LS) | + S(ST_NOT_POSSIBLE_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED_WAIT_BREAK) | + S(ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK) | + S(ST_LOCALLY_SWITCHED), + .name = "LOCALLY_SWITCHED", + .action = lcls_locally_switched_fn, + .onenter = lcls_locally_switched_onenter, + }, + /* received an "other" break, waiting for the local break */ + [ST_LOCALLY_SWITCHED_WAIT_BREAK] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_OTHER_BREAK) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NO_LONGER_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED) | + S(ST_LOCALLY_SWITCHED_WAIT_BREAK), + .name = "LOCALLY_SWITCHED_WAIT_BREAK", + .action = lcls_locally_switched_wait_break_fn, + }, + /* received a local break, waiting for the "other" break */ + [ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_OTHER_BREAK) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NO_LONGER_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED) | + S(ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK), + .name = "LOCALLY_SWITCHED_WAIT_OTHER_BREAK", + .action = lcls_locally_switched_wait_other_break_fn, + }, + + +}; + +struct osmo_fsm lcls_fsm = { + .name = "LCLS", + .states = lcls_fsm_states, + .num_states = ARRAY_SIZE(lcls_fsm_states), + .allstate_event_mask = 0, + .allstate_action = NULL, + .cleanup = lcls_fsm_cleanup, + .timer_cb = NULL, + .log_subsys = DLCLS, + .event_names = lcls_event_names, +}; diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c index fefc04180..5c6a872b4 100644 --- a/src/osmo-bsc/osmo_bsc_main.c +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -42,9 +42,15 @@ #include #include #include +#include +#include #include #include +#include +#include +#include +#include #include @@ -147,6 +153,369 @@ static void handle_options(int argc, char **argv) } } +/* Callback function for NACK on the OML NM */ +static int oml_msg_nack(struct nm_nack_signal_data *nack) +{ + if (nack->mt == NM_MT_GET_ATTR_NACK) { + LOGP(DNM, LOGL_ERROR, "BTS%u does not support Get Attributes " + "OML message.\n", nack->bts->nr); + return 0; + } + + if (nack->mt == NM_MT_SET_BTS_ATTR_NACK) + LOGP(DNM, LOGL_ERROR, "Failed to set BTS attributes. That is fatal. " + "Was the bts type and frequency properly specified?\n"); + else + LOGP(DNM, LOGL_ERROR, "Got %s NACK going to drop the OML links.\n", + abis_nm_nack_name(nack->mt)); + + if (!nack->bts) { + LOGP(DNM, LOGL_ERROR, "Unknown bts. Can not drop it.\n"); + return 0; + } + + if (is_ipaccess_bts(nack->bts)) + ipaccess_drop_oml(nack->bts); + + return 0; +} + +/* 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_nack_signal_data *nack; + + switch (signal) { + case S_NM_NACK: + nack = signal_data; + return oml_msg_nack(nack); + default: + break; + } + return 0; +} + +/* Produce a MA as specified in 10.5.2.21 */ +static int 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 */ + struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc; + struct bitvec *ts_arfcn = &ts->hopping.arfcns; + struct bitvec *ma = &ts->hopping.ma; + unsigned int num_cell_arfcns, bitnum, n_chan; + int i; + + /* re-set the MA to all-zero */ + ma->cur_bit = 0; + ts->hopping.ma_len = 0; + 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++; + } + + /* pad it to octet-aligned number of bits */ + ts->hopping.ma_len = num_cell_arfcns / 8; + if (num_cell_arfcns % 8) + ts->hopping.ma_len++; + + n_chan = 0; + for (i = 0; i < 1024; i++) { + if (!bitvec_get_bit_pos(cell_chan, i)) + continue; + /* set the corresponding bit in the MA */ + bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan; + if (bitvec_get_bit_pos(ts_arfcn, i)) + bitvec_set_bit_pos(ma, bitnum, 1); + else + bitvec_set_bit_pos(ma, bitnum, 0); + n_chan++; + } + + /* ARFCN 0 is special: It is coded last in the bitmask */ + if (bitvec_get_bit_pos(cell_chan, 0)) { + n_chan++; + /* set the corresponding bit in the MA */ + bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan; + if (bitvec_get_bit_pos(ts_arfcn, 0)) + bitvec_set_bit_pos(ma, bitnum, 1); + else + bitvec_set_bit_pos(ma, bitnum, 0); + } + + return 0; +} + +static void bootstrap_rsl(struct gsm_bts_trx *trx) +{ + unsigned int i; + + LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) " + "on ARFCN %u using MCC-MNC %s LAC=%u CID=%u BSIC=%u\n", + trx->bts->nr, trx->nr, trx->arfcn, + osmo_plmn_name(&bsc_gsmnet->plmn), + trx->bts->location_area_code, + trx->bts->cell_identity, trx->bts->bsic); + + if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) { + rsl_nokia_si_begin(trx); + } + + /* + * Trigger ACC ramping before sending system information to BTS. + * This ensures that RACH control in system information is configured correctly. + * TRX 0 should be usable and unlocked, otherwise starting ACC ramping is pointless. + */ + if (trx_is_usable(trx) && trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + acc_ramp_trigger(&trx->bts->acc_ramp); + + gsm_bts_trx_set_system_infos(trx); + + if (trx->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); + } + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + generate_ma_for_ts(ts); + gsm_ts_check_init(ts); + } +} + +/* 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 ts_no, lchan_no; + /* 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; + /* was static in system_information.c */ + extern int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts); + 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); + break; + case S_L_INP_TEI_DN: + LOGP(DLMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx); + + if (isd->link_type == E1INP_SIGN_OML) + rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_OML_FAIL]); + 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); + } + + /* + * free all allocated channels. change the nm_state so the + * trx and trx_ts becomes unusable and chan_alloc.c can not + * allocate from it. + */ + for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_no]; + + for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) { + if (ts->lchan[lchan_no].state != LCHAN_S_NONE) + lchan_free(&ts->lchan[lchan_no]); + lchan_reset(&ts->lchan[lchan_no]); + } + } + + gsm_bts_mo_reset(trx->bts); + + abis_nm_clear_queue(trx->bts); + break; + default: + break; + } + + return 0; +} + +static int bootstrap_bts(struct gsm_bts *bts) +{ + int i, n; + + 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; + } + + /* Control Channel Description is set from vty/config */ + + /* T3212 is set from vty/config */ + + /* Set ccch config by looking at ts config */ + for (n=0, i=0; i<8; i++) + n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0; + + /* Indicate R99 MSC in SI3 */ + bts->si_common.chan_desc.mscr = 1; + + switch (n) { + case 0: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C; + /* Limit reserved block to 2 on combined channel according to + 3GPP TS 44.018 Table 10.5.2.11.1 */ + if (bts->si_common.chan_desc.bs_ag_blks_res > 2) { + LOGP(DNM, LOGL_NOTICE, "CCCH is combined with SDCCHs, " + "reducing BS-AG-BLKS-RES value %d -> 2\n", + bts->si_common.chan_desc.bs_ag_blks_res); + bts->si_common.chan_desc.bs_ag_blks_res = 2; + } + break; + case 1: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC; + break; + case 2: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC; + break; + case 3: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC; + break; + case 4: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC; + break; + default: + LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n"); + return -EINVAL; + } + + bts->si_common.cell_options.pwrc = 0; /* PWRC not set */ + + bts->si_common.cell_sel_par.acs = 0; + + bts->si_common.ncc_permitted = 0xff; + + bts->chan_load_samples_idx = 0; + + /* ACC ramping is initialized from vty/config */ + + /* Initialize the BTS state */ + gsm_bts_mo_reset(bts); + + return 0; +} + +static int bsc_network_configure(const char *config_file) +{ + struct gsm_bts *bts; + int rc; + + 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); + 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); + if (rc < 0) + return rc; + + osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL); + 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); + if (rc < 0) { + LOGP(DNM, LOGL_FATAL, "Error bootstrapping BTS\n"); + return rc; + } + rc = e1_reconfig_bts(bts); + if (rc < 0) { + LOGP(DNM, LOGL_FATAL, "Error enabling E1 input driver\n"); + return rc; + } + } + + return 0; +} + static int bsc_vty_go_parent(struct vty *vty) { switch (vty->node) { diff --git a/src/osmo-bsc/paging.c b/src/osmo-bsc/paging.c new file mode 100644 index 000000000..d6bff2a42 --- /dev/null +++ b/src/osmo-bsc/paging.c @@ -0,0 +1,473 @@ +/* Paging helper and manager.... */ +/* (C) 2009,2013 by Holger Hans Peter Freyther + * 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 . + * + */ + +/* + * Relevant specs: + * 12.21: + * - 9.4.12 for CCCH Local Threshold + * + * 05.58: + * - 8.5.2 CCCH Load indication + * - 9.3.15 Paging Load + * + * Approach: + * - Send paging command to subscriber + * - On Channel Request we will remember the reason + * - After the ACK we will request the identity + * - Then we will send assign the gsm_subscriber and + * - and call a callback + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +void *tall_paging_ctx = NULL; + +#define PAGING_TIMER 0, 500000 + +/* + * TODO MSCSPLIT: the paging in libbsc is closely tied to MSC land in that the + * MSC realm callback functions used to be invoked from the BSC/BTS level. So + * this entire file needs to be rewired for use with an A interface. + */ + +/* + * 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) +{ + osmo_timer_del(&to_be_deleted->T3113); + llist_del(&to_be_deleted->entry); + bsc_subscr_put(to_be_deleted->bsub); + talloc_free(to_be_deleted); +} + +static void page_ms(struct gsm_paging_request *request) +{ + uint8_t mi[128]; + unsigned int mi_len; + unsigned int page_group; + struct gsm_bts *bts = request->bts; + + log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub); + + LOGP(DPAG, LOGL_INFO, "(bts=%d) Going to send paging commands: imsi: %s tmsi: " + "0x%08x for ch. type %d (attempt %d)\n", bts->nr, request->bsub->imsi, + request->bsub->tmsi, request->chan_type, request->attempts); + + if (request->bsub->tmsi == GSM_RESERVED_TMSI) + mi_len = gsm48_generate_mid_from_imsi(mi, request->bsub->imsi); + else + mi_len = gsm48_generate_mid_from_tmsi(mi, request->bsub->tmsi); + + page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc, + str_to_imsi(request->bsub->imsi)); + gsm0808_page(bts, page_group, mi_len, mi, request->chan_type); + log_set_context(LOG_CTX_BSC_SUBSCR, NULL); +} + +static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts) +{ + if (llist_empty(&paging_bts->pending_requests)) + return; + + if (!osmo_timer_pending(&paging_bts->work_timer)) + osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER); +} + + +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; + + LOGP(DPAG, LOGL_NOTICE, "(bts=%d) No PCH LOAD IND, adding 20 slots)\n", + paging_bts->bts->nr); + paging_bts->available_slots = 20; + paging_handle_pending_requests(paging_bts); +} + +/*! count the number of free channels for given RSL channel type required + * \param[in] BTS on which we shall count + * \param[in] rsl_type the RSL channel needed type + * \returns number of free channels matching \a rsl_type in \a bts */ +static int can_send_pag_req(struct gsm_bts *bts, int rsl_type) +{ + struct pchan_load pl; + int count; + + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + + switch (rsl_type) { + case RSL_CHANNEED_TCH_F: + case RSL_CHANNEED_TCH_ForH: + goto count_tch; + break; + case RSL_CHANNEED_SDCCH: + goto count_sdcch; + break; + case RSL_CHANNEED_ANY: + default: + if (bts->network->pag_any_tch) + goto count_tch; + else + goto count_sdcch; + break; + } + + return 0; + + /* could available SDCCH */ +count_sdcch: + count = 0; + count += pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].total + - pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].used; + count += pl.pchan[GSM_PCHAN_CCCH_SDCCH4].total + - pl.pchan[GSM_PCHAN_CCCH_SDCCH4].used; + return bts->paging.free_chans_need > count; + +count_tch: + count = 0; + count += pl.pchan[GSM_PCHAN_TCH_F].total + - pl.pchan[GSM_PCHAN_TCH_F].used; + if (bts->network->neci) + count += pl.pchan[GSM_PCHAN_TCH_H].total + - pl.pchan[GSM_PCHAN_TCH_H].used; + return bts->paging.free_chans_need > count; +} + +/* + * 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. + */ +static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts) +{ + struct gsm_paging_request *request = NULL; + + /* + * 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 */ + 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; + } + + /* Skip paging if the bts is down. */ + if (!request->bts->oml_link) + goto skip_paging; + + /* 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); +} + +static void paging_worker(void *data) +{ + struct gsm_bts_paging_state *paging_bts = data; + + paging_handle_pending_requests(paging_bts); +} + +/*! initialize the bts paging state, if it hasn't been initialized yet */ +static void paging_init_if_needed(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; +} + +/*! 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) +{ + struct gsm_paging_request *req; + + llist_for_each_entry(req, &bts->pending_requests, entry) { + if (bsub == req->bsub) + return 1; + } + + return 0; +} + +/*! Call-back once T3113 (paging timeout) expires for given paging_request */ +static void paging_T3113_expired(void *data) +{ + struct gsm_paging_request *req = (struct gsm_paging_request *)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)); + + /* must be destroyed before calling cbfn, to prevent double free */ + rate_ctr_inc(&req->bts->bts_ctrs->ctr[BTS_CTR_PAGING_EXPIRED]); + + /* destroy it now. Do not access req afterwards */ + paging_remove_request(&req->bts->paging, req); +} + +/*! Start paging + paging timer for given subscriber on given BTS + * \param bts BTS on which to page + * \param[in] bsub subscriber we want to page + * \param[in] type type of radio channel we're requirign + * \param[in] msc MSC which has issue this paging + * \returns 0 on success, negative on error */ +static int _paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub, int type, + struct bsc_msc_data *msc) +{ + struct gsm_bts_paging_state *bts_entry = &bts->paging; + struct gsm_paging_request *req; + + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ATTEMPTED]); + + if (paging_pending_request(bts_entry, bsub)) { + LOGP(DPAG, LOGL_INFO, "(bts=%d) Paging request already pending for %s\n", + bts->nr, bsc_subscr_name(bsub)); + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ALREADY]); + return -EEXIST; + } + + LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Start paging of subscriber %s\n", bts->nr, + bsc_subscr_name(bsub)); + req = talloc_zero(tall_paging_ctx, struct gsm_paging_request); + OSMO_ASSERT(req); + req->bsub = bsc_subscr_get(bsub); + req->bts = bts; + req->chan_type = type; + req->msc = msc; + osmo_timer_setup(&req->T3113, paging_T3113_expired, req); + osmo_timer_schedule(&req->T3113, bts->network->T3113, 0); + llist_add_tail(&req->entry, &bts_entry->pending_requests); + paging_schedule_if_needed(bts_entry); + + return 0; +} + +/*! Handle PAGING request from MSC for one (matching) BTS + * \param bts BTS on which to page + * \param[in] bsub subscriber we want to page + * \param[in] type type of radio channel we're requirign + * \param[in] msc MSC which has issue this paging + * returns 1 on success; 0 in case of error (e.g. TRX down) */ +int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub, int type, + struct bsc_msc_data *msc) +{ + int rc; + + /* skip all currently inactive TRX */ + 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(bts, bsub, type, msc); + if (rc < 0) + return 0; + return 1; +} + +/*! Stop paging a given subscriber on a given BTS. + * If \a conn is non-NULL, we also call the paging call-back function + * to notify the paging originator that paging has completed. + * \param[in] bts BTS on which we shall stop paging + * \param[in] bsub subscriber which we shall stop paging + * \param[in] conn connection to the subscriber (if any) + * \param[in] msg message received from subscrbier (if any) + * \returns 0 if an active paging request was stopped, an error code otherwise. */ +/* we consciously ignore the type of the request here */ +static int _paging_request_stop(struct gsm_bts *bts, struct bsc_subscr *bsub, + struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm_bts_paging_state *bts_entry = &bts->paging; + struct gsm_paging_request *req, *req2; + + paging_init_if_needed(bts); + + llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests, + entry) { + if (req->bsub == bsub) { + /* now give up the data structure */ + paging_remove_request(&bts->paging, req); + LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Stop paging %s\n", bts->nr, + bsc_subscr_name(bsub)); + return 0; + } + } + + return -ENOENT; +} + +/*! Stop paging on all other bts' + * \param[in] bts_list list of BTSs to iterate + * \param[in] _bts BTS which has received a paging response + * \param[in] bsub subscriber + * \param[in] msgb L3 message that we have received from \a bsub on \a _bts */ +void paging_request_stop(struct llist_head *bts_list, + struct gsm_bts *_bts, struct bsc_subscr *bsub, + struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm_bts *bts; + + log_set_context(LOG_CTX_BSC_SUBSCR, bsub); + + /* Stop this first and dispatch the request */ + if (_bts) { + if (_paging_request_stop(_bts, bsub, conn, msg) == 0) { + rate_ctr_inc(&_bts->bts_ctrs->ctr[BTS_CTR_PAGING_RESPONDED]); + rate_ctr_inc(&_bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_RESPONDED]); + } + } + + /* Make sure to cancel this everywhere else */ + llist_for_each_entry(bts, bts_list, list) { + /* Sort of an optimization. */ + if (bts == _bts) + continue; + _paging_request_stop(bts, bsub, NULL, NULL); + } +} + + +/*! Update the BTS paging buffer slots on given BTS */ +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); +} + +/*! Count the number of pending paging requests on given BTS */ +unsigned int paging_pending_requests_nr(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; +} + +/*! Find any paging data for the given subscriber at the given BTS. */ +struct bsc_msc_data *paging_get_msc(struct gsm_bts *bts, struct bsc_subscr *bsub) +{ + struct gsm_paging_request *req; + + llist_for_each_entry(req, &bts->paging.pending_requests, entry) + if (req->bsub == bsub) + return req->msc; + + return NULL; +} + +/*! Flush all paging requests at a given BTS for a given MSC (or NULL if all MSC should be flushed). */ +void paging_flush_bts(struct gsm_bts *bts, struct bsc_msc_data *msc) +{ + struct gsm_paging_request *req, *req2; + + paging_init_if_needed(bts); + + llist_for_each_entry_safe(req, req2, &bts->paging.pending_requests, entry) { + if (msc && req->msc != msc) + continue; + /* now give up the data structure */ + LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Stop paging %s (flush)\n", bts->nr, + bsc_subscr_name(req->bsub)); + paging_remove_request(&bts->paging, req); + } +} + +/*! Flush all paging requests issued by \a msc on any BTS in \a net */ +void paging_flush_network(struct gsm_network *net, struct bsc_msc_data *msc) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + paging_flush_bts(bts, msc); +} diff --git a/src/osmo-bsc/pcu_sock.c b/src/osmo-bsc/pcu_sock.c new file mode 100644 index 000000000..64422c724 --- /dev/null +++ b/src/osmo-bsc/pcu_sock.c @@ -0,0 +1,740 @@ +/* pcu_sock.c: Connect from PCU via unix domain socket */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2009-2012 by Andreas Eversberg + * (C) 2012 by Holger Hans Peter Freyther + * 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 + * (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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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 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", +}; + +/* Check if BTS has a PCU connection */ +static bool pcu_connected(struct gsm_bts *bts) +{ + struct pcu_sock_state *state = bts->pcu_state; + + if (!state) + return false; + if (state->conn_bfd.fd <= 0) + return false; + return true; +} + +/* + * PCU messages + */ + +/* Set up an message buffer to package an pcu interface message */ +struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + + msg = msgb_alloc(sizeof(struct gsm_pcu_if), "pcu_sock_tx"); + if (!msg) + return NULL; + + msgb_put(msg, sizeof(struct gsm_pcu_if)); + pcu_prim = (struct gsm_pcu_if *) msg->data; + pcu_prim->msg_type = msg_type; + pcu_prim->bts_nr = bts_nr; + + return msg; +} + +/* Helper function exclusivly used by pcu_if_signal_cb() */ +static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) { + if (ts->pchan == GSM_PCHAN_PDCH) + return true; + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + /* When we're busy deactivating the PDCH, we first set + * DEACT_PENDING, tell the PCU about it and wait for a + * response. So DEACT_PENDING means "no PDCH" to the PCU. + * Similarly, when we're activating PDCH, we set the + * ACT_PENDING and wait for an activation response from the + * PCU, so ACT_PENDING means "is PDCH". */ + if (ts->flags & TS_F_PDCH_ACTIVE) + return !(ts->flags & TS_F_PDCH_DEACT_PENDING); + else + return (ts->flags & TS_F_PDCH_ACT_PENDING); + } + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + /* + * When we're busy de-/activating the PDCH, we first set + * ts->dyn.pchan_want, tell the PCU about it and wait for a + * response. So only care about dyn.pchan_want here. + */ + return ts->dyn.pchan_want == GSM_PCHAN_PDCH; + } + return false; +} + +/* Send BTS properties to the PCU */ +static int pcu_tx_info_ind(struct gsm_bts *bts) +{ + struct msgb *msg; + 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_trx *trx; + struct gsm_bts_trx_ts *ts; + int i, j; + + OSMO_ASSERT(bts); + OSMO_ASSERT(bts->network); + + LOGP(DPCU, LOGL_INFO, "Sending info for BTS %d\n",bts->nr); + + rlcc = &bts->gprs.cell.rlc_cfg; + + msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr); + if (!msg) + return -ENOMEM; + + pcu_prim = (struct gsm_pcu_if *) msg->data; + 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; + + /* RAI */ + info_ind->mcc = bts->network->plmn.mcc; + info_ind->mnc = bts->network->plmn.mnc; + info_ind->mnc_3_digits = bts->network->plmn.mnc_3_digits; + info_ind->lac = bts->location_area_code; + info_ind->rac = bts->gprs.rac; + + /* NSE */ + info_ind->nsei = bts->gprs.nse.nsei; + memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7); + memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11); + + /* cell attributes */ + info_ind->cell_id = bts->cell_identity; + info_ind->repeat_time = rlcc->paging.repeat_time; + info_ind->repeat_count = rlcc->paging.repeat_count; + info_ind->bvci = bts->gprs.cell.bvci; + info_ind->t3142 = rlcc->parameter[RLC_T3142]; + info_ind->t3169 = rlcc->parameter[RLC_T3169]; + info_ind->t3191 = rlcc->parameter[RLC_T3191]; + info_ind->t3193_10ms = rlcc->parameter[RLC_T3193]; + info_ind->t3195 = rlcc->parameter[RLC_T3195]; + info_ind->n3101 = rlcc->parameter[RLC_N3101]; + info_ind->n3103 = rlcc->parameter[RLC_N3103]; + info_ind->n3105 = rlcc->parameter[RLC_N3105]; + info_ind->cv_countdown = rlcc->parameter[CV_COUNTDOWN]; + if (rlcc->cs_mask & (1 << GPRS_CS1)) + info_ind->flags |= PCU_IF_FLAG_CS1; + if (rlcc->cs_mask & (1 << GPRS_CS2)) + info_ind->flags |= PCU_IF_FLAG_CS2; + if (rlcc->cs_mask & (1 << GPRS_CS3)) + info_ind->flags |= PCU_IF_FLAG_CS3; + if (rlcc->cs_mask & (1 << GPRS_CS4)) + info_ind->flags |= PCU_IF_FLAG_CS4; + if (bts->gprs.mode == BTS_GPRS_EGPRS) { + if (rlcc->cs_mask & (1 << GPRS_MCS1)) + info_ind->flags |= PCU_IF_FLAG_MCS1; + if (rlcc->cs_mask & (1 << GPRS_MCS2)) + info_ind->flags |= PCU_IF_FLAG_MCS2; + if (rlcc->cs_mask & (1 << GPRS_MCS3)) + info_ind->flags |= PCU_IF_FLAG_MCS3; + if (rlcc->cs_mask & (1 << GPRS_MCS4)) + info_ind->flags |= PCU_IF_FLAG_MCS4; + if (rlcc->cs_mask & (1 << GPRS_MCS5)) + info_ind->flags |= PCU_IF_FLAG_MCS5; + if (rlcc->cs_mask & (1 << GPRS_MCS6)) + info_ind->flags |= PCU_IF_FLAG_MCS6; + if (rlcc->cs_mask & (1 << GPRS_MCS7)) + info_ind->flags |= PCU_IF_FLAG_MCS7; + if (rlcc->cs_mask & (1 << GPRS_MCS8)) + info_ind->flags |= PCU_IF_FLAG_MCS8; + 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" + info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT]; +#warning "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]; + info_ind->nsvci[i] = nsvc->nsvci; + info_ind->local_port[i] = nsvc->local_port; + info_ind->remote_port[i] = nsvc->remote_port; + info_ind->remote_ip[i] = nsvc->remote_ip; + } + + for (i = 0; i < ARRAY_SIZE(info_ind->trx); i++) { + 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 (j = 0; j < ARRAY_SIZE(trx->ts); j++) { + ts = &trx->ts[j]; + if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED + && ts_should_be_pdch(ts)) { + info_ind->trx[i].pdch_mask |= (1 << j); + info_ind->trx[i].tsc[j] = + (ts->tsc >= 0) ? ts->tsc : bts->bsic & 7; + LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: " + "available (tsc=%d arfcn=%d)\n", + trx->nr, ts->nr, + info_ind->trx[i].tsc[j], + info_ind->trx[i].arfcn); + } + } + } + + return pcu_sock_send(bts, msg); +} + +void pcu_info_update(struct gsm_bts *bts) +{ + if (pcu_connected(bts)) + pcu_tx_info_ind(bts); +} + +/* Forward rach indication to PCU */ +int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn, + uint8_t is_11bit, enum ph_burst_type burst_type) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + 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); + return -ENODEV; + } + + LOGP(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); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + rach_ind = &pcu_prim->u.rach_ind; + + rach_ind->sapi = PCU_IF_SAPI_RACH; + rach_ind->ra = ra; + rach_ind->qta = qta; + rach_ind->fn = fn; + rach_ind->is_11bit = is_11bit; + rach_ind->burst_type = burst_type; + + return pcu_sock_send(bts, msg); +} + +/* Confirm the sending of an immediate assignment to the pcu */ +int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_data_cnf_dt *data_cnf_dt; + + LOGP(DPCU, LOGL_INFO, "Sending PCH confirm with direct TLLI\n"); + + msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_DT, 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_dt->sapi = PCU_IF_SAPI_PCH; + data_cnf_dt->tlli = tlli; + + return pcu_sock_send(bts, msg); +} + +/* we need to decode the raw RR paging messsage (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) +{ + struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) raw_rr_msg; + uint8_t chan_needed; + unsigned int mi_len; + uint8_t *mi; + int rc; + + switch (p1->msg_type) { + case GSM48_MT_RR_PAG_REQ_1: + chan_needed = (p1->cneed2 << 2) | p1->cneed1; + mi_len = p1->data[0]; + mi = p1->data+1; + LOGP(DPCU, LOGL_ERROR, "PCU Sends paging " + "request type %02x (chan_needed=%02x, mi_len=%u, mi=%s)\n", + p1->msg_type, chan_needed, mi_len, + osmo_hexdump_nospc(mi,mi_len)); + /* NOTE: We will have to add 2 to mi_len and subtract 2 from + * the mi pointer because rsl_paging_cmd() will perform the + * reverse operations. This is because rsl_paging_cmd() is + * normally expected to chop off the element identifier (0xC0) + * and the length field. In our parameter, we do not have + * those fields included. */ + rc = rsl_paging_cmd(bts, paging_group, mi_len+2, mi-2, + chan_needed, true); + break; + case GSM48_MT_RR_PAG_REQ_2: + case GSM48_MT_RR_PAG_REQ_3: + LOGP(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 " + "request type %02x\n", p1->msg_type); + rc = -EINVAL; + break; + } + + return rc; +} + +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; + + LOGP(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; + 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 */ + + if (data_req->len < 5) { + LOGP(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); + + 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 (rc) { + msgb_free(msg); + rc = -EIO; + } + break; + default: + LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " + "unsupported sapi %d\n", data_req->sapi); + rc = -EINVAL; + } + + return rc; +} + +static int pcu_rx(struct gsm_network *net, uint8_t msg_type, + struct gsm_pcu_if *pcu_prim) +{ + int rc = 0; + struct gsm_bts *bts; + + /* FIXME: allow multiple BTS */ + bts = llist_entry(net->bts_list.next, struct gsm_bts, list); + + switch (msg_type) { + case PCU_IF_MSG_DATA_REQ: + case PCU_IF_MSG_PAG_REQ: + rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req); + break; + default: + LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n", + msg_type); + rc = -EINVAL; + } + + return rc; +} + +/* + * PCU socket interface + */ + +static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg) +{ + struct pcu_sock_state *state = bts->pcu_state; + struct osmo_fd *conn_bfd; + struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data; + + if (!state) { + if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) + LOGP(DPCU, LOGL_INFO, "PCU socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) + LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + conn_bfd->when |= BSC_FD_WRITE; + + return 0; +} + +static void pcu_sock_close(struct pcu_sock_state *state) +{ + 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 |= BSC_FD_READ; + +#if 0 + /* remove si13, ... */ + bts->si_valid &= ~(1 << SYSINFO_TYPE_13); + osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); +#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]; + if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED + && ts->pchan == GSM_PCHAN_PDCH) { + printf("l1sap_chan_rel(trx,gsm_lchan2chan_nr(ts->lchan));\n"); + } + } + } + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +static int pcu_sock_read(struct osmo_fd *bfd) +{ + struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data; + struct gsm_pcu_if *pcu_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx"); + if (!msg) + return -ENOMEM; + + pcu_prim = (struct gsm_pcu_if *) msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) + return 0; + goto close; + } + + rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim); + + /* as we always synchronously process the message in pcu_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + pcu_sock_close(state); + return -1; +} + +static int pcu_sock_write(struct osmo_fd *bfd) +{ + 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 &= ~BSC_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 |= BSC_FD_WRITE; + break; + } + goto close; + } + +dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + pcu_sock_close(state); + + return -1; +} + +static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & BSC_FD_READ) + rc = pcu_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = pcu_sock_write(bfd); + + return rc; +} + +/* accept connection comming 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 sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); + if (rc < 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"); + /* We already have one PCU connected, this is all we support */ + state->listen_bfd.when &= ~BSC_FD_READ; + close(rc); + return 0; + } + + conn_bfd->fd = rc; + conn_bfd->when = BSC_FD_READ; + conn_bfd->cb = pcu_sock_cb; + conn_bfd->data = state; + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DPCU, LOGL_ERROR, "Failed to register new connection " + "fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n"); + + return 0; +} + +/* Open connection to PCU */ +int pcu_sock_init(const char *path, struct gsm_bts *bts) +{ + struct pcu_sock_state *state; + struct osmo_fd *bfd; + int rc; + + state = talloc_zero(NULL, struct pcu_sock_state); + if (!state) + return -ENOMEM; + + INIT_LLIST_HEAD(&state->upqueue); + state->net = bts->network; + state->conn_bfd.fd = -1; + + bfd = &state->listen_bfd; + + bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, + OSMO_SOCK_F_BIND); + if (bfd->fd < 0) { + LOGP(DPCU, LOGL_ERROR, "Could not create unix socket: %s\n", + strerror(errno)); + talloc_free(state); + return -1; + } + + bfd->when = BSC_FD_READ; + bfd->cb = pcu_sock_accept; + bfd->data = state; + + rc = osmo_fd_register(bfd); + if (rc < 0) { + LOGP(DPCU, LOGL_ERROR, "Could not register listen fd: %d\n", + rc); + close(bfd->fd); + talloc_free(state); + return rc; + } + + bts->pcu_state = state; + return 0; +} + +/* Close connection to PCU */ +void pcu_sock_exit(struct gsm_bts *bts) +{ + struct pcu_sock_state *state = bts->pcu_state; + struct osmo_fd *bfd, *conn_bfd; + + if (!state) + return; + + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd > 0) + pcu_sock_close(state); + bfd = &state->listen_bfd; + close(bfd->fd); + osmo_fd_unregister(bfd); + talloc_free(state); + bts->pcu_state = NULL; +} + diff --git a/src/osmo-bsc/penalty_timers.c b/src/osmo-bsc/penalty_timers.c new file mode 100644 index 000000000..b80fec946 --- /dev/null +++ b/src/osmo-bsc/penalty_timers.c @@ -0,0 +1,129 @@ +/* (C) 2018 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include + +#include +#include + +struct penalty_timers { + struct llist_head timers; +}; + +struct penalty_timer { + struct llist_head entry; + 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; +} + +void penalty_timers_add(struct penalty_timers *pt, void *for_object, int timeout) +{ + struct penalty_timer *timer; + unsigned int now; + unsigned int then; + now = time_now(); + + if (timeout <= 0) + return; + + then = now + timeout; + + /* timer already running for that BTS? */ + llist_for_each_entry(timer, &pt->timers, entry) { + if (timer->for_object != for_object) + continue; + /* raise, if running timer will timeout earlier or has timed + * out already, otherwise keep later timeout */ + if (timer->timeout < then) + timer->timeout = then; + return; + } + + /* add new timer */ + timer = talloc_zero(pt, struct penalty_timer); + if (!timer) + return; + + timer->for_object = for_object; + timer->timeout = then; + + llist_add_tail(&timer->entry, &pt->timers); +} + +unsigned int penalty_timers_remaining(struct penalty_timers *pt, void *for_object) +{ + struct penalty_timer *timer; + unsigned int now = time_now(); + unsigned int max_remaining = 0; + llist_for_each_entry(timer, &pt->timers, entry) { + unsigned int remaining; + if (timer->for_object != for_object) + continue; + if (now >= timer->timeout) + continue; + remaining = timer->timeout - now; + if (remaining > max_remaining) + max_remaining = remaining; + } + return max_remaining; +} + +void penalty_timers_clear(struct penalty_timers *pt, void *for_object) +{ + struct penalty_timer *timer, *timer2; + llist_for_each_entry_safe(timer, timer2, &pt->timers, entry) { + if (for_object && timer->for_object != for_object) + 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/rest_octets.c b/src/osmo-bsc/rest_octets.c new file mode 100644 index 000000000..9f2b4c0ab --- /dev/null +++ b/src/osmo-bsc/rest_octets.c @@ -0,0 +1,878 @@ +/* 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 + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* 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 = budget - 25; + 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); + + /* No Serving Cell Priority Parameters Descr. */ + bitvec_set_bit(bv, 0); + /* 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); + + appended = append_eutran_neib_cell(bv, bts, rem); + if (!appended) { /* appending is impossible within current budget: rollback */ + bv->cur_bit = old; + return; + } + + /* 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) + + ::= +{L | H } +{L | H } +{ < 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) > } +; +*/ +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 > } } ; + + < 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 ** = } } ; + < Extension Information > ::= + { 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit > + < BEP_PERIOD : bit(4) > } + < PFC_FEATURE_MODE : bit > + < DTM_SUPPORT : 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 */ + if (gco->supports_egprs_11bit_rach == 0) { + bitvec_set_bit(bv, + gco->ext_info.use_egprs_p_ch_req); + } else { + bitvec_set_bit(bv, 0); + } + + /* 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/system_information.c b/src/osmo-bsc/system_information.c new file mode 100644 index 000000000..d99153f24 --- /dev/null +++ b/src/osmo-bsc/system_information.c @@ -0,0 +1,1210 @@ +/* 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) 2008-2010 by Harald Welte + * (C) 2012 Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* + * DCS1800 and PCS1900 have overlapping ARFCNs. We would need to set the + * ARFCN_PCS flag on the 1900 ARFCNs but this would increase cell_alloc + * and other arrays to make sure (ARFCN_PCS + 1024)/8 ARFCNs fit into the + * 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) +{ + enum gsm_band band = gsm_arfcn2band(arfcn); + + /* normal case */ + if (band == bts->band) + return 1; + /* deal with ARFCN_PCS not set */ + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) + return 1; + + return 0; +} + +static int is_dcs_net(const struct gsm_bts *bts) +{ + if (bts->band == GSM_BAND_850) + return 0; + if (bts->band == GSM_BAND_1900) + return 0; + return 1; +} + +/* Return p(n) for given NR_OF_TDD_CELLS - see Table 9.1.54.1a, 3GPP TS 44.018 */ +unsigned range1024_p(unsigned n) +{ + switch (n) { + case 0: return 0; + case 1: return 10; + case 2: return 19; + case 3: return 28; + case 4: return 36; + case 5: return 44; + case 6: return 52; + case 7: return 60; + case 8: return 67; + case 9: return 74; + case 10: return 81; + case 11: return 88; + case 12: return 95; + case 13: return 102; + case 14: return 109; + case 15: return 116; + case 16: return 122; + default: return 0; + } +} + +/* Return q(m) for given NR_OF_TDD_CELLS - see Table 9.1.54.1b, 3GPP TS 44.018 */ +unsigned range512_q(unsigned m) +{ + switch (m) { + case 0: return 0; + case 1: return 9; + case 2: return 17; + case 3: return 25; + case 4: return 32; + case 5: return 39; + case 6: return 46; + case 7: return 53; + case 8: return 59; + case 9: return 65; + case 10: return 71; + case 11: return 77; + case 12: return 83; + case 13: return 89; + case 14: return 95; + case 15: return 101; + case 16: return 106; + case 17: return 111; + case 18: return 116; + case 19: return 121; + case 20: return 126; + default: return 0; + } +} + +size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e) +{ + unsigned i, ret = 0; + + if (!e) + return 0; + + for (i = 0; i < e->length; i++) + if (e->arfcn[i] != OSMO_EARFCN_INVALID) + ret++; + + return ret; +} + +/* generate SI2quater messages, return rest octets length of last generated message or negative error code */ +static int make_si2quaters(struct gsm_bts *bts, bool counting) +{ + int rc; + bool memory_exceeded = true; + struct gsm48_system_information_type_2quater *si2q; + + for (bts->si2q_index = 0; bts->si2q_index < SI2Q_MAX_NUM; bts->si2q_index++) { + si2q = GSM_BTS_SI2Q(bts, bts->si2q_index); + if (counting) { /* that's legitimate if we're called for counting purpose: */ + if (bts->si2q_count < bts->si2q_index) + bts->si2q_count = bts->si2q_index; + } else { + memset(si2q, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2q->header.l2_plen = GSM48_LEN2PLEN(22); + si2q->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2q->header.skip_indicator = 0; + si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater; + } + + rc = rest_octets_si2quater(si2q->rest_octets, bts); + if (rc < 0) + return rc; + + if (bts->u_offset >= bts->si_common.uarfcn_length && + bts->e_offset >= si2q_earfcn_count(&bts->si_common.si2quater_neigh_list)) { + memory_exceeded = false; + break; + } + } + + if (memory_exceeded) + return -ENOMEM; + + return rc; +} + +/* we generate SI2q rest octets twice to get proper estimation but it's one time cost anyway */ +uint8_t si2q_num(struct gsm_bts *bts) +{ + int rc = make_si2quaters(bts, true); + uint8_t num = bts->si2q_index + 1; /* number of SI2quater messages */ + + /* N. B: si2q_num() should NEVER be called during actual SI2q rest octets generation + we're not re-entrant because of the following code: */ + bts->u_offset = 0; + bts->e_offset = 0; + + if (rc < 0) + return 0xFF; /* return impossible index as an indicator of error in generating SI2quater */ + + return num; +} + +/* 3GPP TS 44.018, Table 9.1.54.1 - prepend diversity bit to scrambling code */ +static inline uint16_t encode_fdd(uint16_t scramble, bool diversity) +{ + if (diversity) + return scramble | (1 << 9); + return scramble; +} + +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); + + if (r < 0) + return r; + + if (e->thresh_hi && thresh_hi != e->thresh_hi) + r = 1; + + e->thresh_hi = thresh_hi; + + if (thresh_lo != EARFCN_THRESH_LOW_INVALID) { + if (e->thresh_lo_valid && e->thresh_lo != thresh_lo) + r = EARFCN_THRESH_LOW_INVALID; + e->thresh_lo = thresh_lo; + e->thresh_lo_valid = true; + } + + if (qrx != EARFCN_QRXLV_INVALID) { + if (e->qrxlm_valid && e->qrxlm != qrx) + r = EARFCN_QRXLV_INVALID + 1; + e->qrxlm = qrx; + e->qrxlm_valid = true; + } + + if (prio != EARFCN_PRIO_INVALID) { + if (e->prio_valid && e->prio != prio) + r = EARFCN_PRIO_INVALID; + e->prio = prio; + e->prio_valid = true; + } + + return r; +} + +/* Scrambling Code as defined in 3GPP TS 25.213 is 9 bit long so number below is unreacheable upper bound */ +#define SC_BOUND 600 + +/* Find position for a given UARFCN (take SC into consideration if it's available) in a sorted list + N. B: we rely on the assumption that (uarfcn, scramble) tuple is unique in the lists */ +static int uarfcn_sc_pos(const struct gsm_bts *bts, uint16_t uarfcn, uint16_t scramble) +{ + const uint16_t *sc = bts->si_common.data.scramble_list; + uint16_t i, scramble0 = encode_fdd(scramble, false), scramble1 = encode_fdd(scramble, true); + for (i = 0; i < bts->si_common.uarfcn_length; i++) + if (uarfcn == bts->si_common.data.uarfcn_list[i]) { + if (scramble < SC_BOUND) { + if (scramble0 == sc[i] || scramble1 == sc[i]) + return i; + } else + return i; + } + + return -1; +} + +int bts_uarfcn_del(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble) +{ + uint16_t *ual = bts->si_common.data.uarfcn_list, *scl = bts->si_common.data.scramble_list; + size_t len = bts->si_common.uarfcn_length; + int pos = uarfcn_sc_pos(bts, arfcn, scramble); + + if (pos < 0) + return -EINVAL; + + if (pos != len - 1) { /* move the tail if necessary */ + memmove(ual + pos, ual + pos + 1, 2 * (len - pos + 1)); + memmove(scl + pos, scl + pos + 1, 2 * (len - pos + 1)); + } + + bts->si_common.uarfcn_length--; + return 0; +} + +int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool diversity) +{ + 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), + *ual = bts->si_common.data.uarfcn_list, + *scl = bts->si_common.data.scramble_list; + + 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; + + /* move the tail to make space for inserting if necessary */ + if (i < len) { + memmove(ual + i + 1, ual + i, (len - i) * 2); + memmove(scl + i + 1, scl + i, (len - i) * 2); + } + + /* insert into appropriate position */ + ual[i] = arfcn; + scl[i] = scr; + bts->si_common.uarfcn_length++; + /* try to generate SI2q */ + si2q = si2q_num(bts); + + if (si2q <= SI2Q_MAX_NUM) { + bts->si2q_count = si2q - 1; + return 0; + } + + /* rollback after unsuccessful generation */ + bts_uarfcn_del(bts, arfcn, scramble); + return -ENOSPC; +} + +static inline int use_arfcn(const struct gsm_bts *bts, const bool bis, const bool ter, + const bool pgsm, const int arfcn) +{ + if (bts->force_combined_si) + return !bis && !ter; + if (!bis && !ter && band_compatible(bts, arfcn)) + return 1; + /* Correct but somehow broken with either the nanoBTS or the iPhone5 */ + if (bis && pgsm && band_compatible(bts, arfcn) && (arfcn < 1 || arfcn > 124)) + return 1; + if (ter && !band_compatible(bts, arfcn)) + return 1; + return 0; +} + +/* Frequency Lists as per TS 04.08 10.5.2.13 */ + +/* 10.5.2.13.2: Bit map 0 format */ +static int freq_list_bm0_set_arfcn(uint8_t *chan_list, unsigned int arfcn) +{ + unsigned int byte, bit; + + if (arfcn > 124 || arfcn < 1) { + LOGP(DRR, LOGL_ERROR, "Bitmap 0 only supports ARFCN 1...124\n"); + return -EINVAL; + } + + /* the bitmask is from 1..124, not from 0..123 */ + arfcn--; + + byte = arfcn / 8; + bit = arfcn % 8; + + chan_list[GSM48_CELL_CHAN_DESC_SIZE-1-byte] |= (1 << bit); + + return 0; +} + +/* 10.5.2.13.7: Variable bit map format */ +static int freq_list_bmrel_set_arfcn(uint8_t *chan_list, unsigned int arfcn) +{ + unsigned int byte, bit; + unsigned int min_arfcn; + unsigned int bitno; + + min_arfcn = (chan_list[0] & 1) << 9; + min_arfcn |= chan_list[1] << 1; + min_arfcn |= (chan_list[2] >> 7) & 1; + + /* The lower end of our bitmaks is always implicitly included */ + if (arfcn == min_arfcn) + return 0; + + if (((arfcn - min_arfcn) & 1023) > 111) { + LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn); + return -EINVAL; + } + + bitno = (arfcn - min_arfcn) & 1023; + byte = bitno / 8; + bit = bitno % 8; + + chan_list[2 + byte] |= 1 << (7 - bit); + + return 0; +} + +/* generate a variable bitmap */ +static inline int enc_freq_lst_var_bitmap(uint8_t *chan_list, + struct bitvec *bv, const struct gsm_bts *bts, + bool bis, bool ter, int min, bool pgsm) +{ + int i; + + /* set it to 'Variable bitmap format' */ + chan_list[0] = 0x8e; + + chan_list[0] |= (min >> 9) & 1; + chan_list[1] = (min >> 1); + chan_list[2] = (min & 1) << 7; + + for (i = 0; i < bv->data_len*8; i++) { + /* see notes in bitvec2freq_list */ + if (bitvec_get_bit_pos(bv, i) + && ((!bis && !ter && band_compatible(bts,i)) + || (bis && pgsm && band_compatible(bts,i) && (i < 1 || i > 124)) + || (ter && !band_compatible(bts, i)))) { + int rc = freq_list_bmrel_set_arfcn(chan_list, i); + if (rc < 0) + return rc; + } + } + + return 0; +} + +int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w, + int f0, uint8_t *chan_list) +{ + /* + * Manipulate the ARFCN list according to the rules in J4 depending + * on the selected range. + */ + int rc, f0_included; + + range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included); + + rc = 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); + default: + return -ERANGE; + }; + + return f0_included; +} + +/* generate a frequency list with the range 512 format */ +static inline int enc_freq_lst_range(uint8_t *chan_list, + 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_used = 0; + int i, range, f0; + + /* + * Select ARFCNs according to the rules in bitvec2freq_list + */ + for (i = 0; i < bv->data_len * 8; ++i) { + /* More ARFCNs than the maximum */ + if (arfcns_used > ARRAY_SIZE(arfcns)) + return -1; + /* Check if we can select it? */ + if (bitvec_get_bit_pos(bv, i) && use_arfcn(bts, bis, ter, pgsm, i)) + arfcns[arfcns_used++] = i; + } + + /* + * Check if the given list of ARFCNs can be encoded. + */ + range = range_enc_determine_range(arfcns, arfcns_used, &f0); + if (range == ARFCN_RANGE_INVALID) + return -2; + + memset(w, 0, sizeof(w)); + return range_encode(range, arfcns, arfcns_used, w, f0, chan_list); +} + +/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ +static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv, + const struct gsm_bts *bts, bool bis, bool ter) +{ + int i, rc, min = -1, max = -1, arfcns = 0; + bool pgsm = false; + memset(chan_list, 0, 16); + + if (bts->band == GSM_BAND_900 + && bts->c0->arfcn >= 1 && bts->c0->arfcn <= 124) + pgsm = true; + /* 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; + } + } + return 0; + } + + for (i = 0; i < bv->data_len*8; i++) { + /* in case of SI2 or SI5 allow all neighbours in same band + * in case of SI*bis, allow neighbours in same band ouside pgsm + * in case of SI*ter, allow neighbours in different bands + */ + if (!bitvec_get_bit_pos(bv, i)) + continue; + if (!use_arfcn(bts, bis, ter, pgsm, i)) + continue; + /* count the arfcns we want to carry */ + arfcns += 1; + + /* 955..1023 < 0..885 */ + if (min < 0) + min = i; + if (i >= 955 && min < 955) + min = i; + if (i >= 955 && min >= 955 && i < min) + min = i; + if (i < 955 && min < 955 && i < min) + min = i; + if (max < 0) + max = i; + if (i < 955 && max >= 955) + max = i; + if (i >= 955 && max >= 955 && i > max) + max = i; + if (i < 955 && max < 955 && i > max) + max = i; + } + + if (max == -1) { + /* Empty set, use 'bit map 0 format' */ + chan_list[0] = 0; + return 0; + } + + /* Now find the best encoding */ + if (((max - min) & 1023) <= 111) + return enc_freq_lst_var_bitmap(chan_list, bv, bts, bis, + ter, min, pgsm); + + /* Attempt to do the range encoding */ + rc = enc_freq_lst_range(chan_list, bv, bts, bis, ter, pgsm); + if (rc >= 0) + return 0; + + LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, arfcns=%d " + "can not generate ARFCN list", min, max, arfcns); + return -EINVAL; +} + +/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ +/* static*/ int generate_cell_chan_list(uint8_t *chan_list, 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); + + /* first we generate a bitvec of all TRX ARFCN's in our BTS */ + 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]; + /* 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); + } + } + } + + /* then we generate a GSM 04.08 frequency list from the bitvec */ + return bitvec2freq_list(chan_list, bv, bts, false, false); +} + +/*! generate a cell channel list as per Section 10.5.2.22 of 04.08 + * \param[out] chan_list caller-provided output buffer + * \param[in] bts BTS descriptor used for input data + * \param[in] si5 Are we generating SI5xxx (true) or SI2xxx (false) + * \param[in] bis Are we generating SIXbis (true) or not (false) + * \param[in] ter Are we generating SIXter (true) or not (false) + */ +static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts, + bool si5, bool bis, bool ter) +{ + struct gsm_bts *cur_bts; + struct bitvec *bv; + int rc; + + if (si5 && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) + bv = &bts->si_common.si5_neigh_list; + else + bv = &bts->si_common.neigh_list; + + /* Generate list of neighbor cells if we are in automatic mode */ + if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) { + /* Zero-initialize the bit-vector */ + memset(bv->data, 0, bv->data_len); + + /* first we generate a bitvec of the BCCH ARFCN's in our BSC */ + llist_for_each_entry(cur_bts, &bts->network->bts_list, list) { + if (cur_bts == bts) + continue; + bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1); + } + } + + /* then we generate a GSM 04.08 frequency list from the bitvec */ + rc = bitvec2freq_list(chan_list, bv, bts, bis, ter); + if (rc < 0) + return rc; + + /* Set BA-IND depending on whether we're generating SI2 or SI5. + * 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 */ + if (si5) + chan_list[0] |= 0x10; + else + chan_list[0] &= ~0x10; + + return rc; +} + +static int list_arfcn(uint8_t *chan_list, uint8_t mask, char *text) +{ + int n = 0, i; + struct gsm_sysinfo_freq freq[1024]; + + memset(freq, 0, sizeof(freq)); + gsm48_decode_freq_list(freq, chan_list, 16, 0xce, 1); + for (i = 0; i < 1024; i++) { + if (freq[i].mask) { + if (!n) + LOGP(DRR, LOGL_INFO, "%s", text); + LOGPC(DRR, LOGL_INFO, " %d", i); + n++; + } + } + if (n) + LOGPC(DRR, LOGL_INFO, "\n"); + + return n; +} + +static int generate_si1(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_1 *si1 = (struct gsm48_system_information_type_1 *) GSM_BTS_SI(bts, t); + + memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si1->header.l2_plen = GSM48_LEN2PLEN(21); + si1->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si1->header.skip_indicator = 0; + si1->header.system_information = GSM48_MT_RR_SYSINFO_1; + + rc = generate_cell_chan_list(si1->cell_channel_description, bts); + if (rc < 0) + return rc; + list_arfcn(si1->cell_channel_description, 0xce, "Serving cell:"); + + si1->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si1->rach_control, &bts->acc_ramp); + + /* + * 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)); + + return sizeof(*si1) + rc; +} + +static int generate_si2(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2 *si2 = (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, t); + + memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2->header.l2_plen = GSM48_LEN2PLEN(22); + si2->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2->header.skip_indicator = 0; + si2->header.system_information = GSM48_MT_RR_SYSINFO_2; + + rc = generate_bcch_chan_list(si2->bcch_frequency_list, bts, false, false, false); + if (rc < 0) + return rc; + list_arfcn(si2->bcch_frequency_list, 0xce, + "SI2 Neighbour cells in same band:"); + + si2->ncc_permitted = bts->si_common.ncc_permitted; + si2->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si2->rach_control, &bts->acc_ramp); + + return sizeof(*si2); +} + +static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2bis *si2b = + (struct gsm48_system_information_type_2bis *) GSM_BTS_SI(bts, t); + int n; + + memset(si2b, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2b->header.l2_plen = GSM48_LEN2PLEN(22); + si2b->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2b->header.skip_indicator = 0; + si2b->header.system_information = GSM48_MT_RR_SYSINFO_2bis; + + rc = generate_bcch_chan_list(si2b->bcch_frequency_list, bts, false, true, false); + if (rc < 0) + return rc; + n = list_arfcn(si2b->bcch_frequency_list, 0xce, + "Neighbour cells in same band, but outside P-GSM:"); + if (n) { + /* indicate in SI2 and SI2bis: there is an extension */ + struct gsm48_system_information_type_2 *si2 = + (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, SYSINFO_TYPE_2); + si2->bcch_frequency_list[0] |= 0x20; + si2b->bcch_frequency_list[0] |= 0x20; + } else + bts->si_valid &= ~(1 << SYSINFO_TYPE_2bis); + + si2b->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si2b->rach_control, &bts->acc_ramp); + + /* SI2bis Rest Octets as per 3GPP TS 44.018 §10.5.2.33 */ + rc = rest_octets_si2bis(si2b->rest_octets); + + return sizeof(*si2b) + rc; +} + +static int generate_si2ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2ter *si2t = + (struct gsm48_system_information_type_2ter *) GSM_BTS_SI(bts, t); + int n; + + memset(si2t, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2t->header.l2_plen = GSM48_LEN2PLEN(22); + si2t->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2t->header.skip_indicator = 0; + si2t->header.system_information = GSM48_MT_RR_SYSINFO_2ter; + + rc = generate_bcch_chan_list(si2t->ext_bcch_frequency_list, bts, false, false, true); + if (rc < 0) + return rc; + n = list_arfcn(si2t->ext_bcch_frequency_list, 0x8e, + "Neighbour cells in different band:"); + if (!n) + bts->si_valid &= ~(1 << SYSINFO_TYPE_2ter); + + /* SI2ter Rest Octets as per 3GPP TS 44.018 §10.5.2.33a */ + rc = rest_octets_si2ter(si2t->rest_octets); + + return sizeof(*si2t) + rc; +} + +/* SI2quater messages are optional - we only generate them when neighbor UARFCNs or EARFCNs are configured */ +static inline bool si2quater_not_needed(struct gsm_bts *bts) +{ + unsigned i = MAX_EARFCN_LIST; + + if (bts->si_common.si2quater_neigh_list.arfcn) + for (i = 0; i < MAX_EARFCN_LIST; i++) + if (bts->si_common.si2quater_neigh_list.arfcn[i] != OSMO_EARFCN_INVALID) + break; + + if (!bts->si_common.uarfcn_length && i == MAX_EARFCN_LIST) { + bts->si_valid &= ~(1 << SYSINFO_TYPE_2quater); /* mark SI2q as invalid if no (E|U)ARFCNs are present */ + return true; + } + + return false; +} + +static int generate_si2quater(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2quater *si2q; + + if (si2quater_not_needed(bts)) /* generate rest_octets for SI2q only when necessary */ + return GSM_MACBLOCK_LEN; + + bts->u_offset = 0; + bts->e_offset = 0; + bts->si2q_index = 0; + bts->si2q_count = si2q_num(bts) - 1; + + rc = make_si2quaters(bts, false); + if (rc < 0) + return rc; + + OSMO_ASSERT(bts->si2q_count == bts->si2q_index); + OSMO_ASSERT(bts->si2q_count <= SI2Q_MAX_NUM); + + return sizeof(*si2q) + rc; +} + +static struct gsm48_si_ro_info si_info = { + .selection_params = { + .present = 0, + }, + .power_offset = { + .present = 0, + }, + .si2ter_indicator = false, + .early_cm_ctrl = true, + .scheduling = { + .present = 0, + }, + .gprs_ind = { + .si13_position = 0, + .ra_colour = 0, + .present = 1, + }, + .early_cm_restrict_3g = false, + .si2quater_indicator = false, + .lsa_params = { + .present = 0, + }, + .cell_id = 0, /* FIXME: doesn't the bts have this? */ + .break_ind = 0, +}; + +static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_3 *si3 = (struct gsm48_system_information_type_3 *) GSM_BTS_SI(bts, t); + + memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si3->header.l2_plen = GSM48_LEN2PLEN(18); + si3->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si3->header.skip_indicator = 0; + si3->header.system_information = GSM48_MT_RR_SYSINFO_3; + + si3->cell_identity = htons(bts->cell_identity); + gsm48_generate_lai2(&si3->lai, bts_lai(bts)); + si3->control_channel_desc = bts->si_common.chan_desc; + si3->cell_options = bts->si_common.cell_options; + si3->cell_sel_par = bts->si_common.cell_sel_par; + si3->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si3->rach_control, &bts->acc_ramp); + + /* allow/disallow DTXu */ + gsm48_set_dtx(&si3->cell_options, bts->dtxu, bts->dtxu, true); + + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) { + LOGP(DRR, LOGL_INFO, "SI 2ter is included.\n"); + si_info.si2ter_indicator = true; + } else { + si_info.si2ter_indicator = false; + } + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater)) { + LOGP(DRR, LOGL_INFO, "SI 2quater is included, based on %zu EARFCNs and %zu UARFCNs.\n", + si2q_earfcn_count(&bts->si_common.si2quater_neigh_list), bts->si_common.uarfcn_length); + si_info.si2quater_indicator = true; + } else { + si_info.si2quater_indicator = false; + } + si_info.early_cm_ctrl = bts->early_classmark_allowed; + si_info.early_cm_restrict_3g = !bts->early_classmark_allowed_3g; + + /* SI3 Rest Octets (10.5.2.34), containing + 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); + + return sizeof(*si3) + rc; +} + +static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_4 *si4 = (struct gsm48_system_information_type_4 *) GSM_BTS_SI(bts, t); + struct gsm_lchan *cbch_lchan; + uint8_t *restoct = si4->data; + + /* length of all IEs present except SI4 rest octets and l2_plen */ + int l2_plen = sizeof(*si4) - 1; + + memset(si4, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si4->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si4->header.skip_indicator = 0; + si4->header.system_information = GSM48_MT_RR_SYSINFO_4; + + gsm48_generate_lai2(&si4->lai, bts_lai(bts)); + si4->cell_sel_par = bts->si_common.cell_sel_par; + si4->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si4->rach_control, &bts->acc_ramp); + + /* Optional: CBCH Channel Description + CBCH Mobile Allocation */ + cbch_lchan = gsm_bts_get_cbch(bts); + if (cbch_lchan) { + struct gsm48_chan_desc cd; + gsm48_lchan2chan_desc(&cd, cbch_lchan); + tv_fixed_put(si4->data, GSM48_IE_CBCH_CHAN_DESC, 3, + (uint8_t *) &cd); + l2_plen += 3 + 1; + restoct += 3 + 1; + /* we don't use hopping and thus don't need a CBCH MA */ + } + + si4->header.l2_plen = GSM48_LEN2PLEN(l2_plen); + + /* SI4 Rest Octets (10.5.2.35), containing + Optional Power offset, GPRS Indicator, + Cell Identity, LSA ID, Selection Parameter */ + rc = rest_octets_si4(restoct, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - restoct); + + return l2_plen + 1 + rc; +} + +static int generate_si5(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_5 *si5; + uint8_t *output = GSM_BTS_SI(bts, t); + int rc, l2_plen = 18; + + 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: + *output++ = GSM48_LEN2PLEN(l2_plen); + l2_plen++; + break; + default: + break; + } + + si5 = (struct gsm48_system_information_type_5 *) output; + + /* l2 pseudo length, not part of msg: 18 */ + si5->rr_protocol_discriminator = GSM48_PDISC_RR; + si5->skip_indicator = 0; + si5->system_information = GSM48_MT_RR_SYSINFO_5; + rc = generate_bcch_chan_list(si5->bcch_frequency_list, bts, true, false, false); + if (rc < 0) + return rc; + list_arfcn(si5->bcch_frequency_list, 0xce, + "SI5 Neighbour cells in same band:"); + + /* 04.08 9.1.37: L2 Pseudo Length of 18 */ + return l2_plen; +} + +static int generate_si5bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_5bis *si5b; + uint8_t *output = GSM_BTS_SI(bts, t); + int rc, l2_plen = 18; + int n; + + 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: + *output++ = GSM48_LEN2PLEN(l2_plen); + l2_plen++; + break; + default: + break; + } + + si5b = (struct gsm48_system_information_type_5bis *) output; + + /* l2 pseudo length, not part of msg: 18 */ + si5b->rr_protocol_discriminator = GSM48_PDISC_RR; + si5b->skip_indicator = 0; + si5b->system_information = GSM48_MT_RR_SYSINFO_5bis; + rc = generate_bcch_chan_list(si5b->bcch_frequency_list, bts, true, true, false); + if (rc < 0) + return rc; + n = list_arfcn(si5b->bcch_frequency_list, 0xce, + "Neighbour cells in same band, but outside P-GSM:"); + if (n) { + /* indicate in SI5 and SI5bis: there is an extension */ + struct gsm48_system_information_type_5 *si5 = + (struct gsm48_system_information_type_5 *) GSM_BTS_SI(bts, SYSINFO_TYPE_5)+1; + si5->bcch_frequency_list[0] |= 0x20; + si5b->bcch_frequency_list[0] |= 0x20; + } else + bts->si_valid &= ~(1 << SYSINFO_TYPE_5bis); + + /* 04.08 9.1.37: L2 Pseudo Length of 18 */ + return l2_plen; +} + +static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_5ter *si5t; + uint8_t *output = GSM_BTS_SI(bts, t); + int rc, l2_plen = 18; + int n; + + 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: + *output++ = GSM48_LEN2PLEN(l2_plen); + l2_plen++; + break; + default: + break; + } + + si5t = (struct gsm48_system_information_type_5ter *) output; + + /* l2 pseudo length, not part of msg: 18 */ + si5t->rr_protocol_discriminator = GSM48_PDISC_RR; + si5t->skip_indicator = 0; + si5t->system_information = GSM48_MT_RR_SYSINFO_5ter; + rc = generate_bcch_chan_list(si5t->bcch_frequency_list, bts, true, false, true); + if (rc < 0) + return rc; + n = list_arfcn(si5t->bcch_frequency_list, 0x8e, + "Neighbour cells in different band:"); + if (!n) + bts->si_valid &= ~(1 << SYSINFO_TYPE_5ter); + + /* 04.08 9.1.37: L2 Pseudo Length of 18 */ + return l2_plen; +} + +static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_6 *si6; + uint8_t *output = GSM_BTS_SI(bts, t); + int l2_plen = 11; + int rc; + + 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: + *output++ = GSM48_LEN2PLEN(l2_plen); + l2_plen++; + break; + default: + break; + } + + si6 = (struct gsm48_system_information_type_6 *) output; + + /* l2 pseudo length, not part of msg: 11 */ + si6->rr_protocol_discriminator = GSM48_PDISC_RR; + si6->skip_indicator = 0; + si6->system_information = GSM48_MT_RR_SYSINFO_6; + si6->cell_identity = htons(bts->cell_identity); + gsm48_generate_lai2(&si6->lai, bts_lai(bts)); + si6->cell_options = bts->si_common.cell_options; + si6->ncc_permitted = bts->si_common.ncc_permitted; + /* allow/disallow DTXu */ + 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)); + + 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, + .supports_egprs_11bit_rach = 0, + .ext_info = { + /* The values below are just guesses ! */ + .egprs_supported = 0, + .use_egprs_p_ch_req = 1, + .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 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); + int ret; + + memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si13->header.rr_protocol_discriminator = GSM48_PDISC_RR; + 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_default.cell_opts.supports_egprs_11bit_rach = + bts->gprs.supports_egprs_11bit_rach; + + ret = rest_octets_si13(si13->rest_octets, &si13_default); + if (ret < 0) + return ret; + + /* length is coded in bit 2 an up */ + si13->header.l2_plen = 0x01; + + return sizeof (*si13) + ret; +} + +typedef int (*gen_si_fn_t)(enum osmo_sysinfo_type t, struct gsm_bts *bts); + +static const gen_si_fn_t gen_si_fn[_MAX_SYSINFO_TYPE] = { + [SYSINFO_TYPE_1] = &generate_si1, + [SYSINFO_TYPE_2] = &generate_si2, + [SYSINFO_TYPE_2bis] = &generate_si2bis, + [SYSINFO_TYPE_2ter] = &generate_si2ter, + [SYSINFO_TYPE_2quater] = &generate_si2quater, + [SYSINFO_TYPE_3] = &generate_si3, + [SYSINFO_TYPE_4] = &generate_si4, + [SYSINFO_TYPE_5] = &generate_si5, + [SYSINFO_TYPE_5bis] = &generate_si5bis, + [SYSINFO_TYPE_5ter] = &generate_si5ter, + [SYSINFO_TYPE_6] = &generate_si6, + [SYSINFO_TYPE_13] = &generate_si13, +}; + +int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) +{ + gen_si_fn_t gen_si; + + 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; + case BTS_GPRS_NONE: + si_info.gprs_ind.present = 0; + break; + } + + memcpy(&si_info.selection_params, + &bts->si_common.cell_ro_sel_par, + sizeof(struct gsm48_si_selection_params)); + + gen_si = gen_si_fn[si_type]; + if (!gen_si) + return -EINVAL; + + return gen_si(si_type, bts); +} diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 6e21ccc78..543344b53 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -46,15 +46,18 @@ endif bs11_config_SOURCES = \ bs11_config.c \ + stubs.c \ $(NULL) bs11_config_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/abis_nm.o \ + $(top_builddir)/src/osmo-bsc/bts_siemens_bs11.o \ + $(top_builddir)/src/osmo-bsc/e1_config.o \ + $(top_builddir)/src/osmo-bsc/gsm_data.o \ + $(top_builddir)/src/osmo-bsc/net_init.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOABIS_LIBS) \ - $(LIBOSMOSIGTRAN_LIBS) \ - $(LIBOSMOMGCPCLIENT_LIBS) \ $(NULL) isdnsync_SOURCES = \ @@ -114,12 +117,14 @@ osmo_meas_udp2db_CFLAGS = \ meas_json_SOURCES = \ meas_json.c \ + stubs.c \ $(NULL) meas_json_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/gsm_data.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) \ $(NULL) meas_json_CFLAGS = \ diff --git a/src/utils/bs11_config.c b/src/utils/bs11_config.c index 8d4de0152..ae307c796 100644 --- a/src/utils/bs11_config.c +++ b/src/utils/bs11_config.c @@ -32,7 +32,6 @@ #include -#include #include #include #include @@ -915,7 +914,7 @@ int main(int argc, char **argv) handle_options(argc, argv); bts_model_bs11_init(); - gsmnet = bsc_network_init(tall_bs11cfg_ctx); + gsmnet = gsm_network_init(tall_bs11cfg_ctx); if (!gsmnet) { fprintf(stderr, "Unable to allocate gsm network\n"); exit(1); diff --git a/src/utils/stubs.c b/src/utils/stubs.c new file mode 100644 index 000000000..624797fde --- /dev/null +++ b/src/utils/stubs.c @@ -0,0 +1,36 @@ +/* Stubs required for linking */ + +/* (C) 2018 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 . + * + */ + +#include +struct gsm_bts_trx_ts; +struct msgb; + +bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts) +{ + /* No TS init required here. */ + return true; +} + +int abis_rsl_rcvmsg(struct msgb *msg) +{ + /* No RSL handling here */ + return 0; +} diff --git a/tests/abis/Makefile.am b/tests/abis/Makefile.am index 8dc829fc3..60054d94d 100644 --- a/tests/abis/Makefile.am +++ b/tests/abis/Makefile.am @@ -25,7 +25,9 @@ abis_test_SOURCES = \ $(NULL) abis_test_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/abis_nm.o \ + $(top_builddir)/src/osmo-bsc/gsm_data.o \ + $(top_builddir)/src/osmo-bsc/net_init.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOABIS_LIBS) \ $(LIBOSMOGSM_LIBS) \ diff --git a/tests/abis/abis_test.c b/tests/abis/abis_test.c index faf9ea533..c6f29f577 100644 --- a/tests/abis/abis_test.c +++ b/tests/abis/abis_test.c @@ -186,3 +186,5 @@ int main(int argc, char **argv) struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) { OSMO_ASSERT(0); } + +bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts) { return true; } diff --git a/tests/bsc/Makefile.am b/tests/bsc/Makefile.am index a436c27f0..a930629fa 100644 --- a/tests/bsc/Makefile.am +++ b/tests/bsc/Makefile.am @@ -28,11 +28,28 @@ noinst_PROGRAMS = \ bsc_test_SOURCES = \ bsc_test.c \ - $(top_srcdir)/src/osmo-bsc/osmo_bsc_filter.c \ $(NULL) bsc_test_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/abis_nm.o \ + $(top_builddir)/src/osmo-bsc/abis_rsl.o \ + $(top_builddir)/src/osmo-bsc/arfcn_range_encode.o \ + $(top_builddir)/src/osmo-bsc/bsc_api.o \ + $(top_builddir)/src/osmo-bsc/bsc_dyn_ts.o \ + $(top_builddir)/src/osmo-bsc/osmo_bsc_filter.o \ + $(top_builddir)/src/osmo-bsc/bsc_rll.o \ + $(top_builddir)/src/osmo-bsc/bsc_subscriber.o \ + $(top_builddir)/src/osmo-bsc/chan_alloc.o \ + $(top_builddir)/src/osmo-bsc/gsm_04_08_utils.o \ + $(top_builddir)/src/osmo-bsc/gsm_04_80_utils.o \ + $(top_builddir)/src/osmo-bsc/gsm_data.o \ + $(top_builddir)/src/osmo-bsc/handover_cfg.o \ + $(top_builddir)/src/osmo-bsc/handover_logic.o \ + $(top_builddir)/src/osmo-bsc/net_init.o \ + $(top_builddir)/src/osmo-bsc/paging.o \ + $(top_builddir)/src/osmo-bsc/pcu_sock.o \ + $(top_builddir)/src/osmo-bsc/rest_octets.o \ + $(top_builddir)/src/osmo-bsc/system_information.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ diff --git a/tests/bsc/bsc_test.c b/tests/bsc/bsc_test.c index 08d376016..3a9946790 100644 --- a/tests/bsc/bsc_test.c +++ b/tests/bsc/bsc_test.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -123,7 +122,7 @@ static void test_scan(void) { int i; - struct gsm_network *net = bsc_network_init(ctx); + struct gsm_network *net = gsm_network_init(ctx); struct gsm_bts *bts = gsm_bts_alloc(net, 0); struct bsc_msc_data *msc; struct gsm_subscriber_connection *conn; diff --git a/tests/gsm0408/Makefile.am b/tests/gsm0408/Makefile.am index 9a74d449a..6d10b9fd9 100644 --- a/tests/gsm0408/Makefile.am +++ b/tests/gsm0408/Makefile.am @@ -23,7 +23,11 @@ gsm0408_test_SOURCES = \ $(NULL) gsm0408_test_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/arfcn_range_encode.o \ + $(top_builddir)/src/osmo-bsc/gsm_data.o \ + $(top_builddir)/src/osmo-bsc/net_init.o \ + $(top_builddir)/src/osmo-bsc/rest_octets.o \ + $(top_builddir)/src/osmo-bsc/system_information.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOABIS_LIBS) \ diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c index a934806b5..9552fb11d 100644 --- a/tests/gsm0408/gsm0408_test.c +++ b/tests/gsm0408/gsm0408_test.c @@ -24,7 +24,6 @@ #include #include -#include #include #include #include @@ -817,7 +816,7 @@ int main(int argc, char **argv) osmo_init_logging2(tall_bsc_ctx, &log_info); log_set_log_level(osmo_stderr_target, LOGL_INFO); - net = bsc_network_init(tall_bsc_ctx); + net = gsm_network_init(tall_bsc_ctx); if (!net) { printf("Network init failure.\n"); return EXIT_FAILURE; @@ -848,3 +847,8 @@ int main(int argc, char **argv) struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) { OSMO_ASSERT(0); } + +bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts) +{ + return true; +} diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am index 957bbeeb9..07491d57f 100644 --- a/tests/handover/Makefile.am +++ b/tests/handover/Makefile.am @@ -34,7 +34,35 @@ handover_test_LDFLAGS =\ $(NULL) handover_test_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/a_reset.o \ + $(top_builddir)/src/osmo-bsc/abis_nm.o \ + $(top_builddir)/src/osmo-bsc/abis_rsl.o \ + $(top_builddir)/src/osmo-bsc/arfcn_range_encode.o \ + $(top_builddir)/src/osmo-bsc/bsc_api.o \ + $(top_builddir)/src/osmo-bsc/bsc_dyn_ts.o \ + $(top_builddir)/src/osmo-bsc/bsc_init.o \ + $(top_builddir)/src/osmo-bsc/bsc_rll.o \ + $(top_builddir)/src/osmo-bsc/bsc_subscr_conn_fsm.o \ + $(top_builddir)/src/osmo-bsc/bsc_subscriber.o \ + $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts.o \ + $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.o \ + $(top_builddir)/src/osmo-bsc/bts_sysmobts.o \ + $(top_builddir)/src/osmo-bsc/chan_alloc.o \ + $(top_builddir)/src/osmo-bsc/gsm_04_08_utils.o \ + $(top_builddir)/src/osmo-bsc/gsm_04_80_utils.o \ + $(top_builddir)/src/osmo-bsc/gsm_data.o \ + $(top_builddir)/src/osmo-bsc/handover_cfg.o \ + $(top_builddir)/src/osmo-bsc/handover_decision.o \ + $(top_builddir)/src/osmo-bsc/handover_decision_2.o \ + $(top_builddir)/src/osmo-bsc/handover_logic.o \ + $(top_builddir)/src/osmo-bsc/meas_rep.o \ + $(top_builddir)/src/osmo-bsc/osmo_bsc_lcls.o \ + $(top_builddir)/src/osmo-bsc/net_init.o \ + $(top_builddir)/src/osmo-bsc/paging.o \ + $(top_builddir)/src/osmo-bsc/pcu_sock.o \ + $(top_builddir)/src/osmo-bsc/penalty_timers.o \ + $(top_builddir)/src/osmo-bsc/rest_octets.o \ + $(top_builddir)/src/osmo-bsc/system_information.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOABIS_LIBS) \ diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c index 82afbe5ce..26074a2fd 100644 --- a/tests/handover/handover_test.c +++ b/tests/handover/handover_test.c @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -202,7 +201,7 @@ static struct gsm_bts *create_bts(int arfcn) struct e1inp_sign_link *rsl_link; int i; - bts = gsm_bts_alloc_register(bsc_gsmnet, GSM_BTS_TYPE_OSMOBTS, 0x3f); + bts = bsc_bts_alloc_register(bsc_gsmnet, GSM_BTS_TYPE_OSMOBTS, 0x3f); if (!bts) { printf("No resource for bts1\n"); return NULL; @@ -1347,8 +1346,8 @@ int main(int argc, char **argv) struct gsm_lchan *lchan[256]; int lchan_num = 0; int i; - int algorithm; struct bsc_api bsc_api = {}; + int algorithm; int test_case_i; int last_test_i; @@ -1373,8 +1372,7 @@ int main(int argc, char **argv) log_set_print_category_hex(osmo_stderr_target, 0); log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); - /* Create a dummy network */ - bsc_gsmnet = bsc_network_init(ctx); + bsc_network_alloc(); if (!bsc_gsmnet) exit(1); diff --git a/tests/nanobts_omlattr/Makefile.am b/tests/nanobts_omlattr/Makefile.am index c2b2c3b7a..aa7045e49 100644 --- a/tests/nanobts_omlattr/Makefile.am +++ b/tests/nanobts_omlattr/Makefile.am @@ -23,7 +23,9 @@ nanobts_omlattr_test_SOURCES = \ $(NULL) nanobts_omlattr_test_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/abis_nm.o \ + $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.o \ + $(top_builddir)/src/osmo-bsc/gsm_data.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOABIS_LIBS) \ diff --git a/tests/nanobts_omlattr/nanobts_omlattr_test.c b/tests/nanobts_omlattr/nanobts_omlattr_test.c index 8e8626d13..72dabe542 100644 --- a/tests/nanobts_omlattr/nanobts_omlattr_test.c +++ b/tests/nanobts_omlattr/nanobts_omlattr_test.c @@ -305,3 +305,6 @@ int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) { OSMO_ASSERT(0); } + +bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts) +{ return true; } diff --git a/tests/subscr/Makefile.am b/tests/subscr/Makefile.am index 8d14ebf9d..e56d142bc 100644 --- a/tests/subscr/Makefile.am +++ b/tests/subscr/Makefile.am @@ -31,7 +31,7 @@ bsc_subscr_test_SOURCES = \ $(NULL) bsc_subscr_test_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/osmo-bsc/bsc_subscriber.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOABIS_LIBS) \ $(LIBOSMOGSM_LIBS) \ -- cgit v1.2.3