aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore74
-rw-r--r--.gitreview3
-rw-r--r--.mailmap12
-rw-r--r--AUTHORS9
-rw-r--r--COPYING661
-rw-r--r--Makefile.am30
-rw-r--r--README26
-rw-r--r--README.vty-tests11
-rw-r--r--configure.ac204
-rw-r--r--contrib/Makefile.am1
-rw-r--r--contrib/a-link/sccp-split-by-con.lua170
-rwxr-xr-xcontrib/jenkins.sh52
-rw-r--r--contrib/systemd/Makefile.am5
-rw-r--r--contrib/systemd/osmo-bsc.service12
-rw-r--r--debian/abisip-find.install1
-rw-r--r--debian/changelog601
-rw-r--r--debian/compat1
-rw-r--r--debian/control66
-rw-r--r--debian/copyright148
-rw-r--r--debian/osmo-bsc-bs11-utils.install2
-rw-r--r--debian/osmo-bsc-ipaccess-utils.install2
-rw-r--r--debian/osmo-bsc-meas-utils.install2
-rw-r--r--debian/osmo-bsc.install5
-rwxr-xr-xdebian/rules62
-rw-r--r--debian/source/format1
-rw-r--r--doc/BS11-OML.txt31
-rw-r--r--doc/Makefile.am37
-rw-r--r--doc/assignment-fsm.dot42
-rw-r--r--doc/assignment.msc72
-rw-r--r--doc/e1-data-model.txt172
-rw-r--r--doc/examples/Makefile.am30
-rw-r--r--doc/examples/osmo-bsc/osmo-bsc-minimal.cfg33
-rw-r--r--doc/examples/osmo-bsc/osmo-bsc.cfg94
-rw-r--r--doc/examples/osmo-bsc/osmo-bsc_custom-sccp.cfg80
-rw-r--r--doc/handover-inter-bsc-in-fsm.dot42
-rw-r--r--doc/handover-inter-bsc-in.msc74
-rw-r--r--doc/handover-inter-bsc-out-fsm.dot27
-rw-r--r--doc/handover-inter-bsc-out.msc47
-rw-r--r--doc/handover-intra-bsc-fsm.dot30
-rw-r--r--doc/handover.msc82
-rw-r--r--doc/handover.txt89
-rw-r--r--doc/lchan-fsm.dot41
-rw-r--r--doc/lchan-rtp-fsm.dot44
-rw-r--r--doc/lchan.msc216
-rw-r--r--doc/legend_for_fsm_diagrams.dot24
-rw-r--r--doc/legend_for_ladder_diagrams.msc29
-rw-r--r--doc/mgw-endpoint-fsm.dot24
-rw-r--r--doc/mgw-endpoint.msc105
-rw-r--r--doc/timeslot-fsm.dot37
-rw-r--r--doc/timeslot.msc100
-rw-r--r--doc/ts-and-lchan-fsm-lifecycle.msc116
-rwxr-xr-xgit-version-gen151
-rw-r--r--include/Makefile.am8
-rw-r--r--include/compat_af_isdn.h39
-rw-r--r--include/mISDNif.h387
-rw-r--r--include/osmocom/Makefile.am3
-rw-r--r--include/osmocom/bsc/Makefile.am60
-rw-r--r--include/osmocom/bsc/a_reset.h38
-rw-r--r--include/osmocom/bsc/abis_nm.h178
-rw-r--r--include/osmocom/bsc/abis_om2000.h129
-rw-r--r--include/osmocom/bsc/abis_rsl.h120
-rw-r--r--include/osmocom/bsc/acc_ramp.h161
-rw-r--r--include/osmocom/bsc/arfcn_range_encode.h26
-rw-r--r--include/osmocom/bsc/assignment_fsm.h44
-rw-r--r--include/osmocom/bsc/bsc_msc_data.h197
-rw-r--r--include/osmocom/bsc/bsc_msg_filter.h103
-rw-r--r--include/osmocom/bsc/bsc_rll.h19
-rw-r--r--include/osmocom/bsc/bsc_subscr_conn_fsm.h82
-rw-r--r--include/osmocom/bsc/bsc_subscriber.h44
-rw-r--r--include/osmocom/bsc/bss.h19
-rw-r--r--include/osmocom/bsc/bts_ipaccess_nanobts_omlattr.h32
-rw-r--r--include/osmocom/bsc/chan_alloc.h41
-rw-r--r--include/osmocom/bsc/codec_pref.h29
-rw-r--r--include/osmocom/bsc/ctrl.h11
-rw-r--r--include/osmocom/bsc/debug.h31
-rw-r--r--include/osmocom/bsc/e1_config.h13
-rw-r--r--include/osmocom/bsc/gsm_04_08_rr.h60
-rw-r--r--include/osmocom/bsc/gsm_04_80.h7
-rw-r--r--include/osmocom/bsc/gsm_08_08.h16
-rw-r--r--include/osmocom/bsc/gsm_data.h1626
-rw-r--r--include/osmocom/bsc/gsm_timers.h56
-rw-r--r--include/osmocom/bsc/handover.h81
-rw-r--r--include/osmocom/bsc/handover_cfg.h289
-rw-r--r--include/osmocom/bsc/handover_decision.h5
-rw-r--r--include/osmocom/bsc/handover_decision_2.h9
-rw-r--r--include/osmocom/bsc/handover_fsm.h80
-rw-r--r--include/osmocom/bsc/handover_vty.h8
-rw-r--r--include/osmocom/bsc/ipaccess.h55
-rw-r--r--include/osmocom/bsc/lchan_fsm.h86
-rw-r--r--include/osmocom/bsc/lchan_rtp_fsm.h45
-rw-r--r--include/osmocom/bsc/lchan_select.h6
-rw-r--r--include/osmocom/bsc/meas_feed.h43
-rw-r--r--include/osmocom/bsc/meas_rep.h67
-rw-r--r--include/osmocom/bsc/mgw_endpoint_fsm.h59
-rw-r--r--include/osmocom/bsc/misdn.h27
-rw-r--r--include/osmocom/bsc/neighbor_ident.h58
-rw-r--r--include/osmocom/bsc/network_listen.h16
-rw-r--r--include/osmocom/bsc/openbscdefines.h34
-rw-r--r--include/osmocom/bsc/osmo_bsc.h43
-rw-r--r--include/osmocom/bsc/osmo_bsc_grace.h36
-rw-r--r--include/osmocom/bsc/osmo_bsc_lcls.h41
-rw-r--r--include/osmocom/bsc/osmo_bsc_reset.h34
-rw-r--r--include/osmocom/bsc/osmo_bsc_rf.h66
-rw-r--r--include/osmocom/bsc/osmo_bsc_sigtran.h46
-rw-r--r--include/osmocom/bsc/osmux.h41
-rw-r--r--include/osmocom/bsc/paging.h79
-rw-r--r--include/osmocom/bsc/pcu_if.h35
-rw-r--r--include/osmocom/bsc/pcuif_proto.h193
-rw-r--r--include/osmocom/bsc/penalty_timers.h41
-rw-r--r--include/osmocom/bsc/rest_octets.h122
-rw-r--r--include/osmocom/bsc/rs232.h9
-rw-r--r--include/osmocom/bsc/signal.h195
-rw-r--r--include/osmocom/bsc/system_information.h22
-rw-r--r--include/osmocom/bsc/timeslot_fsm.h53
-rw-r--r--include/osmocom/bsc/ussd.h10
-rw-r--r--include/osmocom/bsc/vty.h38
-rw-r--r--m4/README3
-rw-r--r--m4/ax_check_compile_flag.m474
-rw-r--r--osmoappdesc.py28
-rw-r--r--src/Makefile.am27
-rw-r--r--src/ipaccess/Makefile.am71
-rw-r--r--src/ipaccess/abisip-find.c476
-rw-r--r--src/ipaccess/ipaccess-config.c1155
-rw-r--r--src/ipaccess/ipaccess-firmware.c135
-rw-r--r--src/ipaccess/ipaccess-proxy.c1250
-rw-r--r--src/ipaccess/network_listen.c257
-rw-r--r--src/ipaccess/stubs.c46
-rw-r--r--src/libfilter/Makefile.am27
-rw-r--r--src/libfilter/bsc_msg_acc.c136
-rw-r--r--src/libfilter/bsc_msg_filter.c339
-rw-r--r--src/libfilter/bsc_msg_vty.c149
-rw-r--r--src/osmo-bsc/Makefile.am105
-rw-r--r--src/osmo-bsc/a_reset.c220
-rw-r--r--src/osmo-bsc/abis_bs11.c21
-rw-r--r--src/osmo-bsc/abis_nm.c3057
-rw-r--r--src/osmo-bsc/abis_nm_ipaccess.c89
-rw-r--r--src/osmo-bsc/abis_nm_vty.c188
-rw-r--r--src/osmo-bsc/abis_om2000.c2771
-rw-r--r--src/osmo-bsc/abis_om2000_vty.c604
-rw-r--r--src/osmo-bsc/abis_rsl.c2248
-rw-r--r--src/osmo-bsc/acc_ramp.c363
-rw-r--r--src/osmo-bsc/arfcn_range_encode.c340
-rw-r--r--src/osmo-bsc/assignment_fsm.c661
-rw-r--r--src/osmo-bsc/bsc_ctrl_commands.c500
-rw-r--r--src/osmo-bsc/bsc_ctrl_lookup.c123
-rw-r--r--src/osmo-bsc/bsc_init.c291
-rw-r--r--src/osmo-bsc/bsc_rf_ctrl.c542
-rw-r--r--src/osmo-bsc/bsc_rll.c139
-rw-r--r--src/osmo-bsc/bsc_subscr_conn_fsm.c1071
-rw-r--r--src/osmo-bsc/bsc_subscriber.c181
-rw-r--r--src/osmo-bsc/bsc_vty.c5179
-rw-r--r--src/osmo-bsc/bts_ericsson_rbs2000.c209
-rw-r--r--src/osmo-bsc/bts_init.c30
-rw-r--r--src/osmo-bsc/bts_ipaccess_nanobts.c653
-rw-r--r--src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c241
-rw-r--r--src/osmo-bsc/bts_nokia_site.c1755
-rw-r--r--src/osmo-bsc/bts_siemens_bs11.c608
-rw-r--r--src/osmo-bsc/bts_sysmobts.c63
-rw-r--r--src/osmo-bsc/bts_unknown.c40
-rw-r--r--src/osmo-bsc/chan_alloc.c169
-rw-r--r--src/osmo-bsc/codec_pref.c470
-rw-r--r--src/osmo-bsc/e1_config.c299
-rw-r--r--src/osmo-bsc/gsm_04_08_rr.c961
-rw-r--r--src/osmo-bsc/gsm_04_80_utils.c42
-rw-r--r--src/osmo-bsc/gsm_08_08.c666
-rw-r--r--src/osmo-bsc/gsm_data.c1707
-rw-r--r--src/osmo-bsc/gsm_timers.c207
-rw-r--r--src/osmo-bsc/gsm_timers_vty.c118
-rw-r--r--src/osmo-bsc/handover_cfg.c86
-rw-r--r--src/osmo-bsc/handover_decision.c297
-rw-r--r--src/osmo-bsc/handover_decision_2.c1949
-rw-r--r--src/osmo-bsc/handover_fsm.c1236
-rw-r--r--src/osmo-bsc/handover_logic.c198
-rw-r--r--src/osmo-bsc/handover_vty.c177
-rw-r--r--src/osmo-bsc/lchan_fsm.c1409
-rw-r--r--src/osmo-bsc/lchan_rtp_fsm.c761
-rw-r--r--src/osmo-bsc/lchan_select.c260
-rw-r--r--src/osmo-bsc/meas_feed.c185
-rw-r--r--src/osmo-bsc/meas_rep.c134
-rw-r--r--src/osmo-bsc/mgw_endpoint_fsm.c777
-rw-r--r--src/osmo-bsc/neighbor_ident.c255
-rw-r--r--src/osmo-bsc/neighbor_ident_vty.c580
-rw-r--r--src/osmo-bsc/net_init.c85
-rw-r--r--src/osmo-bsc/osmo_bsc_bssap.c1136
-rw-r--r--src/osmo-bsc/osmo_bsc_ctrl.c778
-rw-r--r--src/osmo-bsc/osmo_bsc_filter.c163
-rw-r--r--src/osmo-bsc/osmo_bsc_grace.c149
-rw-r--r--src/osmo-bsc/osmo_bsc_lcls.c900
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c933
-rw-r--r--src/osmo-bsc/osmo_bsc_msc.c121
-rw-r--r--src/osmo-bsc/osmo_bsc_sigtran.c603
-rw-r--r--src/osmo-bsc/osmo_bsc_vty.c1021
-rw-r--r--src/osmo-bsc/paging.c478
-rw-r--r--src/osmo-bsc/pcu_sock.c716
-rw-r--r--src/osmo-bsc/penalty_timers.c129
-rw-r--r--src/osmo-bsc/rest_octets.c878
-rw-r--r--src/osmo-bsc/system_information.c1264
-rw-r--r--src/osmo-bsc/timeslot_fsm.c932
-rw-r--r--src/utils/Makefile.am135
-rw-r--r--src/utils/bs11_config.c985
-rw-r--r--src/utils/isdnsync.c189
-rw-r--r--src/utils/meas_db.c330
-rw-r--r--src/utils/meas_db.h17
-rw-r--r--src/utils/meas_json.c192
-rw-r--r--src/utils/meas_pcap2db.c138
-rw-r--r--src/utils/meas_udp2db.c126
-rw-r--r--src/utils/meas_vis.c310
-rw-r--r--tests/Makefile.am82
-rw-r--r--tests/abis/Makefile.am35
-rw-r--r--tests/abis/abis_test.c191
-rw-r--r--tests/abis/abis_test.ok16
-rw-r--r--tests/atlocal.in0
-rw-r--r--tests/bsc/Makefile.am53
-rw-r--r--tests/bsc/bsc_test.c254
-rw-r--r--tests/bsc/bsc_test.ok4
-rw-r--r--tests/codec_pref/Makefile.am34
-rw-r--r--tests/codec_pref/codec_pref_test.c725
-rw-r--r--tests/codec_pref/codec_pref_test.ok2089
-rwxr-xr-xtests/ctrl_test_runner.py531
-rw-r--r--tests/gsm0408/Makefile.am37
-rw-r--r--tests/gsm0408/gsm0408_test.c998
-rw-r--r--tests/gsm0408/gsm0408_test.ok233
-rw-r--r--tests/handover/Makefile.am97
-rw-r--r--tests/handover/handover_test.c1793
-rw-r--r--tests/handover/handover_test.ok1
-rw-r--r--tests/handover/neighbor_ident_test.c270
-rw-r--r--tests/handover/neighbor_ident_test.err0
-rw-r--r--tests/handover/neighbor_ident_test.ok186
-rw-r--r--tests/handover_cfg.vty622
-rw-r--r--tests/nanobts_omlattr/Makefile.am33
-rw-r--r--tests/nanobts_omlattr/nanobts_omlattr_test.c321
-rw-r--r--tests/nanobts_omlattr/nanobts_omlattr_test.ok26
-rw-r--r--tests/neighbor_ident.vty334
-rw-r--r--tests/osmo-bsc.vty19
-rw-r--r--tests/subscr/Makefile.am40
-rw-r--r--tests/subscr/bsc_subscr_test.c143
-rw-r--r--tests/subscr/bsc_subscr_test.err20
-rw-r--r--tests/subscr/bsc_subscr_test.ok11
-rw-r--r--tests/testsuite.at220
-rwxr-xr-xtests/vty_test_runner.py298
240 files changed, 71513 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..5a0901661
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,74 @@
+debian/*.log
+*.o
+*.lo
+*.a
+.deps
+Makefile
+Makefile.in
+bscconfig.h
+bscconfig.h.in
+*.pc
+
+*.*~
+*.sw?
+.libs
+*.pyc
+*.gcda
+*.gcno
+
+**/TAGS
+
+#configure
+aclocal.m4
+autom4te.cache/
+config.log
+config.status
+config.guess
+config.sub
+configure
+compile
+depcomp
+install-sh
+missing
+stamp-h1
+libtool
+ltmain.sh
+m4/*.m4
+
+# git-version-gen magic
+.tarball-version
+.version
+osmo-bsc-*.tar.bz2
+osmo-bsc-*.tar.gz
+
+# apps and app data
+hlr.sqlite3
+src/utils/bs11_config
+src/ipaccess/ipaccess-config
+src/ipaccess/abisip-find
+src/ipaccess/ipaccess-firmware
+src/ipaccess/ipaccess-proxy
+src/utils/isdnsync
+src/osmo-bsc_nat/osmo-bsc_nat
+src/osmo-bsc_nat/*.cfg*
+src/osmo-bsc/osmo-bsc
+src/osmo-bsc/*.cfg*
+src/utils/meas_vis
+src/utils/meas_json
+src/utils/osmo-meas-pcap2db
+src/utils/osmo-meas-udp2db
+
+tags
+/deps
+
+#tests
+tests/testsuite.dir
+tests/*/*_test
+
+tests/atconfig
+tests/atlocal
+tests/package.m4
+tests/testsuite
+tests/testsuite.log
+
+writtenconfig/
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 000000000..bd724b7e3
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,3 @@
+[gerrit]
+host=gerrit.osmocom.org
+project=osmo-bsc
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 000000000..cda405790
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,12 @@
+Harald Welte <laforge@gnumonks.org>
+Harald Welte <laforge@gnumonks.org> <laflocal@hanuman.gnumonks.org>
+Harald Welte <laforge@gnumonks.org> <laflocal@goeller.de.gnumonks.org>
+Holger Hans Peter Freyther <holger@moiji-mobile.com> <zecke@selfish.org>
+Holger Hans Peter Freyther <holger@moiji-mobile.com> <ich@tamarin.(none)>
+Holger Hans Peter Freyther <holgre@moiji-mobile.com> <holger@freyther.de>
+Andreas Eversberg <jolly@eversberg.eu>
+Andreas Eversberg <jolly@eversberg.eu> <Andreas.Eversberg@versatel.de>
+Andreas Eversberg <jolly@eversberg.eu> <root@nuedel.(none)>
+Pablo Neira Ayuso <pablo@soleta.eu> <pablo@gnumonks.org>
+Max Suraev <msuraev@sysmocom.de>
+Tom Tsou <tom.tsou@ettus.com> <tom@tsou.cc>
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 000000000..91af51520
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,9 @@
+Harald Welte <laforge@gnumonks.org>
+Holger Freyther <zecke@selfish.org>
+Jan Luebbe <jluebbe@debian.org>
+Stefan Schmidt <stefan@datenfreihafen.org>
+Daniel Willmann <daniel@totalueberwachung.de>
+Andreas Eversberg <Andreas.Eversberg@versatel.de>
+Sylvain Munaut <246tnt@gmail.com>
+Jacob Erlbeck <jerlbeck@sysmocom.de>
+Neels Hofmeyr <nhofmeyr@sysmocom.de>
diff --git a/COPYING b/COPYING
new file mode 100644
index 000000000..dba13ed2d
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 000000000..60a5b4e1b
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,30 @@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+
+## FIXME: automake >= 1.13 or autoconf >= 2.70 provide better suited AC_CONFIG_MACRO_DIRS for configure.ac
+## remove line below when OE toolchain is updated to version which include those
+ACLOCAL_AMFLAGS = -I m4
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+SUBDIRS = \
+ doc \
+ include \
+ src \
+ tests \
+ contrib \
+ $(NULL)
+
+BUILT_SOURCES = $(top_srcdir)/.version
+EXTRA_DIST = git-version-gen osmoappdesc.py .version
+
+DISTCHECK_CONFIGURE_FLAGS = \
+ --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
+
+@RELMAKE@
+
+$(top_srcdir)/.version:
+ echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+ echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/README b/README
new file mode 100644
index 000000000..e84849b10
--- /dev/null
+++ b/README
@@ -0,0 +1,26 @@
+About OsmoBSC
+=============
+
+OsmoBSC originated from the OpenBSC project, which started as a minimalistic
+all-in-one implementation of the GSM Network. In 2017, OpenBSC had reached
+maturity and diversity (including M3UA SIGTRAN and 3G support in the form of
+IuCS and IuPS interfaces) that naturally lead to a separation of the all-in-one
+approach to fully independent separate programs as in typical GSM networks.
+
+OsmoBSC was one of the parts split off from the old openbsc.git. Before, it
+worked as a standalone osmo-bsc binary as well as a combination of libbsc and
+libmsc, i.e. the old OsmoNITB. Since the standalone OsmoMSC with a true A
+interface (and IuCS for 3G support) is available, OsmoBSC exists only as a
+separate standalone entity.
+
+OsmoBSC exposes
+- A over IP towards an MSC (e.g. OsmoMSC);
+- Abis interfaces towards various kinds of BTS;
+- The Osmocom typical telnet VTY and CTRL interfaces.
+
+Find OsmoBSC issue tracker and wiki online at
+https://osmocom.org/projects/osmobsc
+https://osmocom.org/projects/osmobsc/wiki
+
+OsmoBSC-NAT is a specialized solution to navigating RTP streams through a NAT.
+(Todo: describe in more detail)
diff --git a/README.vty-tests b/README.vty-tests
new file mode 100644
index 000000000..0669ea8e8
--- /dev/null
+++ b/README.vty-tests
@@ -0,0 +1,11 @@
+To run the configuration parsing and output (VTY) test suite, first install
+
+ git://git.osmocom.org/python/osmo-python-tests
+
+and pass the following configure options here:
+
+ ./configure --enable-external-tests
+
+The VTY tests are then included in the standard check target:
+
+ make check
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 000000000..c6fa9a1c0
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,204 @@
+dnl Process this file with autoconf to produce a configure script
+AC_INIT([osmo-bsc],
+ m4_esyscmd([./git-version-gen .tarball-version]),
+ [openbsc@lists.osmocom.org])
+
+dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
+AC_CONFIG_AUX_DIR([.])
+
+AM_INIT_AUTOMAKE([dist-bzip2])
+AC_CONFIG_TESTDIR(tests)
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl include release helper
+RELMAKE='-include osmo-release.mk'
+AC_SUBST([RELMAKE])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+LT_INIT
+
+dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
+AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
+if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
+ AC_MSG_WARN([You need to install pkg-config])
+fi
+PKG_PROG_PKG_CONFIG([0.20])
+
+dnl check for AX_CHECK_COMPILE_FLAG
+m4_ifdef([AX_CHECK_COMPILE_FLAG], [], [
+ AC_MSG_ERROR([Please install autoconf-archive; re-run 'autoreconf -fi' for it to take effect.])
+ ])
+
+dnl checks for libraries
+AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""])
+AC_SUBST(LIBRARY_DL)
+
+
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.5.1)
+PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.3.0)
+PKG_CHECK_MODULES(LIBOSMOSIGTRAN, libosmo-sigtran >= 0.10.0)
+PKG_CHECK_MODULES(LIBOSMOSCCP, libosmo-sccp >= 0.10.0)
+PKG_CHECK_MODULES(LIBOSMOMGCPCLIENT, libosmo-mgcp-client >= 1.4.0)
+
+dnl checks for header files
+AC_HEADER_STDC
+
+found_pcap=yes
+AC_CHECK_HEADERS(pcap/pcap.h,,found_pcap=no)
+AM_CONDITIONAL(HAVE_PCAP, test "$found_pcap" = yes)
+
+found_cdk=yes
+AC_CHECK_HEADERS(cdk/cdk.h,,found_cdk=no)
+AM_CONDITIONAL(HAVE_LIBCDK, test "$found_cdk" = yes)
+
+found_sqlite3=yes
+PKG_CHECK_MODULES(SQLITE3, sqlite3, ,found_sqlite3=no)
+AM_CONDITIONAL(HAVE_SQLITE3, test "$found_sqlite3" = yes)
+AC_SUBST(found_sqlite3)
+
+
+dnl Checks for typedefs, structures and compiler characteristics
+
+AC_ARG_ENABLE(sanitize,
+ [AS_HELP_STRING(
+ [--enable-sanitize],
+ [Compile with address sanitizer enabled],
+ )],
+ [sanitize=$enableval], [sanitize="no"])
+if test x"$sanitize" = x"yes"
+then
+ CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
+ CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
+fi
+
+AC_ARG_ENABLE(werror,
+ [AS_HELP_STRING(
+ [--enable-werror],
+ [Turn all compiler warnings into errors, with exceptions:
+ a) deprecation (allow upstream to mark deprecation without breaking builds);
+ b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
+ ]
+ )],
+ [werror=$enableval], [werror="no"])
+if test x"$werror" = x"yes"
+then
+ WERROR_FLAGS="-Werror"
+ WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
+ WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
+ CFLAGS="$CFLAGS $WERROR_FLAGS"
+ CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
+fi
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
+ [ AC_MSG_RESULT([yes])
+ SYMBOL_VISIBILITY="-fvisibility=hidden"],
+ AC_MSG_RESULT([no]))
+CFLAGS="$saved_CFLAGS"
+AC_SUBST(SYMBOL_VISIBILITY)
+
+AX_CHECK_COMPILE_FLAG([-Werror=implicit], [CFLAGS="$CFLAGS -Werror=implicit"])
+AX_CHECK_COMPILE_FLAG([-Werror=maybe-uninitialized], [CFLAGS="$CFLAGS -Werror=maybe-uninitialized"])
+AX_CHECK_COMPILE_FLAG([-Werror=memset-transposed-args], [CFLAGS="$CFLAGS -Werror=memset-transposed-args"])
+AX_CHECK_COMPILE_FLAG([-Werror=null-dereference], [CFLAGS="$CFLAGS -Werror=null-dereference"])
+AX_CHECK_COMPILE_FLAG([-Werror=sizeof-array-argument], [CFLAGS="$CFLAGS -Werror=sizeof-array-argument"])
+AX_CHECK_COMPILE_FLAG([-Werror=sizeof-pointer-memaccess], [CFLAGS="$CFLAGS -Werror=sizeof-pointer-memaccess"])
+
+# Coverage build taken from WebKit's configure.in
+AC_MSG_CHECKING([whether to enable code coverage support])
+AC_ARG_ENABLE(coverage,
+ AC_HELP_STRING([--enable-coverage],
+ [enable code coverage support [default=no]]),
+ [],[enable_coverage="no"])
+AC_MSG_RESULT([$enable_coverage])
+if test "$enable_coverage" = "yes"; then
+ COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs"
+ COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs"
+ AC_SUBST([COVERAGE_CFLAGS])
+ AC_SUBST([COVERAGE_LDFLAGS])
+fi
+
+AC_ARG_ENABLE(profile,
+ [AS_HELP_STRING([--enable-profile], [Compile with profiling support enabled], )],
+ [profile=$enableval], [profile="no"])
+if test x"$profile" = x"yes"
+then
+ CFLAGS="$CFLAGS -pg"
+ CPPFLAGS="$CPPFLAGS -pg"
+fi
+
+AC_ARG_ENABLE([external_tests],
+ AC_HELP_STRING([--enable-external-tests],
+ [Include the VTY/CTRL tests in make check [default=no]]),
+ [enable_ext_tests="$enableval"],[enable_ext_tests="no"])
+if test "x$enable_ext_tests" = "xyes" ; then
+ AC_CHECK_PROG(PYTHON2_AVAIL,python2,yes)
+ if test "x$PYTHON2_AVAIL" != "xyes" ; then
+ AC_MSG_ERROR([Please install python2 to run the VTY/CTRL tests.])
+ fi
+ AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmotestvty.py,yes)
+ if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then
+ AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.])
+ fi
+fi
+AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
+AC_MSG_RESULT([$enable_ext_tests])
+AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes")
+
+# https://www.freedesktop.org/software/systemd/man/daemon.html
+AC_ARG_WITH([systemdsystemunitdir],
+ [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
+ [with_systemdsystemunitdir=auto])
+AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [
+ def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
+
+ AS_IF([test "x$def_systemdsystemunitdir" = "x"],
+ [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
+ [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])])
+ with_systemdsystemunitdir=no],
+ [with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
+AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
+ [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
+AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
+
+AC_MSG_RESULT([CFLAGS="$CFLAGS"])
+AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
+
+dnl Generate the output
+AM_CONFIG_HEADER(bscconfig.h)
+
+AC_OUTPUT(
+ include/Makefile
+ include/osmocom/Makefile
+ include/osmocom/bsc/Makefile
+ src/Makefile
+ src/libfilter/Makefile
+ src/osmo-bsc/Makefile
+ src/ipaccess/Makefile
+ src/utils/Makefile
+ tests/Makefile
+ tests/atlocal
+ tests/gsm0408/Makefile
+ tests/bsc/Makefile
+ tests/codec_pref/Makefile
+ tests/abis/Makefile
+ tests/subscr/Makefile
+ tests/nanobts_omlattr/Makefile
+ tests/handover/Makefile
+ doc/Makefile
+ doc/examples/Makefile
+ contrib/Makefile
+ contrib/systemd/Makefile
+ Makefile)
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
new file mode 100644
index 000000000..3439c97be
--- /dev/null
+++ b/contrib/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = systemd
diff --git a/contrib/a-link/sccp-split-by-con.lua b/contrib/a-link/sccp-split-by-con.lua
new file mode 100644
index 000000000..f5d5502ae
--- /dev/null
+++ b/contrib/a-link/sccp-split-by-con.lua
@@ -0,0 +1,170 @@
+-- Split trace based on SCCP Source
+-- There are still bugs to find... bugs bugs bugs... hmm
+do
+ local function init_listener()
+ print("CREATED LISTENER")
+ local tap = Listener.new("ip", "sccp && (ip.src == 172.16.1.81 || ip.dst == 172.16.1.81)")
+ local sccp_type_field = Field.new("sccp.message_type")
+ local sccp_src_field = Field.new("sccp.slr")
+ local sccp_dst_field = Field.new("sccp.dlr")
+ local msg_type_field = Field.new("gsm_a.dtap_msg_mm_type")
+ local lu_rej_field = Field.new("gsm_a.dtap.rej_cause")
+ local ip_src_field = Field.new("ip.src")
+ local ip_dst_field = Field.new("ip.dst")
+
+ --
+ local bssmap_msgtype_field = Field.new("gsm_a.bssmap_msgtype")
+ -- assignment failure 0x03
+ --
+
+ --
+ local dtap_cause_field = Field.new("gsm_a_dtap.cause")
+ local dtap_cc_field = Field.new("gsm_a.dtap_msg_cc_type")
+
+ local connections = {}
+
+ function check_failure(con)
+ check_lu_reject(con)
+ check_disconnect(con)
+ check_failures(con)
+ end
+
+ -- cipher mode reject
+ function check_failures(con)
+ local msgtype = bssmap_msgtype_field()
+ if not msgtype then
+ return
+ end
+
+ msgtype = tonumber(msgtype)
+ if msgtype == 89 then
+ print("Cipher mode reject")
+ con[4] = true
+ elseif msgtype == 0x03 then
+ print("Assignment failure")
+ con[4] = true
+ elseif msgtype == 0x22 then
+ print("Clear Request... RF failure?")
+ con[4] = true
+ end
+ end
+
+ -- check if a DISCONNECT is normal
+ function check_disconnect(con)
+ local msg_type = dtap_cc_field()
+ if not msg_type then
+ return
+ end
+
+ if tonumber(msg_type) ~= 0x25 then
+ return
+ end
+
+ local cause = dtap_cause_field()
+ if not cause then
+ return
+ end
+
+ cause = tonumber(cause)
+ if cause ~= 0x10 then
+ print("DISCONNECT != Normal")
+ con[4] = true
+ end
+ end
+
+ -- check if we have a LU Reject
+ function check_lu_reject(con)
+ local msg_type = msg_type_field()
+ if not msg_type then
+ return
+ end
+
+ msg_type = tonumber(tostring(msg_type))
+ if msg_type == 0x04 then
+ print("LU REJECT with " .. tostring(lu_rej_field()))
+ con[4] = true
+ end
+ end
+
+ function tap.packet(pinfo,tvb,ip)
+ local ip_src = tostring(ip_src_field())
+ local ip_dst = tostring(ip_dst_field())
+ local sccp_type = tonumber(tostring(sccp_type_field()))
+ local sccp_src = sccp_src_field()
+ local sccp_dst = sccp_dst_field()
+
+ local con
+
+ if sccp_type == 0x01 then
+ elseif sccp_type == 0x2 then
+ local src = string.format("%s-%s", ip_src, tostring(sccp_src))
+ local dst = string.format("%s-%s", ip_dst, tostring(sccp_dst))
+ local datestring = os.date("%Y%m%d%H%M%S")
+ local pcap_name = string.format("alink_trace_%s-%s_%s.pcap", src, dst, datestring)
+ local dumper = Dumper.new_for_current(pcap_name)
+
+ local con = { ip_src, tostring(sccp_src), tostring(sccp_dst), false, dumper, pcap_name }
+
+ dumper:dump_current()
+ connections[src] = con
+ connections[dst] = con
+ elseif sccp_type == 0x4 then
+ -- close a connection... remove it from the list
+ local src = string.format("%s-%s", ip_src, tostring(sccp_src))
+ local dst = string.format("%s-%s", ip_dst, tostring(sccp_dst))
+
+ local con = connections[src]
+ if not con then
+ return
+ end
+
+ con[5]:dump_current()
+ con[5]:flush()
+
+ -- this causes a crash on unpacted wireshark
+ con[5]:close()
+
+ -- the connection had a failure
+ if con[4] == true then
+ local datestring = os.date("%Y%m%d%H%M%S")
+ local new_name = string.format("alink_failure_%s_%s-%s.pcap", datestring, con[2], con[3])
+ os.rename(con[6], new_name)
+ else
+ os.remove(con[6])
+ end
+
+
+ -- clear the old connection
+ connections[src] = nil
+ connections[dst] = nil
+
+ elseif sccp_type == 0x5 then
+ -- not handled yet... we should verify stuff here...
+ local dst = string.format("%s-%s", ip_dst, tostring(sccp_dst))
+ local con = connections[dst]
+ if not con then
+ return
+ end
+ con[5]:dump_current()
+ elseif sccp_type == 0x6 then
+ local dst = string.format("%s-%s", ip_dst, tostring(sccp_dst))
+ local con = connections[dst]
+ if not con then
+ print("DON'T KNOW THIS CONNECTION for " .. ip_dst)
+ return
+ end
+ con[5]:dump_current()
+ check_failure(con)
+ end
+
+ end
+ function tap.draw()
+ print("DRAW")
+ end
+ function tap.reset()
+ print("RESET")
+ end
+ end
+
+ init_listener()
+end
diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh
new file mode 100755
index 000000000..0be4fe032
--- /dev/null
+++ b/contrib/jenkins.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+# jenkins build helper script for openbsc. This is how we build on jenkins.osmocom.org
+
+if ! [ -x "$(command -v osmo-build-dep.sh)" ]; then
+ echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !"
+ exit 2
+fi
+
+
+set -ex
+
+base="$PWD"
+deps="$base/deps"
+inst="$deps/install"
+export deps inst
+
+osmo-clean-workspace.sh
+
+mkdir "$deps" || true
+
+osmo-build-dep.sh libosmocore "" '--disable-doxygen --enable-gnutls'
+
+verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]")
+
+export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
+export LD_LIBRARY_PATH="$inst/lib"
+
+osmo-build-dep.sh libosmo-abis
+osmo-build-dep.sh libosmo-netif
+osmo-build-dep.sh libosmo-sccp
+osmo-build-dep.sh osmo-mgw
+
+set +x
+echo
+echo
+echo
+echo " =============================== osmo-bsc ==============================="
+echo
+set -x
+
+cd "$base"
+autoreconf --install --force
+./configure --enable-sanitize --enable-external-tests --enable-werror
+$MAKE $PARALLEL_MAKE
+LD_LIBRARY_PATH="$inst/lib" $MAKE check \
+ || cat-testlogs.sh
+LD_LIBRARY_PATH="$inst/lib" \
+ DISTCHECK_CONFIGURE_FLAGS="--enable-vty-tests --enable-external-tests --enable-werror" \
+ $MAKE distcheck \
+ || cat-testlogs.sh
+
+osmo-clean-workspace.sh
diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am
new file mode 100644
index 000000000..5119adfd6
--- /dev/null
+++ b/contrib/systemd/Makefile.am
@@ -0,0 +1,5 @@
+if HAVE_SYSTEMD
+EXTRA_DIST = osmo-bsc.service
+systemdsystemunit_DATA = \
+ osmo-bsc.service
+endif
diff --git a/contrib/systemd/osmo-bsc.service b/contrib/systemd/osmo-bsc.service
new file mode 100644
index 000000000..67dcd7ed1
--- /dev/null
+++ b/contrib/systemd/osmo-bsc.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Osmocom Base Station Controller (BSC)
+Wants=osmo-mgw.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/osmo-bsc -c /etc/osmocom/osmo-bsc.cfg -s
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/abisip-find.install b/debian/abisip-find.install
new file mode 100644
index 000000000..1e19459eb
--- /dev/null
+++ b/debian/abisip-find.install
@@ -0,0 +1 @@
+usr/bin/abisip-find
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 000000000..2e07507ef
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,601 @@
+osmo-bsc (1.3.0) unstable; urgency=medium
+
+ [ Philipp Maier ]
+ * a_reset: cleanup + remove dead code
+ * gscon: remove dead code
+ * gscon: pick suitable payload type / encoding name for MGCP
+ * lcls: set codec info when performing MGW operation
+ * codec_pref: move match_codec_pref() to separate c-file and add unit-test
+ * codec_pref: check bts codec support
+ * chan_alloc: reset rtp voice related bits in lchan_free()
+ * rsl: use 3GPP assigned payload type constants from libosmo-netif
+
+ [ Stefan Sperling ]
+ * show all global counters of osmo-bsc in vty
+ * add counter for connection attempts from BTS with unknown unit id
+ * fix misaligned memory write access in abis_nm_ipaccess_rsl_connect()
+ * increment 'paging responded' counter for active paging only
+ * fix handling of invalid pchan names in vty
+
+ [ Pau Espin Pedrol ]
+ * nat: Add jitter buffer on the uplink receiver
+ * acc_ramp: Increase log level of some messages
+ * chan_alloc: Print bts nr on chan alloc failure
+ * abis_rsl.c: Fix whitespace
+ * abis_rsl: rsl_rx_chan_rqd: Format bts log string as in everywhere else
+ * pcu_sock: Log event pcu_sock created
+ * osmo-bsc: Clean help description of cmd line parameters
+ * osmo-bsc: Add -V param to print version
+ * debian: Move meas related binaries into new package osmo-bsc-meas-utils
+ * bsc-filter: Remove unused func barr_adapt and set barr_find static
+ * bsc_vty: Write access list entries when storing bsc config
+ * Init access_lists before passing it as a parameter
+ * Rename bsc_msg_acc_lst_vty_init to have more uniform prefix
+ * filter: vty: Print policy list in cmd show access-list
+ * filter: Replace '.' in counter names with ':'
+ * filter: Allocate each ctr group with a different idx
+ * ctrl: Avoid sending back received ERROR msgs
+
+ [ Neels Hofmeyr ]
+ * bsc_api.c: actually log with context
+ * abis_rsl.h: drop unused rsl_chan_activate() declaration
+ * cosmetic: bsc_dyn_ts.c: make local functions static
+ * cosmetic: define TCH_F_PDCH_PENDING_MASK as actual bitwise or
+ * cosmetic: logging and ordering in handle_ass_compl()
+ * doc: add msc charts on Assignment/Handover internals
+ * tests: remove bssap_test
+ * tests: remove channel_test
+ * bsc_test: drop "scan to MSC" code path
+ * dissolve libbsc: move all to src/osmo-bsc, link .o files
+ * remove struct bsc_api
+ * cosmetic: magic number: use RSL_ACT_ constant for chan act
+ * cosmetic: gscon: undup code: add common assignment_failed()
+ * assignment: signal assignment failure on chan act nack
+ * log: fix logging in rsl_rx_chan_act_nack()
+ * log: assignment: add two logs on unexpected lchan release
+ * use libosmocore's gsm0808_permitted_speech(), gsm0808_chosen_channel()
+ * cosmetic: penalty timers: constify, tweak doc
+ * cosmetic: bsc_subscr_alloc: log initial get
+ * gscon: put subscriber a little later
+ * try to pick up subsrc IMSI on l3-compl
+ * store subscriber identity on paging
+ * cosmetic: handover_test: add IMSI to subscr for logging
+ * doc: tweak msc charts on Assignment/Handover: act_timer
+ * doc: add lchan-release.msc
+ * doc: add ms-channel-request.msc
+ * doc: charts: illustrate new plan for ts and lchans
+ * cosmetic: gscon: drop odd use of OSMO_STRINGIFY
+ * HO: introduce T7, T8, T101 timers
+ * drop dead code: conn->T10, handled by gscon instead
+ * make T10 configurable like the rest of them
+ * fix dyn TS init: properly identify BTS on OML OPSTART ACK
+ * cosmetic / linking: move str_to_imsi() out of abis_rsl.c
+ * cosmetic: name osmo-bsc's root ctx 'osmo-bsc', not 'openbsc'
+ * call osmo_xua_msg_tall_ctx_init()
+ * fix handover start: dealloc ho if event not permitted
+ * ho cfg: fix unit strings
+ * hodec2 log: less verbose, more concise logging
+ * various logging: fix missing/extra newlines
+ * BTS codec pref legacy compat: allow all codecs per default
+
+ [ Harald Welte ]
+ * bsc: Fix check for MSC-side FSM allocation failure
+ * vty: Permit selection of other ASP protocol than M3UA
+ * bsc: Add mgcp_port_to_cic() to determine CIC from RTP Port
+ * bsc: Use correct MGCP endpoint name for IPA/SCCPlite
+ * bsc: Don't reject ASSIGNMENT for Audio in IPA/SCCPlite case
+ * bsc: Don't include AoIP IEs in ASSIGNMENT COMPLETE over SCCPlite
+ * bsc: Don't create MSC-side MGCP connection in IPA/SCCPlite case
+ * remove remaining bits of osmo-bsc_nat
+ * Remove 'struct bsc_msc_connection' + fix IPA-encapsulated CTRL
+ * move 'extern struct gsm_network *bsc_gsmnet" to header file
+ * VTY: Print some more information in "show conns"
+ * Add initial 3GPP LCLS support to OsmoBSC
+ * LCLS: add VTY config to enable/disable LCLS on per-MSC basis
+ * Reject ASSIGNMENT REQ with CIC but no AoIP transp addr in AoIP case
+ * Ignore "dest" command in MSC node
+ * Explicitly register CTRL-over-IPA callback with libosmo-sigtran
+ * Remove unused logging subsystems DCC and DMGCP
+ * remove traces of osmo-bsc_nat in python test (osmoappdesc/test_runner)
+ * Add missing event string name for GSCON_EV_LCLS_FAIL
+ * bsc_subscr_conn_fsm: BSC must not release SCCP connection
+ * absi_rsl: Fix segfault in rsl_rx_conn_fail()
+
+ [ Daniel Willmann ]
+ * git-version-gen: Don't check for .git directory
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Fri, 27 Jul 2018 19:25:05 +0200
+
+osmo-bsc (1.2.1) unstable; urgency=medium
+
+ [ Philipp Maier ]
+ * bsc_api/GSCON: prevent unnecessary channel mode modifications
+
+ [ Neels Hofmeyr ]
+ * resurrect meas_feed.c: vty, vty-test
+ * dyn ts, bts_ipaccess_nanobts.c: init PDCH on Chan OPSTART ACK
+ * dyn TS, assignment: set lchan state to LCHAN_S_ACT_REQ in the proper place
+ * dyn TS, assignment: allow switch from PDCH with associated conn
+ * dyn TS: init only when both RSL and the Channel OM are established
+ * dyn TS: allow any pchan type changes, fix for gprs mode none
+ * debug log: verbosely log all lchan alloc choices
+ * deprecate dyn_ts_allow_tch_f and by default allow all TCH
+ * fix default fallbacks in audio_support_to_gsm88()
+ * log: indicate hr/fr in audio_support_to_gsm88() error
+ * cosmetic: dyn ts init: undup logging for gprs = none
+
+ [ Vadim Yanitskiy ]
+ * osmo_bsc_vty.c: fix: write MGW configuration
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 15 May 2018 14:10:38 +0200
+
+osmo-bsc (1.2.0) unstable; urgency=medium
+
+ [ Neels Hofmeyr ]
+ * vty: skip installing cmds now always installed by default
+ * bssap: paging: page entire BSS for unimplemented cell id list
+ * fix build: bssap test broke by undefined references
+ * osmo-bsc RESET FSM: use distinct struct names
+ * osmo-bsc: SCCP addrs: default only if unset, reject invalid
+ * osmo-bsc vty: be fatal for addressbook entry errors
+ * use osmo_sccp_inst_addr_name() instead of looking up ss7
+ * add --enable-sanitize config option
+ * bsc_init: fix Werror: define rc for 2quater with si2q_count == 0
+ * bsc filter: don't ignore imsi-allow on "global" filter level
+ * compiler warnings: drop some unused variables
+ * compiler warnings: constify in abis_nm.c
+ * cleanup: drop unused gsm_bts.role
+ * compiler warnings: add includes in abis_rsl.h, gsm_data_shared.h
+ * cosmetic: handover.h: use "#pragma once", declare structs, comments
+ * examples: add osmo-bsc-minimal.cfg
+ * HO prep: pass gsm_network to gsm_bts_alloc() already
+ * fix segfault upon release paging on BSSMAP Reset: init llist
+ * log typo fix in gsm0808_cipher_mode()
+ * debug log: log Cipher Mode info upon sending down RSL/A-bis
+ * fix bssmap_handle_cipher_mode()'s encryption decision
+ * abisip-find: add getopt option parsing in preparation for a new option
+ * abisip-find: add -l to list base stations instead of streaming replies
+ * abisip-find: update copyright
+ * abisip-find: add timeout option
+ * abisip-find: add --interval option
+ * vty: fix 'show lchan ...' arg [lchan_nr] to [<0-7>]
+ * vty: change handover command's arg LCHAN_NR to <0-7>
+ * vty: cosmetic: use common BTS, TRX, TS, LCHAN strings
+ * vty: add various manual handover and assignment trigger commands
+ * osmo_bsc_mgcp: cosmetic: introduce mgcp_init(), soak up fsm init
+ * HO: fix recovery from failed handover
+ * HO prep: introduce per-BTS handover config, with defaults on net node
+ * HO: add indicators for inter-cell and async ho, use for chan act type
+ * cosmetic: explicitly init ho_ref start value
+ * fixup: neigh_meas_avg: detect invalid window size as <=0, log if invalid
+ * fixup: neigh_meas_avg: fix condition to reduce window size
+ * HO: enable handover by initializing at startup; rename init function
+ * HO: add handover algo 2 parameters; skip HO 1 if HO 2 is configured
+ * HO: rename gsm_bts_neighbor() to bts_by_arfcn_bsic()
+ * HO: make bts_by_arfcn_bsic() public
+ * libcommon: eliminate bsc_version.c
+ * libcommon: eliminate common_vty.c
+ * libcommon: eliminate debug.c
+ * libcommon: eliminate socket.c
+ * libcommon: eliminate talloc_ctx.c
+ * ipaccess-proxy: don't redefine tall_bsc_ctx
+ * libcommon: join gsm_data_shared.* into gsm_data.*
+ * drop libcommon completely, move remaining files to libbsc
+ * libcommon-cs: move a_reset.c into libbsc
+ * libcommon-cs: move gsm_network_init() into bsc_network_init()
+ * gsm_network: drop unused trans_list
+ * libcommon_cs: move gsm48 bits to libbsc
+ * libcommon-cs: move vty bits to libbsc/bsc_vty.c
+ * common_cs.h: mv gsm_encr to gsm_data.h
+ * drop libcommon-cs completely
+ * drop unused common.h
+ * gsm_network: drop unused subscr_epxire_timer
+ * vty: 'show bts': write '(none)' if none are found.
+ * vty: 'show bts': fix indenting
+ * bts chan_load: ignore unusable BTS
+ * handover_logic.c: always do inter-cell channel activation
+ * handover_logic.c: on HO command, send new lchan's MS power
+ * HO: process_meas_rep: guard against modulo zero
+ * HO: cosmetic: bsc_handover_start(): "fix" memcpy for AMR config
+ * HO: add new_lchan_type arg to bsc_handover_start()
+ * HO: cosmetic: bsc_handover_start_lchan_change(): tweak local vars
+ * HO: always do async handover
+ * HO: bsc_handover_start_lchan_change(): set MS to max power on handover
+ * HO: logging: more logs, and more concise logging
+ * HO: move penalty timers to own file as proper API
+ * HO: store speech codec list from BSSMAP Assignment in conn
+ * HO: cfg: tweak vty write
+ * vty: 'show bts': list the TRXs' ARFCNs
+ * vty: 'show bts': print neighbor cells
+ * HO: cfg: separate hodec1 from hodec2 parameters
+ * HO: lchan: store last seen measurement report nr, tweak log
+ * HO: clearly mark conn penalty timer member for hodec2
+ * HO: cosmetic: handover_decision.c: make process_meas_rep() return void
+ * HO: introduce ho decision callbacks
+ * HO: cosmetic: getting a chan activ nack on a non-ho lchan is not an error
+ * HO: Implement load based handover, as handover_decision_2.c
+ * HO: vty: rename ho decision 1 vty to 'handover1' with 'handover' alias
+ * cosmetic: adjust copyrights on handover_cfg.c,_vty.c
+ * HO: vty: clearly mark 'handover foo' as legacy alias for 'handover1 foo'
+ * drop unused libbsc/meas_proc.c
+ * HO: fix minor issues found by coverity
+ * bsc_api.c: fix log string format
+ * fix build: gprs_ra_id_by_bts(): ensure to init all values
+ * compiler warning: chan_compat_with_mode(): clearly handle all enum vals
+ * add test for gsm48_ra_id_by_bts()
+ * add test for abis_nm_ipaccess_cgi()
+ * ctrl_test_runner: add tests for 3-digit MNC
+ * gsm48_ra_id_by_bts(): struct gsm48_ra_id* instead of buf
+ * cosmetic: bsc_network_init(): imply default 001-01 PLMN
+ * implement support for 3-digit MNC with leading zeros
+ * gsm48_parse_meas_rep(): set num_cell=0 if no neighbor cells are reported
+ * cosmetic: hodec2: log nr of neighbors in meas report
+ * cosmetic: typo in log: handover_decision2.c: 'measuements'
+ * handover_test: explicitly wrap abis_rsl_sendmsg()
+ * pcu_if: implement support for 3-digit MNC
+ * configure: add --enable-werror
+ * cosmetic: abis_nm: use osmo_cell_global_id, parse 3-digit MNC
+ * fixup: apply mnc3 change also in ipaccess/network_listen.c
+ * vty: drop unused vty definitions (*_NODE, msc_*)
+ * gsm0408_test: drop LAI encoding test
+ * range_enc_arfcns: avoid runtime error on zero size
+ * fix gsm0408_test: properly free bts struct after each test
+ * move init from gsm_bts_alloc_register() to gsm_bts_alloc(); fix gsm0408_test
+ * cosmetic: gsm0408_test: drop unused arg from bts_init()
+ * ctx cleanup: use non-NULL talloc ctx for osmo_init_logging2()
+ * vty: re-add 'timeout-ping' and 'timeout-pong' as dummy commands
+ * resurrect meas_feed.c from openbsc.git history
+ * resurrect meas_feed.c: make it compile, add logging
+
+ [ Max ]
+ * Check OML state per-BTS
+ * OML: consider administrative state when reporting
+ * cosmetic: remove obsolete ROLE_BSC
+ * cosmetic: tighten function type signatures
+ * cosmetic: drop unused include
+ * cosmetic: mark gsm_objclass2mo as static
+ * OML: expand status reporting checks
+ * Generate SI2ter Rest Octets
+ * Generate SI2bis Rest Octets
+ * Fix tests after rate_ctr change
+ * Remove unneeded .py scripts
+ * Enable sanitize for CI tests
+ * Migrate from OpenSSL to osmo_get_rand_id()
+ * Add optional profiling support
+ * Fix .deb builds
+ * Check and handle SMS encoding failure
+ * Remove obsolete ./configure option
+ * cosmetic: remove duplicated code
+ * RSL: print link state per-TRX
+ * vty: fix OML link state printing
+ * cosmetic: log prim operation as text
+
+ [ Philipp Maier ]
+ * mgcp: use osmo-mgw to switch RTP streams
+ * cosmetic: remove distracting newline
+ * cosmetic: reorder case list
+ * cosmetic: replace term MGCP-GW with MGW
+ * mgcp: add missing out state
+ * mgcp: remove unused variable
+ * reset: remove name variable from reset context
+ * doc: add example configuration for osmo-mgw
+ * auth: remove obsolete VTY commands
+ * bssap: remove libosmo-legacy-mgcp dependancy
+ * sccp-lite: remove obsolete VTY commands
+ * auth: remove unused structs
+ * auth: remove obsolete VTY commands
+ * mgcp: use hexadecimal digits in endpoint names
+ * mgcp: use mgw assigned connection identifiers
+ * mgcp: add missing switch case
+ * mgcp: do not fail silently on snprintf()
+ * cosmetic: remove duplicate logging
+ * cosmetic: do not cast void pointer
+ * cosmetic: add missing log prefix
+ * cosmetic: correct sourcecode formatting
+ * cosmetic: use fsm pointer from parameter list
+ * mgcp: cosmetic fixups
+ * paging: paging_flush_bts: be sure pending_requests is initalized
+ * cosmetic: osmo_bsc_mgcp: improve comments
+ * mgcp: cancel transactions on timeout
+ * mgcp: validate rtp connection data in MGW response (ip/port)
+ * mgcp: log file and line of calls to handle_error()
+ * cosmetic: mgcp: remove duplicate logging
+ * abis_rsl: permit first EstablishInd only on SAPI=0
+ * abis_rsl: do not allow SACCH in MF mode on SAPI=0
+ * SIGTRAN: correct wrong log category
+ * bsc_api: drop unknown RR messages.
+ * paging: page all bts when no cell is associated
+ * paging: fix paging attemt rate counter
+ * a_reset: Add FSM event names
+ * gsm_data: use feature list from libosmocore
+ * bsc_vty: display bts features in show bts
+ * cosmetic: remove unused enum members
+ * cosmetic: fix typo
+ * cosmetic: fix argument order of forward_dtap()
+ * cosmetic: remove needless fixme note.
+ * cosmetic: fix incomplete sentence in comment.
+ * Cosmetic: fix missing semicolon after osmo-assert
+ * cosmetic: remove dead code and obsolete fixmes
+ * cosmetic: remove old, already commented-out code
+ * ipaccess: make ipaccess-config build again
+ * bs11: make bs11_config build again
+ * cosmetic: remove dead code: osmo_bsc_reset.c
+ * gscon: fix illegal state transitions
+ * cosmetic: remove dead code
+ * cosmetic: Add fixme note for OS#3112
+ * inform A-RESET FSM about MSC CR timeouts
+ * gscon: fix assignment of signalling channels
+ * cosmetic: Add note about libosmo-legacy-mgcp to configure.ac
+
+ [ Harald Welte ]
+ * debian: Add dependency to libosmo-mgcp-client-dev
+ * debian: Increase required libosmo-legacy-mgcp-dev version
+ * configure.ac/debian: Require libosmo-mgcp-client-dev >= 1.2.0
+ * osmo-bsc: Print NOTICE message on unimplemented BSSMAP UDT
+ * Move many counters from BSC-global to per-BTS granularity
+ * rate_ctr: Use ':' as separator, not '.'
+ * Remove 'msc' counter group from BSC
+ * Change T3101 default from 10s to 3s.
+ * paging.c: add more documentation on what the functions actually do
+ * paging: Remove obsolete paging call-back support
+ * paging: Stop all paging if MSC sends us BSSMAP RESET
+ * Fix per-BTS counter group index
+ * libbsc: paging: more reasonable (and detailed) paging statistics
+ * Reduce T3113 default from 60s to 10s
+ * Add per-BTS rate_ctr for total + failed number of RSL CHAN_ACT
+ * Add new per-BTS "rsl:unknown" counter to count unknown RSL messages
+ * Add a new counter "rsl:ipa_nack" to count number of IPA related NACKs
+ * Add new "chan:mode_modify_nack" counter to count RSL MODE MODIFY NACK
+ * Remove dead code left over from NITB split
+ * Remove unused RRLP options/codec
+ * Remove bogus vty config for LU reject cause
+ * Remove bogus MM INFO configuration
+ * Remove some more dead code
+ * remove libosmo-sccp dependency for osmo-bsc
+ * osmo_bsc_bssap.c: Spelling fixes in comment
+ * Remove unused struct osmo_bsc_sccp_con member sccp_queue_size
+ * osmo_bsc.h: document every field in 'struct osmo_bsc_sccp_con'
+ * osmo-bsc: Move user plane/voice related bits into sub-structure
+ * gsm_data.h: Document all fields of gsm_subscriber_connection
+ * remove unused 'lac' member of 'struct gsm_subscriber_connection'
+ * BSC: Add "show subscriber all" command
+ * BSC: Fix bsc_subsc leak on paging
+ * bsc_test.c: Use proper network/bts/lchan structures
+ * cosmetic: Hide all accesses to conn->bts behind conn_get_bts()
+ * Reduce T3109 default from 19s to 5s
+ * Make libcommon, libcommon-cs, libfilter, utils depend on mgcp/sigtran
+ * cosmetic: Remove data/len variables in bssmap_handle_assignm_req()
+ * bssmap_handle_assignm_req(): Decode channel type as first step
+ * remove obsolete gsm_subscriber_connection.bts member
+ * update.gitignore with 'tags' files and 'deps' directory
+ * gsm_data_shared.h: Remove unused sacch_deact member field
+ * vty: print RTP IP of lchan if actually bound; print remote (mgw) IP
+ * osmo-bsc: Add talloc context introspection via VTY
+ * Structural reform: Get rid of osmo_bsc_sccp_con
+ * vty: Permit codec-list containing both full-rate and half-rate codecs
+ * logging: Remove obsolete log categories
+ * Permit set of multiple different A5 ciphers
+ * bssmap_handle_assignm_req(): Use proper cause values
+ * bssmap_handle_assignm_req(): Use more conscise error/log message texts
+ * bssmap_handle_assignm_req(): Don't print log statemens in malloc failure case
+ * chan_compat_with_mode: signalling works over all channel types
+ * osmo-bts/nanobts: Set RACH_Busy Threshold to -90 dBm
+ * Align syntax of "handover" + "assignment" command with that of lchan act/deact
+ * Revert "Generate the S_L_INP_TEI_UP signal earlier."
+ * bsc_vty: Merge more VTY documentation string #defines
+ * sysinfo: Fix regression causing missing L2 Pseudo-Length in SI5/SI6
+ (Closes: #3059)
+ * introduce an osmo_fsm for gsm_subscriber_connection
+ * cosmetic: Fix infinite number of formatting errors in gscon_fsm_states
+ * abis_nm: Improve and fix OML logging
+ * paging: Unify formatting of log messages with (bts=%d) prefix
+ * RR: Send RR STATUS in case of unsupported/unknown message
+ * BSSAP: Fix test_codec_pref() implementation for AMR
+ * BSSAP: document match_codec_pref() more thoroughly
+ * GSCON FSM: Fix argument order when calling gsm0808_assign_req()
+ * bssmap: State correct speech codec in ASSIGNMENT COMPLETE
+ * Start Dynamic PDCH Initialization after RSL is up
+ * "show timeslot": Show dynamic PDCH state also for Osmocom-style dyn PDCH
+
+ [ Alexander Couzens ]
+ * debian: remove doublicated project name in example files
+ * use _NUM_CHREQ_T to define the size of ctype_by_chreq
+ * pcuif_proto.h: fix whitespaces and indention
+ * pcuif_proto.h: add features of version 7 (txt indication)
+
+ [ Pau Espin Pedrol ]
+ * tests: Fix selection of python version
+ * Use type bool for boolean fields in gsm48_si_ro_info
+ * vty: Add cmd to configure 3g Early Classmark Sending
+ * cosmetic: bsc_vty: Fix trailing whitespace
+ * cosmetic: bsc_vty: Document bvci reserved values
+ * osmo_bsc_bssap.c: Fix discard of const qualifier in assignment
+ * debian: Move abisip-find from osmo-bsc to its own package
+ * abisip-find: Add option to bind to a specific source address
+ * abisip-find: Force stdout buffer flush
+ * abisip-find: Add --format-json option
+ * ipaccess-config: Enable logging all categories to print errors
+ * ipaccess-config: Add missing path with log error
+ * ipaccess-config: Improve handling of last parameter
+ * abisip-find: Improve use information output
+ * ipaccess-config: Check cmdlie arg unit-id format
+ * bsc_api.c: bsc_handle_lchan_signal: Remove unused variable
+ * bsc_subscr_conn_fsm.c: Fix wrong param list passed to LOGPFSML
+ * tests: handover_test.c: Add missing header
+ * pcu_sock.c: Avoid breaking strict-aliasing on ptr derreference
+ * contrib: jenkins.sh: Add --enable-werror flag
+ * contrib: osmo-bsc.service: Update description
+ * contrib: osmo-bsc.service: Fix osmo-mgw.service dependency
+ * libbsc: nokia_site: Fix uninitialized return val
+ * bsc_vty: Fix uninitialized var false positive on gcc 7.3.1
+ * paging: paging_request_bts: Fix wrong return value
+ * bssap: Log non handled paging requests
+ * libbsc: set_net_mcc_mnc_apply: Fix memleak on parsing incorrect mcc mnc
+ * bsc_nat: ctrl: fix memleak on reply receival
+ * bsc_nat: forward_to_bsc: remove one level of indentation
+ * bsc_nat: forward_to_bsc: Fix memleak on send failure
+ * bsc_nat: Drop redundant ccon ptr in bsc_cmd_list
+ * bsc_nat: ctrl: Fix crash on receveing bsc reply
+ * use osmo_init_logging2
+ * chan_alloc.c: Fix log var formatting issues
+ * abis_rsl.c: abis_rsl_rx_cchan: Print msg type name for unimplemented messages received
+ * abis_rsl.c: Clean ericsson specific imm assign code
+ * gsm_data_shared.h: Remove unused enum gsm_paging_event
+
+ [ Stefan Sperling ]
+ * Fix "CTRL GET msc_connection_status" response.
+ * Support control connection status query for a particular MSC.
+ * Implement support for paging by LAI.
+ * Add TAGS files (produced by 'make tags') to .gitignore file.
+ * Implement support for CELL_IDENT_NO_CELL.
+ * Implement support for paging based on CI (cell identifier).
+ * Move BTS selection for paging from osmo_bsc_grace.c into osmo_bsc_bssap.c.
+ * Implement support for paging based on a Cell Global Identifier.
+ * Implement support for paging based on LAC and CI.
+ * Show the BTS number for outgoing paging commands in debug log.
+ * Split paging cases in bssmap_handle_paging() off into helper functions.
+ * Remove an unused variable.
+ * Improve an error message in page_lai_and_lac()
+ * Make "waiting indicator" of IMMEDIATE ASSIGN REJECT dynamic.
+ * Add stat items for the BTS's channel load average and T3122.
+ * Make RSL connection attempts time out.
+ * fix handover_test link error
+ * Add support for Access Control Class ramping.
+ * Generate the S_L_INP_TEI_UP signal earlier.
+ * use libosmocore to parse cell identifiers in osmo-bsc
+ * fix an error message in bssmap_handle_paging()
+ * change return type of page_subscriber() to void
+ * Generate the S_L_INP_TEI_UP signal earlier.
+ * fix a format string error in bts_update_t3122_chan_load()
+ * fix initialization of acc ramping
+ * only log actual access control class ramping changes
+ * ensure that acc_ramp_init() is only called once
+ * trigger acc ramping based on trx rf-locked state
+ * rename helper functions in the acc ramp code to avoid confusion
+ * trigger acc ramping on state-changed-event reports
+ * only trigger acc ramping if trx 0 is usable and unlocked
+ * fix handling of state changes in acc ramping
+ * properly skip paging is OML link is down
+ * extend documentation of paging_flush_bts()
+ * flush paging when RSL link is dropped
+
+ [ Vadim Yanitskiy ]
+ * bsc/gsm_04_80.h: use '#pragma once' instead of includes
+ * bsc/gsm_04_80.h: clean up useless declarations
+ * libbsc/bsc_vty.c: prevent uninitialized access
+ * doc/examples: use NECI = 1 by default
+
+ [ Ivan Kluchnikov ]
+ * handover_decision: Fix condition for power budget handover attempt
+ * handover_decision: log HO causes more accurately
+
+ [ Andreas Eversberg ]
+ * HO: Send Channel Mode and Multirate IE along with handover command
+ * HO: Add function to count currently ongoing handovers to a given BTS
+ * Fix: If paging for half rate was requested, use hr, if supported by MS
+ * HO: Assign SDCCH on channel request
+ * Fix of checking TCH rate at chan_compat_with_mode
+ * HO: Count the actual meas.rep. get_meas_rep_avg fails if not reached
+ * HO: Count neighbor measurements and reduce window of neigh_meas_avg
+ * HO: Changed availablilty of ts_is_usable() from static to extern
+ * HO: Always update rqd_ta after receiving measurement report
+ * HO: If handover logic is used to do assignment, signal assignment result
+ * HO: Add handover decision debugging category
+ * Do not perform assignment, if the new channel equals the current one
+ * Allow assignment to TCH channel with signalling only mode
+ * Correctly set T3105 for ipaccess BTS type
+ * HO: fix: increase the number of measurement report history to 10
+ * HO: Change debug category at handover decision: DHO -> DHODEC
+ * HO: Count number of free timeslot on a given BTS
+ * HO: add queue to cache DTAP messages during handover/assignment
+ * Fix: meas_rep.c will only use valid DL measurement reports
+ * HO: Add a penalty timer list to the subscriber connection entity
+
+ [ Keith ]
+ * Cosmetic: Fix typo: Siganlling->Signalling
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Thu, 03 May 2018 18:40:11 +0200
+
+osmo-bsc (1.1.2) unstable; urgency=medium
+
+ * Debian: depend on libosmo-sigtran (bsc) and libosmo-sccp (bsc-nat)
+ * debian/control: Specify versions of packages we depend upon
+
+ -- Harald Welte <laforge@gnumonks.org> Sun, 29 Oct 2017 09:03:33 +0100
+
+osmo-bsc (1.1.1) unstable; urgency=medium
+
+ [ Neels Hofmeyr ]
+ * jenkins: use osmo-clean-workspace.sh before and after build
+
+ [ Harald Welte ]
+ * Debian: re-introduce missing build dependency to libssl-dev
+
+ -- Harald Welte <laforge@gnumonks.org> Sat, 28 Oct 2017 21:49:00 +0200
+
+osmo-bsc (1.1.0) unstable; urgency=medium
+
+ [ Alexander Couzens ]
+ * Initial release.
+ * debian/rules: show testsuite.log when tests are failing
+
+ [ Neels Hofmeyr ]
+ * jenkins: fix build: osmo-mgw from master, not pre_release
+ * drop files unrelated to osmo-bsc
+ * rename openbsc.pc to osmo-bsc.pc
+ * rewrite README
+ * move include/openbsc to include/osmocom/bsc
+ * drop MGCP client from osmo-bsc
+ * fix vty tests: vty no longer goes to parent node implicitly
+ * doc/examples: tweak osmo-bsc.cfg, add osmo-bsc_custom-sccp.cfg
+ * add ';' after OSMO_ASSERT()
+
+ [ Harald Welte ]
+ * configure.ac: No more libosmogb dependency
+ * configure.ac: remove --enable-osmo-bsc, --enable-nat
+ * configure.ac: remove smpp_mirror, which has no relation to a BSC
+ * contrib/jenkins.sh: MGCP is unconditional now
+ * configure.ac: Remove --enable-mgcp-transcoding
+ * configure.ac: Remove --enable-iu
+ * configure.ac: Remove checks for libgtp + c-ares
+ * configure.ac: Remove check for GMTOFF
+ * configure.ac: Package is now called osmo-bsc, not openbsc
+ * libbsc: document arguments of generate_bcch_chan_list()
+ * Make sure BA-IND in all SI2xxx is '0' and in all SI5xxx is '1'
+ * gsm0408_test: Verify that BA-IND is 0 in SI2xxx and 1 in SI5xxx
+ * .gitignore: Update to post-NITB-split realities
+ * Remove any references to RANAP and Iu
+ * Fix nanobts_omlattr unit test
+ * nanobts_omlattra_test: Initialize logging before executing tests
+ * osmo-bsc: Initialize logging before initializing rate_ctr
+ * Rename osmo_fsm to avoid illegal space in name + more meaningful name
+
+ [ Max ]
+ * Make TRX rf locking more visible
+ * SI13: drop PBCCH-related bits
+ * Wrap channel state assignment in macro
+ * Further cleanup leftovers from BSC/MSC split
+ * CTRL: cleanup write-only command functions
+ * Show OML link uptime in vty
+ * Fix repo split aftermath
+ * SI2q: cleanup UARFCN addition
+ * OML: consider RSL link state
+ * SI2q: fix generation for multiple UARFCNs
+ * Remove pkg-config file
+ * ctrl: add oml-uptime command
+ * SI1q: fix EARFCN appender
+
+ [ Pau Espin Pedrol ]
+ * Remove unneeded dbi dependency
+ * bsc_api: Fix NULL secondary_lchan access in handle_ass_fail
+ * libbsc: Use correct printf formatting for uint64_t
+ * bsc_vty: Improve description of mid-call-text cmd
+
+ -- Harald Welte <laforge@gnumonks.org> Sat, 28 Oct 2017 11:19:03 +0200
+
+osmo-bsc (0.1.0) UNRELEASED; urgency=low
+
+ [ Alexander Couzens ]
+ * Initial release.
+
+ -- Alexander Couzens <lynxis@fe80.eu> Tue, 08 Aug 2017 01:12:56 +0000
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 000000000..ec635144f
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 000000000..141cb5e52
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,66 @@
+Source: osmo-bsc
+Section: net
+Priority: extra
+Maintainer: Alexander Couzens <lynxis@fe80.eu>
+Build-Depends: debhelper (>=9),
+ dh-autoreconf,
+ autotools-dev,
+ autoconf,
+ automake,
+ libtool,
+ pkg-config,
+ python-minimal,
+ libcdk5-dev,
+ libtalloc-dev,
+ libosmocore-dev (>= 0.12.0),
+ libosmo-sccp-dev (>= 0.10.0),
+ libosmo-sigtran-dev (>= 0.10.0),
+ libosmo-abis-dev (>= 0.5.1),
+ libosmo-netif-dev (>= 0.3.0),
+ libosmo-mgcp-client-dev (>= 1.4.0)
+Standards-Version: 3.9.8
+Vcs-Git: git://git.osmocom.org/osmo-bsc.git
+Vcs-Browser: https://git.osmocom.org/osmo-bsc/
+Homepage: https://projects.osmocom.org/projects/osmo-bsc
+
+Package: osmo-bsc
+Architecture: any
+Multi-Arch: foreign
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: OsmoBSC: Osmocom's Base Station Controller for 2G circuit-switched mobile networks
+
+Package: osmo-bsc-dbg
+Section: debug
+Architecture: any
+Multi-Arch: same
+Depends: osmo-bsc (= ${binary:Version}), ${misc:Depends}
+Description: OsmoBSC: Osmocom's Base Station Controller for 2G circuit-switched mobile networks
+
+Package: abisip-find
+Architecture: any
+Multi-Arch: foreign
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Command line utility to find ip.access compatible BTS
+
+Package: osmo-bsc-ipaccess-utils
+Architecture: any
+Multi-Arch: foreign
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Command line utilities for ip.access nanoBTS
+ This package contains utilities that are specific for nanoBTS when being used
+ together with OpenBSC. It contains mainly two tools: ipaccess-config and ipaccess-proxy.
+
+Package: osmo-bsc-bs11-utils
+Architecture: any
+Multi-Arch: foreign
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Command line utilities for Siemens BS-11 BTS
+ There is a tool in this package for configuring the Siemens BS-11 BTS.
+ Additionally, it contains one tool for making use of an ISDN-card and the
+ public telephone network as frequency standard for the E1 line.
+
+Package: osmo-bsc-meas-utils
+Architecture: any
+Multi-Arch: foreign
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Description: Command line utilities to manage measurement reports.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 000000000..c748589c8
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,148 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: osmo-bsc
+Source: git://git.osmocom.org/osmo-bsc
+
+Files: *
+Copyright: 2008-2015 Holger Hans Peter Freyther <zecke@selfish.org>
+ 2008-2016 Harald Welte <laforge@gnumonks.org>
+ 2009-2015 On-Waves
+ 2010-2011 Daniel Willmann <daniel@totalueberwachung.de>
+ 2011-2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ 2014-2015 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ 2008 Jan Luebbe <jluebbe@debian.org>
+ 2013 Andreas Eversberg <jolly@eversberg.eu>
+License: AGPL-3.0+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+ .
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Files: src/libbsc/bsc_ctrl_lookup.c
+ src/libbsc/pcu_sock.c
+Copyright: 2008-2010 Harald Welte <laforge@gnumonks.org>
+ 2009-2012 Andreas Eversberg <jolly@eversberg.eu>
+ 2010-2011 Daniel Willmann <daniel@totalueberwachung.de>
+ 2010-2011 On-Waves
+ 2012 Holger Hans Peter Freyther
+License: GPL-2.0+
+ 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.
+ .
+ On Debian systems, the complete text of the GNU General Public License
+ Version 2 can be found in `/usr/share/common-licenses/GPL-2'.
+
+Files: osmoappdesc.py
+Copyright: 2013 Katerina Barone-Adesi <kat.obsc@gmail.com>
+License: GPL-3.0+
+ 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 3 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+ .
+ Most systems won't be able to use these, so they're separated out
+ .
+ On Debian systems, the complete text of the GNU General Public License
+ Version 3 can be found in `/usr/share/common-licenses/GPL-3'.
+
+Files: tests/vty_test_runner.py
+Copyright: 2013 Holger Hans Peter Freyther
+ 2013 Katerina Barone-Adesi <kat.obsc@gmail.com>
+License: GPL-3.0+
+ 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 3 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General Public License
+ Version 3 can be found in `/usr/share/common-licenses/GPL-3'.
+
+Files: include/mISDNif.h
+Copyright: 2008 Karsten Keil <kkeil@novell.com>
+License: LGPL-2.1
+ This code is free software; you can redistribute it and/or modify
+ it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
+ version 2.1 as published by the Free Software Foundation.
+ .
+ This code 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 LESSER GENERAL PUBLIC LICENSE for more details.
+ .
+ On Debian systems, the complete text of the GNU Lesser General Public License
+ Version 2.1 can be found in `/usr/share/common-licenses/LGPL-2.1'.
+
+Files: include/openbsc/bsc_msc_data.h
+Copyright: 2010-2015 Holger Hans Peter Freyther <zecke@selfish.org>
+ 2010-2015 On-Waves
+License: AGPL-3.0+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+ .
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ NOTE: This is about a *remote* MSC for OsmoBSC and is not part of libmsc.
+
+Files: src/libbsc/bts_nokia_site.c
+Copyright: 2011 Dieter Spaar <spaar@mirider.augusta.de>
+License: AGPL-3.0+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+ .
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ 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.
+
diff --git a/debian/osmo-bsc-bs11-utils.install b/debian/osmo-bsc-bs11-utils.install
new file mode 100644
index 000000000..37cd20a15
--- /dev/null
+++ b/debian/osmo-bsc-bs11-utils.install
@@ -0,0 +1,2 @@
+usr/bin/bs11_config
+usr/bin/isdnsync
diff --git a/debian/osmo-bsc-ipaccess-utils.install b/debian/osmo-bsc-ipaccess-utils.install
new file mode 100644
index 000000000..ff82ff6f0
--- /dev/null
+++ b/debian/osmo-bsc-ipaccess-utils.install
@@ -0,0 +1,2 @@
+usr/bin/ipaccess-config
+usr/bin/ipaccess-proxy
diff --git a/debian/osmo-bsc-meas-utils.install b/debian/osmo-bsc-meas-utils.install
new file mode 100644
index 000000000..94a03d0d5
--- /dev/null
+++ b/debian/osmo-bsc-meas-utils.install
@@ -0,0 +1,2 @@
+usr/bin/meas_json
+usr/bin/meas_vis
diff --git a/debian/osmo-bsc.install b/debian/osmo-bsc.install
new file mode 100644
index 000000000..8f91b03bc
--- /dev/null
+++ b/debian/osmo-bsc.install
@@ -0,0 +1,5 @@
+/etc/osmocom/osmo-bsc.cfg
+lib/systemd/system/osmo-bsc.service
+usr/bin/osmo-bsc
+usr/share/doc/osmo-bsc/examples/osmo-bsc/osmo-bsc_custom-sccp.cfg usr/share/doc/osmo-bsc/examples
+usr/share/doc/osmo-bsc/examples/osmo-bsc/osmo-bsc.cfg usr/share/doc/osmo-bsc/examples
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 000000000..ffc99db85
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,62 @@
+#!/usr/bin/make -f
+# You must remove unused comment lines for the released package.
+# See debhelper(7) (uncomment to enable)
+# This is an autogenerated template for debian/rules.
+#
+# Output every command that modifies files on the build system.
+#export DH_VERBOSE = 1
+#
+# Copy some variable definitions from pkg-info.mk and vendor.mk
+# under /usr/share/dpkg/ to here if they are useful.
+#
+# See FEATURE AREAS/ENVIRONMENT in dpkg-buildflags(1)
+# Apply all hardening options
+#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+# Package maintainers to append CFLAGS
+#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
+# Package maintainers to append LDFLAGS
+#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
+#
+# With debhelper version 9 or newer, the dh command exports
+# all buildflags. So there is no need to include the
+# /usr/share/dpkg/buildflags.mk file here if compat is 9 or newer.
+#
+# These are rarely used code. (START)
+#
+# The following include for *.mk magically sets miscellaneous
+# variables while honoring existing values of pertinent
+# environment variables:
+#
+# Architecture-related variables such as DEB_TARGET_MULTIARCH:
+#include /usr/share/dpkg/architecture.mk
+# Vendor-related variables such as DEB_VENDOR:
+#include /usr/share/dpkg/vendor.mk
+# Package-related variables such as DEB_DISTRIBUTION
+#include /usr/share/dpkg/pkg-info.mk
+#
+# You may alternatively set them susing a simple script such as:
+# DEB_VENDOR ?= $(shell dpkg-vendor --query Vendor)
+#
+# These are rarely used code. (END)
+#
+
+# main packaging script based on dh7 syntax
+%:
+ dh $@ --with autoreconf
+
+# debmake generated override targets
+CONFIGURE_FLAGS += --with-systemdsystemunitdir=/lib/systemd/system
+override_dh_auto_configure:
+ dh_auto_configure -- $(CONFIGURE_FLAGS)
+#
+# Do not install libtool archive, python .pyc .pyo
+#override_dh_install:
+# dh_install --list-missing -X.la -X.pyc -X.pyo
+
+# See https://www.debian.org/doc/manuals/developers-reference/best-pkging-practices.html#bpp-dbg
+override_dh_strip:
+ dh_strip -posmo-bsc --dbg-package=osmo-bsc-dbg
+
+# Print test results in case of a failure
+override_dh_auto_test:
+ dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false)
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 000000000..89ae9db8f
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/doc/BS11-OML.txt b/doc/BS11-OML.txt
new file mode 100644
index 000000000..e5c3299c9
--- /dev/null
+++ b/doc/BS11-OML.txt
@@ -0,0 +1,31 @@
+The Siemens BS-11 supports the following additional GSM 12.21 OML operations:
+
+
+CREATE OBJECT
+
+abis_om_fom_hdr.obj_class can be
+A3:
+A5: ALCO, BBSIG, CCLK, GPSU, LI, PA
+A8: EnvaBTSE
+A9: BPORT
+
+the abis_om_obj_inst.trx_nr field indicates the index of object, whereas the
+abis_om_fom_hdr.bts_nr indicates the type of the object.
+
+enum abis_bs11_objtype {
+ BS11_OBJ_ALCO = 0x01,
+ BS11_OBJ_BBSIG = 0x02, /* obj_class: 0,1 */
+ BS11_OBJ_TRX1 = 0x03, /* only DEACTIVATE TRX1 */
+ BS11_OBJ_CCLK = 0x04,
+ BS11_OBJ_GPSU = 0x06,
+ BS11_OBJ_LI = 0x07,
+ BS11_OBJ_PA = 0x09, /* obj_class: 0, 1*/
+};
+
+In case of CREATE ENVABTSE, the abis_om_obj_inst.trx_nr indicates the EnvaBTSEx
+number.
+
+In case of A9 (CREAETE BPORT), the abis_om_obj_inst.bts_nr indicates which BPORT
+shall be used.
+
+
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 000000000..9b87bf339
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,37 @@
+SUBDIRS = \
+ examples \
+ $(NULL)
+
+msc: \
+ $(builddir)/legend_for_ladder_diagrams.png \
+ $(builddir)/handover.png \
+ $(builddir)/assignment.png \
+ $(builddir)/timeslot.png \
+ $(builddir)/lchan.png \
+ $(builddir)/ts-and-lchan-fsm-lifecycle.png \
+ $(builddir)/handover-inter-bsc-out.png \
+ $(builddir)/handover-inter-bsc-in.png \
+ $(builddir)/mgw-endpoint.png \
+ $(NULL)
+
+dot: \
+ $(builddir)/legend_for_fsm_diagrams.png \
+ $(builddir)/assignment-fsm.png \
+ $(builddir)/timeslot-fsm.png \
+ $(builddir)/lchan-fsm.png \
+ $(builddir)/lchan-rtp-fsm.png \
+ $(builddir)/mgw-endpoint-fsm.png \
+ $(builddir)/handover-intra-bsc-fsm.png \
+ $(builddir)/handover-inter-bsc-out-fsm.png \
+ $(builddir)/handover-inter-bsc-in-fsm.png \
+ $(NULL)
+
+$(builddir)/%.png: $(srcdir)/%.msc
+ mscgen -T png -o $@ $<
+
+$(builddir)/%.png: $(srcdir)/%.dot
+ dot -Tpng $< > $@
+
+.PHONY: poll
+poll:
+ while true; do $(MAKE) msc dot; sleep 1; done
diff --git a/doc/assignment-fsm.dot b/doc/assignment-fsm.dot
new file mode 100644
index 000000000..5a3a2b91f
--- /dev/null
+++ b/doc/assignment-fsm.dot
@@ -0,0 +1,42 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="Assignment FSM"
+
+ WAIT_LCHAN_ACTIVE
+ WAIT_RR_ASS_COMPLETE
+ WAIT_LCHAN_ESTABLISHED
+ WAIT_MGW_ENDPOINT_TO_MSC
+ terminate [shape=octagon]
+
+ gscon [label="conn FSM",shape=box3d]
+ gscon2 [label="conn FSM",shape=box3d]
+ lchan [label="lchan FSM\n(new lchan)",shape=box3d]
+ old_lchan [label="old lchan",shape=box3d]
+
+ bssap [label="osmo_bsc_bssap.c",shape=box]
+
+ invisible [style="invisible"]
+ invisible -> bssap [label="BSSMAP Assignment Request",style=dotted]
+ invisible -> old_lchan [style=invisible,arrowhead=none]
+
+ bssap -> gscon [label="GSCON_EV_ASSIGNMENT_START\ndata=struct assignment_request",style=dotted]
+
+ gscon -> WAIT_LCHAN_ACTIVE [label="assignment_fsm_start()",style=dotted]
+ WAIT_LCHAN_ACTIVE -> lchan [label="lchan_activate()\nFOR_ASSIGNMENT",style=dotted]
+ lchan -> WAIT_LCHAN_ACTIVE [label="ASSIGNMENT_EV_\nLCHAN_\nACTIVE,ERROR",style=dotted]
+ lchan -> WAIT_LCHAN_ESTABLISHED [label="ASSIGNMENT_EV_\nLCHAN_\nESTABLISHED,ERROR",style=dotted]
+
+ WAIT_LCHAN_ACTIVE -> WAIT_RR_ASS_COMPLETE
+
+ WAIT_RR_ASS_COMPLETE -> old_lchan [label="RR Assignment\nCommand",style=dotted,constraint=false]
+ lchan -> WAIT_RR_ASS_COMPLETE [label="RR Assignment\nComplete",style=dotted]
+
+ WAIT_RR_ASS_COMPLETE -> WAIT_LCHAN_ESTABLISHED
+
+ WAIT_LCHAN_ESTABLISHED -> WAIT_MGW_ENDPOINT_TO_MSC [label="TCH"]
+ WAIT_LCHAN_ESTABLISHED -> terminate [label="non-TCH"]
+ WAIT_MGW_ENDPOINT_TO_MSC -> terminate
+ WAIT_MGW_ENDPOINT_TO_MSC -> gscon2 [label="gscon_connect_\nmgw_to_msc()",style=dotted]
+ gscon2 -> WAIT_MGW_ENDPOINT_TO_MSC [label="ASSIGNMENT_EV_\nMSC_MGW_OK",style=dotted]
+ terminate -> gscon2 [label="GSCON_EV_\nASSIGNMENT_END",style=dotted]
+}
diff --git a/doc/assignment.msc b/doc/assignment.msc
new file mode 100644
index 000000000..4e690a811
--- /dev/null
+++ b/doc/assignment.msc
@@ -0,0 +1,72 @@
+msc {
+ hscale=2;
+ ms [label="MS/BTS"], lchan[label="BSC lchan FSM"], ass[label="BSC Assignment FSM"],
+ gscon[label="BSC conn FSM"], msc_[label="MSC"];
+
+ ms note msc_ [label="lchan allocation sequence for BSSMAP Assignment Request"];
+
+ gscon <= msc_ [label="BSSMAP Assignment Request"];
+ gscon note gscon [label="GSCON_EV_ASSIGNMENT_START\n data=struct assignment_request"];
+ gscon abox gscon [label="ST_ASSIGNMENT"];
+ ass <- gscon [label="assignment_fsm_start()"];
+ ass abox ass [label="ASSIGNMENT_ST_\nWAIT_LCHAN_ACTIVE"];
+
+ |||;
+ --- [label="On any error (no lchan, etc.)"];
+ ass box ass [label="on_assignment_failure()"];
+ ass => msc_ [label="BSSMAP Assignment Failure"];
+ ass abox ass [label="terminate"];
+ ass -> gscon [label="GSCON_EV_ASSIGNMENT_END"];
+ gscon abox gscon [label="ST_ACTIVE"];
+ ---;
+ |||;
+ lchan abox lchan [label="UNUSED"];
+ ass box ass [label="conn->assignment.new_lchan = lchan_select_by_chan_mode()"];
+ lchan <- ass [label="lchan_activate(FOR_ASSIGNMENT)"];
+ lchan abox lchan [label="WAIT_TS_READY"];
+ lchan rbox lchan [label="most details omitted, see lchan_fsm and lchan_rtp_fsm diagrams"];
+ ...;
+ |||;
+ --- [label="on lchan FSM error or timeout"];
+ lchan -> ass [label="ASSIGNMENT_EV_LCHAN_ERROR"];
+ ass box ass [label="on_assignment_failure()"];
+ ass rbox gscon [label="See 'On any error' above"];
+ --- [label="END: 'on error'"];
+ ...;
+ ...;
+
+ lchan abox lchan [label="LCHAN_ST_WAIT_ACTIV_ACK"];
+ ms <= lchan [label="RSL Chan Activ"];
+ ...;
+ ms => lchan [label="RSL Chan Activ ACK"];
+ lchan -> ass [label="ASSIGNMENT_EV_LCHAN_ACTIVE"];
+ ass abox ass [label="ASSIGNMENT_ST_\nWAIT_RR_ASS_COMPLETE"];
+ ms <= ass [label="RR Assignment Command"];
+ lchan note ass [label="The lchan FSM will continue with RSL and RTP while the Assignment FSM waits.
+ ASSIGNMENT_EV_LCHAN_ESTABLISHED means that both RSL and RTP are established.
+ Usually, RTP will be done first, and the ASSIGNMENT_EV_LCHAN_ESTABLISHED may be
+ received even before ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE.
+ assignment_fsm_wait_lchan_established_onenter() decides whether to wait or not."];
+ ...;
+ ms => lchan [label="RSL EST IND"];
+ lchan -> ass [label="ASSIGNMENT_EV_LCHAN_ESTABLISHED",ID="(may come as early as this, or...)"];
+ ms => ass [label="RR Assignment Complete (came with EST IND)"];
+ ass abox ass [label="ASSIGNMENT_ST_\nWAIT_LCHAN_ESTABLISHED"];
+ --- [label="IF lchan is not in LCHAN_ST_ESTABLISHED yet (waiting for RTP)"];
+ ...;
+ lchan rbox lchan [label="when lchan RTP FSM is done with setting up RTP"];
+ lchan -> ass [label="ASSIGNMENT_EV_LCHAN_ESTABLISHED",ID="(...may come only now)"];
+ ---;
+ ass abox ass [label="ASSIGNMENT_ST_WAIT_\nMGW_ENDPOINT_TO_MSC"];
+ ass -> gscon [label="gscon_connect_mgw_to_msc()"];
+ ...;
+ ass <- gscon [label="ASSIGNMENT_EV_MSC_MGW_OK"];
+ ass box ass [label="assignment_success()"];
+ ass => msc_ [label="BSSMAP Assignment Complete"];
+ ass -> gscon [label="gscon_change_primary_lchan()"];
+ lchan <- gscon [label="LCHAN_RTP_EV_ESTABLISHED"];
+ ass abox ass [label="terminate"];
+ ass -> gscon [label="GSCON_EV_ASSIGNMENT_END"];
+ gscon abox gscon [label="ST_ACTIVE"];
+
+}
diff --git a/doc/e1-data-model.txt b/doc/e1-data-model.txt
new file mode 100644
index 000000000..509004fe9
--- /dev/null
+++ b/doc/e1-data-model.txt
@@ -0,0 +1,172 @@
+E1 related data model
+
+This data model describes the physical relationship of the individual
+parts in the network, it is not the logical/protocol side of the GSM
+network.
+
+A BTS is connected to the BSC by some physical link. It could be an actual
+E1 link, but it could also be abis-over-IP with a mixture of TCP and RTP/UDP.
+
+To further complicate the fact, multiple BTS can share one such pysical
+link. On a single E1 line, we can easily accomodate up to three BTS with
+two TRX each.
+
+Thus, it is best for OpenBSC to have some kind of abstraction layer. The BSC's
+view of a BTS connected to it. We call this 'bts_link'. A bts_link can be
+* all the TCP and UDP streams of a Abis-over-IP BTS
+* a set of E1 timeslots for OML, RSL and TRAU connections on a E1 link
+* a serial line exclusively used for OML messages (T-Link)
+
+A bts_link can be registered with the OpenBSC core at runtime.
+
+struct trx_link {
+ struct gsm_bts_trx *trx;
+};
+
+struct bts_link {
+ struct gsm_bts *bts;
+ struct trx_link trx_links[NUM_TRX];
+};
+
+Interface from stack to input core:
+======================================================================
+int abis_rsl_sendmsg(struct msgb *msg);
+ send a message through a RSL link to the TRX specified by the caller in
+ msg->trx.
+
+int abis_rsl_rcvmsg(struct msgb *msg);
+ receive a message from a RSL link from the TRX specified by the
+ caller in msg->trx.
+
+int abis_nm_sendmsg(struct msgb *msg);
+ send a message through a OML link to the BTS specified by the caller in
+ msg->trx->bts. The caller can just use bts->c0 to get the first TRX
+ in a BTS. (OML messages are not really sent to a TRX but to the BTS)
+
+int abis_nm_rcvmsg(struct msgb *msg);
+ receive a message from a OML link from the BTS specified by the caller
+ in msg->trx->bts. The caller can just use bts->c0 to get the first
+ TRX in a BTS.
+
+int abis_link_event(int event, void *data);
+ signal some event (such as layer 1 connect/disconnect) from the
+ input core to the stack.
+
+int subch_demux_in(mx, const uint8_t *data, int len);
+ receive 'len' bytes from a given E1 timeslot (TRAU frames)
+
+int subchan_mux_out(mx, uint8_t *data, int len);
+ obtain 'len' bytes of output data to be sent on E1 timeslot
+
+Intrface by Input Core for Input Plugins
+======================================================================
+
+int btslink_register_plugin();
+
+
+Configuration for the E1 input module
+======================================================================
+
+BTS
+ BTS number
+ number of TRX
+ OML link
+ E1 line number
+ timeslot number
+ [subslot number]
+ SAPI
+ TEI
+ for each TRX
+ RSL link
+ E1 line number
+ timeslot number
+ [subslot number]
+ SAPI
+ TEI
+ for each TS
+ E1 line number
+ timeslot number
+ subslot number
+
+
+E1 input module data model
+======================================================================
+
+
+enum e1inp_sign_type {
+ E1INP_SIGN_NONE,
+ E1INP_SIGN_OML,
+ E1INP_SIGN_RSL,
+};
+
+struct e1inp_sign_link {
+ /* list of signalling links */
+ struct llist_head list;
+
+ enum e1inp_sign_type type;
+
+ /* trx for msg->trx of received msgs */
+ struct gsm_bts_trx *trx;
+
+ /* msgb queue of to-be-transmitted msgs */
+ struct llist_head tx_list;
+
+ /* SAPI and TEI on the E1 TS */
+ uint8_t sapi;
+ uint8_t tei;
+}
+
+enum e1inp_ts_type {
+ E1INP_TS_TYPE_NONE,
+ E1INP_TS_TYPE_SIGN,
+ E1INP_TS_TYPE_TRAU,
+};
+
+/* A timeslot in the E1 interface */
+struct e1inp_ts {
+ enum e1inp_ts_type type;
+ struct e1inp_line *line;
+ union {
+ struct {
+ struct llist_head sign_links;
+ } sign;
+ struct {
+ /* subchannel demuxer for frames from E1 */
+ struct subch_demux demux;
+ /* subchannel muxer for frames to E1 */
+ struct subch_mux mux;
+ } trau;
+ };
+ union {
+ struct {
+ /* mISDN driver has one fd for each ts */
+ struct osmo_fd;
+ } misdn;
+ } driver;
+};
+
+struct e1inp_line {
+ unsigned int num;
+ char *name;
+
+ struct e1inp_ts ts[NR_E1_TS];
+
+ char *e1inp_driver;
+ void *driver_data;
+};
+
+/* Call from the Stack: configuration of this TS has changed */
+int e1inp_update_ts(struct e1inp_ts *ts);
+
+/* Receive a packet from the E1 driver */
+int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
+ uint8_t tei, uint8_t sapi);
+
+/* Send a packet, callback function in the driver */
+int e1driver_tx_ts(struct e1inp_ts *ts, struct msgb *msg)
+
+
+struct e1inp_driver {
+ const char *name;
+ int (*want_write)(struct e1inp_ts *ts);
+};
diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am
new file mode 100644
index 000000000..9d8cd75d6
--- /dev/null
+++ b/doc/examples/Makefile.am
@@ -0,0 +1,30 @@
+OSMOCONF_FILES = \
+ osmo-bsc/osmo-bsc.cfg
+
+osmoconfdir = $(sysconfdir)/osmocom
+osmoconf_DATA = $(OSMOCONF_FILES)
+
+EXTRA_DIST = $(OSMOCONF_FILES)
+
+CFG_FILES = find $(srcdir) -name '*.cfg*' | sed -e 's,^$(srcdir),,'
+
+dist-hook:
+ for f in $$($(CFG_FILES)); do \
+ j="$(distdir)/$$f" && \
+ mkdir -p "$$(dirname $$j)" && \
+ $(INSTALL_DATA) $(srcdir)/$$f $$j; \
+ done
+
+install-data-hook:
+ for f in $$($(CFG_FILES)); do \
+ j="$(DESTDIR)$(docdir)/examples/$$f" && \
+ mkdir -p "$$(dirname $$j)" && \
+ $(INSTALL_DATA) $(srcdir)/$$f $$j; \
+ done
+
+uninstall-hook:
+ @$(PRE_UNINSTALL)
+ for f in $$($(CFG_FILES)); do \
+ j="$(DESTDIR)$(docdir)/examples/$$f" && \
+ $(RM) $$j; \
+ done
diff --git a/doc/examples/osmo-bsc/osmo-bsc-minimal.cfg b/doc/examples/osmo-bsc/osmo-bsc-minimal.cfg
new file mode 100644
index 000000000..850e29c84
--- /dev/null
+++ b/doc/examples/osmo-bsc/osmo-bsc-minimal.cfg
@@ -0,0 +1,33 @@
+network
+ network country code 901
+ mobile network code 70
+ bts 0
+ type sysmobts
+ band GSM-1800
+ location_area_code 23
+ ip.access unit_id 1800 0
+ trx 0
+ rf_locked 0
+ arfcn 868
+ nominal power 23
+ timeslot 0
+ phys_chan_config CCCH+SDCCH4
+ timeslot 1
+ phys_chan_config SDCCH8
+ timeslot 2
+ phys_chan_config TCH/F
+ timeslot 3
+ phys_chan_config TCH/F
+ timeslot 4
+ phys_chan_config TCH/F
+ timeslot 5
+ phys_chan_config TCH/F
+ timeslot 6
+ phys_chan_config TCH/F
+ timeslot 7
+ phys_chan_config TCH/F
+e1_input
+ e1_line 0 driver ipa
+msc 0
+ allow-emergency deny
+ codec-list hr3
diff --git a/doc/examples/osmo-bsc/osmo-bsc.cfg b/doc/examples/osmo-bsc/osmo-bsc.cfg
new file mode 100644
index 000000000..eec154db7
--- /dev/null
+++ b/doc/examples/osmo-bsc/osmo-bsc.cfg
@@ -0,0 +1,94 @@
+! osmo-bsc default configuration
+! (assumes STP to run on 127.0.0.1 and uses default point codes)
+!
+e1_input
+ e1_line 0 driver ipa
+network
+ network country code 1
+ mobile network code 1
+ encryption a5 0
+ neci 1
+ paging any use tch 0
+ handover 0
+ handover algorithm 1
+ handover1 window rxlev averaging 10
+ handover1 window rxqual averaging 1
+ handover1 window rxlev neighbor averaging 10
+ handover1 power budget interval 6
+ handover1 power budget hysteresis 3
+ handover1 maximum distance 9999
+ dyn_ts_allow_tch_f 0
+ periodic location update 30
+ bts 0
+ type sysmobts
+ band DCS1800
+ cell_identity 0
+ location_area_code 1
+ base_station_id_code 63
+ ms max power 15
+ cell reselection hysteresis 4
+ rxlev access min 0
+ radio-link-timeout 32
+ channel allocator ascending
+ rach tx integer 9
+ rach max transmission 7
+ channel-descrption attach 1
+ channel-descrption bs-pa-mfrms 5
+ channel-descrption bs-ag-blks-res 1
+ early-classmark-sending forbidden
+ ip.access unit_id 0 0
+ oml ip.access stream_id 255 line 0
+ codec-support fr
+ gprs mode none
+ trx 0
+ rf_locked 0
+ arfcn 871
+ nominal power 23
+ ! to use full TRX power, set max_power_red 0
+ max_power_red 20
+ rsl e1 tei 0
+ timeslot 0
+ phys_chan_config CCCH+SDCCH4
+ hopping enabled 0
+ timeslot 1
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 2
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 3
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 4
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 5
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 6
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 7
+ phys_chan_config TCH/F
+ hopping enabled 0
+msc 0
+ no bsc-welcome-text
+ no bsc-msc-lost-text
+ no bsc-grace-text
+ type normal
+ allow-emergency allow
+ amr-config 12_2k forbidden
+ amr-config 10_2k forbidden
+ amr-config 7_95k forbidden
+ amr-config 7_40k forbidden
+ amr-config 6_70k forbidden
+ amr-config 5_90k allowed
+ amr-config 5_15k forbidden
+ amr-config 4_75k forbidden
+ mgw remote-ip 127.0.0.1
+ mgw remote-port 2427
+ mgw local-port 2727
+ mgw endpoint-range 1 31
+bsc
+ mid-call-timeout 0
+ no missing-msc-text
diff --git a/doc/examples/osmo-bsc/osmo-bsc_custom-sccp.cfg b/doc/examples/osmo-bsc/osmo-bsc_custom-sccp.cfg
new file mode 100644
index 000000000..fd3a34950
--- /dev/null
+++ b/doc/examples/osmo-bsc/osmo-bsc_custom-sccp.cfg
@@ -0,0 +1,80 @@
+! osmo-bsc configuration example for custom SCCP addresses
+!
+e1_input
+ e1_line 0 driver ipa
+network
+ network country code 1
+ mobile network code 1
+ encryption a5 0
+ neci 1
+ paging any use tch 0
+ handover 0
+ handover algorithm 1
+ handover1 window rxlev averaging 10
+ handover1 window rxqual averaging 1
+ handover1 window rxlev neighbor averaging 10
+ handover1 power budget interval 6
+ handover1 power budget hysteresis 3
+ handover1 maximum distance 9999
+ dyn_ts_allow_tch_f 0
+ periodic location update 30
+ bts 0
+ type sysmobts
+ band DCS1800
+ cell_identity 0
+ location_area_code 1
+ base_station_id_code 63
+ ms max power 15
+ cell reselection hysteresis 4
+ rxlev access min 0
+ radio-link-timeout 32
+ channel allocator ascending
+ rach tx integer 9
+ rach max transmission 7
+ channel-descrption attach 1
+ channel-descrption bs-pa-mfrms 5
+ channel-descrption bs-ag-blks-res 1
+ early-classmark-sending forbidden
+ ip.access unit_id 0 0
+ oml ip.access stream_id 255 line 0
+ codec-support fr
+ gprs mode none
+ trx 0
+ rf_locked 0
+ arfcn 871
+ nominal power 23
+ ! to use full TRX power, set max_power_red 0
+ max_power_red 20
+ rsl e1 tei 0
+ timeslot 0
+ phys_chan_config CCCH+SDCCH4
+ hopping enabled 0
+ timeslot 1
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 2
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 3
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 4
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 5
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 6
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 7
+ phys_chan_config TCH/F
+ hopping enabled 0
+cs7 instance 0
+ point-code 0.42.1
+ !asp asp-clnt-OsmoBSC 2905 0 m3ua
+ ! remote-ip 10.23.24.1 ! where to reach the STP
+ sccp-address msc_remote
+ point-code 0.23.1
+msc 0
+ msc-addr msc_remote
diff --git a/doc/handover-inter-bsc-in-fsm.dot b/doc/handover-inter-bsc-in-fsm.dot
new file mode 100644
index 000000000..b52a16d6c
--- /dev/null
+++ b/doc/handover-inter-bsc-in-fsm.dot
@@ -0,0 +1,42 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="Handover FSM: Inter-BSC Incoming"
+
+ old_bss [label="old BSS",shape=box3d]
+ msc [label="MSC",shape=box3d]
+ ho_in [label="inter-BSC HO Incoming",shape=box]
+ gscon [label="gscon FSM",shape=box3d]
+ lchan [label="lchan FSM",shape=box3d]
+ msc2 [label="msc",shape=box3d]
+ old_bsc2 [label="old BSC",shape=box3d]
+ old_lchan [label="old lchan",shape=box3d]
+ terminate [shape=octagon]
+
+ old_bss -> msc [label="BSSMAP Handover Required",style=dotted]
+ msc -> ho_in [label="BSSMAP Handover Request",style=dotted]
+ ho_in -> gscon [label="GSCON_EV_A_CONN_IND\nBSSMAP Handover Request",style=dotted]
+ gscon -> HO_ST_WAIT_LCHAN_ACTIVE [label="handover_start_inter_bsc_in()",style=dotted]
+ HO_ST_WAIT_LCHAN_ACTIVE -> lchan [label="lchan_activate()\nFOR_HANDOVER",style=dotted]
+ lchan -> HO_ST_WAIT_LCHAN_ACTIVE [label="HO_EV_\nLCHAN_ACTIVE,\n_ERROR",style=dotted,constraint=false]
+ HO_ST_WAIT_LCHAN_ACTIVE -> HO_ST_WAIT_RR_HO_DETECT
+
+ HO_ST_WAIT_RR_HO_DETECT -> msc2 [label="BSSMAP\nHandover\nAccept\nwith\nRR Handover\nCommand",style=dotted]
+ msc2 -> old_bsc2 -> old_lchan [label="RR Handover\nCommand",style=dotted]
+ old_lchan -> lchan [label="MS moves",style=dotted,constraint=false]
+
+ lchan -> HO_ST_WAIT_RR_HO_DETECT [label="RR Handover\nDetect",style=dotted]
+ HO_ST_WAIT_RR_HO_DETECT -> WAIT_RR_HO_COMPLETE
+
+ lchan -> WAIT_RR_HO_COMPLETE [label="RR Handover\nComplete",style=dotted]
+ WAIT_RR_HO_COMPLETE -> WAIT_LCHAN_ESTABLISHED
+ lchan -> WAIT_LCHAN_ESTABLISHED [label="HO_EV_LCHAN_\nESTABLISHED",style=dotted]
+
+ WAIT_LCHAN_ESTABLISHED -> terminate [label="non-TCH"]
+ WAIT_LCHAN_ESTABLISHED -> WAIT_MGW_ENDPOINT_TO_MSC
+ WAIT_MGW_ENDPOINT_TO_MSC -> terminate [label="handover_end()"]
+ terminate -> msc2 [label="BSSMAP Handover\nComplete\n/ Failure",style=dotted,constraint=false]
+
+ err [label="on error",shape=box,style=dashed]
+ err -> terminate [style=dashed]
+
+}
diff --git a/doc/handover-inter-bsc-in.msc b/doc/handover-inter-bsc-in.msc
new file mode 100644
index 000000000..9534f908a
--- /dev/null
+++ b/doc/handover-inter-bsc-in.msc
@@ -0,0 +1,74 @@
+msc {
+ hscale=2;
+ ms [label="MS/BTS"], lchan[label="BSC lchan FSM"], ho[label="BSC Handover FSM"],
+ gscon[label="BSC conn FSM"], msc_[label="MSC"];
+
+ ms note msc_ [label="inter-BSC Handover Incoming"];
+
+ gscon <= msc_ [label="N-Connect: BSSMAP Handover Request"];
+ gscon box gscon [label="bsc_subscr_con_allocate()"];
+ gscon abox gscon [label="ST_INIT"];
+ gscon -> gscon [label="GSCON_EV_A_CONN_IND"];
+ ho <- gscon [label="handover_start_inter_bsc_in()"];
+
+ ho abox ho [label="allocate\nHO_ST_NOT_STARTED"];
+ ho box ho [label="lchan_select_by_chan_mode()"];
+ ho abox ho [label="HO_ST_WAIT_\nLCHAN_ACTIVE"];
+ lchan <- ho [label="lchan_activate(FOR_HANDOVER)"];
+ lchan rbox lchan [label="(most details omitted, see lchan_fsm diagrams)"];
+
+ ...;
+ ...;
+ --- [label="On any error or timeout"];
+ ho box ho [label="handover_end(fail)"];
+ ho -> gscon [label="GSCON_EV_HANDOVER_END"];
+ gscon note msc_ [label="There is no specific BSSMAP Handover Request NACK message."];
+ gscon => msc_ [label="BSSMAP Clear Request"];
+ gscon abox gscon [label="ST_CLEARING"];
+ gscon rbox msc_ [label="the usual disconnect dance"];
+ --- [label="END: 'On any error or timeout'"];
+ ...;
+ ...;
+
+ lchan abox lchan [label="LCHAN_ST_WAIT_\nACTIV_ACK"];
+ ms <= lchan [label="RSL Chan Activ"];
+ ...;
+ ms => lchan [label="RSL Chan Activ ACK"];
+ lchan -> ho [label="HO_EV_LCHAN_ACTIVE"];
+ ho abox ho [label="HO_ST_WAIT_\nRR_HO_DETECT"];
+ ho => msc_ [label="BSSMAP Handover Request Acknowledge\nwith RR Handover Command"];
+
+ ...;
+
+ ms => ho [label="RR Handover Detect\nHO_EV_RR_HO_DETECT"];
+ ho => msc_ [label="BSSMAP Handover Detect"];
+ ho abox ho [label="HO_ST_WAIT_\nRR_HO_COMPLETE"];
+
+ ...;
+ lchan note ho [label="The lchan FSM will continue with RSL and RTP while the HO FSM waits.
+ HO_EV_LCHAN_ESTABLISHED means that both RSL and RTP are established.
+ Usually, RTP will be done first, and the HO_EV_LCHAN_ESTABLISHED may be
+ received even before HO_EV_RR_HO_COMPLETE.
+ ho_fsm_wait_lchan_established_onenter() decides whether to wait or not."];
+ ...;
+ ms => lchan [label="RSL EST IND"];
+ lchan -> ho [label="HO_EV_LCHAN_ESTABLISHED",ID="(may come as early as this, or...)"];
+ ms => ho [label="RR Handover Complete (from EST IND)\n HO_EV_RR_HO_COMPLETE"];
+ ho abox ho [label="HO_ST_WAIT_\nLCHAN_ESTABLISHED"];
+ ...;
+ lchan rbox lchan [label="when lchan FSM is done with setting up RTP"];
+ lchan -> ho [label="HO_EV_LCHAN_ESTABLISHED",ID="(...may come only now)"];
+ ho abox ho [label="HO_ST_WAIT_\nMGW_ENDPOINT_TO_MSC"];
+ ho -> gscon [label="gscon_connect_mgw_to_msc()"];
+ ...;
+ ho <- gscon [label="HO_EV_MSC_MGW_OK"];
+ ho box ho [label="handover_end(OK)"];
+ ho => msc_ [label="BSSMAP Handover Complete"];
+
+ ho -> gscon [label="gscon_change_primary_lchan()"];
+ lchan <- gscon [label="LCHAN_RTP_EV_ESTABLISHED"];
+ ho -> gscon [label="GSCON_EV_HANDOVER_END"];
+ gscon abox gscon [label="ST_ACTIVE"];
+ ho box ho [label="detach from parent to not fire another meaningless GSCON_EV_HANDOVER_END"];
+ ho abox ho [label="terminate"];
+}
diff --git a/doc/handover-inter-bsc-out-fsm.dot b/doc/handover-inter-bsc-out-fsm.dot
new file mode 100644
index 000000000..9661b6f80
--- /dev/null
+++ b/doc/handover-inter-bsc-out-fsm.dot
@@ -0,0 +1,27 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="Handover FSM: Inter-BSC Outgoing"
+
+ invisible [style=invisible]
+ invisible -> ho_out [label="Measurement Report\nincluding neighbor\nBSS ARFCN",style=dotted]
+ ho_out [label="inter-BSC HO Outgoing",shape=box]
+ msc [label="MSC",shape=box3d]
+ new_bsc [label="new BSC",shape=box3d]
+ lchan [label="lchan",shape=box3d]
+ terminate [shape=octagon]
+
+ ho_out -> HO_OUT_ST_WAIT_HO_COMMAND [label="handover_start()"]
+ HO_OUT_ST_WAIT_HO_COMMAND -> msc [label="BSSMAP Handover\nRequired",style=dotted]
+ msc -> new_bsc [label="BSSMAP Handover\nRequest",style=dotted]
+ new_bsc -> msc [label="BSSMAP Handover\nRequest Ack",style=dotted]
+ msc -> HO_OUT_ST_WAIT_HO_COMMAND [label="BSSMAP Handover\nCommand",style=dotted]
+
+ HO_OUT_ST_WAIT_HO_COMMAND -> lchan [label="RR Handover\nCommand\nfrom new BSC",style=dotted]
+
+ HO_OUT_ST_WAIT_HO_COMMAND -> HO_OUT_ST_WAIT_CLEAR
+ msc -> HO_OUT_ST_WAIT_CLEAR [label="BSSMAP\nClear\nCommand",style=dotted]
+
+ HO_OUT_ST_WAIT_CLEAR -> terminate
+
+
+}
diff --git a/doc/handover-inter-bsc-out.msc b/doc/handover-inter-bsc-out.msc
new file mode 100644
index 000000000..733b4dab3
--- /dev/null
+++ b/doc/handover-inter-bsc-out.msc
@@ -0,0 +1,47 @@
+msc {
+ hscale=2;
+ ms [label="MS/BTS"], ho[label="BSC Handover FSM"], gscon[label="BSC conn FSM"], msc_[label="MSC"];
+
+ ms note msc_ [label="inter-BSC Handover to another BSS"];
+
+ gscon abox gscon [label="ST_ACTIVE"];
+
+ ms => ho [label="Measurement Report"];
+ ho box ho [label="Handover Decision"];
+ ho box ho [label="handover_request\n(struct handover_out_req)"];
+ ho note gscon [label="To make sure the conn FSM permits a handover, trigger an event:"];
+ ho -> gscon [label="GSCON_EV_HANDOVER_START\ndata=handover_out_req"];
+ gscon abox gscon [label="ST_HANDOVER"];
+ ho <- gscon [label="handover_start\n(handover_out_req)"];
+ ho box ho [label="handover_start_inter_bsc_out()"];
+ ho => msc_ [label="BSSMAP Handover Required"];
+ ho abox ho [label="HO_OUT_ST_WAIT_HO_COMMAND"];
+ ...;
+ ...;
+ --- [label="On Timeout"];
+ ho box ho [label="handover_end(fail)"];
+ ho -> gscon [label="GSCON_EV_HANDOVER_END"];
+ gscon abox gscon [label="ST_ACTIVE"];
+ ms note gscon [label="MS happily continues on the old lchan."];
+ --- [label="END: 'On Timeout'"];
+ ...;
+ ...;
+ ho <= msc_ [label="BSSMAP Handover Command\n HO_OUT_EV_BSSMAP_HO_COMMAND"];
+ ms <= ho [label="Forward L3 Info (RR Handover Command from new BSS)"];
+ ho abox ho [label="HO_OUT_ST_WAIT_CLEAR"];
+ ...;
+ gscon abox gscon [label="ST_HANDOVER_MO_\nWAIT_CLEAR_CMD\nT8"];
+ ms <= gscon [label="RR Handover Command"];
+ ...;
+ ho rbox gscon [label="On Timeout: same as above"];
+ ...;
+ msc_ note msc_ [label="Remote BSS reported Handover Complete to the MSC,
+ this connection has been superseded."];
+ gscon <= msc_ [label="BSSMAP Clear Command\n GSCON_EV_A_CLEAR_CMD"];
+ gscon abox gscon [label="ST_CLEARING"];
+ gscon => msc_ [label="BSSMAP Clear Complete"];
+ ...;
+ gscon <= msc_ [label="DISC IND\n GSCON_EV_A_DISC_IND"];
+ ho abox ho [label="terminate\n(child of conn FSM)"];
+ gscon abox gscon [label="terminate"];
+}
diff --git a/doc/handover-intra-bsc-fsm.dot b/doc/handover-intra-bsc-fsm.dot
new file mode 100644
index 000000000..7cb0d3cfe
--- /dev/null
+++ b/doc/handover-intra-bsc-fsm.dot
@@ -0,0 +1,30 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="Handover FSM: Intra-BSC"
+
+ lchan [label="lchan FSM",shape=box3d]
+ intra [label="intra-BSC HO",shape=box]
+ old_lchan [label="old lchan",shape=box3d]
+ terminate [shape=octagon]
+
+ invisible [style="invisible"]
+ invisible -> intra [label="Measurement Report",style=dotted]
+ invisible -> old_lchan [style=invisible,arrowhead=none]
+
+ intra -> WAIT_LCHAN_ACTIVE [label="handover_start()",style=dotted]
+ WAIT_LCHAN_ACTIVE -> lchan [label="lchan_activate(FOR_HANDOVER)",style=dotted]
+ lchan -> WAIT_LCHAN_ACTIVE [label="HO_EV_\nLCHAN_\nACTIVE,ERROR",style=dotted,constraint=false]
+ WAIT_LCHAN_ACTIVE -> WAIT_RR_HO_DETECT
+ WAIT_RR_HO_DETECT -> old_lchan [label="RR Handover\nCommand",style=dotted,constraint=false]
+
+ lchan -> WAIT_RR_HO_DETECT [label="RR Handover\nDetect",style=dotted]
+ WAIT_RR_HO_DETECT -> WAIT_RR_HO_COMPLETE
+
+ lchan -> WAIT_RR_HO_COMPLETE [label="RR Handover\nComplete",style=dotted]
+ WAIT_RR_HO_COMPLETE -> WAIT_LCHAN_ESTABLISHED
+ lchan -> WAIT_LCHAN_ESTABLISHED [label="HO_EV_LCHAN_\nESTABLISHED",style=dotted]
+
+ WAIT_LCHAN_ESTABLISHED -> terminate [label="non-TCH"]
+ WAIT_LCHAN_ESTABLISHED -> WAIT_MGW_ENDPOINT_TO_MSC
+ WAIT_MGW_ENDPOINT_TO_MSC -> terminate [label="handover_end()"]
+}
diff --git a/doc/handover.msc b/doc/handover.msc
new file mode 100644
index 000000000..1a2580a06
--- /dev/null
+++ b/doc/handover.msc
@@ -0,0 +1,82 @@
+# Handover between cells, intra-BSC
+msc {
+ hscale=2;
+ ms [label="MS via BTS"], lchan[label="BSC lchan FSM"], ho[label="BSC Handover FSM"],
+ gscon[label="BSC conn FSM"], msc_[label="MSC"];
+
+ ms note msc_ [label="intra-BSC Handover"];
+
+ gscon abox gscon [label="ST_ACTIVE"];
+
+ ms => ho [label="Measurement Report"];
+ ho box ho [label="Handover Decision"];
+ ho box ho [label="handover_request\n(struct handover_out_req)"];
+ ho note gscon [label="To make sure the conn FSM permits a handover, trigger an event:"];
+ ho -> gscon [label="GSCON_EV_HANDOVER_START\ndata=handover_out_req"];
+ gscon abox gscon [label="ST_HANDOVER"];
+ ho <- gscon [label="handover_start\n(handover_out_req)"];
+ ho box ho [label="handover_start_intra_bsc()"];
+ ho abox ho [label="allocate\nHO_ST_NOT_STARTED"];
+
+ ...;
+ ...;
+ --- [label="On any error or timeout"];
+ ho box ho [label="handover_end(fail)"];
+ ho -> gscon [label="GSCON_EV_HANDOVER_END"];
+ gscon abox gscon [label="ST_ACTIVE"];
+ ms note gscon [label="MS happily continues on the old lchan."];
+ --- [label="END: 'On any error or timeout'"];
+ ...;
+ ...;
+
+ ho box ho [label="lchan_select_by_type()"];
+ ho abox ho [label="HO_ST_WAIT_\nLCHAN_ACTIVE"];
+ lchan <- ho [label="lchan_activate(FOR_HANDOVER)"];
+ lchan rbox lchan [label="(most details omitted, see lchan_fsm diagrams)"];
+
+ ...;
+ ...;
+ --- [label="On lchan error or timeout"];
+ lchan -> ho [label="HO_EV_LCHAN_ERROR"];
+ ho rbox gscon [label="same as above"];
+ --- [label="END: 'On lchan error or timeout'"];
+ ...;
+ ...;
+
+ lchan abox lchan [label="LCHAN_ST_WAIT_ACTIV_ACK"];
+ ms <= lchan [label="RSL Chan Activ"];
+ ...;
+ ms => lchan [label="RSL Chan Activ ACK"];
+ lchan -> ho [label="HO_EV_LCHAN_ACTIVE"];
+ ho abox ho [label="HO_ST_WAIT_\nRR_HO_DETECT"];
+ ...;
+ ms => ho [label="RR Handover Detect\nHO_EV_RR_HO_DETECT"];
+ lchan note ho [label="At this point we should start to switch the MGW over to the new lchan.
+ But this is not implemented yet, as was not before introducing these FSMs."];
+ ho abox ho [label="HO_ST_WAIT_\nRR_HO_COMPLETE"];
+ ...;
+ lchan note ho [label="The lchan FSM will continue with RSL and RTP while the HO FSM waits.
+ HO_EV_LCHAN_ESTABLISHED means that both RSL and RTP are established.
+ Usually, RTP will be done first, and the HO_EV_LCHAN_ESTABLISHED may be
+ received even before HO_EV_RR_HO_COMPLETE.
+ ho_fsm_wait_lchan_established_onenter() decides whether to wait or not."];
+ ...;
+ ms => lchan [label="RSL EST IND"];
+ lchan -> ho [label="HO_EV_LCHAN_ESTABLISHED",ID="(may come as early as this, or...)"];
+ ms => ho [label="RR Handover Complete (from EST IND)\n HO_EV_RR_HO_COMPLETE"];
+ ho abox ho [label="HO_ST_WAIT_\nLCHAN_ESTABLISHED"];
+ ...;
+ lchan rbox lchan [label="when lchan FSM is done with setting up RTP"];
+ lchan -> ho [label="HO_EV_LCHAN_ESTABLISHED",ID="(...may come only now)"];
+ ho abox ho [label="HO_ST_WAIT_\nMGW_ENDPOINT_TO_MSC"];
+ ho -> gscon [label="gscon_connect_mgw_to_msc()"];
+ ...;
+ ho <- gscon [label="HO_EV_MSC_MGW_OK"];
+ ho box ho [label="handover_end(OK)"];
+ ho -> gscon [label="gscon_change_primary_lchan()"];
+ lchan <- gscon [label="LCHAN_RTP_EV_ESTABLISHED"];
+ ho -> gscon [label="GSCON_EV_HANDOVER_END"];
+ gscon abox gscon [label="ST_ACTIVE"];
+ ho box ho [label="detach from parent to not fire another meaningless GSCON_EV_HANDOVER_END"];
+ ho abox ho [label="terminate"];
+}
diff --git a/doc/handover.txt b/doc/handover.txt
new file mode 100644
index 000000000..ac19e8725
--- /dev/null
+++ b/doc/handover.txt
@@ -0,0 +1,89 @@
+Ideas about a handover algorithm
+======================================================================
+
+This is mostly based on the results presented in Chapter 8 of "Performance
+Enhancements in a Frequency Hopping GSM Network" by Thomas Toftegaard Nielsen
+and Joeroen Wigard.
+
+
+=== Reasons for performing handover ===
+
+Section 2.1.1: Handover used in their CAPACITY simulation:
+
+1) Interference Handover
+
+Average RXLEV is satisfactory high, but average RXQUAL too low indicates
+interference to the channel. Handover should be made.
+
+2) Bad Quality
+
+Averaged RXQUAL is lower than a threshold
+
+3) Low Level / Signal Strength
+
+Average RXLEV is lower than a threshold
+
+4) Distance Handover
+
+MS is too far away from a cell (measured by TA)
+
+5) Power budget / Better Cell
+
+RX Level of neighbor cell is at least "HO Margin dB" dB better than the
+current serving cell.
+
+=== Ideal parameters for HO algorithm ===
+
+Chapter 8, Section 2.2, Table 24:
+
+Window RXLEV averaging: 10 SACCH frames (no weighting)
+Window RXQUAL averaging: 1 SACCH frame (no averaging)
+Level Threashold: 1 of the last 1 AV-RXLEV values < -110dBm
+Quality Threshold: 3 of the last 4 AV-RXQUAL values >= 5
+Interference Threshold: 1 of the last AV-RXLEV > -85 dBm &
+ 3 of the last 4 AV-RXQUAL values >= 5
+Power Budget: Level of neighbor cell > 3 dB better
+Power Budget Interval: Every 6 SACCH frames (6 seconds ?!?)
+Distance Handover: Disabled
+Evaluation rule 1: RXLEV of the candidate cell a tleast -104 dBm
+Evaluation rule 2: Level of candidate cell > 3dB better own cell
+Timer Successful HO: 5 SACCH frames
+Timer Unsuccessful HO: 1 SACCH frame
+
+In a non-frequency hopping case, RXQUAL threshold can be decreased to
+RXLEV >= 4
+
+When frequency hopping is enabled, the following additional parameters
+should be introduced:
+
+* No intra-cell handover
+* Use a HO Margin of 2dB
+
+=== Handover Channel Reservation ===
+
+In loaded network, each cell should reserve some channels for handovers,
+rather than using all of them for new call establishment. This reduces the
+need to drop calls due to failing handovers, at the expense of failing new call
+attempts.
+
+=== Dynamic HO Margin ===
+
+The handover margin (hysteresis) should depend on the RXQUAL. Optimal results
+were achieved with the following settings:
+* RXQUAL <= 4: 9 dB
+* RXQUAL == 5: 6 dB
+* RXQUAL >= 6: 1 dB
+
+
+
+== Actual Handover on a protocol level ==
+
+After the BSC has decided a handover shall be done, it has to
+
+# allocate a channel at the new BTS
+# allocate a handover reference
+# activate the channel on the BTS side using RSL CHANNEL ACTIVATION,
+ indicating the HO reference
+# BTS responds with CHAN ACT ACK, including GSM frame number
+# BSC sends 04.08 HO CMD to MS using old BTS
+
diff --git a/doc/lchan-fsm.dot b/doc/lchan-fsm.dot
new file mode 100644
index 000000000..b726b0c87
--- /dev/null
+++ b/doc/lchan-fsm.dot
@@ -0,0 +1,41 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="lchan FSM"
+
+ invisible [style="invisible"]
+ UNUSED [penwidth=3.0]
+ ESTABLISHED [penwidth=3.0]
+
+ ts [label="timeslot FSM",shape=box3d]
+ rtp [label="lchan_rtp\nFSM",shape=box3d]
+
+ UNUSED -> WAIT_TS_READY [label="lchan_allocate()"]
+ WAIT_TS_READY -> WAIT_ACTIV_ACK
+ WAIT_ACTIV_ACK -> WAIT_RLL_RTP_ESTABLISH
+ WAIT_RLL_RTP_ESTABLISH -> ESTABLISHED
+
+ ESTABLISHED -> WAIT_RLL_RTP_RELEASED [label="LCHAN_EV_\nRELEASE"]
+ WAIT_RLL_RTP_RELEASED -> WAIT_BEFORE_RF_RELEASE
+ WAIT_RLL_RTP_RELEASED -> WAIT_RF_RELEASE_ACK [label="timeout",style=dashed,constraint=false]
+
+ WAIT_BEFORE_RF_RELEASE -> WAIT_RF_RELEASE_ACK [label="T3111"]
+ WAIT_RF_RELEASE_ACK -> UNUSED
+ WAIT_RF_RELEASE_ACK -> WAIT_AFTER_ERROR [label="release was\ndue to error"]
+ WAIT_AFTER_ERROR -> UNUSED [label="T3111+2s"]
+
+ WAIT_TS_READY -> ts [label="TS_EV_\nLCHAN_\nREQUESTED",style=dotted,penwidth=3]
+ UNUSED -> ts [label="TS_EV_\nLCHAN_\nUNUSED",style=dotted,penwidth=3]
+ ts -> WAIT_TS_READY [label="LCHAN_EV_\nTS_READY",style=dotted]
+
+ WAIT_TS_READY -> rtp [label="TCH",style=dotted]
+
+ WAIT_TS_READY -> UNUSED [label="error/timeout",style=dashed,constraint=false]
+ {WAIT_ACTIV_ACK,WAIT_RF_RELEASE_ACK} -> BORKEN [label="error/timeout",style=dashed]
+ BORKEN -> WAIT_AFTER_ERROR [label="late RF Release ACK"]
+ WAIT_RLL_RTP_ESTABLISH -> WAIT_RLL_RTP_RELEASED [label=error,style=dashed]
+
+ WAIT_ACTIV_ACK -> rtp [label="LCHAN_RTP_EV_LCHAN_READY",style=dotted]
+ rtp -> WAIT_RLL_RTP_ESTABLISH [label="LCHAN_EV_RTP_READY",style=dotted]
+ rtp -> ESTABLISHED [label="LCHAN_EV_RTP_RELEASED",style=dotted]
+
+}
diff --git a/doc/lchan-rtp-fsm.dot b/doc/lchan-rtp-fsm.dot
new file mode 100644
index 000000000..d5df643b5
--- /dev/null
+++ b/doc/lchan-rtp-fsm.dot
@@ -0,0 +1,44 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="lchan RTP FSM"
+
+ lchan [label="lchan\nFSM",shape=box3d]
+ lchan2 [label="lchan\nFSM",shape=box3d]
+ ho_as [label="Handover or Assignment FSM",shape=box3d]
+ invisible [style=invisible]
+ ho [label="Handover FSM",shape=box3d]
+ mgwep [label="mgw endpoint\nFSM",shape=box3d]
+ start [label="lchan_rtp_fsm_start()",shape=box]
+ WAIT_READY_TO_SWITCH_RTP [label="WAIT_READY_TO_SWITCH_RTP\nonly if wait_before_switching_rtp"]
+ terminate [shape=octagon]
+
+ lchan -> start [style=dashed]
+ start -> WAIT_MGW_ENDPOINT_AVAILABLE
+ start -> WAIT_LCHAN_READY [label="re-use existing\nendpoint CI"]
+
+ WAIT_MGW_ENDPOINT_AVAILABLE -> mgwep [label="gscon_ensure_mgw_endpoint()\nand CRCX to-BTS",style=dashed]
+ mgwep -> WAIT_MGW_ENDPOINT_AVAILABLE [label="LCHAN_RTP_EV_\nMGW_ENDPOINT_\n{AVAILABLE,ERROR}",style=dashed]
+ WAIT_MGW_ENDPOINT_AVAILABLE -> WAIT_LCHAN_READY
+
+ lchan -> WAIT_LCHAN_READY [label="LCHAN_RTP_EV_LCHAN_READY",style=dashed]
+ WAIT_LCHAN_READY -> WAIT_IPACC_CRCX_ACK [label="IPACC BTS"]
+ WAIT_LCHAN_READY -> WAIT_READY_TO_SWITCH_RTP
+ WAIT_IPACC_CRCX_ACK -> WAIT_IPACC_MDCX_ACK
+ WAIT_IPACC_MDCX_ACK -> WAIT_READY_TO_SWITCH_RTP
+ invisible -> ho [label="HO DETECT",style=dashed]
+ ho -> WAIT_READY_TO_SWITCH_RTP [label="LCHAN_RTP_EV_READY_TO_SWITCH",style=dashed]
+ WAIT_READY_TO_SWITCH_RTP -> WAIT_MGW_ENDPOINT_CONFIGURED
+ WAIT_MGW_ENDPOINT_CONFIGURED -> mgwep [label="MDCX",style=dashed]
+ mgwep -> WAIT_MGW_ENDPOINT_CONFIGURED [label="LCHAN_RTP_EV_\nMGW_ENDPOINT_\nCONFIGURED",style=dashed]
+ WAIT_MGW_ENDPOINT_CONFIGURED -> RTP_READY
+ RTP_READY -> lchan2 [label="LCHAN_EV_\nRTP_READY",style=dashed]
+ RTP_READY -> RTP_ESTABLISHED
+ lchan2 -> RTP_ESTABLISHED [label="LCHAN_RTP_EV_\nRELEASE",style=dashed]
+ RTP_ESTABLISHED -> terminate
+ RTP_READY -> RTP_ROLLBACK
+ RTP_ROLLBACK -> terminate
+ terminate -> lchan2 [label="LCHAN_EV_\nRTP_RELEASED",style=dashed]
+
+ lchan2 -> ho_as [label="XX_EV_LCHAN_\nESTABLISHED",style=dashed]
+ ho_as -> RTP_READY [label="LCHAN_RTP_EV_\n{ESTABLISHED,\nROLLBACK}",style=dashed]
+}
diff --git a/doc/lchan.msc b/doc/lchan.msc
new file mode 100644
index 000000000..e2caa4875
--- /dev/null
+++ b/doc/lchan.msc
@@ -0,0 +1,216 @@
+msc {
+ hscale=2;
+ ms [label="MS/BTS"], ts [label="BSC timeslot FSM"],
+ lchan[label="BSC lchan FSM"], rtp[label="BSC lchan RTP FSM"],mgwep[label="BSC MGW endpoint FSM"];
+
+ ms box mgwep [label="lchan allocation sequence"];
+ lchan abox lchan [label="LCHAN_ST_UNUSED"];
+ ...;
+ lchan rbox lchan [label="lchan_activate(activate_info)"];
+ lchan note lchan [label="Dispatching event to make sure the lchan FSM permits activation."];
+ lchan -> lchan [label="LCHAN_EV_ACTIVATE\ndata = activate_info"];
+ lchan abox lchan [label="LCHAN_ST_\nWAIT_TS_READY"];
+ ts <- lchan [label="TS_EV_LCHAN_REQUESTED"];
+ ts rbox ts [label="Most details omitted. See timeslot FSM diagrams."];
+ ts note ts [label="A dyn TS may be in PDCH mode and will asynchronously switch off PDCH first. A
+ non-dynamic TS is ready immediately."];
+ |||;
+ --- [label="IF requires_voice_stream"];
+ lchan -> rtp [label="lchan_rtp_fsm_start()"];
+ rtp abox rtp [label="allocate\n LCHAN_RTP_ST_\nWAIT_MGW_ENDPOINT_\nAVAILABLE"];
+ --- [label="IF no endpoint-CI yet"];
+ rtp box rtp [label="gscon_ensure_mgw_endpoint()"];
+ rtp -> mgwep [label="mgw_endpoint_ci_add(to-BTS)"];
+ rtp -> mgwep [label="CRCX to-BTS"];
+ mgwep rbox mgwep [label="MGCP: CRCX"];
+ ...;
+ mgwep rbox mgwep [label="MGCP: CRCX OK"];
+ rtp <- mgwep [label="LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE"];
+ rtp note mgwep [label="The CRCX OK has assigned us a new endpoint CI number"];
+ rtp abox rtp [label="LCHAN_RTP_ST_WAIT_LCHAN_READY"];
+ --- [label="END: no endpoint-CI yet"];
+ --- [label="END: requires_voice_stream"];
+ |||;
+ ...;
+ ts -> lchan [label="LCHAN_EV_TS_READY"];
+ lchan abox lchan [label="LCHAN_ST_\nWAIT_ACTIV_ACK"];
+ --- [label="IF FOR_MS_CHANNEL_REQUEST"];
+ ms <= lchan [label="RSL Chan Activ (RSL_ACT_INTRA_IMM_ASS)"];
+ --- [label="ELSE: FOR_ASSIGNMENT"];
+ ms <= lchan [label="RSL Chan Activ (RSL_ACT_INTRA_NORM_ASS)"];
+ --- [label="ELSE: FOR_HANDOVER"];
+ ms <= lchan [label="RSL Chan Activ (RSL_ACT_INTER_ASYNC)"];
+ --- [label="END"];
+ ...;
+ ms rbox lchan [label="On timeout or Chan Activ NACK, see: 'On any error', 'unrecoverable'"];
+ ...;
+ ms => lchan [label="RSL Chan Activ ACK"];
+ lchan box lchan [label="lchan_fsm_post_activ_ack()"];
+
+ --- [label="IF FOR_MS_CHANNEL_REQUEST"];
+ ms <= lchan [label="RR Immediate Assignment"];
+ --- [label="ELSE: FOR_ASSIGNMENT"];
+ lchan rbox lchan [label="dispatch\nASSIGNMENT_EV_\nLCHAN_ACTIVE\n(see Assignment FSM diagrams)"];
+ ms <= lchan [label="RR Assignment Command"];
+ --- [label="ELSE: FOR_HANDOVER"];
+ lchan rbox lchan [label="dispatch\nHO_EV_LCHAN_ACTIVE\n(see Handover FSM diagrams)"];
+ --- [label="END"];
+
+
+ lchan abox lchan [label="LCHAN_ST_WAIT_\nRLL_RTP_ESTABLISH\nT3101"];
+ |||;
+ |||;
+ --- [label="IF requires_voice_stream"];
+ lchan -> rtp [label="LCHAN_RTP_EV_LCHAN_READY"];
+ |||;
+ --- [label="IF ip.access style BTS"];
+ rtp abox rtp [label="LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK"];
+ ms <= rtp [label="IPACC CRCX"];
+ ...;
+ ms => rtp [label="IPACC CRCX ACK (BTS RTP port info)"];
+ rtp abox rtp [label="LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK"];
+ ms <= rtp [label="IPACC MDCX (MGW RTP port info)"];
+ ...;
+ ms => rtp [label="IPACC MDCX ACK"];
+ --- [label="END ip.access style BTS"];
+ |||;
+ rtp box rtp [label="lchan_rtp_fsm_switch_rtp()"];
+ |||;
+ --- [label="IF wait_before_switching_rtp"];
+ rtp note rtp [label="During Handover, wait for HO DETECT before redirecting an existing endpoint
+ CI towards the new lchan."];
+ rtp abox rtp [label="LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP"];
+ ...;
+ ms => rtp [label="HO DETECT (via Handover FSM)"];
+ --- [label="END: wait_before_switching_rtp"];
+ |||;
+ rtp abox rtp [label="LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED"];
+ rtp box rtp [label="connect_mgw_endpoint_to_lchan()"];
+ rtp -> mgwep [label="MDCX to-BTS"];
+ mgwep rbox mgwep [label="MGCP: MDCX"];
+ ...;
+ mgwep rbox mgwep [label="MGCP: MDCX OK"];
+ rtp <- mgwep [label="LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED"];
+ rtp abox rtp [label="LCHAN_RTP_ST_READY"];
+ lchan <- rtp [label="LCHAN_EV_RTP_READY"];
+ rtp note rtp [label="RTP FSM stays ready for Rollback until final establish event"];
+ ...;
+ lchan -> rtp [label="LCHAN_RTP_EV_ESTABLISHED\nvia gscon_change_primary_lchan()"];
+ rtp abox rtp [label="LCHAN_RTP_ST_\nESTABLISHED"];
+ --- [label="END: requires_voice_stream"];
+ |||;
+ |||;
+
+ ...;
+ ms => lchan [label="RLL Establish Ind"];
+ lchan abox lchan [label="LCHAN_ST_\nESTABLISHED"];
+ lchan box lchan [label="lchan_on_fully_established()"];
+ --- [label="IF FOR_MS_CHANNEL_REQUEST"];
+ ms note lchan [label="No action required. The MS will have sent an L3 message in the RLL
+ Establish Ind and is then free to dispatch DTAP."];
+ --- [label="ELSE: FOR_ASSIGNMENT"];
+ lchan rbox lchan [label="dispatch\nASSIGNMENT_EV_\nLCHAN_ESTABLISHED\n(see Assignment FSM diagrams)"];
+ --- [label="ELSE: FOR_HANDOVER"];
+ lchan rbox lchan [label="dispatch\nHO_EV_LCHAN_ESTABLISHED\n(see Handover FSM diagrams)"];
+ --- [label="END"];
+ ...;
+ --- [label="IF requires_voice_stream"];
+ lchan rbox lchan [label="Assignment or Handover FSM:"];
+ lchan -> mgwep [label="CRCX/MDCX to-MSC"];
+ ...;
+ lchan <- mgwep [label="OK"];
+ lchan box lchan [label="gscon_change_primary_lchan()"];
+ lchan -> rtp [label="LCHAN_RTP_EV_ESTABLISHED"];
+ rtp abox rtp [label="LCHAN_RTP_ST_\nESTABLISHED"];
+ rtp box rtp [label="Forget any Rollback info"];
+ --- [label="END: requires_voice_stream"];
+
+ ...;
+ ...;
+ ...;
+
+ ms rbox mgwep [label="When the MS or BTS release the lchan"];
+ lchan abox lchan [label="LCHAN_ST_\nESTABLISHED"];
+ ms -> lchan [label="RLL Release Ind for SAPI=0"];
+ lchan abox lchan [label="LCHAN_ST_WAIT_RLL_RTP_RELEASED"];
+ lchan rbox lchan [label="Continue at 'common release' below"];
+ ...;
+ ...;
+ ms rbox mgwep [label="When the BSC decides to release the lchan"];
+ lchan box lchan [label="lchan_release()"];
+ lchan abox lchan [label="LCHAN_ST_WAIT_RLL_RTP_RELEASED"];
+ ms <= lchan [label="RR Release"];
+ lchan rbox lchan [label="common release"];
+ --- [label="IF RTP FSM present"];
+ lchan -> rtp [label="LCHAN_RTP_EV_RELEASE"];
+ --- [label="END: RTP FSM present"];
+ ms <= lchan [label="RSL Deactivate SACCH"];
+ ms <= lchan [label="RSL Release Request (Local End)",ID="for each SAPI except [0]"];
+ lchan note lchan [label="for ms->nokia.no_loc_rel_cnf we do not expect Release Confirm
+ messages and immediately mark all SAPIs as released"];
+
+ ...;
+ lchan <- rtp [label="LCHAN_EV_RTP_RELEASED"];
+ ...;
+ ms => lchan [label="RLL Release Confirm",ID="for each SAPI except [0]"];
+ ...;
+ lchan box lchan [label="Stay in\nLCHAN_ST_WAIT_\nRLL_RTP_RELEASED\nuntil only SAPI[0] remains active"];
+ lchan abox lchan [label="LCHAN_ST_WAIT_\nBEFORE_RF_RELEASE\nT3111"];
+ ...;
+ lchan box lchan [label="T3111 expires"];
+ lchan box lchan [label="lchan_fsm_pre_rf_release()"];
+ lchan abox lchan [label="LCHAN_ST_WAIT_\nRF_RELEASE_ACK\nT3111"];
+ ms <= lchan [label="RSL RF Channel Release"];
+ ...;
+ lchan rbox lchan [label="On timeout, continue at: 'On any error', 'unrecoverable'"];
+ ...;
+ ms => lchan [label="RSL RF Channel Release Ack"];
+ |||;
+ --- [label="IF release_in_error"];
+ lchan abox lchan [label="LCHAN_ST_WAIT_\nAFTER_ERROR\n(timeout: T3111+2 s, T993111)"];
+ ...;
+ lchan box lchan [label="timer expires"];
+ --- [label="END: release_in_error"];
+ |||;
+ lchan abox lchan [label="LCHAN_ST_UNUSED"];
+ ts <- lchan [label="TS_EV_LCHAN_UNUSED"];
+ |||;
+ |||;
+ |||;
+
+ ms rbox mgwep [label="On any error"];
+ |||;
+ --- [label="IF FOR_MS_CHANNEL_REQUEST"];
+ ms <= lchan [label="RR Immediate Assign Reject"];
+ --- [label="ELSE: FOR_ASSIGNMENT"];
+ lchan rbox lchan [label="dispatch\nASSIGNMENT_EV_\nLCHAN_ERROR\n(see Assignment FSM diagrams)"];
+ --- [label="ELSE: FOR_HANDOVER"];
+ lchan rbox lchan [label="dispatch\nHO_EV_LCHAN_ERROR\n(see Handover FSM diagrams)"];
+ --- [label="END"];
+ |||;
+ --- [label="IF fi_rtp present"];
+ lchan -> rtp [label="LCHAN_RTP_EV_ROLLBACK"];
+ rtp rbox rtp [label="If to-BTS is not established yet, ROLLBACK is synonymous to LCHAN_RTP_EV_RELEASE"];
+ rtp rbox rtp [label="If there is no old_lchan, just DLCX instead"];
+ rtp abox rtp [label="LCHAN_RTP_ST_ROLLBACK"];
+ rtp box rtp [label="connect_mgw_endpoint_to_lchan()\nusing old_lchan"];
+ rtp -> mgwep [label="MDCX to-BTS"];
+ mgwep rbox mgwep [label="MGCP: MDCX"];
+ ...;
+ mgwep rbox mgwep [label="MGCP: MDCX OK"];
+ rtp <- mgwep [label="LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED"];
+ rtp abox rtp [label="terminate"];
+ lchan <- rtp [label="LCHAN_EV_RTP_RELEASED"];
+ --- [label="END: fi_rtp present"];
+ |||;
+ |||;
+ --- [label="IF unrecoverable error"];
+ lchan abox lchan [label="LCHAN_ST_BORKEN"];
+ ms note lchan [label="The broken state usually stays around
+ until the BTS disconnects."];
+ ...;
+ ms note lchan [label="If an ACK comes in late, for specific BTS models, we may choose to
+ 'repair' the lchan so that it is usable again."];
+ ms -> lchan [label="RF Chan Release ACK"];
+ lchan rbox lchan [label="continue above at\nLCHAN_ST_WAIT_\nAFTER_ERROR"];
+}
diff --git a/doc/legend_for_fsm_diagrams.dot b/doc/legend_for_fsm_diagrams.dot
new file mode 100644
index 000000000..732a894cd
--- /dev/null
+++ b/doc/legend_for_fsm_diagrams.dot
@@ -0,0 +1,24 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="LEGEND FOR FSM GRAPHS"
+
+ box [label="function_call()\nputs FSM into state",shape="box"]
+ STATE [label="FSM_STATE"]
+ STATE2 [label="FSM_STATE"]
+ STATE3 [label="FSM_STATE"]
+ box -> STATE
+ STATE -> STATE2 [label="state transition"]
+ STATE2 -> STATE3
+
+ STATE -> STATE3 [label="transition\non error",style=dashed]
+
+ other [label="other FSM\ninstance\nor remote program",shape=box3d]
+ STATE2 -> other [label="event",style=dotted]
+ other -> STATE2 [label="event",style=dotted]
+
+ terminate [shape=octagon]
+ STATE3 -> terminate
+
+ err [label="common error\ntransition",shape=box,style=dashed]
+ err -> STATE3 [style=dashed]
+}
diff --git a/doc/legend_for_ladder_diagrams.msc b/doc/legend_for_ladder_diagrams.msc
new file mode 100644
index 000000000..a581fe467
--- /dev/null
+++ b/doc/legend_for_ladder_diagrams.msc
@@ -0,0 +1,29 @@
+msc {
+ A [label="FSM instance"],B [label="FSM instance"], C [label="remote program"];
+ |||;
+ ||| [label="LADDER DIAGRAM LEGEND"];
+ |||;
+
+ A rbox C [label="Group Heading"];
+
+ A box A [label="function call or action"];
+ A -> B [label="event within program"];
+ B abox B [label="enter FSM state"];
+ B => C [label="network protocol message"];
+ ...;
+ ... [label="asynchronous wait time"];
+ ...;
+ B <= C [label="network protocol message"];
+ |||;
+ ||| [label="continue synchronously"];
+ |||;
+ A <- B [label="event within program"];
+ A rbox A [label="flow detail: 'continue at...'"];
+ ...;
+ ...;
+ --- [label="IF conditional"];
+ ||| [label="..."];
+ --- [label="END: conditional"];
+ ...;
+ B note B [label="arbitrary prose"];
+}
diff --git a/doc/mgw-endpoint-fsm.dot b/doc/mgw-endpoint-fsm.dot
new file mode 100644
index 000000000..ac7c2bfd7
--- /dev/null
+++ b/doc/mgw-endpoint-fsm.dot
@@ -0,0 +1,24 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="MGW Endpoint FSM"
+
+ gscon_ensure_mgw_endpoint [label="gscon_ensure_mgw_endpoint()",shape="box"]
+ UNUSED
+ WAIT_MGW_RESPONSE
+ IN_USE
+ terminate [shape=octagon]
+ mgcp [label="mgcp client FSM\n(libosmo-mgcp-client)",shape=box3d]
+ notify [label="notify target FI",shape=box3d]
+ gscon [label="parent FI\n(gscon)",shape=box3d]
+
+ gscon_ensure_mgw_endpoint -> UNUSED
+ UNUSED -> WAIT_MGW_RESPONSE [label="first\nmgw_endpoint_ci_request(CRCX)"]
+ WAIT_MGW_RESPONSE -> mgcp [label="mgcp_conn_create()\nmgcp_conn_modify()\nmgcp_conn_delete()",style=dotted]
+ mgcp -> WAIT_MGW_RESPONSE [label="CI[i] event",style=dotted]
+ WAIT_MGW_RESPONSE -> IN_USE
+ IN_USE -> notify [label="notify event for\nindividual CI request",style=dotted]
+ IN_USE -> WAIT_MGW_RESPONSE [label="additional\nmgw_endpoint_ci_request()\nCRCX,MDCX,DLCX"]
+
+ WAIT_MGW_RESPONSE -> terminate [label="all CI DLCX'd"]
+ terminate -> gscon [label="GSCON_EV_FORGET_MGW_ENDPOINT",style=dotted]
+}
diff --git a/doc/mgw-endpoint.msc b/doc/mgw-endpoint.msc
new file mode 100644
index 000000000..7084d1d2a
--- /dev/null
+++ b/doc/mgw-endpoint.msc
@@ -0,0 +1,105 @@
+msc {
+ hscale=2;
+ notify [label="calling FSM"], mgwep[label="MGW endpoint FSM"], mgcp[label="mgcp client FSM"],
+ mgw[label="MGW"];
+
+ notify note mgw [label="MGW endpoint FSM\nmanages multiple CI for one endpoint"];
+
+ |||;
+
+ notify rbox notify [label="conn FSM"];
+ notify box notify [label="gscon_ensure_mgw_endpoint()"];
+ notify -> mgwep [label="mgw_endpoint_alloc()"];
+ mgwep abox mgwep [label="MGWEP_ST_UNUSED"];
+
+ ...;
+ ...;
+ ...;
+ notify rbox mgw [label="CRCX"];
+
+ notify rbox notify [label="lchan RTP FSM"];
+ notify -> mgwep [label="mgw_endpoint_ci_add()"];
+ mgwep note mgwep [label="Return an unassigned endpoint CI slot in the local array"];
+ ...;
+ mgwep note mgwep [label="First request on a CI must be a CRCX"];
+ notify -> mgwep [label="mgw_endpoint_ci_request(CRCX)"];
+ notify note mgwep [label="verb=CRCX\nverb_info='rtpbridge/*@mgw'\nnotify_event"];
+ mgwep box mgwep [label="CI[x].pending=true"];
+ mgwep abox mgwep [label="MGWEP_ST_WAIT_MGW_RESPONSE"];
+ |||;
+ notify note mgwep [label="If more mgw_endpoint_ci_request() are triggered, they will be set to
+ 'pending' and wait until all ongoing requests are through and MGWEP_ST_IN_USE is
+ reached."];
+ |||;
+ mgwep box mgwep [label="for each pending CI:\nsend_verb()"];
+ mgwep -> mgcp [label="CI[x]: mgcp_conn_create()"];
+ mgwep note mgcp [label="Each CI[i] has two events from the FSM instance event range assigned, one
+ for success, one for failure. These are passed to the mgcp client FSM."];
+ mgcp => mgw [label="CRCX"];
+ ...;
+ mgcp <= mgw [label="CRCX OK"];
+ mgcp note mgw [label="MGW returns:\n'rtpbridge/123@mgw',\nnew CI identifier '123abc',\n
+ MGW side RTP IP:port"];
+ mgwep <- mgcp [label="CI[x] success event"];
+ mgwep box mgwep [label="on_success(CI[x])"];
+ mgwep note mgwep [label="CI[x].rtp_info = IP:port\nmgcp_ci_str = '123abc'\n
+ endpoint name = 'rtpbridge/123@mgw'"];
+ notify <- mgwep [label="notify_event from mgw_endpoint_ci_request()"];
+ notify note mgwep [label="notify_event will be one of:\n
+ LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE (towards BTS)\n
+ ASSIGNMENT_EV_MSC_MGW_OK (towards MSC)\n
+ HO_EV_MSC_MGW_OK (towards MSC)"];
+ mgwep abox mgwep [label="MGWEP_ST_IN_USE"];
+
+ ...;
+ ...;
+ ...;
+ notify rbox mgw [label="MDCX"];
+
+ mgwep note mgwep [label="Second or later request on a CI must be MDCX or DLCX"];
+ notify -> mgwep [label="mgw_endpoint_ci_request(MDCX)"];
+ notify note mgwep [label="verb=MDCX\nverb_info=BTS RTP IP:port\n
+ automatic: full endpoint name as from CRCX OK\n
+ notify_event\n"];
+ mgwep box mgwep [label="CI[x].pending=true"];
+ mgwep abox mgwep [label="MGWEP_ST_WAIT_MGW_RESPONSE"];
+ mgwep box mgwep [label="for each pending CI:\nsend_verb()"];
+ mgwep -> mgcp [label="CI[x]: mgcp_conn_modify()"];
+ mgcp => mgw [label="MDCX"];
+ ...;
+ mgcp <= mgw [label="MDCX OK"];
+ mgwep <- mgcp [label="CI[x] success event"];
+ mgwep box mgwep [label="on_success(CI[x])"];
+ notify <- mgwep [label="notify_event from mgw_endpoint_ci_request()"];
+ notify note mgwep [label="notify_event will be one of:\n
+ LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED (towards BTS)\n
+ ASSIGNMENT_EV_MSC_MGW_OK (towards MSC)\n
+ HO_EV_MSC_MGW_OK (towards MSC)"];
+ mgwep abox mgwep [label="MGWEP_ST_IN_USE"];
+
+ ...;
+ ...;
+ ...;
+ notify rbox mgw [label="DLCX"];
+
+ notify -> mgwep [label="mgw_endpoint_ci_dlcx()"];
+ mgwep box mgwep [label="mgw_endpoint_ci_request(DLCX)"];
+ mgwep box mgwep [label="CI[x].pending=true"];
+ mgwep abox mgwep [label="MGWEP_ST_WAIT_MGW_RESPONSE"];
+ mgwep box mgwep [label="for each pending CI:\nsend_verb()"];
+ mgwep -> mgcp [label="CI[x]: mgcp_conn_delete()"];
+ mgcp => mgw [label="DLCX"];
+ mgcp box mgcp [label="detach from parent fi"];
+ mgwep box mgwep [label="forget and clear CI[x]"];
+ --- [label="IF other CI remain valid"];
+ mgwep abox mgwep [label="MGWEP_ST_IN_USE"];
+ --- [label="IF no CI remain on endpoint"];
+ mgwep abox mgwep [label="terminate"];
+ notify rbox notify [label="conn FSM"];
+ notify <- mgwep [label="GSCON_EV_FORGET_MGW_ENDPOINT"];
+ ---;
+ ...;
+ mgcp <= mgw [label="DLCX OK"];
+ mgcp abox mgcp [label="terminate"];
+
+}
diff --git a/doc/timeslot-fsm.dot b/doc/timeslot-fsm.dot
new file mode 100644
index 000000000..95a4e1f43
--- /dev/null
+++ b/doc/timeslot-fsm.dot
@@ -0,0 +1,37 @@
+digraph G {
+rankdir=TB
+labelloc=t; label="Timeslot FSM"
+
+ invisible [style="invisible"]
+ invisible2 [style="invisible"]
+ NOT_INITIALIZED
+ lchan [label="lchan FSM",shape=box3d]
+ UNUSED
+ IN_USE
+ BORKEN
+ PDCH
+ WAIT_PDCH_ACT
+ WAIT_PDCH_DEACT
+
+ invisible -> NOT_INITIALIZED [label="OML\nOpstart ACK",style=dotted]
+ invisible2 -> NOT_INITIALIZED [label="RSL\nbootstrapped",style=dotted]
+
+ NOT_INITIALIZED -> UNUSED [label="OML+RSL ready"]
+
+ UNUSED -> IN_USE [label="first\nlchan\nrequested\nby lchan\nFSM"]
+ IN_USE -> UNUSED [label="last lchan\nunused"]
+
+ UNUSED -> PDCH [label="onenter:\ndedicated PDCH\nand GPRS\nis enabled"]
+ UNUSED -> WAIT_PDCH_ACT [label="onenter:\ndyn TS\nand GPRS\nis enabled"]
+ WAIT_PDCH_ACT -> PDCH [label="dyn TS:\nPDCH activated"]
+
+ PDCH -> WAIT_PDCH_DEACT [label="dyn TS:\nlchan of specific\npchan requested"]
+ WAIT_PDCH_DEACT -> UNUSED [label="lchan\nunused\n(e.g. error)",style=dashed]
+ WAIT_PDCH_DEACT -> IN_USE [label="dyn TS:\nPDCH released"]
+
+ lchan -> {UNUSED} [label="TS_EV_LCHAN_\nREQUESTED",style=dotted]
+ {IN_USE} -> lchan [label="LCHAN_EV_\nTS_READY",style=dotted]
+ lchan -> IN_USE [label="TS_EV_LCHAN_\nUNUSED",style=dotted]
+
+ {WAIT_PDCH_ACT,WAIT_PDCH_DEACT} -> BORKEN [label=timeout,style=dashed]
+}
diff --git a/doc/timeslot.msc b/doc/timeslot.msc
new file mode 100644
index 000000000..02e7bb3ea
--- /dev/null
+++ b/doc/timeslot.msc
@@ -0,0 +1,100 @@
+msc {
+ bts [label="MS/BTS"], bsc[label="BSC"], bsc_ts[label="BSC timeslot FSM"], bsc_lchan[label="BSC lchan FSM"];
+
+ bsc_ts abox bsc_ts [label="NOT_INITIALIZED"];
+
+ ...;
+ bsc note bsc_ts [label="OML and RSL may be established in any order"];
+ bts => bsc_ts [label="OML: Channel OPSTART ACK"];
+ bsc -> bsc_ts [label="RSL bootstrapped"];
+ bsc_ts abox bsc_ts [label="UNUSED"];
+
+ |||;
+ bts rbox bsc_lchan [label="UNUSED, onenter"];
+ bsc_ts abox bsc_ts [label="UNUSED"];
+ --- [label="GPRS enabled?"];
+ --- [label="IF: dedicated PDCH?"];
+ bsc_ts abox bsc_ts [label="PDCH"];
+
+ |||;
+ --- [label="IF: dynamic timeslot"];
+ bsc_ts abox bsc_ts [label="WAIT_PDCH_ACT (4s, T23001)"];
+ bts <= bsc_ts [label="RSL Chan Activ of PDCH",ID="Osmocom style"];
+ bts <= bsc_ts [label="RSL PDCH Act",ID="ip.access style"];
+ ...;
+ --- [label="timeout:"];
+ bsc_ts abox bsc_ts [label="BORKEN"];
+ ---;
+ ...;
+ bts => bsc_ts [label="RSL RF Chan Activ ACK",ID="Osmocom style"];
+ bts => bsc_ts [label="RSL PDCH Act ACK",ID="ip.access style"];
+ bsc_ts abox bsc_ts [label="PDCH"];
+
+ --- [label="END: GPRS enabled?"];
+ ...;
+ ...;
+
+ bts rbox bsc_lchan [label="UNUSED, on event"];
+ bsc_ts abox bsc_ts [label="UNUSED"];
+ bsc_ts <- bsc_lchan [label="TS_EV_LCHAN_REQUESTED (data=lchan)"];
+ bsc_ts abox bsc_ts [label="IN_USE"];
+ bsc_ts -> bsc_lchan [label="LCHAN_EV_TS_READY"];
+ bts <= bsc_lchan [label="RSL Chan Activ (and so on)"];
+ ...;
+ bts rbox bsc_lchan [label="IN_USE, second lchan"];
+ bsc_ts abox bsc_ts [label="IN_USE"];
+ bsc_ts <- bsc_lchan [label="TS_EV_LCHAN_REQUESTED (data=lchan)"];
+ bsc_ts -> bsc_lchan [label="LCHAN_EV_TS_READY"];
+ bts <= bsc_lchan [label="RSL Chan Activ (and so on)"];
+ ...;
+ ...;
+ bts rbox bsc_lchan [label="IN_USE, when lchan FSM releases (both regularly, or due to error)"];
+ bsc_ts abox bsc_ts [label="IN_USE"];
+ bsc_ts <- bsc_lchan [label="TS_EV_LCHAN_UNUSED (data=lchan)"];
+ --- [label="IF all lchan->fi->state == LCHAN_ST_UNUSED"];
+ bsc_ts abox bsc_ts [label="UNUSED"];
+ ---;
+ ...;
+ ...;
+
+
+ bts rbox bsc_lchan [label="PDCH, on lchan request"];
+ bsc_ts note bsc_lchan [label="TS_EV_LCHAN_REQUESTED should only come in on
+ lchans where it makes sense, both from TS kind as well as not
+ conflicting with other users of the lchan."];
+
+ bsc_ts abox bsc_ts [label="PDCH"];
+ bsc_ts <- bsc_lchan [label="TS_EV_LCHAN_REQUESTED"];
+ bsc_ts abox bsc_ts [label="WAIT_PDCH_DEACT (4s, T23001)"];
+ bts <= bsc_ts [label="RSL RF Chan Release of PDCH",ID="Osmocom style"];
+ bts <= bsc_ts [label="RSL PDCH Deact",ID="ip.access style"];
+ ...;
+ --- [label="timeout:"];
+ bsc_ts abox bsc_ts [label="BORKEN"];
+ bsc_ts -> bsc_lchan [label="LCHAN_EV_TS_ERROR"];
+ ---;
+ ...;
+ bts => bsc_ts [label="RSL RF Chan Release ACK",ID="Osmocom style"];
+ bts => bsc_ts [label="RSL PDCH Deact ACK",ID="ip.access style"];
+ --- [label="IF all lchan->fi->state == LCHAN_ST_UNUSED"];
+ bsc_ts note bsc_lchan [label="If the lchan FSM decided to give up in the
+ meantime, nr of active lchans might have dropped back to zero."];
+ bsc_ts abox bsc_ts [label="UNUSED"];
+ bsc_ts note bsc_ts [label="onenter at UNUSED state will trigger back to
+ PDCH mode"];
+ |||;
+ --- [label="IF at least one lchan->state != LCHAN_ST_UNUSED"];
+ bsc_ts abox bsc_ts [label="IN_USE"];
+ bsc_ts rbox bsc_ts [label="Continue at 'IN_USE' above"];
+ ...;
+ ...;
+
+ bts rbox bsc_lchan [label="on erratic event"];
+ bsc_ts -> bsc_lchan [label="LCHAN_EV_TS_ERROR"];
+ bsc_lchan box bsc_lchan [label="release lchan"];
+ ...;
+ bsc_ts <- bsc_lchan [label="TS_EV_LCHAN_UNUSED"];
+ bsc_ts note bsc_ts [label="log error but ignore"];
+ ...;
+
+}
diff --git a/doc/ts-and-lchan-fsm-lifecycle.msc b/doc/ts-and-lchan-fsm-lifecycle.msc
new file mode 100644
index 000000000..9275f1f42
--- /dev/null
+++ b/doc/ts-and-lchan-fsm-lifecycle.msc
@@ -0,0 +1,116 @@
+msc {
+ hscale=2;
+ bts [label="MS/BTS"], bsc[label="BSC"], bsc_ts[label="BSC timeslot FSM"], bsc_lchan[label="BSC lchan FSM"];
+
+ bsc box bsc [label="gsm_bts_alloc()"];
+ bsc box bsc [label="bts->c0 = gsm_bts_trx_alloc()"];
+ bsc -> bsc_ts;
+ bsc_ts box bsc_ts [label="trx->ts[*].fi = osmo_fsm_inst_alloc(timeslot_fsm)"];
+ bsc_ts abox bsc_ts [label="TS_ST_NOT_INITIALIZED"];
+ bsc -> bsc_lchan;
+ bsc_lchan box bsc_lchan [label="ts->lchan[*].ts = ts;\nts->lchan[*].nr = i;"];
+ bsc_lchan box bsc_lchan [label="ts->lchan[*].fi = NULL"];
+ bsc_ts note bsc_lchan [label="lchan_select() will only pick lchans from initialized timeslots of
+ the right pchan kind. lchan_select() shall OSMO_ASSERT(lchan->fi)."];
+ ...;
+ ...;
+
+ bts rbox bsc_lchan [label="reading config file"];
+ ...;
+ bsc box bsc [label="timeslot N"];
+ bsc box bsc [label="phys_chan_config X"];
+ bsc_ts box bsc_ts [label="ts->pchan_from_config = X"];
+ bsc_ts note bsc_ts [label="still TS_ST_NOT_INITIALIZED"];
+ ...;
+ bsc box bsc [label="trx 1..*"];
+ bsc box bsc [label="bts->trx_list add gsm_bts_trx_alloc()"];
+ bsc_ts rbox bsc_lchan [label="same as for c0 above"];
+ ...;
+ ...;
+ bts rbox bsc_lchan [label="Starting Operation"];
+ bts => bsc_ts [label="OML Channel OPSTART ACK"];
+ bsc_ts box bsc_ts [label="ts_on_oml_opstart()"];
+ bsc_ts box bsc_ts [label="ts->pchan_on_init = pchan_from_config"];
+ --- [label="IF dedicated TS"];
+ bsc_ts box bsc_ts [label="ts->pchan = ts->pchan_on_init"];
+ --- [label="ELSE: dyn TS"];
+ bsc_ts box bsc_ts [label="ts->pchan = NONE"];
+ --- [label="END: dyn TS"];
+ bsc_ts note bsc_lchan [label="Normally, the lchan FSM never terminates. Logic dictates that
+ the lchan is a child of the timeslot FSM, but it's not actually of functional importance
+ beyond basic sanity. Parent term event: TS_EV_LCHAN_UNUSED"];
+ bsc_ts box bsc_ts [label="Determine N = maximum number of lchans applicable to pchan_on_init"];
+ bsc_ts -> bsc_lchan;
+ bsc_lchan box bsc_lchan [label="ts->lchan[all N].type = osmo_fsm_inst_alloc(lchan_fsm)"];
+ bsc_lchan abox bsc_lchan [label="LCHAN_ST_UNUSED\n(initial state)"];
+ bsc_ts -> bsc_ts [label="ts_check_init()"];
+ ...;
+ bsc -> bsc_ts [label="RSL bootstrapped"];
+ bsc_ts -> bsc_ts [label="ts_check_init()"];
+ ...;
+ bsc_ts box bsc_ts [label="ts_check_init()"];
+ --- [label="as soon as both OML and RSL are ready:"];
+ bsc_ts box bsc_ts [label="ts_on_init()"];
+ bsc_ts abox bsc_ts [label="TS_ST_UNUSED"];
+ --- [label="dyn TS"];
+ bsc_ts box bsc_ts [label="onenter of TS_ST_UNUSED:"];
+ bsc_ts abox bsc_ts [label="TS_ST_WAIT_PDCH_ACT"];
+ ... [label="..."];
+ bsc_ts abox bsc_ts [label="PDCH"];
+ --- [label="END: dyn TS"];
+ --- [label="END: OML and RSL ready"];
+ ...;
+ bsc box bsc [label="lchan_select() picks an unused lchan"];
+ bsc -> bsc_lchan [label="lchan_allocate()"];
+ bsc_lchan -> bsc_ts [label="TS_EV_LCHAN_REQUESTED"];
+
+ --- [label="dyn TS"];
+ bsc_ts rbox bsc_ts [label="possibly switch from PDCH...\n(see timeslot FSM)"];
+ bsc_ts box bsc_ts [label="ts->pchan =\n requested GSM_PCHAN_XXX type"];
+ ---;
+
+ bsc_ts -> bsc_lchan [label="LCHAN_EV_TS_READY"];
+ bsc_lchan note bsc_lchan [label="RSL Chan Alloc and so fort..."];
+ bsc_lchan abox bsc_lchan [label="LCHAN_ST_ESTABLISHED"];
+ ...;
+ bsc -> bsc_lchan [label="LCHAN_EV_RELEASE"];
+ bsc_lchan note bsc_lchan [label="...RSL RF Chan Release..."];
+ bsc_lchan abox bsc_lchan [label="LCHAN_ST_UNUSED"];
+ bsc_ts <- bsc_lchan [label="TS_EV_LCHAN_UNUSED"];
+ bsc_ts abox bsc_ts [label="TS_ST_UNUSED"];
+ --- [label="dyn TS"];
+ bsc_ts rbox bsc_ts [label="possibly switch to PDCH"];
+ ---;
+ ...;
+ ...;
+ bts rbox bsc_lchan [label="BTS RSL is dropped"];
+ bsc box bsc [label="ipaccess_drop_rsl()"];
+ bsc -> bsc_ts [label="ts[*]:"];
+ bsc_ts abox bsc_ts [label="TS_ST_NOT_INITIALIZED"];
+ bsc_ts note bsc_lchan [label="If it's just the RSL being dropped, transition lchan FSMs to
+ LCHAN_ST_UNUSED, but keep them allocated. Unless OML is re-established, any telnet
+ vty pchan modifications must not take effect."];
+ bsc_ts -> bsc_lchan [label="lchan[*]:"];
+ bsc_lchan abox bsc_lchan [label="LCHAN_ST_UNUSED"];
+ bsc_ts <- bsc_lchan [label="TS_EV_LCHAN_UNUSED (ignored)"];
+ ...;
+ --- [label="when RSL comes back later:"];
+ bsc -> bsc_ts [label="RSL bootstrapped"];
+ bsc_ts box bsc_ts [label="ts_check_init()"];
+ bsc_ts rbox bsc_ts [label="see ts_check_init() above"];
+ ...;
+ ...;
+ bts rbox bsc_lchan [label="BTS OML is dropped"];
+ bsc note bsc [label="As part of OML drop, RSL is also dropped:"];
+ bsc box bsc [label="ipaccess_drop_rsl()"];
+ bsc -> bsc_ts [label="ts[*]:"];
+ bsc_ts abox bsc_ts [label="TS_ST_NOT_INITIALIZED"];
+ bsc rbox bsc [label="see 'BTS RSL is dropped' above"];
+ bsc -> bsc_ts [label="ts[*]:"];
+ bsc_ts -> bsc_lchan [label="lchan[*]:"];
+ bsc_lchan box bsc_lchan [label="osmo_fsm_inst_term()"];
+ bsc_ts <- bsc_lchan [label="TS_EV_LCHAN_UNUSED (ignored)"];
+ bsc_lchan box bsc_lchan [label="lchan->fi = NULL"];
+ bsc rbox bsc [label="Continue at 'Starting Operation'"];
+
+}
diff --git a/git-version-gen b/git-version-gen
new file mode 100755
index 000000000..42cf3d2bd
--- /dev/null
+++ b/git-version-gen
@@ -0,0 +1,151 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-01-28.01
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+# produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+# presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+# a checked-out repository. Created with contents that were learned at
+# the last time autoconf was run, and used by git-version-gen. Must not
+# be present in either $(srcdir) or $(builddir) for git-version-gen to
+# give accurate answers during normal development with a checked out tree,
+# but must be present in a tarball when there is no version control system.
+# Therefore, it cannot be used in any dependencies. GNUmakefile has
+# hooks to force a reconfigure at distribution time to get the value
+# correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+# tarball. Usable in dependencies, particularly for files that don't
+# want to depend on config.h but do want to track version changes.
+# Delete this file prior to any autoconf run where you want to rebuild
+# files to pick up a version string change; and leave it stale to
+# minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+# [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+# echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+# echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+ 1) ;;
+ *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+ v=`cat $tarball_version_file` || exit 1
+ case $v in
+ *$nl*) v= ;; # reject multi-line output
+ [0-9]*) ;;
+ *) v= ;;
+ esac
+ test -z "$v" \
+ && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+ : # use $v
+elif
+ v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+ || git describe --abbrev=4 HEAD 2>/dev/null` \
+ && case $v in
+ [0-9]*) ;;
+ v[0-9]*) ;;
+ *) (exit 1) ;;
+ esac
+then
+ # Is this a new git that lists number of commits since the last
+ # tag or the previous older version that did not?
+ # Newer: v6.10-77-g0f8faeb
+ # Older: v6.10-g0f8faeb
+ case $v in
+ *-*-*) : git describe is okay three part flavor ;;
+ *-*)
+ : git describe is older two part flavor
+ # Recreate the number of commits and rewrite such that the
+ # result is the same as if we were using the newer version
+ # of git describe.
+ vtag=`echo "$v" | sed 's/-.*//'`
+ numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+ v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+ ;;
+ esac
+
+ # Change the first '-' to a '.', so version-comparing tools work properly.
+ # Remove the "g" in git describe's output string, to save a byte.
+ v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+ v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+ '') ;;
+ *) # Append the suffix only if there isn't one already.
+ case $v in
+ *-dirty) ;;
+ *) v="$v-dirty" ;;
+ esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 000000000..740e0887e
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,8 @@
+SUBDIRS = \
+ osmocom \
+ $(NULL)
+
+noinst_HEADERS = \
+ mISDNif.h \
+ compat_af_isdn.h \
+ $(NULL)
diff --git a/include/compat_af_isdn.h b/include/compat_af_isdn.h
new file mode 100644
index 000000000..56cbfb3f2
--- /dev/null
+++ b/include/compat_af_isdn.h
@@ -0,0 +1,39 @@
+#ifdef MISDN_OLD_AF_COMPATIBILITY
+#undef AF_ISDN
+#undef PF_ISDN
+
+extern int AF_ISDN;
+#define PF_ISDN AF_ISDN
+
+int AF_ISDN;
+
+#endif
+
+extern void init_af_isdn(void);
+
+#ifdef AF_COMPATIBILITY_FUNC
+#ifdef MISDN_OLD_AF_COMPATIBILITY
+void init_af_isdn(void)
+{
+ int s;
+
+ /* test for new value */
+ AF_ISDN = 34;
+ s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE);
+ if (s >= 0) {
+ close(s);
+ return;
+ }
+ AF_ISDN = 27;
+ s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE);
+ if (s >= 0) {
+ close(s);
+ return;
+ }
+}
+#else
+void init_af_isdn(void)
+{
+}
+#endif
+#endif
diff --git a/include/mISDNif.h b/include/mISDNif.h
new file mode 100644
index 000000000..8e065d24b
--- /dev/null
+++ b/include/mISDNif.h
@@ -0,0 +1,387 @@
+/*
+ *
+ * Author Karsten Keil <kkeil@novell.com>
+ *
+ * Copyright 2008 by Karsten Keil <kkeil@novell.com>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This code 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 LESSER GENERAL PUBLIC LICENSE for more details.
+ *
+ */
+
+#ifndef mISDNIF_H
+#define mISDNIF_H
+
+#include <stdarg.h>
+#ifdef linux
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/socket.h>
+#else
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#endif
+
+/*
+ * ABI Version 32 bit
+ *
+ * <8 bit> Major version
+ * - changed if any interface become backwards incompatible
+ *
+ * <8 bit> Minor version
+ * - changed if any interface is extended but backwards compatible
+ *
+ * <16 bit> Release number
+ * - should be incremented on every checkin
+ */
+#define MISDN_MAJOR_VERSION 1
+#define MISDN_MINOR_VERSION 1
+#define MISDN_RELEASE 20
+
+/* primitives for information exchange
+ * generell format
+ * <16 bit 0 >
+ * <8 bit command>
+ * BIT 8 = 1 LAYER private
+ * BIT 7 = 1 answer
+ * BIT 6 = 1 DATA
+ * <8 bit target layer mask>
+ *
+ * Layer = 00 is reserved for general commands
+ Layer = 01 L2 -> HW
+ Layer = 02 HW -> L2
+ Layer = 04 L3 -> L2
+ Layer = 08 L2 -> L3
+ * Layer = FF is reserved for broadcast commands
+ */
+
+#define MISDN_CMDMASK 0xff00
+#define MISDN_LAYERMASK 0x00ff
+
+/* generell commands */
+#define OPEN_CHANNEL 0x0100
+#define CLOSE_CHANNEL 0x0200
+#define CONTROL_CHANNEL 0x0300
+#define CHECK_DATA 0x0400
+
+/* layer 2 -> layer 1 */
+#define PH_ACTIVATE_REQ 0x0101
+#define PH_DEACTIVATE_REQ 0x0201
+#define PH_DATA_REQ 0x2001
+#define MPH_ACTIVATE_REQ 0x0501
+#define MPH_DEACTIVATE_REQ 0x0601
+#define MPH_INFORMATION_REQ 0x0701
+#define PH_CONTROL_REQ 0x0801
+
+/* layer 1 -> layer 2 */
+#define PH_ACTIVATE_IND 0x0102
+#define PH_ACTIVATE_CNF 0x4102
+#define PH_DEACTIVATE_IND 0x0202
+#define PH_DEACTIVATE_CNF 0x4202
+#define PH_DATA_IND 0x2002
+#define PH_DATA_E_IND 0x3002
+#define MPH_ACTIVATE_IND 0x0502
+#define MPH_DEACTIVATE_IND 0x0602
+#define MPH_INFORMATION_IND 0x0702
+#define PH_DATA_CNF 0x6002
+#define PH_CONTROL_IND 0x0802
+#define PH_CONTROL_CNF 0x4802
+
+/* layer 3 -> layer 2 */
+#define DL_ESTABLISH_REQ 0x1004
+#define DL_RELEASE_REQ 0x1104
+#define DL_DATA_REQ 0x3004
+#define DL_UNITDATA_REQ 0x3104
+#define DL_INFORMATION_REQ 0x0004
+
+/* layer 2 -> layer 3 */
+#define DL_ESTABLISH_IND 0x1008
+#define DL_ESTABLISH_CNF 0x5008
+#define DL_RELEASE_IND 0x1108
+#define DL_RELEASE_CNF 0x5108
+#define DL_DATA_IND 0x3008
+#define DL_UNITDATA_IND 0x3108
+#define DL_INFORMATION_IND 0x0008
+
+/* intern layer 2 managment */
+#define MDL_ASSIGN_REQ 0x1804
+#define MDL_ASSIGN_IND 0x1904
+#define MDL_REMOVE_REQ 0x1A04
+#define MDL_REMOVE_IND 0x1B04
+#define MDL_STATUS_UP_IND 0x1C04
+#define MDL_STATUS_DOWN_IND 0x1D04
+#define MDL_STATUS_UI_IND 0x1E04
+#define MDL_ERROR_IND 0x1F04
+#define MDL_ERROR_RSP 0x5F04
+
+/* DL_INFORMATION_IND types */
+#define DL_INFO_L2_CONNECT 0x0001
+#define DL_INFO_L2_REMOVED 0x0002
+
+/* PH_CONTROL types */
+/* TOUCH TONE IS 0x20XX XX "0"..."9", "A","B","C","D","*","#" */
+#define DTMF_TONE_VAL 0x2000
+#define DTMF_TONE_MASK 0x007F
+#define DTMF_TONE_START 0x2100
+#define DTMF_TONE_STOP 0x2200
+#define DTMF_HFC_COEF 0x4000
+#define DSP_CONF_JOIN 0x2403
+#define DSP_CONF_SPLIT 0x2404
+#define DSP_RECEIVE_OFF 0x2405
+#define DSP_RECEIVE_ON 0x2406
+#define DSP_ECHO_ON 0x2407
+#define DSP_ECHO_OFF 0x2408
+#define DSP_MIX_ON 0x2409
+#define DSP_MIX_OFF 0x240a
+#define DSP_DELAY 0x240b
+#define DSP_JITTER 0x240c
+#define DSP_TXDATA_ON 0x240d
+#define DSP_TXDATA_OFF 0x240e
+#define DSP_TX_DEJITTER 0x240f
+#define DSP_TX_DEJ_OFF 0x2410
+#define DSP_TONE_PATT_ON 0x2411
+#define DSP_TONE_PATT_OFF 0x2412
+#define DSP_VOL_CHANGE_TX 0x2413
+#define DSP_VOL_CHANGE_RX 0x2414
+#define DSP_BF_ENABLE_KEY 0x2415
+#define DSP_BF_DISABLE 0x2416
+#define DSP_BF_ACCEPT 0x2416
+#define DSP_BF_REJECT 0x2417
+#define DSP_PIPELINE_CFG 0x2418
+#define HFC_VOL_CHANGE_TX 0x2601
+#define HFC_VOL_CHANGE_RX 0x2602
+#define HFC_SPL_LOOP_ON 0x2603
+#define HFC_SPL_LOOP_OFF 0x2604
+
+/* DSP_TONE_PATT_ON parameter */
+#define TONE_OFF 0x0000
+#define TONE_GERMAN_DIALTONE 0x0001
+#define TONE_GERMAN_OLDDIALTONE 0x0002
+#define TONE_AMERICAN_DIALTONE 0x0003
+#define TONE_GERMAN_DIALPBX 0x0004
+#define TONE_GERMAN_OLDDIALPBX 0x0005
+#define TONE_AMERICAN_DIALPBX 0x0006
+#define TONE_GERMAN_RINGING 0x0007
+#define TONE_GERMAN_OLDRINGING 0x0008
+#define TONE_AMERICAN_RINGPBX 0x000b
+#define TONE_GERMAN_RINGPBX 0x000c
+#define TONE_GERMAN_OLDRINGPBX 0x000d
+#define TONE_AMERICAN_RINGING 0x000e
+#define TONE_GERMAN_BUSY 0x000f
+#define TONE_GERMAN_OLDBUSY 0x0010
+#define TONE_AMERICAN_BUSY 0x0011
+#define TONE_GERMAN_HANGUP 0x0012
+#define TONE_GERMAN_OLDHANGUP 0x0013
+#define TONE_AMERICAN_HANGUP 0x0014
+#define TONE_SPECIAL_INFO 0x0015
+#define TONE_GERMAN_GASSENBESETZT 0x0016
+#define TONE_GERMAN_AUFSCHALTTON 0x0016
+
+/* MPH_INFORMATION_IND */
+#define L1_SIGNAL_LOS_OFF 0x0010
+#define L1_SIGNAL_LOS_ON 0x0011
+#define L1_SIGNAL_AIS_OFF 0x0012
+#define L1_SIGNAL_AIS_ON 0x0013
+#define L1_SIGNAL_RDI_OFF 0x0014
+#define L1_SIGNAL_RDI_ON 0x0015
+#define L1_SIGNAL_SLIP_RX 0x0020
+#define L1_SIGNAL_SLIP_TX 0x0021
+
+/*
+ * protocol ids
+ * D channel 1-31
+ * B channel 33 - 63
+ */
+
+#define ISDN_P_NONE 0
+#define ISDN_P_BASE 0
+#define ISDN_P_TE_S0 0x01
+#define ISDN_P_NT_S0 0x02
+#define ISDN_P_TE_E1 0x03
+#define ISDN_P_NT_E1 0x04
+#define ISDN_P_TE_UP0 0x05
+#define ISDN_P_NT_UP0 0x06
+
+#define IS_ISDN_P_TE(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_TE_E1) || \
+ (p == ISDN_P_TE_UP0) || (p == ISDN_P_LAPD_TE))
+#define IS_ISDN_P_NT(p) ((p == ISDN_P_NT_S0) || (p == ISDN_P_NT_E1) || \
+ (p == ISDN_P_NT_UP0) || (p == ISDN_P_LAPD_NT))
+#define IS_ISDN_P_S0(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_NT_S0))
+#define IS_ISDN_P_E1(p) ((p == ISDN_P_TE_E1) || (p == ISDN_P_NT_E1))
+#define IS_ISDN_P_UP0(p) ((p == ISDN_P_TE_UP0) || (p == ISDN_P_NT_UP0))
+
+
+#define ISDN_P_LAPD_TE 0x10
+#define ISDN_P_LAPD_NT 0x11
+
+#define ISDN_P_B_MASK 0x1f
+#define ISDN_P_B_START 0x20
+
+#define ISDN_P_B_RAW 0x21
+#define ISDN_P_B_HDLC 0x22
+#define ISDN_P_B_X75SLP 0x23
+#define ISDN_P_B_L2DTMF 0x24
+#define ISDN_P_B_L2DSP 0x25
+#define ISDN_P_B_L2DSPHDLC 0x26
+
+#define OPTION_L2_PMX 1
+#define OPTION_L2_PTP 2
+#define OPTION_L2_FIXEDTEI 3
+#define OPTION_L2_CLEANUP 4
+
+/* should be in sync with linux/kobject.h:KOBJ_NAME_LEN */
+#define MISDN_MAX_IDLEN 20
+
+struct mISDNhead {
+ unsigned int prim;
+ unsigned int id;
+} __attribute__((packed));
+
+#define MISDN_HEADER_LEN sizeof(struct mISDNhead)
+#define MAX_DATA_SIZE 2048
+#define MAX_DATA_MEM (MAX_DATA_SIZE + MISDN_HEADER_LEN)
+#define MAX_DFRAME_LEN 260
+
+#define MISDN_ID_ADDR_MASK 0xFFFF
+#define MISDN_ID_TEI_MASK 0xFF00
+#define MISDN_ID_SAPI_MASK 0x00FF
+#define MISDN_ID_TEI_ANY 0x7F00
+
+#define MISDN_ID_ANY 0xFFFF
+#define MISDN_ID_NONE 0xFFFE
+
+#define GROUP_TEI 127
+#define TEI_SAPI 63
+#define CTRL_SAPI 0
+
+#define MISDN_MAX_CHANNEL 127
+#define MISDN_CHMAP_SIZE ((MISDN_MAX_CHANNEL + 1) >> 3)
+
+#define SOL_MISDN 0
+
+struct sockaddr_mISDN {
+ sa_family_t family;
+ unsigned char dev;
+ unsigned char channel;
+ unsigned char sapi;
+ unsigned char tei;
+};
+
+struct mISDNversion {
+ unsigned char major;
+ unsigned char minor;
+ unsigned short release;
+};
+
+#define MAX_DEVICE_ID 63
+
+struct mISDN_devinfo {
+ u_int id;
+ u_int Dprotocols;
+ u_int Bprotocols;
+ u_int protocol;
+ u_char channelmap[MISDN_CHMAP_SIZE];
+ u_int nrbchan;
+ char name[MISDN_MAX_IDLEN];
+};
+
+struct mISDN_devrename {
+ u_int id;
+ char name[MISDN_MAX_IDLEN];
+};
+
+struct ph_info_ch {
+ int32_t protocol;
+ int64_t Flags;
+};
+
+struct ph_info_dch {
+ struct ph_info_ch ch;
+ int16_t state;
+ int16_t num_bch;
+};
+
+struct ph_info {
+ struct ph_info_dch dch;
+ struct ph_info_ch bch[];
+};
+
+/* timer device ioctl */
+#define IMADDTIMER _IOR('I', 64, int)
+#define IMDELTIMER _IOR('I', 65, int)
+/* socket ioctls */
+#define IMGETVERSION _IOR('I', 66, int)
+#define IMGETCOUNT _IOR('I', 67, int)
+#define IMGETDEVINFO _IOR('I', 68, int)
+#define IMCTRLREQ _IOR('I', 69, int)
+#define IMCLEAR_L2 _IOR('I', 70, int)
+#define IMSETDEVNAME _IOR('I', 71, struct mISDN_devrename)
+
+static inline int
+test_channelmap(u_int nr, u_char *map)
+{
+ if (nr <= MISDN_MAX_CHANNEL)
+ return map[nr >> 3] & (1 << (nr & 7));
+ else
+ return 0;
+}
+
+static inline void
+set_channelmap(u_int nr, u_char *map)
+{
+ map[nr >> 3] |= (1 << (nr & 7));
+}
+
+static inline void
+clear_channelmap(u_int nr, u_char *map)
+{
+ map[nr >> 3] &= ~(1 << (nr & 7));
+}
+
+/* CONTROL_CHANNEL parameters */
+#define MISDN_CTRL_GETOP 0x0000
+#define MISDN_CTRL_LOOP 0x0001
+#define MISDN_CTRL_CONNECT 0x0002
+#define MISDN_CTRL_DISCONNECT 0x0004
+#define MISDN_CTRL_PCMCONNECT 0x0010
+#define MISDN_CTRL_PCMDISCONNECT 0x0020
+#define MISDN_CTRL_SETPEER 0x0040
+#define MISDN_CTRL_UNSETPEER 0x0080
+#define MISDN_CTRL_RX_OFF 0x0100
+#define MISDN_CTRL_FILL_EMPTY 0x0200
+#define MISDN_CTRL_GETPEER 0x0400
+#define MISDN_CTRL_HW_FEATURES_OP 0x2000
+#define MISDN_CTRL_HW_FEATURES 0x2001
+#define MISDN_CTRL_HFC_OP 0x4000
+#define MISDN_CTRL_HFC_PCM_CONN 0x4001
+#define MISDN_CTRL_HFC_PCM_DISC 0x4002
+#define MISDN_CTRL_HFC_CONF_JOIN 0x4003
+#define MISDN_CTRL_HFC_CONF_SPLIT 0x4004
+#define MISDN_CTRL_HFC_RECEIVE_OFF 0x4005
+#define MISDN_CTRL_HFC_RECEIVE_ON 0x4006
+#define MISDN_CTRL_HFC_ECHOCAN_ON 0x4007
+#define MISDN_CTRL_HFC_ECHOCAN_OFF 0x4008
+
+
+/* socket options */
+#define MISDN_TIME_STAMP 0x0001
+
+struct mISDN_ctrl_req {
+ int op;
+ int channel;
+ int p1;
+ int p2;
+};
+
+/* muxer options */
+#define MISDN_OPT_ALL 1
+#define MISDN_OPT_TEIMGR 2
+
+#endif /* mISDNIF_H */
diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am
new file mode 100644
index 000000000..cf24c62a6
--- /dev/null
+++ b/include/osmocom/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = \
+ bsc \
+ $(NULL)
diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
new file mode 100644
index 000000000..02a4ad8e2
--- /dev/null
+++ b/include/osmocom/bsc/Makefile.am
@@ -0,0 +1,60 @@
+noinst_HEADERS = \
+ a_reset.h \
+ abis_nm.h \
+ abis_om2000.h \
+ abis_rsl.h \
+ acc_ramp.h \
+ arfcn_range_encode.h \
+ assignment_fsm.h \
+ bsc_msg_filter.h \
+ bsc_rll.h \
+ bsc_subscriber.h \
+ bsc_subscr_conn_fsm.h \
+ bss.h \
+ bts_ipaccess_nanobts_omlattr.h \
+ chan_alloc.h \
+ codec_pref.h \
+ ctrl.h \
+ debug.h \
+ e1_config.h \
+ gsm_04_08_rr.h \
+ gsm_04_80.h \
+ gsm_data.h \
+ gsm_timers.h \
+ handover.h \
+ handover_cfg.h \
+ handover_decision.h \
+ handover_decision_2.h \
+ handover_fsm.h \
+ handover_vty.h \
+ ipaccess.h \
+ lchan_fsm.h \
+ lchan_rtp_fsm.h \
+ lchan_select.h \
+ meas_feed.h \
+ meas_rep.h \
+ misdn.h \
+ mgw_endpoint_fsm.h \
+ neighbor_ident.h \
+ network_listen.h \
+ openbscdefines.h \
+ osmo_bsc.h \
+ osmo_bsc_grace.h \
+ osmo_bsc_rf.h \
+ osmo_bsc_sigtran.h \
+ bsc_msc_data.h \
+ osmux.h \
+ paging.h \
+ pcu_if.h \
+ pcuif_proto.h \
+ rest_octets.h \
+ rs232.h \
+ signal.h \
+ system_information.h \
+ timeslot_fsm.h \
+ ussd.h \
+ vty.h \
+ gsm_08_08.h \
+ penalty_timers.h \
+ osmo_bsc_lcls.h \
+ $(NULL)
diff --git a/include/osmocom/bsc/a_reset.h b/include/osmocom/bsc/a_reset.h
new file mode 100644
index 000000000..a09972e18
--- /dev/null
+++ b/include/osmocom/bsc/a_reset.h
@@ -0,0 +1,38 @@
+/* (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+struct bsc_msc_data;
+
+/* Create and start state machine which handles the reset/reset-ack procedure */
+void a_reset_alloc(struct bsc_msc_data *msc, const char *name, void *cb);
+
+/* Confirm that we sucessfully received a reset acknowlege message */
+void a_reset_ack_confirm(struct bsc_msc_data *msc);
+
+/* Report a failed connection */
+void a_reset_conn_fail(struct bsc_msc_data *msc);
+
+/* Report a successful connection */
+void a_reset_conn_success(struct bsc_msc_data *msc);
+
+/* Check if we have a connection to a specified msc */
+bool a_reset_conn_ready(struct bsc_msc_data *msc);
diff --git a/include/osmocom/bsc/abis_nm.h b/include/osmocom/bsc/abis_nm.h
new file mode 100644
index 000000000..45bbe2c02
--- /dev/null
+++ b/include/osmocom/bsc/abis_nm.h
@@ -0,0 +1,178 @@
+/* GSM Network Management 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-2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _NM_H
+#define _NM_H
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/gsm_data.h>
+
+/* max number of attributes represented as 3GPP TS 52.021 §9.4.62 SW Description array */
+#define MAX_BTS_ATTR 5
+
+/* The BCCH info from an ip.access test, in host byte order
+ * and already parsed... */
+struct ipac_bcch_info {
+ struct llist_head list;
+
+ uint16_t info_type;
+ uint8_t freq_qual;
+ uint16_t arfcn;
+ uint8_t rx_lev;
+ uint8_t rx_qual;
+ int16_t freq_err;
+ uint16_t frame_offset;
+ uint32_t frame_nr_offset;
+ uint8_t bsic;
+ struct osmo_cell_global_id cgi;
+ uint8_t ba_list_si2[16];
+ uint8_t ba_list_si2bis[16];
+ uint8_t ba_list_si2ter[16];
+ uint8_t ca_list_si1[16];
+};
+
+/* PUBLIC */
+
+struct msgb;
+
+struct abis_nm_cfg {
+ /* callback for unidirectional reports */
+ int (*report_cb)(struct msgb *,
+ struct abis_om_fom_hdr *);
+ /* callback for software activate requests from BTS */
+ int (*sw_act_req)(struct msgb *);
+};
+
+extern int abis_nm_rcvmsg(struct msgb *msg);
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len);
+int abis_nm_tlv_attr_primary_oml(struct tlv_parsed *tp, struct in_addr *ia, uint16_t *oml_port);
+int abis_nm_tlv_attr_unit_id(struct tlv_parsed *tp, char* unit_id, size_t buf_len);
+int abis_nm_rx(struct msgb *msg);
+int abis_nm_opstart(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, uint8_t i2);
+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);
+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);
+int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx,
+ uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot);
+int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
+ uint8_t e1_port, uint8_t e1_timeslot,
+ uint8_t e1_subslot);
+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);
+int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len);
+int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, uint8_t *attr, int attr_len);
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb);
+int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1,
+ uint8_t i2, uint8_t i3, int nack, uint8_t *attr, int att_len);
+int abis_nm_raw_msg(struct gsm_bts *bts, int len, uint8_t *msg);
+int abis_nm_event_reports(struct gsm_bts *bts, int on);
+int abis_nm_reset_resource(struct gsm_bts *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);
+int abis_nm_software_load_status(struct gsm_bts *bts);
+int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
+ gsm_cbfn *cbfn, void *cb_data);
+
+int abis_nm_conn_mdrop_link(struct gsm_bts *bts, uint8_t e1_port0, uint8_t ts0,
+ uint8_t e1_port1, uint8_t ts1);
+
+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);
+
+/* Siemens / BS-11 specific */
+int abis_nm_bs11_reset_resource(struct gsm_bts *bts);
+int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin);
+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);
+int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, uint8_t idx);
+int abis_nm_bs11_create_bport(struct gsm_bts *bts, uint8_t idx);
+int abis_nm_bs11_delete_object(struct gsm_bts *bts,
+ enum abis_bs11_objtype type, uint8_t idx);
+int abis_nm_bs11_delete_bport(struct gsm_bts *bts, uint8_t idx);
+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);
+int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts);
+int abis_nm_bs11_get_serno(struct gsm_bts *bts);
+int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, uint8_t level);
+int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx);
+int abis_nm_bs11_logon(struct gsm_bts *bts, uint8_t level, const char *name, int on);
+int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on);
+int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on);
+int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password);
+int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked);
+int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts);
+int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value);
+int abis_nm_bs11_get_cclk(struct gsm_bts *bts);
+int abis_nm_bs11_get_state(struct gsm_bts *bts);
+int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname,
+ uint8_t win_size, int forced, gsm_cbfn *cbfn);
+int abis_nm_bs11_set_ext_time(struct gsm_bts *bts);
+int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, uint8_t bport);
+int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, uint8_t bport, enum abis_bs11_line_cfg line_cfg);
+int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect);
+int abis_nm_bs11_restart(struct gsm_bts *bts);
+
+/* ip.access nanoBTS specific commands */
+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);
+int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, uint8_t *attr,
+ int attr_len);
+int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx);
+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);
+int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx,
+ uint32_t ip, uint16_t port, uint8_t stream);
+void abis_nm_ipaccess_cgi(uint8_t *buf, struct gsm_bts *bts);
+int ipac_parse_bcch_info(struct ipac_bcch_info *binf, uint8_t *buf);
+const char *ipacc_testres_name(uint8_t res);
+
+/* Functions calling into other code parts */
+bool all_trx_rsl_connected_unlocked(const struct gsm_bts *bts);
+bool nm_is_running(const struct gsm_nm_state *s);
+
+int abis_nm_vty_init(void);
+
+void abis_nm_clear_queue(struct gsm_bts *bts);
+
+int _abis_nm_sendmsg(struct msgb *msg);
+
+void abis_nm_queue_send_next(struct gsm_bts *bts); /* for bs11_config. */
+
+int abis_nm_select_newest_sw(const struct abis_nm_sw_desc *sw, const size_t len);
+
+/* Helper functions for updating attributes */
+int abis_nm_update_max_power_red(struct gsm_bts_trx *trx);
+
+struct gsm_bts_trx_ts *abis_nm_get_ts(const struct msgb *oml_msg);
+
+#endif /* _NM_H */
diff --git a/include/osmocom/bsc/abis_om2000.h b/include/osmocom/bsc/abis_om2000.h
new file mode 100644
index 000000000..b093a0350
--- /dev/null
+++ b/include/osmocom/bsc/abis_om2000.h
@@ -0,0 +1,129 @@
+#ifndef OPENBSC_ABIS_OM2K_H
+#define OPENBSC_ABIS_OM2K_H
+/* 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 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+enum abis_om2k_mo_cls {
+ OM2K_MO_CLS_TRXC = 0x01,
+ OM2K_MO_CLS_TS = 0x03,
+ OM2K_MO_CLS_TF = 0x04,
+ OM2K_MO_CLS_IS = 0x05,
+ OM2K_MO_CLS_CON = 0x06,
+ OM2K_MO_CLS_DP = 0x07,
+ OM2K_MO_CLS_CF = 0x0a,
+ OM2K_MO_CLS_TX = 0x0b,
+ OM2K_MO_CLS_RX = 0x0c,
+};
+
+enum om2k_mo_state {
+ OM2K_MO_S_RESET = 0,
+ OM2K_MO_S_STARTED,
+ OM2K_MO_S_ENABLED,
+ OM2K_MO_S_DISABLED,
+};
+
+/* on-wire format for IS conn group */
+struct om2k_is_conn_grp {
+ uint16_t icp1;
+ uint16_t icp2;
+ uint8_t cont_idx;
+} __attribute__ ((packed));
+
+/* internal data formant for IS conn group */
+struct is_conn_group {
+ struct llist_head list;
+ uint16_t icp1;
+ uint16_t icp2;
+ uint8_t ci;
+};
+
+/* on-wire format for CON Path */
+struct om2k_con_path {
+ uint16_t ccp;
+ uint8_t ci;
+ uint8_t tag;
+ uint8_t tei;
+} __attribute__ ((packed));
+
+/* internal data format for CON group */
+struct con_group {
+ /* links list of CON groups in BTS */
+ struct llist_head list;
+ struct gsm_bts *bts;
+ /* CON Group ID */
+ uint8_t cg;
+ /* list of CON paths in this group */
+ struct llist_head paths;
+};
+
+/* internal data format for CON path */
+struct con_path {
+ /* links with con_group.paths */
+ struct llist_head list;
+ /* CON Connection Point */
+ uint16_t ccp;
+ /* Contiguity Index */
+ uint8_t ci;
+ /* Tag */
+ uint8_t tag;
+ /* TEI */
+ uint8_t tei;
+};
+
+extern const struct abis_om2k_mo om2k_mo_cf;
+extern const struct abis_om2k_mo om2k_mo_is;
+extern const struct abis_om2k_mo om2k_mo_con;
+extern const struct abis_om2k_mo om2k_mo_tf;
+
+extern const struct value_string om2k_mo_class_short_vals[];
+
+int abis_om2k_rcvmsg(struct msgb *msg);
+
+extern const struct abis_om2k_mo om2k_mo_cf;
+
+int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+ uint8_t operational);
+int abis_om2k_tx_cap_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_is_conf_req(struct gsm_bts *bts);
+int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts);
+int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx);
+int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx);
+int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts);
+
+struct osmo_fsm_inst *om2k_bts_fsm_start(struct gsm_bts *bts);
+void abis_om2k_bts_init(struct gsm_bts *bts);
+void abis_om2k_trx_init(struct gsm_bts_trx *trx);
+
+int abis_om2k_vty_init(void);
+
+struct vty;
+void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts);
+
+#endif /* OPENBCS_ABIS_OM2K_H */
diff --git a/include/osmocom/bsc/abis_rsl.h b/include/osmocom/bsc/abis_rsl.h
new file mode 100644
index 000000000..098d2e6ee
--- /dev/null
+++ b/include/osmocom/bsc/abis_rsl.h
@@ -0,0 +1,120 @@
+/* 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 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _RSL_H
+#define _RSL_H
+
+#include <stdbool.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/bsc/gsm_data.h>
+
+struct gsm_bts;
+struct gsm_lchan;
+struct gsm_bts_trx_ts;
+
+#define GSM48_LEN2PLEN(a) (((a) << 2) | 1)
+
+const char *ip_to_a(uint32_t ip);
+
+int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len);
+int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type,
+ const uint8_t *data, int len);
+int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref);
+int rsl_chan_mode_modify_req(struct gsm_lchan *ts);
+int rsl_encryption_cmd(struct msgb *msg);
+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);
+int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val);
+int rsl_tx_imm_assignment(struct gsm_lchan *lchan);
+int rsl_tx_imm_ass_rej(struct gsm_bts *bts, struct gsm48_req_ref *rqd_ref);
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_command);
+
+int rsl_data_request(struct msgb *msg, uint8_t link_id);
+int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id);
+int rsl_relase_request(struct gsm_lchan *lchan, uint8_t link_id);
+
+/* Ericcson vendor specific RSL extensions */
+int rsl_ericsson_imm_assign_cmd(struct gsm_bts *bts, uint32_t tlli, uint8_t len, uint8_t *val);
+
+/* Siemens vendor-specific RSL extensions */
+int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci);
+
+/* ip.access specfic RSL extensions */
+struct msgb *rsl_make_ipacc_mdcx(const struct gsm_lchan *lchan, uint32_t dest_ip, uint16_t dest_port);
+int rsl_tx_ipacc_crcx(const struct gsm_lchan *lchan);
+int rsl_tx_ipacc_mdcx(const struct gsm_lchan *lchan);
+int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan);
+int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act);
+
+int abis_rsl_rcvmsg(struct msgb *msg);
+
+int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id,
+ enum rsl_rel_mode release_mode);
+
+int rsl_lchan_mark_broken(struct gsm_lchan *lchan, const char *broken);
+
+/* to be provided by external code */
+int rsl_deact_sacch(struct gsm_lchan *lchan);
+
+/* BCCH related code */
+int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf);
+int rsl_ccch_conf_to_bs_ccch_sdcch_comb(int ccch_conf);
+
+int rsl_sacch_info_modify(struct gsm_lchan *lchan, uint8_t type,
+ const uint8_t *data, int len);
+
+int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db);
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm);
+
+/* SMSCB functionality */
+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);
+
+/* some Nokia specific stuff */
+int rsl_nokia_si_begin(struct gsm_bts_trx *trx);
+int rsl_nokia_si_end(struct gsm_bts_trx *trx);
+
+/* required for Nokia BTS power control */
+int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduction);
+
+
+int rsl_release_sapis_from(struct gsm_lchan *lchan, int start,
+ enum rsl_rel_mode release_mode);
+int rsl_start_t3109(struct gsm_lchan *lchan);
+
+int rsl_direct_rf_release(struct gsm_lchan *lchan);
+
+int rsl_tx_dyn_ts_pdch_act_deact(struct gsm_bts_trx_ts *ts, bool activate);
+
+int rsl_forward_layer3_info(struct gsm_lchan *lchan, const uint8_t *l3_info, uint8_t l3_info_len);
+
+int ipacc_speech_mode(enum gsm48_chan_mode tch_mode, enum gsm_chan_t type);
+void ipacc_speech_mode_set_direction(uint8_t *speech_mode, bool send);
+int ipacc_payload_type(enum gsm48_chan_mode tch_mode, enum gsm_chan_t type);
+
+int rsl_tx_rf_chan_release(struct gsm_lchan *lchan);
+
+#endif /* RSL_MT_H */
+
diff --git a/include/osmocom/bsc/acc_ramp.h b/include/osmocom/bsc/acc_ramp.h
new file mode 100644
index 000000000..efb12b088
--- /dev/null
+++ b/include/osmocom/bsc/acc_ramp.h
@@ -0,0 +1,161 @@
+/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Stefan Sperling <ssperling@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+/*!
+ * Access control class (ACC) ramping is used to slowly make the cell available to
+ * an increasing number of MS. This avoids overload at startup time in cases where
+ * a lot of MS would discover the new cell and try to connect to it all at once.
+ */
+
+#define ACC_RAMP_STEP_SIZE_MIN 1 /* allow at most 1 new ACC per ramp step */
+#define ACC_RAMP_STEP_SIZE_DEFAULT ACC_RAMP_STEP_SIZE_MIN
+#define ACC_RAMP_STEP_SIZE_MAX 10 /* allow all ACC in one step (effectively disables ramping) */
+
+#define ACC_RAMP_STEP_INTERVAL_MIN 30 /* 30 seconds */
+#define ACC_RAMP_STEP_INTERVAL_MAX 600 /* 10 minutes */
+
+/*!
+ * Data structure used to manage ACC ramping. Please avoid setting or reading fields
+ * in this structure directly. Use the accessor functions below instead.
+ */
+struct acc_ramp {
+ struct gsm_bts *bts; /*!< backpointer to BTS using this ACC ramp */
+
+ bool acc_ramping_enabled; /*!< whether ACC ramping is enabled */
+
+ /*!
+ * Bitmask which keeps track of access control classes that are currently denied
+ * access. The function acc_ramp_apply() uses this mask to modulate bits from
+ * octets 2 and 3 in RACH Control Parameters (see 3GPP 44.018 10.5.2.29).
+ * Ramping is only concerned with ACCs 0-9. While any of the bits 0-9 is set,
+ * the corresponding ACC is barred.
+ * ACCs 11-15 should always be allowed, and ACC 10 denies emergency calls for
+ * all ACCs from 0-9 inclusive; these ACCs are ignored in this implementation.
+ */
+ uint16_t barred_accs;
+
+ /*!
+ * This controls the maximum number of ACCs to allow per ramping step (1 - 10).
+ * The compile-time default value is ACC_RAMP_STEP_SIZE_DEFAULT.
+ * This value can be changed by VTY configuration.
+ * A value of ACC_RAMP_STEP_SIZE_MAX effectively disables ramping.
+ */
+ unsigned int step_size;
+
+ /*!
+ * Ramping step interval in seconds.
+ * This value depends on the current BTS channel load average, unless
+ * it has been overriden by VTY configuration.
+ */
+ unsigned int step_interval_sec;
+ bool step_interval_is_fixed;
+ struct osmo_timer_list step_timer;
+};
+
+/*!
+ * Enable or disable ACC ramping.
+ * When enabled, ramping begins once acc_ramp_start() is called.
+ * When disabled, an ACC ramping process in progress will continue
+ * unless acc_ramp_abort() is called as well.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline void acc_ramp_set_enabled(struct acc_ramp *acc_ramp, bool enable)
+{
+ acc_ramp->acc_ramping_enabled = enable;
+}
+
+/*!
+ * Return true if ACC ramping is currently enabled, else false.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline bool acc_ramp_is_enabled(struct acc_ramp *acc_ramp)
+{
+ return acc_ramp->acc_ramping_enabled;
+}
+
+/*!
+ * Return the current ACC ramp step size.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline unsigned int acc_ramp_get_step_size(struct acc_ramp *acc_ramp)
+{
+ return acc_ramp->step_size;
+}
+
+/*!
+ * Return the current ACC ramp step interval (in seconds)
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline unsigned int acc_ramp_get_step_interval(struct acc_ramp *acc_ramp)
+{
+ return acc_ramp->step_interval_sec;
+}
+
+/*!
+ * If the step interval is dynamic, return true, else return false.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline bool acc_ramp_step_interval_is_dynamic(struct acc_ramp *acc_ramp)
+{
+ return !(acc_ramp->step_interval_is_fixed);
+}
+
+/*!
+ * Return bitmasks which correspond to access control classes that are currently
+ * denied access. Ramping is only concerned with those bits which control access
+ * for ACCs 0-9, and any of the other bits will always be set to zero in these masks, i.e.
+ * it is safe to OR these bitmasks with the corresponding fields in struct gsm48_rach_control.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline uint8_t acc_ramp_get_barred_t2(struct acc_ramp *acc_ramp)
+{
+ return ((acc_ramp->barred_accs >> 8) & 0x03);
+};
+static inline uint8_t acc_ramp_get_barred_t3(struct acc_ramp *acc_ramp)
+{
+ return (acc_ramp->barred_accs & 0xff);
+}
+
+/*!
+ * Potentially mark certain Access Control Classes (ACCs) as barred in accordance to ACC ramping.
+ * \param[in] rach_control RACH control parameters in which barred ACCs will be configured.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+static inline void acc_ramp_apply(struct gsm48_rach_control *rach_control, struct acc_ramp *acc_ramp)
+{
+ rach_control->t2 |= acc_ramp_get_barred_t2(acc_ramp);
+ rach_control->t3 |= acc_ramp_get_barred_t3(acc_ramp);
+}
+
+void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts);
+int acc_ramp_set_step_size(struct acc_ramp *acc_ramp, unsigned int step_size);
+int acc_ramp_set_step_interval(struct acc_ramp *acc_ramp, unsigned int step_interval);
+void acc_ramp_set_step_interval_dynamic(struct acc_ramp *acc_ramp);
+void acc_ramp_trigger(struct acc_ramp *acc_ramp);
+void acc_ramp_abort(struct acc_ramp *acc_ramp);
diff --git a/include/osmocom/bsc/arfcn_range_encode.h b/include/osmocom/bsc/arfcn_range_encode.h
new file mode 100644
index 000000000..7ec710c33
--- /dev/null
+++ b/include/osmocom/bsc/arfcn_range_encode.h
@@ -0,0 +1,26 @@
+#ifndef ARFCN_RANGE_ENCODE_H
+#define ARFCN_RANGE_ENCODE_H
+
+#include <stdint.h>
+
+enum gsm48_range {
+ ARFCN_RANGE_INVALID = -1,
+ ARFCN_RANGE_128 = 127,
+ ARFCN_RANGE_256 = 255,
+ ARFCN_RANGE_512 = 511,
+ ARFCN_RANGE_1024 = 1023,
+};
+
+#define RANGE_ENC_MAX_ARFCNS 29
+
+int range_enc_determine_range(const int *arfcns, int size, int *f0_out);
+int range_enc_arfcns(enum gsm48_range rng, const int *arfcns, int sze, int *out, int idx);
+int range_enc_find_index(enum gsm48_range rng, const int *arfcns, int size);
+int range_enc_filter_arfcns(int *arfcns, const int sze, const int f0, int *f0_included);
+
+int range_enc_range128(uint8_t *chan_list, int f0, int *w);
+int range_enc_range256(uint8_t *chan_list, int f0, int *w);
+int range_enc_range512(uint8_t *chan_list, int f0, int *w);
+int range_enc_range1024(uint8_t *chan_list, int f0, int f0_incl, int *w);
+
+#endif
diff --git a/include/osmocom/bsc/assignment_fsm.h b/include/osmocom/bsc/assignment_fsm.h
new file mode 100644
index 000000000..156da42fa
--- /dev/null
+++ b/include/osmocom/bsc/assignment_fsm.h
@@ -0,0 +1,44 @@
+/* osmo-bsc API to manage BSSMAP Assignment Command */
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <osmocom/bsc/debug.h>
+
+/* This macro automatically includes a final \n, if omitted. */
+#define LOG_ASSIGNMENT(conn, level, fmt, args...) do { \
+ if (conn->assignment.fi) \
+ LOGPFSML(conn->assignment.fi, level, "%s%s" fmt, \
+ conn->assignment.new_lchan ? gsm_lchan_name(conn->assignment.new_lchan) : "", \
+ conn->assignment.new_lchan ? " " : "", \
+ ## args); \
+ else \
+ LOGP(DMSC, level, "Assignment%s%s: " fmt, \
+ conn->assignment.new_lchan ? " of " : "", \
+ conn->assignment.new_lchan ? gsm_lchan_name(conn->assignment.new_lchan) : "", \
+ ## args); \
+ } while(0)
+
+enum assignment_fsm_state {
+ ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE,
+ ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE,
+ ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED,
+ ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC,
+};
+
+enum assignment_fsm_event {
+ ASSIGNMENT_EV_LCHAN_ACTIVE,
+ ASSIGNMENT_EV_LCHAN_ESTABLISHED,
+ ASSIGNMENT_EV_LCHAN_ERROR,
+ ASSIGNMENT_EV_MSC_MGW_OK,
+ ASSIGNMENT_EV_MSC_MGW_FAIL,
+ ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE,
+ ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL,
+ ASSIGNMENT_EV_CONN_RELEASING,
+};
+
+void assignment_fsm_init();
+
+void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts *bts,
+ struct assignment_request *req);
+void assignment_reset(struct gsm_subscriber_connection *conn);
diff --git a/include/osmocom/bsc/bsc_msc_data.h b/include/osmocom/bsc/bsc_msc_data.h
new file mode 100644
index 000000000..0c2094e3a
--- /dev/null
+++ b/include/osmocom/bsc/bsc_msc_data.h
@@ -0,0 +1,197 @@
+/*
+ * Data for the true BSC
+ *
+ * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2015 by On-Waves
+ * (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * NOTE: This is about a *remote* MSC for OsmoBSC and is not part of libmsc.
+ */
+
+#ifndef _OSMO_MSC_DATA_H
+#define _OSMO_MSC_DATA_H
+
+#include "debug.h"
+
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+
+#include <osmocom/sigtran/osmo_ss7.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+#include <osmocom/sigtran/protocol/sua.h>
+#include <osmocom/sigtran/protocol/m3ua.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/gsm23003.h>
+
+#include <regex.h>
+#include <errno.h>
+
+struct osmo_bsc_rf;
+struct gsm_network;
+
+enum {
+ MSC_CON_TYPE_NORMAL,
+ MSC_CON_TYPE_LOCAL,
+};
+
+enum bsc_lcls_mode {
+ BSC_LCLS_MODE_DISABLED,
+ BSC_LCLS_MODE_MGW_LOOP,
+ BSC_LCLS_MODE_BTS_LOOP,
+};
+
+extern const struct value_string bsc_lcls_mode_names[];
+
+static inline const char *bsc_lcls_mode_name(enum bsc_lcls_mode m)
+{
+ return get_value_string(bsc_lcls_mode_names, m);
+}
+
+/*! /brief Information on a remote MSC for libbsc.
+ */
+struct bsc_msc_data {
+ struct llist_head entry;
+
+ /* Back pointer */
+ struct gsm_network *network;
+
+ int allow_emerg;
+ int type;
+
+ /* local call routing */
+ char *local_pref;
+ regex_t local_pref_reg;
+
+
+ /* Connection data */
+ struct osmo_plmn_id core_plmn;
+ int core_lac;
+ int core_ci;
+ int rtp_base;
+ bool is_authenticated;
+
+ /* audio codecs */
+ struct gsm48_multi_rate_conf amr_conf;
+ struct gsm_audio_support **audio_support;
+ int audio_length;
+ enum bsc_lcls_mode lcls_mode;
+ bool lcls_codec_mismatch_allow;
+
+ /* ussd welcome text */
+ char *ussd_welcome_txt;
+
+ int nr;
+
+ /* ussd msc connection lost text */
+ char *ussd_msc_lost_txt;
+
+ /* ussd text when MSC has entered the grace period */
+ char *ussd_grace_txt;
+
+ char *acc_lst_name;
+
+ /* Sigtran connection data */
+ struct {
+ uint32_t cs7_instance;
+ bool cs7_instance_valid;
+ struct osmo_sccp_instance *sccp;
+ struct osmo_sccp_user *sccp_user;
+
+ /* IPA or M3UA or SUA? */
+ enum osmo_ss7_asp_protocol asp_proto;
+
+ /* Holds a copy of the our local MSC address,
+ * this will be the sccp-address that is associated
+ * with the A interface of this particular BSC,
+ * this address is filled up by the VTY interface */
+ struct osmo_sccp_addr bsc_addr;
+ char *bsc_addr_name;
+
+ /* Holds a copy of the MSC address. This is the
+ * address of the MSC that handles the calls of
+ * this BSC. The address is configured via the
+ * VTY interface */
+ struct osmo_sccp_addr msc_addr;
+ char *msc_addr_name;
+
+ /* Pointer to the osmo-fsm that controls the
+ * BSSMAP RESET procedure */
+ struct osmo_fsm_inst *reset_fsm;
+ } a;
+
+ uint32_t x_osmo_ign;
+ bool x_osmo_ign_configured;
+};
+
+/*
+ * Per BSC data.
+ */
+struct osmo_bsc_data {
+ struct gsm_network *network;
+
+ /* msc configuration */
+ struct llist_head mscs;
+
+ /* rf ctl related bits */
+ char *mid_call_txt;
+ int mid_call_timeout;
+ char *rf_ctrl_name;
+ struct osmo_bsc_rf *rf_ctrl;
+ int auto_off_timeout;
+
+ /* ussd text when there is no MSC available */
+ char *ussd_no_msc_txt;
+
+ char *acc_lst_name;
+};
+
+
+int osmo_bsc_msc_init(struct bsc_msc_data *msc);
+int osmo_bsc_sccp_init(struct gsm_network *gsmnet);
+
+int osmo_bsc_audio_init(struct gsm_network *network);
+
+struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *, int);
+struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *, int);
+
+/* Helper function to calculate the port number for a given
+ * timeslot/multiplex. This functionality is needed to support
+ * the sccp-lite scenario where the MGW is handled externally */
+static inline int mgcp_timeslot_to_port(int multiplex, int timeslot, int base)
+{
+ if (timeslot == 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "Timeslot should not be 0\n");
+ timeslot = 255;
+ }
+
+ return base + (timeslot + (32 * multiplex)) * 2;
+}
+
+static inline int mgcp_port_to_cic(uint16_t port, uint16_t base)
+{
+ if (port < base)
+ return -EINVAL;
+ return (port - base) / 2;
+}
+
+
+#endif
diff --git a/include/osmocom/bsc/bsc_msg_filter.h b/include/osmocom/bsc/bsc_msg_filter.h
new file mode 100644
index 000000000..fe8748526
--- /dev/null
+++ b/include/osmocom/bsc/bsc_msg_filter.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/msgfile.h>
+#include <osmocom/core/linuxrbtree.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <regex.h>
+
+struct vty;
+struct gsm48_hdr;
+
+struct bsc_filter_reject_cause {
+ int lu_reject_cause;
+ int cm_reject_cause;
+};
+
+struct bsc_filter_barr_entry {
+ struct rb_node node;
+
+ char *imsi;
+ int cm_reject_cause;
+ int lu_reject_cause;
+};
+
+enum bsc_filter_acc_ctr {
+ ACC_LIST_LOCAL_FILTER,
+ ACC_LIST_GLOBAL_FILTER,
+};
+
+struct bsc_msg_acc_lst {
+ struct llist_head list;
+
+ /* counter */
+ struct rate_ctr_group *stats;
+
+ /* the name of the list */
+ const char *name;
+ struct llist_head fltr_list;
+};
+
+struct bsc_msg_acc_lst_entry {
+ struct llist_head list;
+
+ /* the filter */
+ char *imsi_allow;
+ regex_t imsi_allow_re;
+ char *imsi_deny;
+ regex_t imsi_deny_re;
+
+ /* reject reasons for the access lists */
+ int cm_reject_cause;
+ int lu_reject_cause;
+};
+
+enum {
+ FLT_CON_TYPE_NONE,
+ FLT_CON_TYPE_LU,
+ FLT_CON_TYPE_CM_SERV_REQ,
+ FLT_CON_TYPE_PAG_RESP,
+ FLT_CON_TYPE_SSA,
+ FLT_CON_TYPE_LOCAL_REJECT,
+ FLT_CON_TYPE_OTHER,
+};
+
+
+struct bsc_filter_state {
+ char *imsi;
+ int imsi_checked;
+ int con_type;
+};
+
+struct bsc_filter_request {
+ void *ctx;
+ struct rb_root *black_list;
+ struct llist_head *access_lists;
+ const char *local_lst_name;
+ const char *global_lst_name;
+ int bsc_nr;
+};
+
+/**
+ * Content filtering.
+ */
+int bsc_msg_filter_initial(struct gsm48_hdr *hdr, size_t size,
+ struct bsc_filter_request *req,
+ int *con_type, char **imsi,
+ struct bsc_filter_reject_cause *cause);
+int bsc_msg_filter_data(struct gsm48_hdr *hdr, size_t size,
+ struct bsc_filter_request *req,
+ struct bsc_filter_state *state,
+ struct bsc_filter_reject_cause *cause);
+
+/* IMSI allow/deny handling */
+struct bsc_msg_acc_lst *bsc_msg_acc_lst_find(struct llist_head *lst, const char *name);
+struct bsc_msg_acc_lst *bsc_msg_acc_lst_get(void *ctx, struct llist_head *lst, const char *name);
+void bsc_msg_acc_lst_delete(struct bsc_msg_acc_lst *lst);
+
+struct bsc_msg_acc_lst_entry *bsc_msg_acc_lst_entry_create(struct bsc_msg_acc_lst *);
+int bsc_msg_acc_lst_check_allow(struct bsc_msg_acc_lst *lst, const char *imsi);
+
+void bsc_msg_acc_lst_vty_init(void *ctx, struct llist_head *lst, int node);
+void bsc_msg_acc_lst_write(struct vty *vty);
diff --git a/include/osmocom/bsc/bsc_rll.h b/include/osmocom/bsc/bsc_rll.h
new file mode 100644
index 000000000..3afe4a8f3
--- /dev/null
+++ b/include/osmocom/bsc/bsc_rll.h
@@ -0,0 +1,19 @@
+#ifndef _BSC_RLL_H
+#define _BSC_RLL_H
+
+#include <osmocom/bsc/gsm_data.h>
+
+enum bsc_rllr_ind {
+ BSC_RLLR_IND_EST_CONF,
+ BSC_RLLR_IND_REL_IND,
+ BSC_RLLR_IND_ERR_IND,
+ BSC_RLLR_IND_TIMEOUT,
+};
+
+int rll_establish(struct gsm_lchan *lchan, uint8_t link_id,
+ void (*cb)(struct gsm_lchan *, uint8_t, void *,
+ enum bsc_rllr_ind),
+ void *data);
+void rll_indication(struct gsm_lchan *lchan, uint8_t link_id, uint8_t type);
+
+#endif /* _BSC_RLL_H */
diff --git a/include/osmocom/bsc/bsc_subscr_conn_fsm.h b/include/osmocom/bsc/bsc_subscr_conn_fsm.h
new file mode 100644
index 000000000..f5ed7bdd8
--- /dev/null
+++ b/include/osmocom/bsc/bsc_subscr_conn_fsm.h
@@ -0,0 +1,82 @@
+#pragma once
+#include <osmocom/core/fsm.h>
+
+enum gscon_fsm_event {
+ /* local SCCP stack tells us incoming conn from MSC */
+ GSCON_EV_A_CONN_IND,
+ /* RSL side requests CONNECT to MSC */
+ GSCON_EV_A_CONN_REQ,
+ /* MSC confirms the SCCP connection */
+ GSCON_EV_A_CONN_CFM,
+ /* MSC has sent BSSMAP CLEAR CMD */
+ GSCON_EV_A_CLEAR_CMD,
+ /* MSC SCCP disconnect indication */
+ GSCON_EV_A_DISC_IND,
+
+ GSCON_EV_ASSIGNMENT_START,
+ GSCON_EV_ASSIGNMENT_END,
+
+ GSCON_EV_HANDOVER_START,
+ GSCON_EV_HANDOVER_END,
+
+ /* RSL CONNection FAILure Indication */
+ GSCON_EV_RSL_CONN_FAIL,
+
+ /* Mobile-originated DTAP (from MS) */
+ GSCON_EV_MO_DTAP,
+ /* Mobile-terminated DTAP (from MSC) */
+ GSCON_EV_MT_DTAP,
+
+ /* Transmit custom SCCP message */
+ GSCON_EV_TX_SCCP,
+
+ /* MDCX response received (MSC) - triggered by LCLS */
+ GSCON_EV_MGW_MDCX_RESP_MSC,
+
+ /* LCLS child FSM has terminated due to hard failure */
+ GSCON_EV_LCLS_FAIL,
+
+ GSCON_EV_FORGET_LCHAN,
+ GSCON_EV_FORGET_MGW_ENDPOINT,
+};
+
+struct gsm_subscriber_connection;
+struct gsm_network;
+struct msgb;
+struct mgwep_ci;
+struct assignment_request;
+struct gsm_lchan;
+
+void bsc_subscr_conn_fsm_init();
+
+/* Allocate a subscriber connection and its associated FSM */
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net);
+void gscon_update_id(struct gsm_subscriber_connection *conn);
+
+void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int link_id, int allow_sacch);
+int gscon_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+struct mgw_endpoint *gscon_ensure_mgw_endpoint(struct gsm_subscriber_connection *conn,
+ uint16_t msc_assigned_cic);
+bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
+ struct gsm_lchan *for_lchan,
+ const char *addr, uint16_t port,
+ struct osmo_fsm_inst *notify,
+ uint32_t event_success, uint32_t event_failure,
+ void *notify_data,
+ struct mgwep_ci **created_ci);
+
+void gscon_start_assignment(struct gsm_subscriber_connection *conn,
+ struct assignment_request *req);
+
+void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *new_lchan);
+void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_release);
+
+void gscon_lchan_releasing(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan);
+void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan);
+
+void gscon_forget_mgw_endpoint_ci(struct gsm_subscriber_connection *conn, struct mgwep_ci *ci);
+
+bool gscon_is_aoip(struct gsm_subscriber_connection *conn);
+bool gscon_is_sccplite(struct gsm_subscriber_connection *conn);
diff --git a/include/osmocom/bsc/bsc_subscriber.h b/include/osmocom/bsc/bsc_subscriber.h
new file mode 100644
index 000000000..93b353935
--- /dev/null
+++ b/include/osmocom/bsc/bsc_subscriber.h
@@ -0,0 +1,44 @@
+/* GSM subscriber details for use in BSC land */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+
+struct log_target;
+
+struct bsc_subscr {
+ struct llist_head entry;
+ int use_count;
+
+ char imsi[GSM23003_IMSI_MAX_DIGITS+1];
+ uint32_t tmsi;
+ uint16_t lac;
+};
+
+const char *bsc_subscr_name(struct bsc_subscr *bsub);
+const char *bsc_subscr_id(struct bsc_subscr *bsub);
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list,
+ const char *imsi);
+struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list,
+ uint32_t tmsi);
+
+struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list,
+ const char *imsi);
+struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list,
+ uint32_t tmsi);
+
+void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi);
+
+struct bsc_subscr *_bsc_subscr_get(struct bsc_subscr *bsub,
+ const char *file, int line);
+struct bsc_subscr *_bsc_subscr_put(struct bsc_subscr *bsub,
+ const char *file, int line);
+#define bsc_subscr_get(bsub) _bsc_subscr_get(bsub, __FILE__, __LINE__)
+#define bsc_subscr_put(bsub) _bsc_subscr_put(bsub, __FILE__, __LINE__)
+
+void log_set_filter_bsc_subscr(struct log_target *target,
+ struct bsc_subscr *bsub);
diff --git a/include/osmocom/bsc/bss.h b/include/osmocom/bsc/bss.h
new file mode 100644
index 000000000..ecb68d627
--- /dev/null
+++ b/include/osmocom/bsc/bss.h
@@ -0,0 +1,19 @@
+#ifndef _BSS_H_
+#define _BSS_H_
+
+#include <osmocom/bsc/gsm_data.h>
+
+struct msgb;
+
+/* start and stop network */
+extern int bsc_network_alloc(void);
+extern int bsc_shutdown_net(struct gsm_network *net);
+
+/* register all supported BTS */
+extern int bts_init(void);
+extern int bts_model_bs11_init(void);
+extern int bts_model_rbs2k_init(void);
+extern int bts_model_nanobts_init(void);
+extern int bts_model_nokia_site_init(void);
+extern int bts_model_sysmobts_init(void);
+#endif
diff --git a/include/osmocom/bsc/bts_ipaccess_nanobts_omlattr.h b/include/osmocom/bsc/bts_ipaccess_nanobts_omlattr.h
new file mode 100644
index 000000000..bc7860b2d
--- /dev/null
+++ b/include/osmocom/bsc/bts_ipaccess_nanobts_omlattr.h
@@ -0,0 +1,32 @@
+/* OML attribute table generator for ipaccess nanobts */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/msgb.h>
+
+struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts);
+struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts);
+struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts);
+struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts);
+struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts,
+ struct gsm_bts_trx *trx);
diff --git a/include/osmocom/bsc/chan_alloc.h b/include/osmocom/bsc/chan_alloc.h
new file mode 100644
index 000000000..97f6cb2db
--- /dev/null
+++ b/include/osmocom/bsc/chan_alloc.h
@@ -0,0 +1,41 @@
+/* Management functions to allocate/release struct gsm_lchan */
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef _CHAN_ALLOC_H
+#define _CHAN_ALLOC_H
+
+#include "gsm_data.h"
+
+struct gsm_subscriber_connection;
+
+/* Allocate a logical channel (SDCCH, TCH, ...) */
+struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, int allow_bigger);
+
+/* Free a logical channel (SDCCH, TCH, ...) */
+void lchan_free(struct gsm_lchan *lchan);
+
+struct pchan_load {
+ struct load_counter pchan[_GSM_PCHAN_MAX];
+};
+
+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);
+
+#endif /* _CHAN_ALLOC_H */
diff --git a/include/osmocom/bsc/codec_pref.h b/include/osmocom/bsc/codec_pref.h
new file mode 100644
index 000000000..51340c118
--- /dev/null
+++ b/include/osmocom/bsc/codec_pref.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <stdbool.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+struct gsm0808_channel_type;
+struct gsm0808_speech_codec_list;
+struct gsm_audio_support;
+struct bts_codec_conf;
+struct bsc_msc_data;
+struct gsm_bts;
+
+int match_codec_pref(enum gsm48_chan_mode *chan_mode,
+ bool *full_rate,
+ uint16_t *s15_s0,
+ const struct gsm0808_channel_type *ct,
+ const struct gsm0808_speech_codec_list *scl,
+ const struct bsc_msc_data *msc,
+ const struct gsm_bts *bts);
+
+void gen_bss_supported_codec_list(struct gsm0808_speech_codec_list *scl,
+ const struct bsc_msc_data *msc,
+ const struct gsm_bts *bts);
+
+int calc_amr_rate_intersection(struct gsm48_multi_rate_conf *c,
+ const struct gsm48_multi_rate_conf *b,
+ const struct gsm48_multi_rate_conf *a);
+
+int check_codec_pref(struct llist_head *mscs);
diff --git a/include/osmocom/bsc/ctrl.h b/include/osmocom/bsc/ctrl.h
new file mode 100644
index 000000000..04ca2cb5a
--- /dev/null
+++ b/include/osmocom/bsc/ctrl.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <osmocom/ctrl/control_cmd.h>
+
+struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net,
+ const char *bind_addr, uint16_t port);
+
+enum bsc_ctrl_node {
+ CTRL_NODE_MSC = _LAST_CTRL_NODE,
+ _LAST_CTRL_NODE_BSC
+};
diff --git a/include/osmocom/bsc/debug.h b/include/osmocom/bsc/debug.h
new file mode 100644
index 000000000..e78ba59a8
--- /dev/null
+++ b/include/osmocom/bsc/debug.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <stdio.h>
+#include <osmocom/core/linuxlist.h>
+
+#define DEBUG
+#include <osmocom/core/logging.h>
+
+/* Debug Areas of the code */
+enum {
+ DRLL,
+ DMM,
+ DRR,
+ DRSL,
+ DNM,
+ DPAG,
+ DMEAS,
+ DMSC,
+ DHO,
+ DHODEC,
+ DREF,
+ DNAT,
+ DCTRL,
+ DFILTER,
+ DPCU,
+ DLCLS,
+ DCHAN,
+ DTS,
+ DAS,
+ Debug_LastEntry,
+};
diff --git a/include/osmocom/bsc/e1_config.h b/include/osmocom/bsc/e1_config.h
new file mode 100644
index 000000000..d8d23c31e
--- /dev/null
+++ b/include/osmocom/bsc/e1_config.h
@@ -0,0 +1,13 @@
+#ifndef _E1_CONFIG_H
+#define _E1_CONFIG_H
+
+struct gsm_bts_trx_ts;
+struct gsm_bts_trx;
+struct gsm_bts;
+
+int e1_reconfig_ts(struct gsm_bts_trx_ts *ts);
+int e1_reconfig_trx(struct gsm_bts_trx *trx);
+int e1_reconfig_bts(struct gsm_bts *bts);
+
+#endif /* _E1_CONFIG_H */
+
diff --git a/include/osmocom/bsc/gsm_04_08_rr.h b/include/osmocom/bsc/gsm_04_08_rr.h
new file mode 100644
index 000000000..8e4f78785
--- /dev/null
+++ b/include/osmocom/bsc/gsm_04_08_rr.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <stdint.h>
+
+struct amr_mode;
+struct amr_multirate_conf;
+struct bsc_subscr;
+struct gsm48_chan_desc;
+struct gsm48_pag_resp;
+struct gsm_lchan;
+struct gsm_meas_rep;
+struct gsm_network;
+struct gsm_subscriber_connection;
+struct msgb;
+
+void gsm_net_update_ctype(struct gsm_network *network);
+enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, uint8_t ra);
+int get_reason_by_chreq(uint8_t ra, int neci);
+int gsm48_send_rr_release(struct gsm_lchan *lchan);
+int send_siemens_mrpci(struct gsm_lchan *lchan,
+ uint8_t *classmark2_lv);
+int gsm48_handle_paging_resp(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, struct bsc_subscr *bsub);
+int gsm48_send_rr_classmark_enquiry(struct gsm_lchan *lchan);
+int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv);
+int gsm48_multirate_config(uint8_t *lv, const struct gsm48_multi_rate_conf *mr_conf,
+ const struct amr_mode *modes, unsigned int num_modes);
+struct msgb *gsm48_make_ho_cmd(struct gsm_lchan *new_lchan, uint8_t power_command, uint8_t ho_ref);
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
+ uint8_t power_command, uint8_t ho_ref);
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_command);
+int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t mode);
+int gsm48_rx_rr_modif_ack(struct msgb *msg);
+int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg);
+int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn);
+int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn,
+ enum gsm48_reject_value value);
+
+struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value);
+int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type);
+int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length,
+ char *mi_string, uint8_t *mi_type);
+struct msgb *gsm48_create_loc_upd_rej(uint8_t cause);
+
+struct msgb *gsm48_create_rr_status(uint8_t cause);
+int gsm48_tx_rr_status(struct gsm_subscriber_connection *conn, uint8_t cause);
+
+#define GSM48_ALLOC_SIZE 2048
+#define GSM48_ALLOC_HEADROOM 256
+
+static inline struct msgb *gsm48_msgb_alloc_name(const char *name)
+{
+ return msgb_alloc_headroom(GSM48_ALLOC_SIZE, GSM48_ALLOC_HEADROOM,
+ name);
+}
+
+uint64_t str_to_imsi(const char *imsi_str);
+
+int gsm48_sendmsg(struct msgb *msg);
+int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id);
diff --git a/include/osmocom/bsc/gsm_04_80.h b/include/osmocom/bsc/gsm_04_80.h
new file mode 100644
index 000000000..649ffe19f
--- /dev/null
+++ b/include/osmocom/bsc/gsm_04_80.h
@@ -0,0 +1,7 @@
+#pragma once
+
+struct gsm_subscriber_connection;
+
+int bsc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level,
+ const char *text);
+int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn);
diff --git a/include/osmocom/bsc/gsm_08_08.h b/include/osmocom/bsc/gsm_08_08.h
new file mode 100644
index 000000000..524129560
--- /dev/null
+++ b/include/osmocom/bsc/gsm_08_08.h
@@ -0,0 +1,16 @@
+/* GSM 08.08 function declarations for osmo-bsc */
+
+#pragma once
+
+#include <stdint.h>
+
+struct gsm_subscriber_connection;
+
+void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci);
+void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_encr);
+int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, uint16_t chosen_channel);
+void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg);
+int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause);
+void bsc_cm_update(struct gsm_subscriber_connection *conn,
+ const uint8_t *cm2, uint8_t cm2_len,
+ const uint8_t *cm3, uint8_t cm3_len);
diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h
new file mode 100644
index 000000000..15158cdd0
--- /dev/null
+++ b/include/osmocom/bsc/gsm_data.h
@@ -0,0 +1,1626 @@
+#ifndef _GSM_DATA_H
+#define _GSM_DATA_H
+
+#include <stdint.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/gsm/bts_features.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/crypt/auth.h>
+
+#include <osmocom/bsc/rest_octets.h>
+
+#include <osmocom/core/bitvec.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/rxlev_stat.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/bsc/bsc_msg_filter.h>
+#include <osmocom/bsc/acc_ramp.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/neighbor_ident.h>
+
+#define GSM_T3122_DEFAULT 10
+
+struct mgcp_client_conf;
+struct mgcp_client;
+struct mgcp_ctx;
+struct gsm0808_cell_id;
+struct mgw_endpoint;
+
+/** annotations for msgb ownership */
+#define __uses
+
+#define OBSC_NM_W_ACK_CB(__msgb) (__msgb)->cb[3]
+
+struct bsc_subscr;
+struct gprs_ra_id;
+struct handover;
+
+#define OBSC_LINKID_CB(__msgb) (__msgb)->cb[3]
+
+#define tmsi_from_string(str) strtoul(str, NULL, 10)
+
+/* 3-bit long values */
+#define EARFCN_PRIO_INVALID 8
+#define EARFCN_MEAS_BW_INVALID 8
+/* 5-bit long values */
+#define EARFCN_QRXLV_INVALID 32
+#define EARFCN_THRESH_LOW_INVALID 32
+
+struct msgb;
+typedef int gsm_cbfn(unsigned int hooknum,
+ unsigned int event,
+ struct msgb *msg,
+ void *data, void *param);
+
+/* Maximum number of neighbor cells whose average we track */
+#define MAX_NEIGH_MEAS 10
+/* Maximum size of the averaging window for neighbor cells */
+#define MAX_WIN_NEIGH_AVG 10
+/* Maximum number of report history we store */
+#define MAX_MEAS_REP 10
+
+/* processed neighbor measurements for one cell */
+struct neigh_meas_proc {
+ uint16_t arfcn;
+ uint8_t bsic;
+ uint8_t rxlev[MAX_WIN_NEIGH_AVG];
+ unsigned int rxlev_cnt;
+ uint8_t last_seen_nr;
+};
+
+struct gsm_classmark {
+ bool classmark1_set;
+ struct gsm48_classmark1 classmark1;
+ uint8_t classmark2_len;
+ uint8_t classmark2[3];
+ uint8_t classmark3_len;
+ uint8_t classmark3[14]; /* if cm3 gets extended by spec, it will be truncated */
+};
+
+enum subscr_sccp_state {
+ SUBSCR_SCCP_ST_NONE,
+ SUBSCR_SCCP_ST_WAIT_CONN_CONF,
+ SUBSCR_SCCP_ST_CONNECTED
+};
+
+struct assignment_request {
+ bool aoip;
+
+ uint16_t msc_assigned_cic;
+
+ char msc_rtp_addr[INET_ADDRSTRLEN];
+ uint16_t msc_rtp_port;
+
+ enum gsm48_chan_mode chan_mode;
+ bool full_rate;
+ uint16_t s15_s0;
+};
+
+struct assignment_fsm_data {
+ struct assignment_request req;
+ bool requires_voice_stream;
+
+ struct osmo_fsm_inst *fi;
+ struct gsm_lchan *new_lchan;
+
+ /* Whether this assignment triggered creation of the MGW endpoint: if the assignment
+ * fails, we will release that again as soon as possible. (If false, the endpoint already
+ * existed before or isn't needed at all.)*/
+ struct mgwep_ci *created_ci_for_msc;
+
+ enum gsm0808_cause failure_cause;
+ enum gsm48_rr_cause rr_cause;
+
+ bool result_rate_ctr_done;
+};
+
+enum hodec_id {
+ HODEC_NONE,
+ HODEC1 = 1,
+ HODEC2 = 2,
+ HODEC_USER,
+ HODEC_REMOTE,
+};
+
+/* For example, to count specific kinds of ongoing handovers, it is useful to be able to OR-combine
+ * scopes. */
+enum handover_scope {
+ HO_NO_HANDOVER = 0,
+ HO_INTRA_CELL = 0x1,
+ HO_INTRA_BSC = 0x2,
+ HO_INTER_BSC_OUT = 0x4,
+ HO_INTER_BSC_IN = 0x8,
+ HO_SCOPE_ALL = 0xffff,
+};
+
+extern const struct value_string handover_scope_names[];
+inline static const char *handover_scope_name(enum handover_scope val)
+{ return get_value_string(handover_scope_names, val); }
+
+struct handover_out_req {
+ enum hodec_id from_hodec_id;
+ struct gsm_lchan *old_lchan;
+ struct neighbor_ident_key target_nik;
+ enum gsm_chan_t new_lchan_type; /*< leave GSM_LCHAN_NONE to use same as old_lchan */
+};
+
+struct handover_in_req {
+ struct gsm0808_channel_type ct;
+ struct gsm0808_speech_codec_list scl;
+ struct gsm0808_encrypt_info ei;
+ struct gsm_classmark classmark;
+ struct gsm0808_cell_id cell_id_serving;
+ char cell_id_serving_name[64];
+ struct gsm0808_cell_id cell_id_target;
+ char cell_id_target_name[64];
+ uint16_t msc_assigned_cic;
+ char msc_assigned_rtp_addr[INET_ADDRSTRLEN];
+ uint16_t msc_assigned_rtp_port;
+};
+
+struct handover {
+ struct osmo_fsm_inst *fi;
+
+ enum hodec_id from_hodec_id;
+ enum handover_scope scope;
+ enum gsm_chan_t new_lchan_type;
+ struct neighbor_ident_key target_cell;
+
+ uint8_t ho_ref;
+ struct gsm_bts *new_bts;
+ struct gsm_lchan *new_lchan;
+ bool async;
+ struct handover_in_req inter_bsc_in;
+ struct mgwep_ci *created_ci_for_msc;
+};
+
+/* active radio connection of a mobile subscriber */
+struct gsm_subscriber_connection {
+ /* global linked list of subscriber_connections */
+ struct llist_head entry;
+
+ /* FSM instance to control the subscriber connection state (RTP, A) */
+ struct osmo_fsm_inst *fi;
+
+ /* libbsc subscriber information (if available) */
+ struct bsc_subscr *bsub;
+
+ /* back pointers */
+ struct gsm_network *network;
+
+ /* the primary / currently active lchan to the BTS/subscriber */
+ struct gsm_lchan *lchan;
+
+ struct assignment_fsm_data assignment;
+
+ /* handover information, if a handover is pending for this conn. */
+ struct handover ho;
+
+ /* buffer/cache for classmark of the ME of the subscriber */
+ struct gsm_classmark classmark;
+
+ /* Queue DTAP messages during handover/assignment (msgb_enqueue()/msgb_dequeue())*/
+ struct llist_head dtap_queue;
+ unsigned int dtap_queue_len;
+
+ struct {
+ int failures;
+ struct penalty_timers *penalty_timers;
+ } hodec2;
+
+ /* "Codec List (MSC Preferred)" as received by the BSSAP Assignment Request. 3GPP 48.008
+ * 3.2.2.103 says:
+ * The "Codec List (MSC Preferred)" shall not include codecs
+ * that are not supported by the MS.
+ * i.e. by heeding the "Codec list (MSC Preferred)", we inherently heed the MS bearer
+ * capabilities, which the MSC is required to translate into the codec list. */
+ struct gsm0808_speech_codec_list codec_list;
+
+ /* flag to prevent multiple simultaneous ciphering commands */
+ int ciphering_handled;
+
+ /* state related to welcome USSD */
+ uint8_t new_subscriber;
+
+ /* state related to osmo_bsc_filter.c */
+ struct bsc_filter_state filter_state;
+
+ /* SCCP connection associatd with this subscriber_connection */
+ struct {
+ /* for advanced ping/pong */
+ int send_ping;
+
+ /* SCCP connection realted */
+ struct bsc_msc_data *msc;
+
+ /* Sigtran connection ID */
+ int conn_id;
+ enum subscr_sccp_state state;
+ } sccp;
+
+ /* for audio handling */
+ struct {
+ uint16_t msc_assigned_cic;
+
+ /* RTP address where the MSC expects us to send the RTP stream coming from the BTS. */
+ char msc_assigned_rtp_addr[INET_ADDRSTRLEN];
+ uint16_t msc_assigned_rtp_port;
+
+ /* The endpoint at the MGW used to join both BTS and MSC side connections, e.g.
+ * "rtpbridge/23@mgw". */
+ struct mgw_endpoint *mgw_endpoint;
+
+ /* The connection identifier of the mgw_endpoint used to transceive RTP towards the MSC.
+ * (The BTS side CI is handled by struct gsm_lchan and the lchan_fsm.) */
+ struct mgwep_ci *mgw_endpoint_ci_msc;
+ } user_plane;
+
+ /* LCLS (local call, local switch) related state */
+ struct {
+ uint8_t global_call_ref[15];
+ uint8_t global_call_ref_len; /* length of global_call_ref */
+ uint8_t config; /* TS 48.008 3.2.2.116 */
+ uint8_t control;/* TS 48.008 3.2.2.117 */
+ /* LCLS FSM */
+ struct osmo_fsm_inst *fi;
+ /* pointer to "other" connection, if Call Leg Relocation was successful */
+ struct gsm_subscriber_connection *other;
+ } lcls;
+};
+
+
+/* 16 is the max. number of SI2quater messages according to 3GPP TS 44.018 Table 10.5.2.33b.1:
+ 4-bit index is used (2#1111 = 10#15) */
+#define SI2Q_MAX_NUM 16
+/* length in bits (for single SI2quater message) */
+#define SI2Q_MAX_LEN 160
+#define SI2Q_MIN_LEN 18
+
+struct osmo_bsc_data;
+
+struct osmo_bsc_sccp_con;
+
+/* Channel Request reason */
+enum gsm_chreq_reason_t {
+ GSM_CHREQ_REASON_EMERG,
+ GSM_CHREQ_REASON_PAG,
+ GSM_CHREQ_REASON_CALL,
+ GSM_CHREQ_REASON_LOCATION_UPD,
+ GSM_CHREQ_REASON_OTHER,
+ GSM_CHREQ_REASON_PDCH,
+};
+
+/* lchans 0..3 are SDCCH in combined channel configuration,
+ use 4 as magic number for BCCH hack - see osmo-bts-../oml.c:opstart_compl() */
+#define CCCH_LCHAN 4
+
+#define TRX_NR_TS 8
+#define TS_MAX_LCHAN 8
+
+#define HARDCODED_ARFCN 123
+#define HARDCODED_BSIC 0x3f /* NCC = 7 / BCC = 7 */
+
+/* for multi-drop config */
+#define HARDCODED_BTS0_TS 1
+#define HARDCODED_BTS1_TS 6
+#define HARDCODED_BTS2_TS 11
+
+#define MAX_VERSION_LENGTH 64
+
+enum gsm_hooks {
+ GSM_HOOK_NM_SWLOAD,
+ GSM_HOOK_RR_PAGING,
+ GSM_HOOK_RR_SECURITY,
+};
+
+enum bts_gprs_mode {
+ BTS_GPRS_NONE = 0,
+ BTS_GPRS_GPRS = 1,
+ BTS_GPRS_EGPRS = 2,
+};
+
+struct gsm_lchan;
+struct osmo_rtp_socket;
+struct rtp_socket;
+
+/* Network Management State */
+struct gsm_nm_state {
+ uint8_t operational;
+ uint8_t administrative;
+ uint8_t availability;
+};
+
+struct gsm_abis_mo {
+ uint8_t obj_class;
+ uint8_t procedure_pending;
+ struct abis_om_obj_inst obj_inst;
+ const char *name;
+ struct gsm_nm_state nm_state;
+ struct tlv_parsed *nm_attr;
+ struct gsm_bts *bts;
+};
+
+/* Ericsson OM2000 Managed Object */
+struct abis_om2k_mo {
+ uint8_t class;
+ uint8_t bts;
+ uint8_t assoc_so;
+ uint8_t inst;
+} __attribute__ ((packed));
+
+struct om2k_mo {
+ struct abis_om2k_mo addr;
+ struct osmo_fsm_inst *fsm;
+};
+
+#define A38_XOR_MIN_KEY_LEN 12
+#define A38_XOR_MAX_KEY_LEN 16
+#define A38_COMP128_KEY_LEN 16
+#define RSL_ENC_ALG_A5(x) (x+1)
+#define MAX_EARFCN_LIST 32
+
+/* is the data link established? who established it? */
+#define LCHAN_SAPI_UNUSED 0
+#define LCHAN_SAPI_MS 1
+#define LCHAN_SAPI_NET 2
+
+/* BTS ONLY */
+#define MAX_NUM_UL_MEAS 104
+#define LC_UL_M_F_L1_VALID (1 << 0)
+#define LC_UL_M_F_RES_VALID (1 << 1)
+
+struct bts_ul_meas {
+ /* BER in units of 0.01%: 10.000 == 100% ber, 0 == 0% ber */
+ uint16_t ber10k;
+ /* timing advance offset (in quarter bits) */
+ int16_t ta_offs_qbits;
+ /* C/I ratio in dB */
+ float c_i;
+ /* flags */
+ uint8_t is_sub:1;
+ /* RSSI in dBm * -1 */
+ uint8_t inv_rssi;
+};
+
+struct bts_codec_conf {
+ uint8_t hr;
+ uint8_t efr;
+ uint8_t amr;
+};
+
+struct amr_mode {
+ uint8_t mode;
+ uint8_t threshold;
+ uint8_t hysteresis;
+};
+
+struct amr_multirate_conf {
+ uint8_t gsm48_ie[2];
+ struct amr_mode ms_mode[4];
+ struct amr_mode bts_mode[4];
+ uint8_t num_modes;
+};
+/* /BTS ONLY */
+
+enum lchan_csd_mode {
+ LCHAN_CSD_M_NT,
+ LCHAN_CSD_M_T_1200_75,
+ LCHAN_CSD_M_T_600,
+ LCHAN_CSD_M_T_1200,
+ LCHAN_CSD_M_T_2400,
+ LCHAN_CSD_M_T_9600,
+ LCHAN_CSD_M_T_14400,
+ LCHAN_CSD_M_T_29000,
+ LCHAN_CSD_M_T_32000,
+};
+
+/* State of the SAPIs in the lchan */
+enum lchan_sapi_state {
+ LCHAN_SAPI_S_NONE,
+ LCHAN_SAPI_S_REQ,
+ LCHAN_SAPI_S_ASSIGNED,
+ LCHAN_SAPI_S_REL,
+ LCHAN_SAPI_S_ERROR,
+};
+
+#define MAX_A5_KEY_LEN (128/8)
+
+struct gsm_encr {
+ uint8_t alg_id;
+ uint8_t key_len;
+ uint8_t key[MAX_A5_KEY_LEN];
+};
+
+#define LOGPLCHAN(lchan, ss, level, fmt, args...) \
+ LOGP(ss, level, "%s (ss=%d,%s) (%s) " fmt, \
+ lchan ? gsm_ts_and_pchan_name(lchan->ts) : "-", \
+ lchan ? lchan->nr : 0, \
+ lchan ? gsm_lchant_name(lchan->type) : "-", \
+ bsc_subscr_name(lchan && lchan->conn ? lchan->conn->bsub : NULL), \
+ ## args)
+
+/* usage:
+ * struct gsm_lchan *lchan;
+ * struct gsm_bts_trx_ts *ts = get_some_timeslot();
+ * ts_for_each_lchan(lchan, ts) {
+ * LOGPLCHAN(DMAIN, LOGL_DEBUG, "hello world\n");
+ * }
+ * Iterate only those lchans that have an FSM allocated. */
+#define ts_for_each_lchan(lchan, ts) ts_as_pchan_for_each_lchan(lchan, ts, (ts)->pchan_is)
+
+/* Same as ts_for_each_lchan() but with an explicit pchan kind (GSM_PCHAN_* constant).
+ * Iterate only those lchans that have an FSM allocated. */
+#define ts_as_pchan_for_each_lchan(lchan, ts, as_pchan) \
+ for (lchan = (ts)->lchan; \
+ ((lchan - (ts)->lchan) < ARRAY_SIZE((ts)->lchan)) \
+ && lchan->fi \
+ && lchan->nr < pchan_subslots(as_pchan); \
+ lchan++)
+
+enum lchan_activate_mode {
+ FOR_NONE,
+ FOR_MS_CHANNEL_REQUEST,
+ FOR_ASSIGNMENT,
+ FOR_HANDOVER,
+ FOR_VTY,
+};
+
+extern const struct value_string lchan_activate_mode_names[];
+static inline const char *lchan_activate_mode_name(enum lchan_activate_mode activ_for)
+{ return get_value_string(lchan_activate_mode_names, activ_for); }
+
+struct gsm_lchan {
+ /* The TS that we're part of */
+ struct gsm_bts_trx_ts *ts;
+ /* The logical subslot number in the TS */
+ uint8_t nr;
+ char *name;
+
+ char *last_error;
+
+ struct osmo_fsm_inst *fi;
+ struct osmo_fsm_inst *fi_rtp;
+ struct mgwep_ci *mgw_endpoint_ci_bts;
+
+ struct {
+ enum lchan_activate_mode activ_for;
+ bool activ_ack; /*< true as soon as RSL Chan Activ Ack is received */
+ bool immediate_assignment_sent;
+ /*! This flag ensures that when an lchan activation has succeeded, and we have already
+ * sent ACKs like Immediate Assignment or BSSMAP Assignment Complete, and if other errors
+ * occur later, e.g. during release, that we don't send a NACK out of context. */
+ bool concluded;
+ bool requires_voice_stream;
+ bool wait_before_switching_rtp; /*< true = requires LCHAN_EV_READY_TO_SWITCH_RTP */
+ uint16_t msc_assigned_cic;
+ enum gsm0808_cause gsm0808_error_cause;
+ struct gsm_lchan *re_use_mgw_endpoint_from_lchan;
+ } activate;
+
+ struct {
+ /* If an event to release the lchan comes in while still waiting for responses, just mark this
+ * flag, so that the lchan will gracefully release at the next sensible junction. */
+ bool requested;
+ bool do_rr_release;
+
+ /* There is an RSL error cause of value 0, so we need a separate flag. */
+ bool in_error;
+ /* RSL error code, RSL_ERR_* */
+ uint8_t rsl_error_cause;
+
+ /* If a release event is being handled, ignore other ricocheting release events until that
+ * release handling has concluded. */
+ bool in_release_handler;
+ } release;
+
+ /* The logical channel type */
+ enum gsm_chan_t type;
+ /* RSL channel mode */
+ enum rsl_cmod_spd rsl_cmode;
+ /* If TCH, traffic channel mode */
+ enum gsm48_chan_mode tch_mode;
+ enum lchan_csd_mode csd_mode;
+ /* Power levels for MS and BTS */
+ uint8_t bs_power;
+ uint8_t ms_power;
+ /* Encryption information */
+ struct gsm_encr encr;
+
+ /* AMR bits */
+ uint8_t mr_ms_lv[7];
+ uint8_t mr_bts_lv[7];
+
+ /* Established data link layer services */
+ uint8_t sapis[8];
+
+ struct {
+ uint32_t bound_ip; /*< where the BTS receives RTP */
+ uint16_t bound_port;
+ uint32_t connect_ip; /*< where the BTS sends RTP to (MGW) */
+ uint16_t connect_port;
+ uint16_t conn_id;
+ uint8_t rtp_payload;
+ uint8_t rtp_payload2;
+ uint8_t speech_mode;
+
+ /* info we need to postpone the AoIP
+ * assignment completed message */
+ struct {
+ uint8_t rr_cause;
+ bool valid;
+ } ass_compl;
+ } abis_ip;
+
+ uint8_t rqd_ta;
+
+ /* table of neighbor cell measurements */
+ struct neigh_meas_proc neigh_meas[MAX_NEIGH_MEAS];
+
+ /* cache of last measurement reports on this lchan */
+ struct gsm_meas_rep meas_rep[MAX_MEAS_REP];
+ int meas_rep_idx;
+ int meas_rep_count;
+ uint8_t meas_rep_last_seen_nr;
+
+ /* GSM Random Access data */
+ /* TODO: don't allocate this, rather keep an "is_present" flag */
+ struct gsm48_req_ref *rqd_ref;
+
+ struct gsm_subscriber_connection *conn;
+};
+
+/* One Timeslot in a TRX */
+struct gsm_bts_trx_ts {
+ struct gsm_bts_trx *trx;
+ /* number of this timeslot at the TRX */
+ uint8_t nr;
+
+ struct osmo_fsm_inst *fi;
+ char *last_errmsg;
+
+ /* vty phys_chan_config setting, not necessarily in effect in case it was changed in the telnet
+ * vty after OML activation. Gets written on vty 'write file'. */
+ enum gsm_phys_chan_config pchan_from_config;
+ /* When the timeslot OML is established, pchan_from_config is copied here. This is the pchan
+ * currently in effect; for dynamic ts, this is the dyn kind (GSM_PCHAN_TCH_F_TCH_H_PDCH or
+ * GSM_PCHAN_TCH_F_PDCH) and does not show the pchan type currently active. */
+ enum gsm_phys_chan_config pchan_on_init;
+ /* This is the *actual* pchan type currently active. For dynamic timeslots, this reflects either
+ * GSM_PCHAN_NONE or one of the standard GSM_PCHAN_TCH_F, GSM_PCHAN_TCH_H, GSM_PCHAN_PDCH.
+ * Callers can use this transparently without being aware of dyn ts. */
+ enum gsm_phys_chan_config pchan_is;
+
+ /* After a PDCH ACT NACK, we shall not infinitely loop to try and ACT again.
+ * Also marks a timeslot where PDCH was deactivated by VTY. This is cleared whenever a timeslot
+ * enters IN_USE state, i.e. after each TCH use we try to PDCH ACT once again. */
+ bool pdch_act_allowed;
+
+ /* Whether TS_EV_OML_READY was received */
+ bool is_oml_ready;
+ /* Whether TS_EV_RSL_READY was received */
+ bool is_rsl_ready;
+
+ struct gsm_abis_mo mo;
+ struct tlv_parsed nm_attr;
+ uint8_t nm_chan_comb;
+ int tsc; /* -1 == use BTS TSC */
+
+ struct {
+ /* Parameters below are configured by VTY */
+ int enabled;
+ uint8_t maio;
+ uint8_t hsn;
+ struct bitvec arfcns;
+ uint8_t arfcns_data[1024/8];
+ /* This is the pre-computed MA for channel assignments */
+ struct bitvec ma;
+ uint8_t ma_len; /* part of ma_data that is used */
+ uint8_t ma_data[8]; /* 10.5.2.21: max 8 bytes value part */
+ } hopping;
+
+ /* To which E1 subslot are we connected */
+ struct gsm_e1_subslot e1_link;
+
+ union {
+ struct {
+ struct om2k_mo om2k_mo;
+ } rbs2000;
+ };
+
+ struct gsm_lchan lchan[TS_MAX_LCHAN];
+};
+
+/* One TRX in a BTS */
+struct gsm_bts_trx {
+ /* list header in bts->trx_list */
+ struct llist_head list;
+
+ struct gsm_bts *bts;
+ /* number of this TRX in the BTS */
+ uint8_t nr;
+ /* human readable name / description */
+ char *description;
+ /* how do we talk RSL with this TRX? */
+ struct gsm_e1_subslot rsl_e1_link;
+ uint8_t rsl_tei;
+ struct e1inp_sign_link *rsl_link;
+
+ /* Timeout for initiating the RSL connection. */
+ struct osmo_timer_list rsl_connect_timeout;
+
+ /* Some BTS (specifically Ericsson RBS) have a per-TRX OML Link */
+ struct e1inp_sign_link *oml_link;
+
+ struct gsm_abis_mo mo;
+ struct tlv_parsed nm_attr;
+ struct {
+ struct gsm_abis_mo mo;
+ } bb_transc;
+
+ uint16_t arfcn;
+ int nominal_power; /* in dBm */
+ unsigned int max_power_red; /* in actual dB */
+
+ union {
+ struct {
+ struct {
+ struct gsm_abis_mo mo;
+ } bbsig;
+ struct {
+ struct gsm_abis_mo mo;
+ } pa;
+ } bs11;
+ struct {
+ unsigned int test_state;
+ uint8_t test_nr;
+ struct rxlev_stats rxlev_stat;
+ } ipaccess;
+ struct {
+ struct {
+ struct om2k_mo om2k_mo;
+ } trxc;
+ struct {
+ struct om2k_mo om2k_mo;
+ } rx;
+ struct {
+ struct om2k_mo om2k_mo;
+ } tx;
+ } rbs2000;
+ };
+ struct gsm_bts_trx_ts ts[TRX_NR_TS];
+};
+
+#define GSM_BTS_SI2Q(bts, i) (struct gsm48_system_information_type_2quater *)((bts)->si_buf[SYSINFO_TYPE_2quater][i])
+#define GSM_BTS_HAS_SI(bts, i) ((bts)->si_valid & (1 << i))
+#define GSM_BTS_SI(bts, i) (void *)((bts)->si_buf[i][0])
+#define GSM_LCHAN_SI(lchan, i) (void *)((lchan)->si.buf[i][0])
+
+enum gsm_bts_type {
+ GSM_BTS_TYPE_UNKNOWN,
+ GSM_BTS_TYPE_BS11,
+ GSM_BTS_TYPE_NANOBTS,
+ GSM_BTS_TYPE_RBS2000,
+ GSM_BTS_TYPE_NOKIA_SITE,
+ GSM_BTS_TYPE_OSMOBTS,
+ _NUM_GSM_BTS_TYPE
+};
+
+enum gsm_bts_type_variant {
+ BTS_UNKNOWN,
+ BTS_OSMO_LITECELL15,
+ BTS_OSMO_OCTPHY,
+ BTS_OSMO_SYSMO,
+ BTS_OSMO_TRX,
+ _NUM_BTS_VARIANT
+};
+
+/* Used by OML layer for BTS Attribute reporting */
+enum bts_attribute {
+ BTS_TYPE_VARIANT,
+ BTS_SUB_MODEL,
+ TRX_PHY_VERSION,
+};
+
+struct vty;
+
+struct gsm_bts_model {
+ struct llist_head list;
+
+ enum gsm_bts_type type;
+ enum gsm_bts_type_variant variant;
+ const char *name;
+
+ bool started;
+ int (*start)(struct gsm_network *net);
+ int (*oml_rcvmsg)(struct msgb *msg);
+ char * (*oml_status)(const struct gsm_bts *bts);
+
+ void (*e1line_bind_ops)(struct e1inp_line *line);
+
+ void (*config_write_bts)(struct vty *vty, struct gsm_bts *bts);
+ void (*config_write_trx)(struct vty *vty, struct gsm_bts_trx *trx);
+ void (*config_write_ts)(struct vty *vty, struct gsm_bts_trx_ts *ts);
+
+ /* Should SI2bis and SI2ter be disabled by default on this BTS model? */
+ bool force_combined_si;
+
+ struct tlv_definition nm_att_tlvdef;
+
+ /* features of a given BTS model set via gsm_bts_model_register() locally */
+ struct bitvec features;
+ uint8_t _features_data[MAX_BTS_FEATURES/8];
+};
+
+
+
+/*
+ * This keeps track of the paging status of one BTS. It
+ * includes a number of pending requests, a back pointer
+ * to the gsm_bts, a timer and some more state.
+ */
+struct gsm_bts_paging_state {
+ /* pending requests */
+ struct llist_head pending_requests;
+ struct gsm_bts *bts;
+
+ struct osmo_timer_list work_timer;
+ struct osmo_timer_list credit_timer;
+
+ /* free chans needed */
+ int free_chans_need;
+
+ /* load */
+ uint16_t available_slots;
+};
+
+struct gsm_envabtse {
+ struct gsm_abis_mo mo;
+};
+
+struct gsm_bts_gprs_nsvc {
+ struct gsm_bts *bts;
+ /* data read via VTY config file, to configure the BTS
+ * via OML from BSC */
+ int id;
+ uint16_t nsvci;
+ uint16_t local_port; /* on the BTS */
+ uint16_t remote_port; /* on the SGSN */
+ uint32_t remote_ip; /* on the SGSN */
+
+ struct gsm_abis_mo mo;
+};
+
+enum gprs_rlc_par {
+ RLC_T3142,
+ RLC_T3169,
+ RLC_T3191,
+ RLC_T3193,
+ RLC_T3195,
+ RLC_N3101,
+ RLC_N3103,
+ RLC_N3105,
+ CV_COUNTDOWN,
+ T_DL_TBF_EXT, /* ms */
+ T_UL_TBF_EXT, /* ms */
+ _NUM_RLC_PAR
+};
+
+enum gprs_cs {
+ GPRS_CS1,
+ GPRS_CS2,
+ GPRS_CS3,
+ GPRS_CS4,
+ GPRS_MCS1,
+ GPRS_MCS2,
+ GPRS_MCS3,
+ GPRS_MCS4,
+ GPRS_MCS5,
+ GPRS_MCS6,
+ GPRS_MCS7,
+ GPRS_MCS8,
+ GPRS_MCS9,
+ _NUM_GRPS_CS
+};
+
+struct gprs_rlc_cfg {
+ uint16_t parameter[_NUM_RLC_PAR];
+ struct {
+ uint16_t repeat_time; /* ms */
+ uint8_t repeat_count;
+ } paging;
+ uint32_t cs_mask; /* bitmask of gprs_cs */
+ uint8_t initial_cs;
+ uint8_t initial_mcs;
+};
+
+
+enum neigh_list_manual_mode {
+ NL_MODE_AUTOMATIC = 0,
+ NL_MODE_MANUAL = 1,
+ NL_MODE_MANUAL_SI5SEP = 2, /* SI2 and SI5 have separate neighbor lists */
+};
+
+enum bts_loc_fix {
+ BTS_LOC_FIX_INVALID = 0,
+ BTS_LOC_FIX_2D = 1,
+ BTS_LOC_FIX_3D = 2,
+};
+
+extern const struct value_string bts_loc_fix_names[];
+
+struct bts_location {
+ struct llist_head list;
+ time_t tstamp;
+ enum bts_loc_fix valid;
+ double lat;
+ double lon;
+ double height;
+};
+
+/* Channel load counter */
+struct load_counter {
+ unsigned int total;
+ unsigned int used;
+};
+
+/* Useful to track N-N relations between BTS, for example neighbors. */
+struct gsm_bts_ref {
+ struct llist_head entry;
+ struct gsm_bts *bts;
+};
+
+/* One BTS */
+struct gsm_bts {
+ /* list header in net->bts_list */
+ struct llist_head list;
+
+ /* Geographical location of the BTS */
+ struct llist_head loc_list;
+
+ /* number of ths BTS in network */
+ uint8_t nr;
+ /* human readable name / description */
+ char *description;
+ /* Cell Identity */
+ uint16_t cell_identity;
+ /* location area code of this BTS */
+ uint16_t location_area_code;
+ /* Base Station Identification Code (BSIC), lower 3 bits is BCC,
+ * which is used as TSC for the CCCH */
+ uint8_t bsic;
+ /* type of BTS */
+ enum gsm_bts_type type;
+ enum gsm_bts_type_variant variant;
+ struct gsm_bts_model *model;
+ enum gsm_band band;
+ char version[MAX_VERSION_LENGTH];
+ char sub_model[MAX_VERSION_LENGTH];
+
+ /* features of a given BTS set/reported via OML */
+ struct bitvec features;
+ uint8_t _features_data[MAX_BTS_FEATURES/8];
+
+ /* Connected PCU version (if any) */
+ char pcu_version[MAX_VERSION_LENGTH];
+
+ /* maximum Tx power that the MS is permitted to use in this cell */
+ int ms_max_power;
+
+ /* how do we talk OML with this TRX? */
+ struct gsm_e1_subslot oml_e1_link;
+ uint8_t oml_tei;
+ struct e1inp_sign_link *oml_link;
+ /* Timer to use for deferred drop of OML link, see \ref ipaccess_drop_oml_deferred */
+ struct osmo_timer_list oml_drop_link_timer;
+ /* when OML link was established */
+ time_t uptime;
+
+ /* Abis network management O&M handle */
+ struct abis_nm_h *nmh;
+
+ struct gsm_abis_mo mo;
+
+ /* number of this BTS on given E1 link */
+ uint8_t bts_nr;
+
+ /* DTX features of this BTS */
+ enum gsm48_dtx_mode dtxu;
+ bool dtxd;
+
+ /* paging state and control */
+ struct gsm_bts_paging_state paging;
+
+ /* CCCH is on C0 */
+ struct gsm_bts_trx *c0;
+
+ struct {
+ struct gsm_abis_mo mo;
+ } site_mgr;
+
+ /* bitmask of all SI that are present/valid in si_buf */
+ uint32_t si_valid;
+ /* 3GPP TS 44.018 Table 10.5.2.33b.1 INDEX and COUNT for SI2quater */
+ uint8_t si2q_index; /* distinguish individual SI2quater messages */
+ uint8_t si2q_count; /* si2q_index for the last (highest indexed) individual SI2quater message */
+ /* buffers where we put the pre-computed SI */
+ sysinfo_buf_t si_buf[_MAX_SYSINFO_TYPE][SI2Q_MAX_NUM];
+ /* offsets used while generating SI2quater */
+ size_t e_offset;
+ size_t u_offset;
+
+ /* ip.accesss Unit ID's have Site/BTS/TRX layout */
+ union {
+ struct {
+ uint16_t site_id;
+ uint16_t bts_id;
+ uint32_t flags;
+ uint32_t rsl_ip;
+ } ip_access;
+ struct {
+ struct {
+ struct gsm_abis_mo mo;
+ } cclk;
+ struct {
+ struct gsm_abis_mo mo;
+ } rack;
+ struct gsm_envabtse envabtse[4];
+ } bs11;
+ struct {
+ struct {
+ struct om2k_mo om2k_mo;
+ struct gsm_abis_mo mo;
+ struct llist_head conn_groups;
+ } cf;
+ struct {
+ struct om2k_mo om2k_mo;
+ struct gsm_abis_mo mo;
+ struct llist_head conn_groups;
+ } is;
+ struct {
+ struct om2k_mo om2k_mo;
+ struct gsm_abis_mo mo;
+ struct llist_head conn_groups;
+ } con;
+ struct {
+ struct om2k_mo om2k_mo;
+ struct gsm_abis_mo mo;
+ } dp;
+ struct {
+ struct om2k_mo om2k_mo;
+ struct gsm_abis_mo mo;
+ } tf;
+ uint32_t use_superchannel:1;
+ } rbs2000;
+ struct {
+ uint8_t bts_type;
+ unsigned int configured:1,
+ skip_reset:1,
+ no_loc_rel_cnf:1,
+ bts_reset_timer_cnf,
+ did_reset:1,
+ wait_reset:1;
+ struct osmo_timer_list reset_timer;
+ } nokia;
+ };
+
+ /* Not entirely sure how ip.access specific this is */
+ struct {
+ uint8_t supports_egprs_11bit_rach;
+ enum bts_gprs_mode mode;
+ struct {
+ struct gsm_abis_mo mo;
+ uint16_t nsei;
+ uint8_t timer[7];
+ } nse;
+ struct {
+ struct gsm_abis_mo mo;
+ uint16_t bvci;
+ uint8_t timer[11];
+ struct gprs_rlc_cfg rlc_cfg;
+ } cell;
+ struct gsm_bts_gprs_nsvc nsvc[2];
+ uint8_t rac;
+ uint8_t net_ctrl_ord;
+ bool ctrl_ack_type_use_block;
+ } gprs;
+
+ /* RACH NM values */
+ int rach_b_thresh;
+ int rach_ldavg_slots;
+
+ /* transceivers */
+ int num_trx;
+ struct llist_head trx_list;
+
+ /* SI related items */
+ int force_combined_si;
+ bool force_combined_si_set;
+ int bcch_change_mark;
+
+ /* Abis NM queue */
+ struct llist_head abis_queue;
+ int abis_nm_pend;
+
+ struct gsm_network *network;
+
+ /* should the channel allocator allocate channels from high TRX to TRX0,
+ * rather than starting from TRX0 and go upwards? */
+ int chan_alloc_reverse;
+
+ enum neigh_list_manual_mode neigh_list_manual_mode;
+ /* parameters from which we build SYSTEM INFORMATION */
+ struct {
+ struct gsm48_rach_control rach_control;
+ uint8_t ncc_permitted;
+ struct gsm48_cell_sel_par cell_sel_par;
+ struct gsm48_si_selection_params cell_ro_sel_par; /* rest octet */
+ struct gsm48_cell_options cell_options;
+ struct gsm48_control_channel_descr chan_desc;
+ struct bitvec neigh_list;
+ struct bitvec cell_alloc;
+ struct bitvec si5_neigh_list;
+ struct osmo_earfcn_si2q si2quater_neigh_list;
+ size_t uarfcn_length; /* index for uarfcn and scramble lists */
+ struct {
+ /* bitmask large enough for all possible ARFCN's */
+ uint8_t neigh_list[1024/8];
+ uint8_t cell_alloc[1024/8];
+ /* If the user wants a different neighbor list in SI5 than in SI2 */
+ uint8_t si5_neigh_list[1024/8];
+ uint8_t meas_bw_list[MAX_EARFCN_LIST];
+ uint16_t earfcn_list[MAX_EARFCN_LIST];
+ uint16_t uarfcn_list[MAX_EARFCN_LIST];
+ uint16_t scramble_list[MAX_EARFCN_LIST];
+ } data;
+ } si_common;
+ bool early_classmark_allowed;
+ bool early_classmark_allowed_3g;
+ /* for testing only: Have an infinitely long radio link timeout */
+ bool infinite_radio_link_timeout;
+
+ /* do we use static (user-defined) system information messages? (bitmask) */
+ uint32_t si_mode_static;
+
+ /* access control class ramping */
+ struct acc_ramp acc_ramp;
+
+ /* exclude the BTS from the global RF Lock handling */
+ int excl_from_rf_lock;
+
+ /* supported codecs beside FR */
+ struct bts_codec_conf codec;
+
+ /* BTS dependencies bit field */
+ uint32_t depends_on[256/(8*4)];
+
+ /* full and half rate multirate config */
+ struct amr_multirate_conf mr_full;
+ struct amr_multirate_conf mr_half;
+
+ /* PCU socket state */
+ char *pcu_sock_path;
+ struct pcu_sock_state *pcu_state;
+
+ struct rate_ctr_group *bts_ctrs;
+ struct osmo_stat_item_group *bts_statg;
+
+ struct handover_cfg *ho;
+
+ /* A list of struct gsm_bts_ref, indicating neighbors of this BTS.
+ * When the si_common neigh_list is in automatic mode, it is populated from this list as well as
+ * gsm_network->neighbor_bss_cells. */
+ struct llist_head local_neighbors;
+
+ /* BTS-specific overrides for timer values from struct gsm_network. */
+ uint8_t T3122; /* ASSIGMENT REJECT wait indication */
+
+ /* Periodic channel load measurements are used to maintain T3122. */
+ struct load_counter chan_load_samples[7];
+ int chan_load_samples_idx;
+ uint8_t chan_load_avg; /* current channel load average in percent (0 - 100). */
+};
+
+/* One rejected BTS */
+struct gsm_bts_rejected {
+ /* list header in net->bts_rejected */
+ struct llist_head list;
+
+ uint16_t site_id;
+ uint16_t bts_id;
+ char ip[INET6_ADDRSTRLEN];
+ time_t time;
+};
+
+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(const struct gsm_network *net, int num);
+bool gsm_bts_matches_lai(const struct gsm_bts *bts, const struct osmo_location_area_id *lai);
+bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cell_id *cell_id);
+struct gsm_bts *gsm_bts_by_cell_id(const struct gsm_network *net,
+ const struct gsm0808_cell_id *cell_id,
+ int match_idx);
+int gsm_bts_local_neighbor_add(struct gsm_bts *bts, struct gsm_bts *neighbor);
+int gsm_bts_local_neighbor_del(struct gsm_bts *bts, const struct gsm_bts *neighbor);
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts);
+struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num);
+
+enum gsm_bts_type str2btstype(const char *arg);
+const char *btstype2str(enum gsm_bts_type type);
+
+enum bts_attribute str2btsattr(const char *s);
+const char *btsatttr2str(enum bts_attribute v);
+
+enum gsm_bts_type_variant str2btsvariant(const char *arg);
+const char *btsvariant2str(enum gsm_bts_type_variant v);
+
+extern const struct value_string gsm_chreq_descs[];
+extern const struct value_string gsm_pchant_names[];
+extern const struct value_string gsm_pchant_descs[];
+extern const struct value_string gsm_pchan_ids[];
+const char *gsm_pchan_name(enum gsm_phys_chan_config c);
+static inline const char *gsm_pchan_id(enum gsm_phys_chan_config c)
+{ return get_value_string(gsm_pchan_ids, c); }
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name);
+const char *gsm_lchant_name(enum gsm_chan_t c);
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c);
+char *gsm_trx_name(const struct gsm_bts_trx *trx);
+char *gsm_ts_name(const struct gsm_bts_trx_ts *ts);
+char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts);
+char *gsm_lchan_name_compute(const struct gsm_lchan *lchan);
+
+static inline char *gsm_lchan_name(const struct gsm_lchan *lchan)
+{
+ return lchan->name;
+}
+
+void gsm_abis_mo_reset(struct gsm_abis_mo *mo);
+
+struct gsm_nm_state *
+gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst);
+void *
+gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst);
+
+/* reset the state of all MO in the BTS */
+void gsm_bts_mo_reset(struct gsm_bts *bts);
+
+uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+ uint8_t ts_nr, uint8_t lchan_nr);
+uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan);
+uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan,
+ enum gsm_phys_chan_config as_pchan);
+
+void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
+ const struct gsm_lchan *lchan);
+void gsm48_lchan2chan_desc_as_configured(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan);
+
+/* return the gsm_lchan for the CBCH (if it exists at all) */
+struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts);
+
+/*
+ * help with parsing regexps
+ */
+int gsm_parse_reg(void *ctx, regex_t *reg, char **str,
+ int argc, const char **argv) __attribute__ ((warn_unused_result));
+
+static inline uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts)
+{
+ if (ts->tsc != -1)
+ return ts->tsc;
+ else
+ return ts->trx->bts->bsic & 7;
+}
+
+struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ int *rc);
+
+enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts);
+uint8_t pchan_subslots(enum gsm_phys_chan_config pchan);
+bool ts_is_tch(struct gsm_bts_trx_ts *ts);
+
+
+static inline struct gsm_bts *conn_get_bts(struct gsm_subscriber_connection *conn) {
+ OSMO_ASSERT(conn->lchan);
+ return conn->lchan->ts->trx->bts;
+}
+
+enum {
+ BTS_CTR_CHREQ_TOTAL,
+ BTS_CTR_CHREQ_NO_CHANNEL,
+ BTS_CTR_CHAN_RF_FAIL,
+ BTS_CTR_CHAN_RLL_ERR,
+ BTS_CTR_BTS_OML_FAIL,
+ BTS_CTR_BTS_RSL_FAIL,
+ BTS_CTR_CODEC_AMR_F,
+ BTS_CTR_CODEC_AMR_H,
+ BTS_CTR_CODEC_EFR,
+ BTS_CTR_CODEC_V1_FR,
+ BTS_CTR_CODEC_V1_HR,
+ BTS_CTR_PAGING_ATTEMPTED,
+ BTS_CTR_PAGING_ALREADY,
+ BTS_CTR_PAGING_RESPONDED,
+ BTS_CTR_PAGING_EXPIRED,
+ BTS_CTR_CHAN_ACT_TOTAL,
+ BTS_CTR_CHAN_ACT_NACK,
+ BTS_CTR_RSL_UNKNOWN,
+ BTS_CTR_RSL_IPA_NACK,
+ BTS_CTR_MODE_MODIFY_NACK,
+};
+
+static const struct rate_ctr_desc bts_ctr_description[] = {
+ [BTS_CTR_CHREQ_TOTAL] = {"chreq:total", "Received channel requests."},
+ [BTS_CTR_CHREQ_NO_CHANNEL] = {"chreq:no_channel", "Sent to MS no channel available."},
+ [BTS_CTR_CHAN_RF_FAIL] = {"chan:rf_fail", "Received a RF failure indication from BTS."},
+ [BTS_CTR_CHAN_RLL_ERR] = {"chan:rll_err", "Received a RLL failure with T200 cause from BTS."},
+ [BTS_CTR_BTS_OML_FAIL] = {"oml_fail", "Received a TEI down on a OML link."},
+ [BTS_CTR_BTS_RSL_FAIL] = {"rsl_fail", "Received a TEI down on a OML link."},
+ [BTS_CTR_CODEC_AMR_F] = {"codec:amr_f", "Count the usage of AMR/F codec by channel mode requested."},
+ [BTS_CTR_CODEC_AMR_H] = {"codec:amr_h", "Count the usage of AMR/H codec by channel mode requested."},
+ [BTS_CTR_CODEC_EFR] = {"codec:efr", "Count the usage of EFR codec by channel mode requested."},
+ [BTS_CTR_CODEC_V1_FR] = {"codec:fr", "Count the usage of FR codec by channel mode requested."},
+ [BTS_CTR_CODEC_V1_HR] = {"codec:hr", "Count the usage of HR codec by channel mode requested."},
+
+ [BTS_CTR_PAGING_ATTEMPTED] = {"paging:attempted", "Paging attempts for a subscriber."},
+ [BTS_CTR_PAGING_ALREADY] = {"paging:already", "Paging attempts ignored as subsciber was already being paged."},
+ [BTS_CTR_PAGING_RESPONDED] = {"paging:responded", "Paging attempts with successful paging response."},
+ [BTS_CTR_PAGING_EXPIRED] = {"paging:expired", "Paging Request expired because of timeout T3113."},
+ [BTS_CTR_CHAN_ACT_TOTAL] = {"chan_act:total", "Total number of Channel Activations."},
+ [BTS_CTR_CHAN_ACT_NACK] = {"chan_act:nack", "Number of Channel Activations that the BTS NACKed"},
+ [BTS_CTR_RSL_UNKNOWN] = {"rsl:unknown", "Number of unknown/unsupported RSL messages received from BTS"},
+ [BTS_CTR_RSL_IPA_NACK] = {"rsl:ipa_nack", "Number of IPA (RTP/dyn-PDCH) related NACKs received from BTS"},
+ [BTS_CTR_MODE_MODIFY_NACK] = {"chan:mode_modify_nack", "Number of Channel Mode Modify NACKs received from BTS"},
+};
+
+static const struct rate_ctr_group_desc bts_ctrg_desc = {
+ "bts",
+ "base transceiver station",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(bts_ctr_description),
+ bts_ctr_description,
+};
+
+enum {
+ BTS_STAT_CHAN_LOAD_AVERAGE,
+ BTS_STAT_T3122,
+};
+
+enum {
+ BSC_CTR_ASSIGNMENT_ATTEMPTED,
+ BSC_CTR_ASSIGNMENT_COMPLETED,
+ BSC_CTR_ASSIGNMENT_STOPPED,
+ BSC_CTR_ASSIGNMENT_NO_CHANNEL,
+ BSC_CTR_ASSIGNMENT_TIMEOUT,
+ BSC_CTR_ASSIGNMENT_FAILED,
+ BSC_CTR_ASSIGNMENT_ERROR,
+ BSC_CTR_HANDOVER_ATTEMPTED,
+ BSC_CTR_HANDOVER_COMPLETED,
+ BSC_CTR_HANDOVER_STOPPED,
+ BSC_CTR_HANDOVER_NO_CHANNEL,
+ BSC_CTR_HANDOVER_TIMEOUT,
+ BSC_CTR_HANDOVER_FAILED,
+ BSC_CTR_HANDOVER_ERROR,
+ BSC_CTR_INTER_BSC_HO_OUT_ATTEMPTED,
+ BSC_CTR_INTER_BSC_HO_OUT_COMPLETED,
+ BSC_CTR_INTER_BSC_HO_OUT_STOPPED,
+ BSC_CTR_INTER_BSC_HO_OUT_TIMEOUT,
+ BSC_CTR_INTER_BSC_HO_OUT_ERROR,
+ BSC_CTR_INTER_BSC_HO_IN_ATTEMPTED,
+ BSC_CTR_INTER_BSC_HO_IN_COMPLETED,
+ BSC_CTR_INTER_BSC_HO_IN_STOPPED,
+ BSC_CTR_INTER_BSC_HO_IN_NO_CHANNEL,
+ BSC_CTR_INTER_BSC_HO_IN_FAILED,
+ BSC_CTR_INTER_BSC_HO_IN_TIMEOUT,
+ BSC_CTR_INTER_BSC_HO_IN_ERROR,
+ BSC_CTR_PAGING_ATTEMPTED,
+ BSC_CTR_PAGING_DETACHED,
+ BSC_CTR_PAGING_RESPONDED,
+ BSC_CTR_UNKNOWN_UNIT_ID,
+};
+
+static const struct rate_ctr_desc bsc_ctr_description[] = {
+ [BSC_CTR_ASSIGNMENT_ATTEMPTED] = {"assignment:attempted", "Assignment attempts."},
+ [BSC_CTR_ASSIGNMENT_COMPLETED] = {"assignment:completed", "Assignment completed."},
+ [BSC_CTR_ASSIGNMENT_STOPPED] = {"assignment:stopped", "Connection ended during Assignment."},
+ [BSC_CTR_ASSIGNMENT_NO_CHANNEL] = {"assignment:no_channel", "Failure to allocate lchan for Assignment."},
+ [BSC_CTR_ASSIGNMENT_TIMEOUT] = {"assignment:timeout", "Assignment timed out."},
+ [BSC_CTR_ASSIGNMENT_FAILED] = {"assignment:failed", "Received Assignment Failure message."},
+ [BSC_CTR_ASSIGNMENT_ERROR] = {"assignment:error", "Assigment failed for other reason."},
+
+ [BSC_CTR_HANDOVER_ATTEMPTED] = {"handover:attempted", "Intra-BSC handover attempts."},
+ [BSC_CTR_HANDOVER_COMPLETED] = {"handover:completed", "Intra-BSC handover completed."},
+ [BSC_CTR_HANDOVER_STOPPED] = {"handover:stopped", "Connection ended during HO."},
+ [BSC_CTR_HANDOVER_NO_CHANNEL] = {"handover:no_channel", "Failure to allocate lchan for HO."},
+ [BSC_CTR_HANDOVER_TIMEOUT] = {"handover:timeout", "Handover timed out."},
+ [BSC_CTR_HANDOVER_FAILED] = {"handover:failed", "Received Handover Fail messages."},
+ [BSC_CTR_HANDOVER_ERROR] = {"handover:error", "Re-assigment failed for other reason."},
+
+ [BSC_CTR_INTER_BSC_HO_OUT_ATTEMPTED] = {"interbsc_ho_out:attempted",
+ "Attempts to handover to remote BSS."},
+ [BSC_CTR_INTER_BSC_HO_OUT_COMPLETED] = {"interbsc_ho_out:completed",
+ "Handover to remote BSS completed."},
+ [BSC_CTR_INTER_BSC_HO_OUT_STOPPED] = {"interbsc_ho_out:stopped", "Connection ended during HO."},
+ [BSC_CTR_INTER_BSC_HO_OUT_TIMEOUT] = {"interbsc_ho_out:timeout", "Handover timed out."},
+ [BSC_CTR_INTER_BSC_HO_OUT_ERROR] = {"interbsc_ho_out:error",
+ "Handover to remote BSS failed for other reason."},
+
+ [BSC_CTR_INTER_BSC_HO_IN_ATTEMPTED] = {"interbsc_ho_in:attempted",
+ "Attempts to handover from remote BSS."},
+ [BSC_CTR_INTER_BSC_HO_IN_COMPLETED] = {"interbsc_ho_in:completed",
+ "Handover from remote BSS completed."},
+ [BSC_CTR_INTER_BSC_HO_IN_STOPPED] = {"interbsc_ho_in:stopped", "Connection ended during HO."},
+ [BSC_CTR_INTER_BSC_HO_IN_NO_CHANNEL] = {"interbsc_ho_in:no_channel",
+ "Failure to allocate lchan for HO."},
+ [BSC_CTR_INTER_BSC_HO_IN_TIMEOUT] = {"interbsc_ho_in:timeout", "Handover from remote BSS timed out."},
+ [BSC_CTR_INTER_BSC_HO_IN_FAILED] = {"interbsc_ho_in:failed", "Received Handover Fail message."},
+ [BSC_CTR_INTER_BSC_HO_IN_ERROR] = {"interbsc_ho_in:error",
+ "Handover from remote BSS failed for other reason."},
+
+ [BSC_CTR_PAGING_ATTEMPTED] = {"paging:attempted", "Paging attempts for a subscriber."},
+ [BSC_CTR_PAGING_DETACHED] = {"paging:detached", "Paging request send failures because no responsible BTS was found."},
+ [BSC_CTR_PAGING_RESPONDED] = {"paging:responded", "Paging attempts with successful response."},
+
+ [BSC_CTR_UNKNOWN_UNIT_ID] = {"abis:unknown_unit_id", "Connection attempts from unknown IPA CCM Unit ID."},
+};
+
+
+
+static const struct rate_ctr_group_desc bsc_ctrg_desc = {
+ "bsc",
+ "base station controller",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(bsc_ctr_description),
+ bsc_ctr_description,
+};
+
+struct gsm_tz {
+ int override; /* if 0, use system's time zone instead. */
+ int hr; /* hour */
+ int mn; /* minute */
+ int dst; /* daylight savings */
+};
+
+struct gsm_network {
+ /* TODO MSCSPLIT the gsm_network struct is basically a kitchen sink for
+ * global settings and variables, "madly" mixing BSC and MSC stuff. Split
+ * this in e.g. struct osmo_bsc and struct osmo_msc, with the things
+ * these have in common, like country and network code, put in yet
+ * separate structs and placed as members in osmo_bsc and osmo_msc. */
+
+ struct osmo_plmn_id plmn;
+
+ /* bit-mask of permitted encryption algorithms. LSB=A5/0, MSB=A5/7 */
+ uint8_t a5_encryption_mask;
+ int neci;
+
+ struct handover_cfg *ho;
+ struct {
+ unsigned int congestion_check_interval_s;
+ struct osmo_timer_list congestion_check_timer;
+ } hodec2;
+
+ struct rate_ctr_group *bsc_ctrs;
+
+ unsigned int num_bts;
+ struct llist_head bts_list;
+ struct llist_head bts_rejected;
+
+ /* shall reference gsm_network_T[] */
+ struct T_def *T_defs;
+
+ enum gsm_chan_t ctype_by_chreq[_NUM_CHREQ_T];
+
+ /* Use a TCH for handling requests of type paging any */
+ int pag_any_tch;
+
+ /* MSC data in case we are a true BSC */
+ struct osmo_bsc_data *bsc_data;
+
+ /* control interface */
+ struct ctrl_handle *ctrl;
+
+ /* Allow or disallow TCH/F on dynamic TCH/F_TCH/H_PDCH; OS#1778 */
+ bool dyn_ts_allow_tch_f;
+
+ /* all active subscriber connections. */
+ struct llist_head subscr_conns;
+
+ /* if override is nonzero, this timezone data is used for all MM
+ * contexts. */
+ /* TODO: in OsmoNITB, tz-override used to be BTS-specific. To enable
+ * BTS|RNC specific timezone overrides for multi-tz networks in
+ * OsmoMSC, this should be tied to the location area code (LAC). */
+ struct gsm_tz tz;
+
+ /* List of all struct bsc_subscr used in libbsc. This llist_head is
+ * allocated so that the llist_head pointer itself can serve as a
+ * talloc context (useful to not have to pass the entire gsm_network
+ * struct to the bsc_subscr_* API, and for bsc_susbscr unit tests to
+ * not require gsm_data.h). In an MSC-without-BSC environment, this
+ * pointer is NULL to indicate absence of a bsc_subscribers list. */
+ struct llist_head *bsc_subscribers;
+
+ /* Timer for periodic channel load measurements to maintain each BTS's T3122. */
+ struct osmo_timer_list t3122_chan_load_timer;
+
+ struct {
+ struct mgcp_client_conf *conf;
+ struct mgcp_client *client;
+ } mgw;
+
+ /* Remote BSS Cell Identifier Lists */
+ struct neighbor_ident_list *neighbor_bss_cells;
+};
+
+struct gsm_audio_support {
+ uint8_t hr : 1,
+ ver : 7;
+};
+
+static inline const struct osmo_location_area_id *bts_lai(struct gsm_bts *bts)
+{
+ static struct osmo_location_area_id lai;
+ lai = (struct osmo_location_area_id){
+ .plmn = bts->network->plmn,
+ .lac = bts->location_area_code,
+ };
+ return &lai;
+}
+
+extern void talloc_ctx_init(void *ctx_root);
+
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type);
+
+enum gsm_bts_type parse_btstype(const char *arg);
+const char *btstype2str(enum gsm_bts_type type);
+struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
+ struct gsm_bts *start_bts);
+
+extern void *tall_bsc_ctx;
+extern int ipacc_rtp_direct;
+
+/* this actaully refers to the IPA transport, not the BTS model */
+static inline int is_ipaccess_bts(struct gsm_bts *bts)
+{
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ case GSM_BTS_TYPE_OSMOBTS:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static inline int is_sysmobts_v2(struct gsm_bts *bts)
+{
+ switch (bts->type) {
+ case GSM_BTS_TYPE_OSMOBTS:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static inline int is_siemens_bts(struct gsm_bts *bts)
+{
+ switch (bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static inline int is_nokia_bts(struct gsm_bts *bts)
+{
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static inline int is_e1_bts(struct gsm_bts *bts)
+{
+ switch (bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ case GSM_BTS_TYPE_RBS2000:
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+extern struct gsm_network *bsc_gsmnet;
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg, int *valid);
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode);
+int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode);
+
+void gsm48_ra_id_by_bts(struct gsm48_ra_id *buf, struct gsm_bts *bts);
+void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts);
+
+int gsm_bts_model_register(struct gsm_bts_model *model);
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *network);
+
+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 *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);
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason);
+struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr);
+int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx);
+int gsm_bts_set_system_infos(struct gsm_bts *bts);
+
+/* generic E1 line operations for all ISDN-based BTS. */
+extern struct e1inp_line_ops bts_isdn_e1inp_line_ops;
+
+extern const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE+1];
+extern const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1];
+
+char *get_model_oml_status(const struct gsm_bts *bts);
+
+unsigned long long bts_uptime(const struct gsm_bts *bts);
+
+/* control interface handling */
+int bsc_base_ctrl_cmds_install(void);
+
+/* dependency handling */
+void bts_depend_mark(struct gsm_bts *bts, int dep);
+void bts_depend_clear(struct gsm_bts *bts, int dep);
+int bts_depend_check(struct gsm_bts *bts);
+int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other);
+
+int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts);
+void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value);
+
+bool classmark_is_r99(struct gsm_classmark *cm);
+
+bool trx_is_usable(const struct gsm_bts_trx *trx);
+bool ts_is_usable(const struct gsm_bts_trx_ts *ts);
+
+int gsm_lchan_type_by_pchan(enum gsm_phys_chan_config pchan);
+enum gsm_phys_chan_config gsm_pchan_by_lchan_type(enum gsm_chan_t type);
+
+void gsm_bts_all_ts_dispatch(struct gsm_bts *bts, uint32_t ts_ev, void *data);
+void gsm_trx_all_ts_dispatch(struct gsm_bts_trx *trx, uint32_t ts_ev, void *data);
+
+int bts_count_free_ts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan);
+
+struct osmo_cell_global_id *cgi_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts);
+
+#endif /* _GSM_DATA_H */
diff --git a/include/osmocom/bsc/gsm_timers.h b/include/osmocom/bsc/gsm_timers.h
new file mode 100644
index 000000000..78f04edd9
--- /dev/null
+++ b/include/osmocom/bsc/gsm_timers.h
@@ -0,0 +1,56 @@
+/* API to define Tnnn timers globally, configure in VTY and use for FSM state changes. */
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/utils.h>
+
+struct osmo_fsm_inst;
+struct vty;
+
+enum T_unit {
+ T_S = 0, /*< most T are in seconds, keep 0 as default. */
+ T_MS, /*< milliseconds */
+ T_M, /*< minutes */
+ T_CUSTOM,
+};
+
+extern const struct value_string T_unit_names[];
+static inline const char *T_unit_name(enum T_unit val)
+{ return get_value_string(T_unit_names, val); }
+
+/* Define a GSM timer of the form Tnnn, with unit, default value and doc string. */
+struct T_def {
+ const int T; /*< T1234 number */
+ const int default_val; /*< timeout duration (according to unit), default value. */
+ const enum T_unit unit;
+ const char *desc;
+ int val; /*< currently active value, e.g. set by user config. */
+};
+
+/* Iterate an array of struct T_def, the last item should be fully zero, i.e. "{}" */
+#define for_each_T_def(d, T_defs) \
+ for (d = T_defs; d && (d->T || d->default_val || d->desc); d++)
+
+int T_def_get(const struct T_def *T_defs, int T, enum T_unit as_unit, int val_if_not_present);
+void T_defs_reset(struct T_def *T_defs);
+struct T_def *T_def_get_entry(struct T_def *T_defs, int T);
+
+void T_defs_vty_init(struct T_def *T_defs, int cfg_parent_node);
+void T_defs_vty_write(struct vty *vty, const char *indent);
+
+
+struct state_timeout {
+ int T;
+ bool keep_timer;
+};
+
+const struct state_timeout *get_state_timeout(uint32_t state,
+ const struct state_timeout *timeouts_array);
+
+#define fsm_inst_state_chg_T(fi, state, timeouts_array, T_defs, default_timeout) \
+ _fsm_inst_state_chg_T(fi, state, timeouts_array, T_defs, default_timeout, \
+ __FILE__, __LINE__)
+int _fsm_inst_state_chg_T(struct osmo_fsm_inst *fi, uint32_t state,
+ const struct state_timeout *timeouts_array,
+ const struct T_def *T_defs, int default_timeout,
+ const char *file, int line);
diff --git a/include/osmocom/bsc/handover.h b/include/osmocom/bsc/handover.h
new file mode 100644
index 000000000..322913da4
--- /dev/null
+++ b/include/osmocom/bsc/handover.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/gsm_data.h>
+
+struct gsm_network;
+struct gsm_lchan;
+struct gsm_bts;
+struct gsm_subscriber_connection;
+struct gsm_meas_rep mr;
+
+enum handover_result {
+ HO_RESULT_OK,
+ HO_RESULT_FAIL_NO_CHANNEL,
+ HO_RESULT_FAIL_RR_HO_FAIL,
+ HO_RESULT_FAIL_TIMEOUT,
+ HO_RESULT_CONN_RELEASE,
+ HO_RESULT_ERROR,
+};
+
+extern const struct value_string handover_result_names[];
+inline static const char *handover_result_name(enum handover_result val)
+{ return get_value_string(handover_result_names, val); }
+
+int bts_handover_count(struct gsm_bts *bts, int ho_scopes);
+
+/* Handover decision algorithms' actions to take on incoming handover-relevant events.
+ *
+ * All events that are interesting for handover decision are actually communicated by S_LCHAN_* signals,
+ * so theoretically, each handover algorithm could evaluate those. However, handover_logic.c cleans up
+ * handover operation state upon receiving some of these signals. To allow a handover decision algorithm
+ * to take advantage of e.g. the struct handover before it is discarded, the handover decision event
+ * handler needs to be invoked before handover_logic.c discards the state. For example, if the handover
+ * decision wants to place a penalty timer upon a handover failure, it still needs to know which target
+ * cell the handover failed for; handover_logic.c erases that knowledge on handover failure, since it
+ * needs to clean up the lchan's handover state.
+ *
+ * The most explicit and safest way to ensure the correct order of event handling is to invoke the
+ * handover decision algorithm's actions from handover_logic.c itself, before cleaning up. This struct
+ * provides the callback functions for this purpose.
+ *
+ * For consistency, also handle signals in this way that aren't actually in danger of interference from
+ * handover_logic.c (which also saves repeated lookup of handover state for lchans). Thus, handover
+ * decision algorithms should not register any signal handler at all.
+ */
+struct handover_decision_callbacks {
+ struct llist_head entry;
+
+ int hodec_id;
+
+ void (*on_measurement_report)(struct gsm_meas_rep *mr);
+ void (*on_handover_end)(struct gsm_subscriber_connection *conn, enum handover_result result);
+};
+
+void handover_decision_callbacks_register(struct handover_decision_callbacks *hdc);
+struct handover_decision_callbacks *handover_decision_callbacks_get(int hodec_id);
+
+int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell_id_list2 *target_cells);
+int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn,
+ struct msgb *rr_ho_command);
+int bsc_tx_bssmap_ho_detect(struct gsm_subscriber_connection *conn);
+enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection *conn,
+ struct gsm_lchan *lchan);
+void bsc_tx_bssmap_ho_failure(struct gsm_subscriber_connection *conn);
+
+struct gsm_bts *bts_by_neighbor_ident(const struct gsm_network *net,
+ const struct neighbor_ident_key *search_for);
+struct neighbor_ident_key *bts_ident_key(const struct gsm_bts *bts);
+
+void handover_parse_inter_bsc_mt(struct gsm_subscriber_connection *conn,
+ struct msgb *ho_request_msg);
+
+void handover_mt_allocate_lchan(struct handover *ho);
+int handover_mt_send_rr_ho_command(struct handover *ho);
diff --git a/include/osmocom/bsc/handover_cfg.h b/include/osmocom/bsc/handover_cfg.h
new file mode 100644
index 000000000..92b5cd44c
--- /dev/null
+++ b/include/osmocom/bsc/handover_cfg.h
@@ -0,0 +1,289 @@
+#pragma once
+
+#include <stdbool.h>
+#include <string.h>
+
+struct vty;
+
+/* handover_cfg is an opaque struct to manage several levels of configuration. There is an overall handover
+ * config on 'network' level and a per-'bts' specific handover config. If the 'bts' level sets no values,
+ * the defaults from 'network' level are used implicitly, and changes take effect immediately. */
+struct handover_cfg;
+
+#define HO_CFG_CONGESTION_CHECK_DEFAULT 10
+
+struct handover_cfg *ho_cfg_init(void *ctx, struct handover_cfg *higher_level_cfg);
+
+#define HO_CFG_STR_HANDOVER1 "Handover options for handover decision algorithm 1\n"
+#define HO_CFG_STR_HANDOVER2 "Handover options for handover decision algorithm 2\n"
+#define HO_CFG_STR_WIN "Measurement averaging settings\n"
+#define HO_CFG_STR_WIN_RXLEV HO_CFG_STR_WIN "Received-Level averaging\n"
+#define HO_CFG_STR_WIN_RXQUAL HO_CFG_STR_WIN "Received-Quality averaging\n"
+#define HO_CFG_STR_POWER_BUDGET "Neighbor cell power triggering\n" "Neighbor cell power triggering\n"
+#define HO_CFG_STR_AVG_COUNT "Number of values to average over\n"
+#define HO_CFG_STR_MIN "Minimum Level/Quality thresholds before triggering HO\n"
+#define HO_CFG_STR_AFS_BIAS "Configure bias to prefer AFS (AMR on TCH/F) over other codecs\n"
+#define HO_CFG_STR_MIN_TCH "Minimum free TCH timeslots before cell is considered congested\n"
+#define HO_CFG_STR_PENALTY_TIME "Set penalty times to wait between repeated handovers\n"
+
+#define as_is(x) (x)
+
+static inline bool a2bool(const char *arg)
+{
+ return (bool)(atoi(arg));
+}
+
+static inline int bool2i(bool arg)
+{
+ return arg? 1 : 0;
+}
+
+static inline bool a2tdma(const char *arg)
+{
+ if (!strcmp(arg, "full"))
+ return true;
+ return false;
+}
+
+static inline const char *tdma2a(bool val)
+{
+ return val? "full" : "subset";
+}
+
+/* The HO_CFG_ONE_MEMBER macro gets redefined, depending on whether to define struct members,
+ * function declarations or definitions... It is of the format
+ * HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL,
+ * VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL,
+ * VTY_WRITE_FMT, VTY_WRITE_CONV,
+ * VTY_DOC)
+ * Then using HO_CFG_ALL_MEMBERS can save a lot of code dup in defining API declaration, API
+ * definitions, VTY commands and VTY write code. Of course this doesn't prevent us from adding manual
+ * members as well, in case future additions don't fit in this scheme.
+ *
+ * TYPE: a type name like int.
+ * NAME: a variable name suitable for a struct member.
+ * DEFAULT_VAL: default value, as passed to the VTY, e.g. '0' or 'foo', without quotes.
+ * VTY_CMD_PREFIX: "handover1 ", "handover2 ", ... or just "" for the common general items.
+ * VTY_CMD: a command string for VTY without any arguments.
+ * VTY_CMD_ARG: VTY value range like '<0-23>' or 'foo|bar', will become '(VTY_CMD_ARG|default)'.
+ * VTY_ARG_EVAL: function name for parsing the VTY arg[0], e.g. 'atoi'.
+ * VTY_WRITE_FMT: printf-like string format for vty_out().
+ * VTY_WRITE_CONV: function name to convert struct value to VTY_WRITE_FMT, e.g. 'as_is'.
+ * VTY_DOC: VTY documentation strings to match VTY_CMD and VTY_CMD_ARGs.
+ */
+#define HO_GENERAL_CFG_ALL_MEMBERS \
+ \
+ HO_CFG_ONE_MEMBER(bool, ho_active, 0, \
+ "", "handover", "0|1", a2bool, "%d", bool2i, \
+ "Handover general config\n" \
+ "Disable in-call handover\n" \
+ "Enable in-call handover\n" \
+ "Enable/disable handover: ") \
+ \
+ HO_CFG_ONE_MEMBER(int, algorithm, 1, \
+ "", "handover algorithm", "1|2", atoi, "%d", as_is, \
+ "Handover general config\n" \
+ "Choose algorithm for handover decision\n" \
+ "Algorithm 1: trigger handover based on comparing current cell and neighbor RxLev and RxQual," \
+ " only.\n" \
+ "Algorithm 2: trigger handover on RxLev/RxQual, and also to balance the load across several" \
+ " cells. Consider available codecs. Prevent repeated handover by penalty timers.\n") \
+
+
+#define HODEC1_CFG_ALL_MEMBERS \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec1_rxlev_avg_win, 10, \
+ "handover1 ", "window rxlev averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER1 \
+ HO_CFG_STR_WIN_RXLEV \
+ "How many RxLev measurements are used for averaging\n" \
+ "RxLev averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec1_rxqual_avg_win, 1, \
+ "handover1 ", "window rxqual averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER1 \
+ HO_CFG_STR_WIN_RXQUAL \
+ "How many RxQual measurements are used for averaging\n" \
+ "RxQual averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec1_rxlev_neigh_avg_win, 10, \
+ "handover1 ", "window rxlev neighbor averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER1 \
+ HO_CFG_STR_WIN_RXLEV \
+ "How many Neighbor RxLev measurements are used for averaging\n" \
+ "How many Neighbor RxLev measurements are used for averaging\n" \
+ "Neighbor RxLev averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec1_pwr_interval, 6, \
+ "handover1 ", "power budget interval", "<1-99>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER1 \
+ HO_CFG_STR_POWER_BUDGET \
+ "How often to check for a better cell (SACCH frames)\n" \
+ "Check for stronger neighbor every N number of SACCH frames\n") \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec1_pwr_hysteresis, 3, \
+ "handover1 ", "power budget hysteresis", "<0-999>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER1 \
+ HO_CFG_STR_POWER_BUDGET \
+ "How many dB stronger must a neighbor be to become a HO candidate\n" \
+ "Neighbor's strength difference in dB\n") \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec1_max_distance, 9999, \
+ "handover1 ", "maximum distance" , "<0-9999>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER1 \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n" \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n" \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n") \
+
+
+#define HODEC2_CFG_ALL_MEMBERS \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec2_rxlev_avg_win, 10, \
+ "handover2 ", "window rxlev averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_WIN_RXLEV \
+ "How many RxLev measurements are used for averaging\n" \
+ "RxLev averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec2_rxqual_avg_win, 1, \
+ "handover2 ", "window rxqual averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_WIN_RXQUAL \
+ "How many RxQual measurements are used for averaging\n" \
+ "RxQual averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec2_rxlev_neigh_avg_win, 10, \
+ "handover2 ", "window rxlev neighbor averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_WIN_RXLEV \
+ "How many Neighbor RxLev measurements are used for averaging\n" \
+ "How many Neighbor RxLev measurements are used for averaging\n" \
+ "Neighbor RxLev averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec2_pwr_interval, 6, \
+ "handover2 ", "power budget interval", "<1-99>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_POWER_BUDGET \
+ "How often to check for a better cell (SACCH frames)\n" \
+ "Check for stronger neighbor every N number of SACCH frames\n") \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec2_pwr_hysteresis, 3, \
+ "handover2 ", "power budget hysteresis", "<0-999>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_POWER_BUDGET \
+ "How many dB stronger must a neighbor be to become a HO candidate\n" \
+ "Neighbor's strength difference in dB\n") \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, hodec2_max_distance, 9999, \
+ "handover2 ", "maximum distance" , "<0-9999>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n" \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n" \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n") \
+ \
+ HO_CFG_ONE_MEMBER(bool, hodec2_as_active, 0, \
+ "handover2 ", "assignment", "0|1", a2bool, "%d", bool2i, \
+ HO_CFG_STR_HANDOVER2 \
+ "Enable or disable in-call channel re-assignment\n" \
+ "Disable in-call assignment\n" \
+ "Enable in-call assignment\n") \
+ \
+ HO_CFG_ONE_MEMBER(bool, hodec2_full_tdma, subset, \
+ "handover2 ", "tdma-measurement", "full|subset", a2tdma, "%s", tdma2a, \
+ HO_CFG_STR_HANDOVER2 \
+ "Define measurement set of TDMA frames\n" \
+ "Full set of 102/104 TDMA frames\n" \
+ "Sub set of 4 TDMA frames (SACCH)\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_min_rxlev, -100, \
+ "handover2 ", "min rxlev", "<-110--50>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_MIN \
+ "How weak may RxLev of an MS become before triggering HO\n" \
+ "minimum RxLev (dBm)\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_min_rxqual, 5, \
+ "handover2 ", "min rxqual", "<0-7>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_MIN \
+ "How bad may RxQual of an MS become before triggering HO\n" \
+ "minimum RxQual\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_afs_bias_rxlev, 0, \
+ "handover2 ", "afs-bias rxlev", "<0-20>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_AFS_BIAS \
+ "RxLev improvement bias for AFS over other codecs\n" \
+ "Virtual RxLev improvement (dB)\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_afs_bias_rxqual, 0, \
+ "handover2 ", "afs-bias rxqual", "<0-7>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_AFS_BIAS \
+ "RxQual improvement bias for AFS over other codecs\n" \
+ "Virtual RxQual improvement\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_tchf_min_slots, 0, \
+ "handover2 ", "min-free-slots tch/f", "<0-9999>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_MIN_TCH \
+ "Minimum free TCH/F timeslots before cell is considered congested\n" \
+ "Number of TCH/F slots\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_tchh_min_slots, 0, \
+ "handover2 ", "min-free-slots tch/h", "<0-9999>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_MIN_TCH \
+ "Minimum free TCH/H timeslots before cell is considered congested\n" \
+ "Number of TCH/H slots\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_ho_max, 9999, \
+ "handover2 ", "max-handovers", "<1-9999>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ "Maximum number of concurrent handovers allowed per cell\n" \
+ "Number\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_penalty_max_dist, 300, \
+ "handover2 ", "penalty-time max-distance", "<0-99999>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_PENALTY_TIME \
+ "Time to suspend handovers after leaving this cell due to exceeding max distance\n" \
+ "Seconds\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_penalty_failed_ho, 60, \
+ "handover2 ", "penalty-time failed-ho", "<0-99999>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_PENALTY_TIME \
+ "Time to suspend handovers after handover failure to this cell\n" \
+ "Seconds\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_penalty_failed_as, 60, \
+ "handover2 ", "penalty-time failed-assignment", "<0-99999>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ HO_CFG_STR_PENALTY_TIME \
+ "Time to suspend handovers after assignment failure in this cell\n" \
+ "Seconds\n") \
+ \
+ HO_CFG_ONE_MEMBER(int, hodec2_retries, 0, \
+ "handover2 ", "retries", "<0-9>", atoi, "%d", as_is, \
+ HO_CFG_STR_HANDOVER2 \
+ "Immediately retry on handover/assignment failure\n" \
+ "Number of retries\n") \
+
+#define HO_CFG_ALL_MEMBERS \
+ HO_GENERAL_CFG_ALL_MEMBERS \
+ HODEC1_CFG_ALL_MEMBERS \
+ HODEC2_CFG_ALL_MEMBERS \
+
+
+/* Declare public API for handover cfg parameters... */
+
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \
+ TYPE ho_get_##NAME(struct handover_cfg *ho); \
+ void ho_set_##NAME(struct handover_cfg *ho, TYPE val); \
+ bool ho_isset_##NAME(struct handover_cfg *ho); \
+ void ho_clear_##NAME(struct handover_cfg *ho); \
+ bool ho_isset_on_parent_##NAME(struct handover_cfg *ho);
+
+HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
diff --git a/include/osmocom/bsc/handover_decision.h b/include/osmocom/bsc/handover_decision.h
new file mode 100644
index 000000000..09430d889
--- /dev/null
+++ b/include/osmocom/bsc/handover_decision.h
@@ -0,0 +1,5 @@
+#pragma once
+
+struct gsm_bts *bts_by_arfcn_bsic(const struct gsm_network *net, uint16_t arfcn, uint8_t bsic);
+
+void handover_decision_1_init(void);
diff --git a/include/osmocom/bsc/handover_decision_2.h b/include/osmocom/bsc/handover_decision_2.h
new file mode 100644
index 000000000..f245b073e
--- /dev/null
+++ b/include/osmocom/bsc/handover_decision_2.h
@@ -0,0 +1,9 @@
+/* Handover Decision Algorithm 2 for intra-BSC (inter-BTS) handover, public API for OsmoBSC */
+
+#pragma once
+struct gsm_bts;
+
+void hodec2_init(struct gsm_network *net);
+
+void hodec2_on_change_congestion_check_interval(struct gsm_network *net, unsigned int new_interval);
+void hodec2_congestion_check(struct gsm_network *net);
diff --git a/include/osmocom/bsc/handover_fsm.h b/include/osmocom/bsc/handover_fsm.h
new file mode 100644
index 000000000..4db08901d
--- /dev/null
+++ b/include/osmocom/bsc/handover_fsm.h
@@ -0,0 +1,80 @@
+/* Handover FSM API for intra-BSC and inter-BSC Handover. */
+#pragma once
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/handover.h>
+
+const char *handover_status(struct gsm_subscriber_connection *conn);
+
+/* This macro automatically includes a final \n, if omitted. */
+#define LOG_HO(conn, level, fmt, args...) do { \
+ if (conn->ho.fi) \
+ LOGPFSML(conn->ho.fi, level, "%s: " fmt, \
+ handover_status(conn), ## args); \
+ else \
+ LOGP(DHODEC, level, "%s: " fmt, \
+ handover_status(conn), ## args); \
+ } while(0)
+
+/* Terminology:
+ * Intra-Cell: stays within one BTS, this should actually be an Assignment.
+ * Intra-BSC: stays within one BSC, but moves between BTSes.
+ * Inter-BSC: moves between BSCs.
+ * Inter-BSC Out: move away from this BSC to another one.
+ * Inter-BSC In: move from another BSC to this one.
+ */
+
+enum handover_fsm_state {
+ HO_ST_NOT_STARTED,
+
+ HO_ST_WAIT_LCHAN_ACTIVE,
+ HO_ST_WAIT_RR_HO_DETECT,
+ HO_ST_WAIT_RR_HO_COMPLETE,
+ HO_ST_WAIT_LCHAN_ESTABLISHED,
+ HO_ST_WAIT_MGW_ENDPOINT_TO_MSC,
+
+ /* The inter-BSC Outgoing Handover FSM has completely separate states, but since it makes sense for it
+ * to also live in conn->ho.fi, it should share the same event enum. From there it is merely
+ * cosmetic to just include the separate fairly trivial states in the same FSM definition.
+ * An inter-BSC Outgoing FSM is almost unnecessary. The sole reason is to wait whether the MSC
+ * indeed clears the conn, and if not to log and count a failed handover attempt. */
+ HO_OUT_ST_WAIT_HO_COMMAND,
+ HO_OUT_ST_WAIT_CLEAR,
+};
+
+enum handover_fsm_event {
+ HO_EV_LCHAN_ACTIVE,
+ HO_EV_LCHAN_ESTABLISHED,
+ HO_EV_LCHAN_ERROR,
+ HO_EV_RR_HO_DETECT,
+ HO_EV_RR_HO_COMPLETE,
+ HO_EV_RR_HO_FAIL,
+ HO_EV_MSC_MGW_OK,
+ HO_EV_MSC_MGW_FAIL,
+ HO_EV_CONN_RELEASING,
+
+ HO_OUT_EV_BSSMAP_HO_COMMAND,
+};
+
+struct ho_out_rx_bssmap_ho_command {
+ const uint8_t *l3_info;
+ const uint8_t l3_info_len;
+};
+
+/* To be sent along with the HO_EV_RR_HO_DETECT */
+struct handover_rr_detect_data {
+ struct msgb *msg;
+ const uint8_t *access_delay;
+};
+
+void handover_fsm_init();
+
+void handover_request(struct handover_out_req *req);
+void handover_start(struct handover_out_req *req);
+void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
+ struct msgb *ho_request_msg);
+void handover_end(struct gsm_subscriber_connection *conn, enum handover_result result);
+
+const char *handover_status(struct gsm_subscriber_connection *conn);
+bool handover_is_sane(struct gsm_subscriber_connection *conn, struct gsm_lchan *old_lchan,
+ struct gsm_lchan *new_lchan);
diff --git a/include/osmocom/bsc/handover_vty.h b/include/osmocom/bsc/handover_vty.h
new file mode 100644
index 000000000..6ad5276e2
--- /dev/null
+++ b/include/osmocom/bsc/handover_vty.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/bsc/handover_cfg.h>
+
+void ho_vty_init();
+void ho_vty_write_net(struct vty *vty, struct gsm_network *net);
+void ho_vty_write_bts(struct vty *vty, struct gsm_bts *bts);
diff --git a/include/osmocom/bsc/ipaccess.h b/include/osmocom/bsc/ipaccess.h
new file mode 100644
index 000000000..692e79576
--- /dev/null
+++ b/include/osmocom/bsc/ipaccess.h
@@ -0,0 +1,55 @@
+#ifndef _IPACCESS_H
+#define _IPACCESS_H
+
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+
+struct gsm_bts;
+struct gsm_bts_trx;
+
+struct ipac_msgt_sccp_state {
+ uint8_t src_ref[3];
+ uint8_t dst_ref[3];
+ uint8_t trans_id;
+ uint8_t invoke_id;
+ char imsi[GSM23003_IMSI_MAX_DIGITS+1];
+ uint8_t data[0];
+} __attribute__((packed));
+
+/*
+ * @add_remove 0 for remove, 1 for add, 3 to asK
+ * @nr_lacs Number of extra lacs inside this package
+ * @lac One lac entry
+ */
+struct ipac_ext_lac_cmd {
+ uint8_t add_remove;
+ uint8_t nr_extra_lacs;
+ uint16_t lac;
+ uint8_t data[0];
+} __attribute__((packed));
+
+void ipaccess_drop_oml(struct gsm_bts *bts);
+void ipaccess_drop_oml_deferred(struct gsm_bts *bts);
+void ipaccess_drop_rsl(struct gsm_bts_trx *trx);
+
+struct sdp_header_item {
+ struct sdp_header_entry header_entry;
+ struct llist_head entry;
+ off_t absolute_offset;
+};
+
+struct sdp_header {
+ struct sdp_firmware firmware_info;
+
+ /* for more_magic a list of sdp_header_entry_list */
+ struct llist_head header_list;
+
+ /* the entry of the sdp_header */
+ struct llist_head entry;
+};
+
+int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned base_offset, struct llist_head *list);
+
+#endif /* _IPACCESS_H */
diff --git a/include/osmocom/bsc/lchan_fsm.h b/include/osmocom/bsc/lchan_fsm.h
new file mode 100644
index 000000000..48cd3836a
--- /dev/null
+++ b/include/osmocom/bsc/lchan_fsm.h
@@ -0,0 +1,86 @@
+/* osmo-bsc API to manage lchans, logical channels in GSM cells. */
+#pragma once
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/debug.h>
+
+/* This macro automatically includes a final \n, if omitted. */
+#define LOG_LCHAN(lchan, level, fmt, args...) do { \
+ if (lchan->fi) \
+ LOGPFSML(lchan->fi, level, "(type=%s) " fmt, gsm_lchant_name(lchan->type), ## args); \
+ else \
+ LOGP(DRSL, level, "%s (not initialized) " fmt, gsm_lchan_name(lchan), ## args); \
+ } while(0)
+
+enum lchan_fsm_state {
+ LCHAN_ST_UNUSED,
+ LCHAN_ST_CBCH, /*< Blocked by CBCH channel combination, not usable as SDCCH. */
+ LCHAN_ST_WAIT_TS_READY,
+ LCHAN_ST_WAIT_ACTIV_ACK, /*< After RSL Chan Act Ack, lchan is active but RTP not configured. */
+ LCHAN_ST_WAIT_RLL_RTP_ESTABLISH,
+ LCHAN_ST_ESTABLISHED, /*< Active and RTP is fully configured. */
+ LCHAN_ST_WAIT_RLL_RTP_RELEASED,
+ LCHAN_ST_WAIT_BEFORE_RF_RELEASE,
+ LCHAN_ST_WAIT_RF_RELEASE_ACK,
+ LCHAN_ST_WAIT_AFTER_ERROR,
+ LCHAN_ST_BORKEN,
+};
+
+enum lchan_fsm_event {
+ LCHAN_EV_ACTIVATE,
+ LCHAN_EV_TS_READY,
+ LCHAN_EV_TS_ERROR,
+ LCHAN_EV_RSL_CHAN_ACTIV_ACK,
+ LCHAN_EV_RSL_CHAN_ACTIV_NACK,
+ LCHAN_EV_RLL_ESTABLISH_IND,
+ LCHAN_EV_RTP_READY,
+ LCHAN_EV_RTP_ERROR,
+ LCHAN_EV_RTP_RELEASED,
+ LCHAN_EV_RLL_REL_IND,
+ LCHAN_EV_RLL_REL_CONF,
+ LCHAN_EV_RSL_RF_CHAN_REL_ACK,
+ LCHAN_EV_RLL_ERR_IND,
+
+ /* FIXME: not yet implemented: Chan Mode Modify, see assignment_fsm_start(). */
+ LCHAN_EV_CHAN_MODE_MODIF_ACK,
+ LCHAN_EV_CHAN_MODE_MODIF_ERROR,
+};
+
+void lchan_fsm_init();
+
+void lchan_fsm_alloc(struct gsm_lchan *lchan);
+void lchan_release(struct gsm_lchan *lchan, bool do_rr_release,
+ bool err, enum gsm48_rr_cause cause_rr);
+
+struct lchan_activate_info {
+ enum lchan_activate_mode activ_for;
+ struct gsm_subscriber_connection *for_conn;
+ /* This always is for a specific lchan, so its lchan->type indicates full or half rate.
+ * When a dyn TS was selected, the lchan->type has been set to the desired rate. */
+ enum gsm48_chan_mode chan_mode;
+ uint16_t s15_s0;
+ bool requires_voice_stream;
+ bool wait_before_switching_rtp;
+ uint16_t msc_assigned_cic;
+ struct gsm_lchan *old_lchan;
+};
+
+void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info);
+void lchan_ready_to_switch_rtp(struct gsm_lchan *lchan);
+
+static inline const char *lchan_state_name(struct gsm_lchan *lchan)
+{
+ return lchan->fi ? osmo_fsm_inst_state_name(lchan->fi) : "NULL";
+}
+
+static inline bool lchan_state_is(struct gsm_lchan *lchan, uint32_t state)
+{
+ return (!lchan->fi && state == LCHAN_ST_UNUSED)
+ || (lchan->fi && lchan->fi->state == state);
+}
+
+bool lchan_may_receive_data(struct gsm_lchan *lchan);
+
+void lchan_forget_conn(struct gsm_lchan *lchan);
+
+void lchan_set_last_error(struct gsm_lchan *lchan, const char *fmt, ...);
diff --git a/include/osmocom/bsc/lchan_rtp_fsm.h b/include/osmocom/bsc/lchan_rtp_fsm.h
new file mode 100644
index 000000000..fa0e74636
--- /dev/null
+++ b/include/osmocom/bsc/lchan_rtp_fsm.h
@@ -0,0 +1,45 @@
+/* osmo-bsc API to manage lchans, logical channels in GSM cells. */
+#pragma once
+
+#define LOG_LCHAN_RTP(lchan, level, fmt, args...) do { \
+ if (lchan->fi_rtp) \
+ LOGPFSML(lchan->fi_rtp, level, fmt, ## args); \
+ else \
+ LOGP(DLMGCP, level, "%s (not initialized) " fmt, gsm_lchan_name(lchan), \
+ ## args); \
+ } while(0)
+
+struct gsm_lchan;
+
+enum lchan_rtp_fsm_state {
+ LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE,
+ LCHAN_RTP_ST_WAIT_LCHAN_READY,
+ LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK,
+ LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK,
+ LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP,
+ LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED,
+ LCHAN_RTP_ST_READY,
+ LCHAN_RTP_ST_ROLLBACK,
+ LCHAN_RTP_ST_ESTABLISHED,
+};
+
+enum lchan_rtp_fsm_event {
+ LCHAN_RTP_EV_LCHAN_READY,
+ LCHAN_RTP_EV_READY_TO_SWITCH_RTP,
+ LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE,
+ LCHAN_RTP_EV_MGW_ENDPOINT_ERROR,
+ LCHAN_RTP_EV_IPACC_CRCX_ACK,
+ LCHAN_RTP_EV_IPACC_CRCX_NACK,
+ LCHAN_RTP_EV_IPACC_MDCX_ACK,
+ LCHAN_RTP_EV_IPACC_MDCX_NACK,
+ LCHAN_RTP_EV_READY_TO_SWITCH,
+ LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED,
+ LCHAN_RTP_EV_ROLLBACK, /*< Give the RTP back to the old lchan, if any */
+ LCHAN_RTP_EV_ESTABLISHED, /*< All done, forget about the old lchan, if any */
+ LCHAN_RTP_EV_RELEASE,
+};
+
+void lchan_rtp_fsm_start(struct gsm_lchan *lchan);
+struct mgwep_ci *lchan_use_mgw_endpoint_ci_bts(struct gsm_lchan *lchan);
+bool lchan_rtp_established(struct gsm_lchan *lchan);
+void lchan_forget_mgw_endpoint(struct gsm_lchan *lchan);
diff --git a/include/osmocom/bsc/lchan_select.h b/include/osmocom/bsc/lchan_select.h
new file mode 100644
index 000000000..4aecdf676
--- /dev/null
+++ b/include/osmocom/bsc/lchan_select.h
@@ -0,0 +1,6 @@
+/* Select a suitable lchan from a given cell. */
+#pragma once
+
+struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts, enum gsm_chan_t type);
+struct gsm_lchan *lchan_select_by_chan_mode(struct gsm_bts *bts,
+ enum gsm48_chan_mode chan_mode, bool full_rate);
diff --git a/include/osmocom/bsc/meas_feed.h b/include/osmocom/bsc/meas_feed.h
new file mode 100644
index 000000000..1849a8932
--- /dev/null
+++ b/include/osmocom/bsc/meas_feed.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/bsc/meas_rep.h>
+
+struct meas_feed_hdr {
+ uint8_t msg_type;
+ uint8_t reserved;
+ uint16_t version;
+};
+
+struct meas_feed_meas {
+ struct meas_feed_hdr hdr;
+ char imsi[15+1];
+ char name[31+1];
+ char scenario[31+1];
+ struct gsm_meas_rep mr;
+ /* The logical channel type, enum gsm_chan_t */
+ uint8_t lchan_type;
+ /* The physical channel type, enum gsm_phys_chan_config */
+ uint8_t pchan_type;
+ /* number of ths BTS in network */
+ uint8_t bts_nr;
+ /* number of this TRX in the BTS */
+ uint8_t trx_nr;
+ /* number of this timeslot at the TRX */
+ uint8_t ts_nr;
+ /* The logical subslot number in the TS */
+ uint8_t ss_nr;
+};
+
+enum meas_feed_msgtype {
+ MEAS_FEED_MEAS = 0,
+};
+
+#define MEAS_FEED_VERSION 1
+
+int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port);
+void meas_feed_scenario_set(const char *name);
+
+void meas_feed_cfg_get(char **host, uint16_t *port);
+const char *meas_feed_scenario_get(void);
diff --git a/include/osmocom/bsc/meas_rep.h b/include/osmocom/bsc/meas_rep.h
new file mode 100644
index 000000000..b0c03f0bb
--- /dev/null
+++ b/include/osmocom/bsc/meas_rep.h
@@ -0,0 +1,67 @@
+#ifndef _MEAS_REP_H
+#define _MEAS_REP_H
+
+#include <stdint.h>
+
+#include <osmocom/gsm/meas_rep.h>
+
+#define MRC_F_PROCESSED 0x0001
+
+/* extracted from a L3 measurement report IE */
+struct gsm_meas_rep_cell {
+ uint8_t rxlev;
+ uint8_t bsic;
+ uint8_t neigh_idx;
+ uint16_t arfcn;
+ unsigned int flags;
+};
+
+#define MEAS_REP_F_UL_DTX 0x01
+#define MEAS_REP_F_DL_VALID 0x02
+#define MEAS_REP_F_BA1 0x04
+#define MEAS_REP_F_DL_DTX 0x08
+#define MEAS_REP_F_MS_TO 0x10
+#define MEAS_REP_F_MS_L1 0x20
+#define MEAS_REP_F_FPC 0x40
+
+/* parsed uplink and downlink measurement result */
+struct gsm_meas_rep {
+ /* back-pointer to the logical channel */
+ struct gsm_lchan *lchan;
+
+ /* number of the measurement report */
+ uint8_t nr;
+ /* flags, see MEAS_REP_F_* */
+ unsigned int flags;
+
+ /* uplink and downlink rxlev, rxqual; full and sub */
+ struct gsm_meas_rep_unidir ul;
+ struct gsm_meas_rep_unidir dl;
+
+ uint8_t bs_power;
+ /* according to 3GPP TS 48.058 § MS Timing Offset [-63; 192] */
+ int16_t ms_timing_offset;
+ struct {
+ int8_t pwr; /* MS power in dBm */
+ uint8_t ta; /* MS timing advance */
+ } ms_l1;
+
+ /* neighbor measurement reports for up to 6 cells */
+ int num_cell;
+ struct gsm_meas_rep_cell cell[6];
+};
+
+/* 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);
+
+/* 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 calc_initial_idx(unsigned int array_size,
+ unsigned int meas_rep_idx,
+ unsigned int num_values);
+
+#endif /* _MEAS_REP_H */
diff --git a/include/osmocom/bsc/mgw_endpoint_fsm.h b/include/osmocom/bsc/mgw_endpoint_fsm.h
new file mode 100644
index 000000000..e264a3c8b
--- /dev/null
+++ b/include/osmocom/bsc/mgw_endpoint_fsm.h
@@ -0,0 +1,59 @@
+/* osmo-bsc API to manage all sides of an MGW endpoint */
+#pragma once
+
+#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+
+#include <osmocom/bsc/debug.h>
+
+/* This macro automatically includes a final \n, if omitted. */
+#define LOG_MGWEP(mgwep, level, fmt, args...) do { \
+ LOGPFSML(mgwep->fi, level, "(%s) " fmt, \
+ mgw_endpoint_name(mgwep), ## args); \
+ } while(0)
+
+enum mgwep_fsm_state {
+ MGWEP_ST_UNUSED,
+ MGWEP_ST_WAIT_MGW_RESPONSE,
+ MGWEP_ST_IN_USE,
+};
+
+enum mgwep_fsm_event {
+ _MGWEP_EV_LAST,
+ /* and MGW response events are allocated dynamically */
+};
+
+struct mgw_endpoint;
+struct mgwep_ci;
+struct T_def;
+
+void mgw_endpoint_fsm_init(struct T_def *T_defs);
+
+struct mgw_endpoint *mgw_endpoint_alloc(struct osmo_fsm_inst *parent, uint32_t parent_term_event,
+ struct mgcp_client *mgcp_client,
+ const char *fsm_id,
+ const char *endpoint_str_fmt, ...);
+
+struct mgwep_ci *mgw_endpoint_ci_add(struct mgw_endpoint *mgwep,
+ const char *label_fmt, ...);
+const struct mgcp_conn_peer *mgwep_ci_get_rtp_info(const struct mgwep_ci *ci);
+bool mgwep_ci_get_crcx_info_to_sockaddr(const struct mgwep_ci *ci, struct sockaddr_storage *dest);
+
+void mgw_endpoint_ci_request(struct mgwep_ci *ci,
+ enum mgcp_verb verb, const struct mgcp_conn_peer *verb_info,
+ struct osmo_fsm_inst *notify,
+ uint32_t event_success, uint32_t event_failure,
+ void *notify_data);
+
+static inline void mgw_endpoint_ci_dlcx(struct mgwep_ci *ci)
+{
+ mgw_endpoint_ci_request(ci, MGCP_VERB_DLCX, NULL, NULL, 0, 0, NULL);
+}
+
+void mgw_endpoint_clear(struct mgw_endpoint *mgwep);
+
+const char *mgw_endpoint_name(const struct mgw_endpoint *mgwep);
+const char *mgwep_ci_name(const struct mgwep_ci *ci);
+const char *mgcp_conn_peer_name(const struct mgcp_conn_peer *info);
+
+enum mgcp_codecs chan_mode_to_mgcp_codec(enum gsm48_chan_mode chan_mode, bool full_rate);
+void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *lchan, bool bss_side);
diff --git a/include/osmocom/bsc/misdn.h b/include/osmocom/bsc/misdn.h
new file mode 100644
index 000000000..9851ad32c
--- /dev/null
+++ b/include/osmocom/bsc/misdn.h
@@ -0,0 +1,27 @@
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MISDN_H
+#define MISDN_H
+
+#include <osmocom/abis/e1_input.h>
+
+int mi_setup(int cardnr, struct e1inp_line *line, int release_l2);
+int mi_e1_line_update(struct e1inp_line *line);
+
+#endif
diff --git a/include/osmocom/bsc/neighbor_ident.h b/include/osmocom/bsc/neighbor_ident.h
new file mode 100644
index 000000000..17bffbc14
--- /dev/null
+++ b/include/osmocom/bsc/neighbor_ident.h
@@ -0,0 +1,58 @@
+/* Manage identity of neighboring BSS cells for inter-BSC handover */
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/linuxlist.h>
+
+struct vty;
+struct gsm_network;
+struct gsm_bts;
+struct neighbor_ident_list;
+struct gsm0808_cell_id_list2;
+
+#define NEIGHBOR_IDENT_KEY_ANY_BTS -1
+
+#define BSIC_ANY 0xff
+
+struct neighbor_ident_key {
+ int from_bts; /*< BTS nr 0..255 or NEIGHBOR_IDENT_KEY_ANY_BTS */
+ uint16_t arfcn;
+ uint8_t bsic;
+};
+
+const char *neighbor_ident_key_name(const struct neighbor_ident_key *ni_key);
+
+struct neighbor_ident_list *neighbor_ident_init(void *talloc_ctx);
+void neighbor_ident_free(struct neighbor_ident_list *nil);
+
+bool neighbor_ident_key_match(const struct neighbor_ident_key *entry,
+ const struct neighbor_ident_key *search_for,
+ bool exact_match);
+
+int neighbor_ident_add(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key,
+ const struct gsm0808_cell_id_list2 *val);
+const struct gsm0808_cell_id_list2 *neighbor_ident_get(const struct neighbor_ident_list *nil,
+ const struct neighbor_ident_key *key);
+bool neighbor_ident_del(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key);
+void neighbor_ident_clear(struct neighbor_ident_list *nil);
+
+void neighbor_ident_iter(const struct neighbor_ident_list *nil,
+ bool (* iter_cb )(const struct neighbor_ident_key *key,
+ const struct gsm0808_cell_id_list2 *val,
+ void *cb_data),
+ void *cb_data);
+
+void neighbor_ident_vty_init(struct gsm_network *net, struct neighbor_ident_list *nil);
+void neighbor_ident_vty_write(struct vty *vty, const char *indent, struct gsm_bts *bts);
+
+#define NEIGHBOR_IDENT_VTY_KEY_PARAMS "arfcn <0-1023> bsic (<0-63>|any)"
+#define NEIGHBOR_IDENT_VTY_KEY_DOC \
+ "ARFCN of neighbor cell\n" "ARFCN value\n" \
+ "BSIC of neighbor cell\n" "BSIC value\n" \
+ "for all BSICs / use any BSIC in this ARFCN\n"
+bool neighbor_ident_vty_parse_key_params(struct vty *vty, const char **argv,
+ struct neighbor_ident_key *key);
+bool neighbor_ident_bts_parse_key_params(struct vty *vty, struct gsm_bts *bts, const char **argv,
+ struct neighbor_ident_key *key);
diff --git a/include/osmocom/bsc/network_listen.h b/include/osmocom/bsc/network_listen.h
new file mode 100644
index 000000000..68d07093d
--- /dev/null
+++ b/include/osmocom/bsc/network_listen.h
@@ -0,0 +1,16 @@
+#ifndef _OPENBSC_NWL_H
+#define _OPENBSC_NWL_H
+
+#include <stdint.h>
+#include <osmocom/bsc/gsm_data.h>
+
+void ipac_nwl_init(void);
+
+/* Start a NWL test. It will raise the S_IPAC_TEST_COMPLETE signal. */
+int ipac_nwl_test_start(struct gsm_bts_trx *trx, uint8_t testnr,
+ const uint8_t *phys_conf, unsigned int phys_conf_len);
+
+int ipac_rxlevstat2whitelist(uint16_t *buf, const struct rxlev_stats *st, uint8_t min_rxlev,
+ uint16_t max_num_arfcns);
+
+#endif /* _OPENBSC_NWL_H */
diff --git a/include/osmocom/bsc/openbscdefines.h b/include/osmocom/bsc/openbscdefines.h
new file mode 100644
index 000000000..c6ac153b8
--- /dev/null
+++ b/include/osmocom/bsc/openbscdefines.h
@@ -0,0 +1,34 @@
+/*
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSCDEFINES_H
+#define OPENBSCDEFINES_H
+
+#ifdef BUILDING_ON_WINDOWS
+ #ifdef BUILDING_OPENBSC
+ #define BSC_API __declspec(dllexport)
+ #else
+ #define BSC_API __declspec(dllimport)
+ #endif
+#else
+ #define BSC_API __attribute__((visibility("default")))
+#endif
+
+#endif
diff --git a/include/osmocom/bsc/osmo_bsc.h b/include/osmocom/bsc/osmo_bsc.h
new file mode 100644
index 000000000..bebfb2faa
--- /dev/null
+++ b/include/osmocom/bsc/osmo_bsc.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <osmocom/bsc/bsc_msg_filter.h>
+
+#define BSS_SEND_USSD 1
+
+enum bsc_con {
+ BSC_CON_SUCCESS,
+ BSC_CON_REJECT_NO_LINK,
+ BSC_CON_REJECT_RF_GRACE,
+ BSC_CON_NO_MEM,
+};
+
+struct bsc_msc_data;
+struct gsm0808_channel_type;
+struct gsm0808_speech_codec_list;
+struct gsm_audio_support;
+struct gsm_subscriber_connection;
+struct gsm_bts;
+
+struct bsc_api *osmo_bsc_api();
+
+int bsc_queue_for_msc(struct gsm_subscriber_connection *conn, struct msgb *msg);
+int bsc_open_connection(struct gsm_subscriber_connection *sccp, struct msgb *msg);
+enum bsc_con bsc_create_new_connection(struct gsm_subscriber_connection *conn,
+ struct bsc_msc_data *msc, int send_ping);
+int bsc_delete_connection(struct gsm_subscriber_connection *sccp);
+
+struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, struct msgb *);
+int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg);
+int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg);
+int bsc_send_welcome_ussd(struct gsm_subscriber_connection *conn);
+
+int bsc_handle_udt(struct bsc_msc_data *msc, struct msgb *msg, unsigned int length);
+int bsc_handle_dt(struct gsm_subscriber_connection *conn, struct msgb *msg, unsigned int len);
+
+int bsc_ctrl_cmds_install();
+
+void bsc_gen_location_state_trap(struct gsm_bts *bts);
+
+struct llist_head *bsc_access_lists(void);
diff --git a/include/osmocom/bsc/osmo_bsc_grace.h b/include/osmocom/bsc/osmo_bsc_grace.h
new file mode 100644
index 000000000..d78e41c82
--- /dev/null
+++ b/include/osmocom/bsc/osmo_bsc_grace.h
@@ -0,0 +1,36 @@
+/*
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OSMO_BSC_GRACE_H
+#define OSMO_BSC_GRACE_H
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/signal.h>
+
+struct bsc_msc_data;
+
+int bsc_grace_allow_new_connection(struct gsm_network *net, struct gsm_bts *bts);
+int bsc_grace_paging_request(enum signal_rf rf_policy,
+ struct bsc_subscr *subscr,
+ int chan_needed,
+ struct bsc_msc_data *msc,
+ struct gsm_bts *bts);
+
+#endif
diff --git a/include/osmocom/bsc/osmo_bsc_lcls.h b/include/osmocom/bsc/osmo_bsc_lcls.h
new file mode 100644
index 000000000..3eaa5891a
--- /dev/null
+++ b/include/osmocom/bsc/osmo_bsc_lcls.h
@@ -0,0 +1,41 @@
+#pragma once
+#include <osmocom/core/fsm.h>
+
+enum lcls_fsm_state {
+ ST_NO_LCLS,
+ ST_NOT_YET_LS,
+ ST_NOT_POSSIBLE_LS,
+ ST_NO_LONGER_LS,
+ ST_REQ_LCLS_NOT_SUPP,
+ ST_LOCALLY_SWITCHED,
+ /* locally switched; received remote break; wait for "local" break */
+ ST_LOCALLY_SWITCHED_WAIT_BREAK,
+ /* locally switched; received break; wait for "other" break */
+ ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK,
+};
+
+enum lcls_event {
+ /* update LCLS config/control based on some BSSMAP signaling */
+ LCLS_EV_UPDATE_CFG_CSC,
+ /* apply LCLS config/control */
+ LCLS_EV_APPLY_CFG_CSC,
+ /* we have been identified as the correlation peer of another conn */
+ LCLS_EV_CORRELATED,
+ /* "other" LCLS connection has enabled local switching */
+ LCLS_EV_OTHER_ENABLED,
+ /* "other" LCLS connection is breaking local switch */
+ LCLS_EV_OTHER_BREAK,
+ /* "other" LCLS connection is dying */
+ LCLS_EV_OTHER_DEAD,
+};
+
+enum gsm0808_lcls_status lcls_get_status(struct gsm_subscriber_connection *conn);
+
+void lcls_update_config(struct gsm_subscriber_connection *conn,
+ const uint8_t *config, const uint8_t *control);
+
+void lcls_apply_config(struct gsm_subscriber_connection *conn);
+
+extern struct osmo_fsm lcls_fsm;
+
+void bssmap_add_lcls_status_if_needed(struct gsm_subscriber_connection *conn, struct msgb *msg);
diff --git a/include/osmocom/bsc/osmo_bsc_reset.h b/include/osmocom/bsc/osmo_bsc_reset.h
new file mode 100644
index 000000000..578f763e6
--- /dev/null
+++ b/include/osmocom/bsc/osmo_bsc_reset.h
@@ -0,0 +1,34 @@
+/* (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* Create and start state machine which handles the reset/reset-ack procedure */
+void start_reset_fsm(struct bsc_msc_data *msc);
+
+/* Confirm that we sucessfully received a reset acknowlege message */
+void reset_ack_confirm(struct bsc_msc_data *msc);
+
+/* Report a failed connection */
+void report_conn_fail(struct bsc_msc_data *msc);
+
+/* Report a successful connection */
+void report_conn_success(struct bsc_msc_data *msc);
+
+/* Check if we have a connection to a specified msc */
+bool sccp_conn_ready(struct bsc_msc_data *msc);
diff --git a/include/osmocom/bsc/osmo_bsc_rf.h b/include/osmocom/bsc/osmo_bsc_rf.h
new file mode 100644
index 000000000..56ac980ca
--- /dev/null
+++ b/include/osmocom/bsc/osmo_bsc_rf.h
@@ -0,0 +1,66 @@
+#ifndef OSMO_BSC_RF
+#define OSMO_BSC_RF
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+
+enum osmo_bsc_rf_opstate {
+ OSMO_BSC_RF_OPSTATE_INOPERATIONAL,
+ OSMO_BSC_RF_OPSTATE_OPERATIONAL,
+};
+
+enum osmo_bsc_rf_adminstate {
+ OSMO_BSC_RF_ADMINSTATE_UNLOCKED,
+ OSMO_BSC_RF_ADMINSTATE_LOCKED,
+};
+
+enum osmo_bsc_rf_policy {
+ OSMO_BSC_RF_POLICY_OFF,
+ OSMO_BSC_RF_POLICY_ON,
+ OSMO_BSC_RF_POLICY_GRACE,
+ OSMO_BSC_RF_POLICY_UNKNOWN,
+};
+
+
+struct gsm_network;
+
+struct osmo_bsc_rf {
+ /* the value of signal.h */
+ int policy;
+ struct osmo_fd listen;
+ struct gsm_network *gsm_network;
+
+ const char *last_state_command;
+
+ char *last_rf_lock_ctrl_command;
+
+ /* delay the command */
+ char last_request;
+ struct osmo_timer_list delay_cmd;
+
+ /* verify that RF is up as it should be */
+ struct osmo_timer_list rf_check;
+
+ /* some handling for the automatic grace switch */
+ struct osmo_timer_list grace_timeout;
+
+ /* auto RF switch-off due lack of MSC connection */
+ struct osmo_timer_list auto_off_timer;
+};
+
+struct osmo_bsc_rf_conn {
+ struct osmo_wqueue queue;
+ struct osmo_bsc_rf *rf;
+};
+
+const char *osmo_bsc_rf_get_opstate_name(enum osmo_bsc_rf_opstate opstate);
+const char *osmo_bsc_rf_get_adminstate_name(enum osmo_bsc_rf_adminstate adminstate);
+const char *osmo_bsc_rf_get_policy_name(enum osmo_bsc_rf_policy policy);
+enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_bts(struct gsm_bts *bts);
+enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bts);
+enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts);
+struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net);
+void osmo_bsc_rf_schedule_lock(struct osmo_bsc_rf *rf, char cmd);
+
+#endif
diff --git a/include/osmocom/bsc/osmo_bsc_sigtran.h b/include/osmocom/bsc/osmo_bsc_sigtran.h
new file mode 100644
index 000000000..bd8b06398
--- /dev/null
+++ b/include/osmocom/bsc/osmo_bsc_sigtran.h
@@ -0,0 +1,46 @@
+/* (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+
+/* Allocate resources to make a new connection oriented sigtran connection
+ * (not the connection ittself!) */
+enum bsc_con osmo_bsc_sigtran_new_conn(struct gsm_subscriber_connection *conn, struct bsc_msc_data *msc);
+
+/* Open a new connection oriented sigtran connection */
+int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+/* Send data to MSC */
+int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+/* Initalize osmo sigtran backhaul */
+int osmo_bsc_sigtran_init(struct llist_head *mscs);
+
+/* Close all open sigtran connections and channels */
+void osmo_bsc_sigtran_reset(const struct bsc_msc_data *msc);
+
+/* Send reset-ack to MSC */
+void osmo_bsc_sigtran_tx_reset_ack(const struct bsc_msc_data *msc);
+
+/* receive + process a CTRL command from the piggy-back on the IPA/SCCPlite link */
+int bsc_sccplite_rx_ctrl(struct osmo_ss7_asp *asp, struct msgb *msg);
diff --git a/include/osmocom/bsc/osmux.h b/include/osmocom/bsc/osmux.h
new file mode 100644
index 000000000..f3ea72a85
--- /dev/null
+++ b/include/osmocom/bsc/osmux.h
@@ -0,0 +1,41 @@
+#ifndef _OPENBSC_OSMUX_H_
+#define _OPENBSC_OSMUX_H_
+
+#include <osmocom/netif/osmux.h>
+
+#define OSMUX_PORT 1984
+
+enum {
+ OSMUX_ROLE_BSC = 0,
+ OSMUX_ROLE_BSC_NAT,
+};
+
+int osmux_init(int role, struct mgcp_config *cfg);
+int osmux_enable_endpoint(struct mgcp_endpoint *endp, struct in_addr *addr, uint16_t port);
+void osmux_disable_endpoint(struct mgcp_endpoint *endp);
+void osmux_allocate_cid(struct mgcp_endpoint *endp);
+void osmux_release_cid(struct mgcp_endpoint *endp);
+
+int osmux_xfrm_to_rtp(struct mgcp_endpoint *endp, int type, char *buf, int rc);
+int osmux_xfrm_to_osmux(int type, char *buf, int rc, struct mgcp_endpoint *endp);
+
+int osmux_send_dummy(struct mgcp_endpoint *endp);
+
+int osmux_get_cid(void);
+void osmux_put_cid(uint8_t osmux_cid);
+int osmux_used_cid(void);
+
+enum osmux_state {
+ OSMUX_STATE_DISABLED = 0,
+ OSMUX_STATE_NEGOTIATING,
+ OSMUX_STATE_ACTIVATING,
+ OSMUX_STATE_ENABLED,
+};
+
+enum osmux_usage {
+ OSMUX_USAGE_OFF = 0,
+ OSMUX_USAGE_ON = 1,
+ OSMUX_USAGE_ONLY = 2,
+};
+
+#endif
diff --git a/include/osmocom/bsc/paging.h b/include/osmocom/bsc/paging.h
new file mode 100644
index 000000000..2be71c39f
--- /dev/null
+++ b/include/osmocom/bsc/paging.h
@@ -0,0 +1,79 @@
+/* Paging helper and manager.... */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef PAGING_H
+#define PAGING_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+
+struct bsc_msc_data;
+
+/**
+ * A pending paging request
+ */
+struct gsm_paging_request {
+ /* list_head for list of all paging requests */
+ struct llist_head entry;
+ /* the subscriber which we're paging. Later gsm_paging_request
+ * should probably become a part of the bsc_subsrc struct? */
+ struct bsc_subscr *bsub;
+ /* back-pointer to the BTS on which we are paging */
+ struct gsm_bts *bts;
+ /* what kind of channel type do we ask the MS to establish */
+ int chan_type;
+
+ /* Timer 3113: how long do we try to page? */
+ struct osmo_timer_list T3113;
+
+ /* How often did we ask the BTS to page? */
+ int attempts;
+
+ /* MSC that has issued this paging */
+ struct bsc_msc_data *msc;
+};
+
+/* schedule paging request */
+int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub, int type,
+ struct bsc_msc_data *msc);
+
+/* stop paging requests */
+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);
+
+/* update paging load */
+void paging_update_buffer_space(struct gsm_bts *bts, uint16_t);
+
+/* pending paging requests */
+unsigned int paging_pending_requests_nr(struct gsm_bts *bts);
+
+struct bsc_msc_data *paging_get_msc(struct gsm_bts *bts, struct bsc_subscr *bsub);
+
+void paging_flush_bts(struct gsm_bts *bts, struct bsc_msc_data *msc);
+void paging_flush_network(struct gsm_network *net, struct bsc_msc_data *msc);
+
+#endif
diff --git a/include/osmocom/bsc/pcu_if.h b/include/osmocom/bsc/pcu_if.h
new file mode 100644
index 000000000..1f398b4aa
--- /dev/null
+++ b/include/osmocom/bsc/pcu_if.h
@@ -0,0 +1,35 @@
+#ifndef _PCU_IF_H
+#define _PCU_IF_H
+
+#include <osmocom/gsm/l1sap.h>
+
+extern int pcu_direct;
+
+struct pcu_sock_state {
+ struct gsm_network *net;
+ struct osmo_fd listen_bfd; /* fd for listen socket */
+ struct osmo_fd conn_bfd; /* fd for connection to lcr */
+ struct llist_head upqueue; /* queue for sending messages */
+};
+
+/* PCU relevant information has changed; Inform PCU (if connected) */
+void pcu_info_update(struct gsm_bts *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);
+
+/* Confirm the sending of an immediate assignment to the pcu */
+int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli);
+
+
+/* Confirm the sending of an immediate assignment to the pcu */
+int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli);
+
+/* Open connection to PCU */
+int pcu_sock_init(const char *path, struct gsm_bts *bts);
+
+/* Close connection to PCU */
+void pcu_sock_exit(struct gsm_bts *bts);
+
+#endif /* _PCU_IF_H */
diff --git a/include/osmocom/bsc/pcuif_proto.h b/include/osmocom/bsc/pcuif_proto.h
new file mode 100644
index 000000000..b9f61b6f1
--- /dev/null
+++ b/include/osmocom/bsc/pcuif_proto.h
@@ -0,0 +1,193 @@
+#ifndef _PCUIF_PROTO_H
+#define _PCUIF_PROTO_H
+
+#include <osmocom/gsm/l1sap.h>
+
+#define PCU_IF_VERSION 0x09
+#define TXT_MAX_LEN 128
+
+/* msg_type */
+#define PCU_IF_MSG_DATA_REQ 0x00 /* send data to given channel */
+#define PCU_IF_MSG_DATA_CNF 0x01 /* confirm (e.g. transmission on PCH) */
+#define PCU_IF_MSG_DATA_IND 0x02 /* receive data from given channel */
+#define PCU_IF_MSG_RTS_REQ 0x10 /* ready to send request */
+#define PCU_IF_MSG_DATA_CNF_DT 0x11 /* confirm (with direct tlli) */
+#define PCU_IF_MSG_RACH_IND 0x22 /* receive RACH */
+#define PCU_IF_MSG_INFO_IND 0x32 /* retrieve BTS info */
+#define PCU_IF_MSG_ACT_REQ 0x40 /* activate/deactivate PDCH */
+#define PCU_IF_MSG_TIME_IND 0x52 /* GSM time indication */
+#define PCU_IF_MSG_PAG_REQ 0x60 /* paging request */
+#define PCU_IF_MSG_TXT_IND 0x70 /* Text indication for BTS */
+
+/* sapi */
+#define PCU_IF_SAPI_RACH 0x01 /* channel request on CCCH */
+#define PCU_IF_SAPI_AGCH 0x02 /* assignment on AGCH */
+#define PCU_IF_SAPI_PCH 0x03 /* paging/assignment on PCH */
+#define PCU_IF_SAPI_BCCH 0x04 /* SI on BCCH */
+#define PCU_IF_SAPI_PDTCH 0x05 /* packet data/control/ccch block */
+#define PCU_IF_SAPI_PRACH 0x06 /* packet random access channel */
+#define PCU_IF_SAPI_PTCCH 0x07 /* packet TA control channel */
+#define PCU_IF_SAPI_AGCH_DT 0x08 /* assignment on AGCH but with additional TLLI */
+
+/* flags */
+#define PCU_IF_FLAG_ACTIVE (1 << 0)/* BTS is active */
+#define PCU_IF_FLAG_SYSMO (1 << 1)/* access PDCH of sysmoBTS directly */
+#define PCU_IF_FLAG_CS1 (1 << 16)
+#define PCU_IF_FLAG_CS2 (1 << 17)
+#define PCU_IF_FLAG_CS3 (1 << 18)
+#define PCU_IF_FLAG_CS4 (1 << 19)
+#define PCU_IF_FLAG_MCS1 (1 << 20)
+#define PCU_IF_FLAG_MCS2 (1 << 21)
+#define PCU_IF_FLAG_MCS3 (1 << 22)
+#define PCU_IF_FLAG_MCS4 (1 << 23)
+#define PCU_IF_FLAG_MCS5 (1 << 24)
+#define PCU_IF_FLAG_MCS6 (1 << 25)
+#define PCU_IF_FLAG_MCS7 (1 << 26)
+#define PCU_IF_FLAG_MCS8 (1 << 27)
+#define PCU_IF_FLAG_MCS9 (1 << 28)
+
+enum gsm_pcu_if_text_type {
+ PCU_VERSION,
+ PCU_OML_ALERT,
+};
+
+struct gsm_pcu_if_txt_ind {
+ uint8_t type; /* gsm_pcu_if_text_type */
+ char text[TXT_MAX_LEN]; /* Text to be transmitted to BTS */
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_data {
+ uint8_t sapi;
+ uint8_t len;
+ uint8_t data[162];
+ uint32_t fn;
+ uint16_t arfcn;
+ uint8_t trx_nr;
+ uint8_t ts_nr;
+ uint8_t block_nr;
+ int8_t rssi;
+ uint16_t ber10k; /* !< \brief BER in units of 0.01% */
+ int16_t ta_offs_qbits; /* !< \brief Burst TA Offset in quarter bits */
+ int16_t lqual_cb; /* !< \brief Link quality in centiBel */
+} __attribute__ ((packed));
+
+/* data confirmation with direct tlli (instead of raw mac block with tlli) */
+struct gsm_pcu_if_data_cnf_dt {
+ uint8_t sapi;
+ uint32_t tlli;
+ uint32_t fn;
+ uint16_t arfcn;
+ uint8_t trx_nr;
+ uint8_t ts_nr;
+ uint8_t block_nr;
+ int8_t rssi;
+ uint16_t ber10k; /* !< \brief BER in units of 0.01% */
+ int16_t ta_offs_qbits; /* !< \brief Burst TA Offset in quarter bits */
+ int16_t lqual_cb; /* !< \brief Link quality in centiBel */
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_rts_req {
+ uint8_t sapi;
+ uint8_t spare[3];
+ uint32_t fn;
+ uint16_t arfcn;
+ uint8_t trx_nr;
+ uint8_t ts_nr;
+ uint8_t block_nr;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_rach_ind {
+ uint8_t sapi;
+ uint16_t ra;
+ int16_t qta;
+ uint32_t fn;
+ uint16_t arfcn;
+ uint8_t is_11bit;
+ uint8_t burst_type;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_info_trx {
+ uint16_t arfcn;
+ uint8_t pdch_mask; /* PDCH channels per TS */
+ uint8_t spare;
+ uint8_t tsc[8]; /* TSC per channel */
+ uint32_t hlayer1;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_info_ind {
+ uint32_t version;
+ uint32_t flags;
+ struct gsm_pcu_if_info_trx trx[8]; /* TRX infos per BTS */
+ uint8_t bsic;
+ /* RAI */
+ uint16_t mcc, mnc;
+ uint8_t mnc_3_digits;
+ uint16_t lac, rac;
+ /* NSE */
+ uint16_t nsei;
+ uint8_t nse_timer[7];
+ uint8_t cell_timer[11];
+ /* cell */
+ uint16_t cell_id;
+ uint16_t repeat_time;
+ uint8_t repeat_count;
+ uint16_t bvci;
+ uint8_t t3142;
+ uint8_t t3169;
+ uint8_t t3191;
+ uint8_t t3193_10ms;
+ uint8_t t3195;
+ uint8_t n3101;
+ uint8_t n3103;
+ uint8_t n3105;
+ uint8_t cv_countdown;
+ uint16_t dl_tbf_ext;
+ uint16_t ul_tbf_ext;
+ uint8_t initial_cs;
+ uint8_t initial_mcs;
+ /* NSVC */
+ uint16_t nsvci[2];
+ uint16_t local_port[2];
+ uint16_t remote_port[2];
+ uint32_t remote_ip[2];
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_act_req {
+ uint8_t activate;
+ uint8_t trx_nr;
+ uint8_t ts_nr;
+ uint8_t spare;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_time_ind {
+ uint32_t fn;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_pag_req {
+ uint8_t sapi;
+ uint8_t chan_needed;
+ uint8_t identity_lv[9];
+} __attribute__ ((packed));
+
+struct gsm_pcu_if {
+ /* context based information */
+ uint8_t msg_type; /* message type */
+ uint8_t bts_nr; /* bts number */
+ uint8_t spare[2];
+
+ union {
+ struct gsm_pcu_if_data data_req;
+ struct gsm_pcu_if_data data_cnf;
+ struct gsm_pcu_if_data_cnf_dt data_cnf_dt;
+ struct gsm_pcu_if_data data_ind;
+ struct gsm_pcu_if_rts_req rts_req;
+ struct gsm_pcu_if_rach_ind rach_ind;
+ struct gsm_pcu_if_txt_ind txt_ind;
+ struct gsm_pcu_if_info_ind info_ind;
+ struct gsm_pcu_if_act_req act_req;
+ struct gsm_pcu_if_time_ind time_ind;
+ struct gsm_pcu_if_pag_req pag_req;
+ } u;
+} __attribute__ ((packed));
+
+#endif /* _PCUIF_PROTO_H */
diff --git a/include/osmocom/bsc/penalty_timers.h b/include/osmocom/bsc/penalty_timers.h
new file mode 100644
index 000000000..3aae8a0e5
--- /dev/null
+++ b/include/osmocom/bsc/penalty_timers.h
@@ -0,0 +1,41 @@
+/* Manage a list of penalty timers per BTS;
+ * initially used by handover algorithm 2 to keep per-BTS timers for each subscriber connection. */
+#pragma once
+
+/* Opaque struct to manage penalty timers */
+struct penalty_timers;
+
+/* Initialize a list of penalty timers.
+ * param ctx: talloc context to allocate in.
+ * returns an empty struct penalty_timers. */
+struct penalty_timers *penalty_timers_init(void *ctx);
+
+/* Add a penalty timer for an arbitary object.
+ * Note: the ownership of for_object remains with the caller; it is handled as a mere void* value, so
+ * invalid pointers can be handled without problems, while common sense dictates that invalidated
+ * pointers (freed objects) should probably be removed from this list. More importantly, the pointer must
+ * match any pointers used to query penalty timers, so for_object should reference some global/singleton
+ * object that tends to stay around longer than the penalty timers.
+ * param pt: penalty timers list as from penalty_timers_init().
+ * param for_object: arbitrary pointer reference to store a penalty timer for (passing NULL is possible,
+ * but note that penalty_timers_clear() will clear all timers if given for_object=NULL).
+ * param timeout: penalty time in seconds. */
+void penalty_timers_add(struct penalty_timers *pt, const void *for_object, int timeout);
+
+/* Return the amount of penalty time remaining for an object.
+ * param pt: penalty timers list as from penalty_timers_init().
+ * param for_object: arbitrary pointer reference to query penalty timers for.
+ * returns seconds remaining until all penalty time has expired. */
+unsigned int penalty_timers_remaining(struct penalty_timers *pt, const void *for_object);
+
+/* Clear penalty timers for one or all objects.
+ * param pt: penalty timers list as from penalty_timers_init().
+ * param for_object: arbitrary pointer reference to clear penalty time for,
+ * or NULL to clear all timers. */
+void penalty_timers_clear(struct penalty_timers *pt, const void *for_object);
+
+/* Free a struct as returned from penalty_timers_init().
+ * Clear all timers from the list, deallocate the list and set the pointer to NULL.
+ * param pt: pointer-to-pointer which references a struct penalty_timers as returned by
+ * penalty_timers_init(); *pt_p will be set to NULL. */
+void penalty_timers_free(struct penalty_timers **pt_p);
diff --git a/include/osmocom/bsc/rest_octets.h b/include/osmocom/bsc/rest_octets.h
new file mode 100644
index 000000000..f7ad682b7
--- /dev/null
+++ b/include/osmocom/bsc/rest_octets.h
@@ -0,0 +1,122 @@
+#ifndef _REST_OCTETS_H
+#define _REST_OCTETS_H
+
+#include <stdbool.h>
+#include <osmocom/gsm/sysinfo.h>
+
+struct gsm_bts;
+
+/* generate SI1 rest octets */
+int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net);
+int rest_octets_si2quater(uint8_t *data, struct gsm_bts *bts);
+int rest_octets_si2ter(uint8_t *data);
+int rest_octets_si2bis(uint8_t *data);
+int rest_octets_si6(uint8_t *data, bool is1800_net);
+
+struct gsm48_si_selection_params {
+ uint16_t penalty_time:5,
+ temp_offs:3,
+ cell_resel_off:6,
+ cbq:1,
+ present:1;
+};
+
+struct gsm48_si_power_offset {
+ uint8_t power_offset:2,
+ present:1;
+};
+
+struct gsm48_si3_gprs_ind {
+ uint8_t si13_position:1,
+ ra_colour:3,
+ present:1;
+};
+
+struct gsm48_lsa_params {
+ uint32_t prio_thr:3,
+ lsa_offset:3,
+ mcc:12,
+ mnc:12;
+ unsigned int present;
+};
+
+struct gsm48_si_ro_info {
+ struct gsm48_si_selection_params selection_params;
+ struct gsm48_si_power_offset power_offset;
+ bool si2ter_indicator;
+ bool early_cm_ctrl;
+ struct {
+ uint8_t where:3,
+ present:1;
+ } scheduling;
+ struct gsm48_si3_gprs_ind gprs_ind;
+ /* SI 3 specific */
+ bool early_cm_restrict_3g;
+ bool si2quater_indicator;
+ /* SI 4 specific */
+ struct gsm48_lsa_params lsa_params;
+ uint16_t cell_id;
+ uint8_t break_ind; /* do we have SI7 + SI8 ? */
+};
+
+
+/* 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);
+
+/* 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);
+
+/* TS 03.60 Chapter 6.3.3.1: Network Mode of Operation */
+enum gprs_nmo {
+ GPRS_NMO_I = 0, /* CS pagin on GPRS paging or traffic channel */
+ GPRS_NMO_II = 1, /* all paging on CCCH */
+ GPRS_NMO_III = 2, /* no paging coordination */
+};
+
+/* TS 04.60 12.24 */
+struct gprs_cell_options {
+ enum gprs_nmo nmo;
+ /* T3168: wait for packet uplink assignment message */
+ uint32_t t3168; /* in milliseconds */
+ /* T3192: wait for release of the TBF after reception of the final block */
+ uint32_t t3192; /* in milliseconds */
+ uint32_t drx_timer_max;/* in seconds */
+ uint32_t bs_cv_max;
+ uint8_t supports_egprs_11bit_rach;
+ bool ctrl_ack_type_use_block; /* use PACKET CONTROL ACKNOWLEDGMENT */
+
+ uint8_t ext_info_present;
+ struct {
+ uint8_t egprs_supported;
+ uint8_t use_egprs_p_ch_req;
+ uint8_t bep_period;
+ uint8_t pfc_supported;
+ uint8_t dtm_supported;
+ uint8_t bss_paging_coordination;
+ } ext_info;
+};
+
+/* TS 04.60 Table 12.9.2 */
+struct gprs_power_ctrl_pars {
+ uint8_t alpha;
+ uint8_t t_avg_w;
+ uint8_t t_avg_t;
+ uint8_t pc_meas_chan;
+ uint8_t n_avg_i;
+};
+
+struct gsm48_si13_info {
+ struct gprs_cell_options cell_opts;
+ struct gprs_power_ctrl_pars pwr_ctrl_pars;
+ uint8_t bcch_change_mark;
+ uint8_t si_change_field;
+ uint8_t rac;
+ uint8_t spgc_ccch_sup;
+ uint8_t net_ctrl_ord;
+ uint8_t prio_acc_thr;
+};
+
+/* Generate SI13 Rest Octests (Chapter 10.5.2.37b) */
+int rest_octets_si13(uint8_t *data, const struct gsm48_si13_info *si13);
+
+#endif /* _REST_OCTETS_H */
diff --git a/include/osmocom/bsc/rs232.h b/include/osmocom/bsc/rs232.h
new file mode 100644
index 000000000..61187ca62
--- /dev/null
+++ b/include/osmocom/bsc/rs232.h
@@ -0,0 +1,9 @@
+#ifndef _RS232_H
+#define _RS232_H
+
+int rs232_setup(const char *serial_port, unsigned int delay_ms,
+ struct gsm_bts *bts);
+
+int handle_serial_msg(struct msgb *msg);
+
+#endif /* _RS232_H */
diff --git a/include/osmocom/bsc/signal.h b/include/osmocom/bsc/signal.h
new file mode 100644
index 000000000..62a3d2c88
--- /dev/null
+++ b/include/osmocom/bsc/signal.h
@@ -0,0 +1,195 @@
+/* Generic signalling/notification infrastructure */
+/* (C) 2009-2010, 2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSC_SIGNAL_H
+#define OPENBSC_SIGNAL_H
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/bsc/gsm_data.h>
+
+#include <osmocom/core/signal.h>
+
+/*
+ * Signalling subsystems
+ */
+enum signal_subsystems {
+ SS_PAGING,
+ SS_ABISIP,
+ SS_NM,
+ SS_LCHAN,
+ SS_CHALLOC,
+ SS_IPAC_NWL,
+ SS_RF,
+ SS_MSC,
+ SS_HO,
+ SS_CCCH,
+};
+
+/* SS_PAGING signals */
+enum signal_paging {
+ S_PAGING_SUCCEEDED,
+ S_PAGING_EXPIRED,
+};
+
+/* SS_ABISIP signals */
+enum signal_abisip {
+ S_ABISIP_CRCX_ACK,
+ S_ABISIP_MDCX_ACK,
+ S_ABISIP_DLCX_IND,
+};
+
+/* SS_NM signals */
+enum signal_nm {
+ S_NM_SW_ACTIV_REP, /* GSM 12.21 software activated report */
+ S_NM_FAIL_REP, /* GSM 12.21 failure event report */
+ S_NM_NACK, /* GSM 12.21 various NM_MT_*_NACK happened */
+ S_NM_IPACC_NACK, /* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_NACK happened */
+ S_NM_IPACC_ACK, /* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_ACK happened */
+ S_NM_IPACC_RESTART_ACK, /* nanoBTS has send a restart ack */
+ S_NM_IPACC_RESTART_NACK,/* nanoBTS has send a restart ack */
+ S_NM_TEST_REP, /* GSM 12.21 Test Report */
+ S_NM_STATECHG_OPER, /* Operational State changed*/
+ S_NM_STATECHG_ADM, /* Administrative State changed */
+ S_NM_OM2K_CONF_RES, /* OM2K Configuration Result */
+ S_NM_OPSTART_ACK, /* Received OPSTART ACK, arg is struct msgb *oml_msg */
+ S_NM_GET_ATTR_REP, /* Received Get Attributes Response, arg is struct msgb *oml_msg */
+};
+
+/* SS_LCHAN signals */
+enum signal_lchan {
+ /*
+ * The lchan got freed with an use_count != 0 and error
+ * recovery needs to be carried out from within the
+ * signal handler.
+ */
+ S_LCHAN_UNEXPECTED_RELEASE,
+ S_LCHAN_ACTIVATE_ACK, /* 08.58 Channel Activate ACK */
+ S_LCHAN_ACTIVATE_NACK, /* 08.58 Channel Activate NACK */
+ S_LCHAN_HANDOVER_COMPL, /* 04.08 Handover Completed */
+ S_LCHAN_HANDOVER_FAIL, /* 04.08 Handover Failed */
+ S_LCHAN_ASSIGNMENT_COMPL, /* 04.08 Assignment Completed */
+ S_LCHAN_ASSIGNMENT_FAIL, /* 04.08 Assignment Failed */
+ S_LCHAN_HANDOVER_DETECT, /* 08.58 Handover Detect */
+ S_LCHAN_MEAS_REP, /* 08.58 Measurement Report */
+};
+
+/* SS_CHALLOC signals */
+enum signal_challoc {
+ S_CHALLOC_ALLOC_FAIL, /* allocation of lchan has failed */
+ S_CHALLOC_FREED, /* lchan has been successfully freed */
+};
+
+/* SS_IPAC_NWL signals */
+enum signal_ipaccess {
+ S_IPAC_NWL_COMPLETE,
+};
+
+enum signal_global {
+ S_GLOBAL_BTS_CLOSE_OM,
+};
+
+/* SS_RF signals */
+enum signal_rf {
+ S_RF_OFF,
+ S_RF_ON,
+ S_RF_GRACE,
+};
+
+struct ipacc_ack_signal_data {
+ struct gsm_bts_trx *trx;
+ uint8_t msg_type;
+};
+
+struct abis_om2k_mo;
+
+struct nm_statechg_signal_data {
+ struct gsm_bts *bts;
+ uint8_t obj_class;
+ void *obj;
+ struct gsm_nm_state *old_state;
+ struct gsm_nm_state *new_state;
+
+ /* This pointer is vaold for TS 12.21 MO */
+ struct abis_om_obj_inst *obj_inst;
+ /* This pointer is vaold for RBS2000 MO */
+ struct abis_om2k_mo *om2k_mo;
+};
+
+struct nm_om2k_signal_data {
+ struct gsm_bts *bts;
+ void *obj;
+ struct abis_om2k_mo *om2k_mo;
+
+ uint8_t accordance_ind;
+};
+
+struct nm_nack_signal_data {
+ struct msgb *msg;
+ struct gsm_bts *bts;
+ uint8_t mt;
+};
+
+struct challoc_signal_data {
+ struct gsm_bts *bts;
+ struct gsm_lchan *lchan;
+ enum gsm_chan_t type;
+};
+
+struct rf_signal_data {
+ struct gsm_network *net;
+};
+
+struct lchan_signal_data {
+ /* The lchan the signal happened on */
+ struct gsm_lchan *lchan;
+ /* Measurement reports on this lchan */
+ struct gsm_meas_rep *mr;
+};
+
+/* MSC signals */
+enum signal_msc {
+ S_MSC_LOST,
+ S_MSC_CONNECTED,
+ S_MSC_AUTHENTICATED,
+};
+
+struct bsc_msc_data;
+struct msc_signal_data {
+ struct bsc_msc_data *data;
+};
+
+/* SS_CCCH signals */
+enum signal_ccch {
+ S_CCCH_PAGING_LOAD,
+ S_CCCH_RACH_LOAD,
+};
+
+struct ccch_signal_data {
+ struct gsm_bts *bts;
+ uint16_t pg_buf_space;
+ uint16_t rach_slot_count;
+ uint16_t rach_busy_count;
+ uint16_t rach_access_count;
+};
+
+#endif
diff --git a/include/osmocom/bsc/system_information.h b/include/osmocom/bsc/system_information.h
new file mode 100644
index 000000000..29f639dca
--- /dev/null
+++ b/include/osmocom/bsc/system_information.h
@@ -0,0 +1,22 @@
+#ifndef _SYSTEM_INFO_H
+#define _SYSTEM_INFO_H
+
+#include <osmocom/gsm/sysinfo.h>
+
+#include <osmocom/bsc/arfcn_range_encode.h>
+
+struct gsm_bts;
+
+int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type type);
+size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e);
+unsigned range1024_p(unsigned n);
+unsigned range512_q(unsigned m);
+int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w,
+ int f0, uint8_t *chan_list);
+uint8_t si2q_num(struct gsm_bts *bts);
+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);
+int bts_uarfcn_del(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble);
+int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble,
+ bool diversity);
+#endif
diff --git a/include/osmocom/bsc/timeslot_fsm.h b/include/osmocom/bsc/timeslot_fsm.h
new file mode 100644
index 000000000..d02e156df
--- /dev/null
+++ b/include/osmocom/bsc/timeslot_fsm.h
@@ -0,0 +1,53 @@
+/* osmo-bsc API to manage timeslot status: init and switch of dynamic PDCH. */
+#pragma once
+
+#include <osmocom/bsc/gsm_data.h>
+
+/* This macro automatically includes a final \n, if omitted. */
+#define LOG_TS(ts, level, fmt, args...) do { \
+ if (ts->fi) \
+ LOGPFSML(ts->fi, level, "%s%s%s" fmt "%s", \
+ ts->pchan_is != ts->pchan_from_config ? "(pchan_is=" : "", \
+ ts->pchan_is != ts->pchan_from_config ? gsm_pchan_name(ts->pchan_is) : "", \
+ ts->pchan_is != ts->pchan_from_config ? ") " : "", \
+## args, \
+ (!fmt || !*fmt || fmt[strlen(fmt)-1] != '\n') ? "\n" : ""); \
+ else \
+ LOGP(DRSL, level, "%s" fmt "%s", \
+ gsm_ts_name(ts), \
+ ## args, \
+ (!fmt || !*fmt || fmt[strlen(fmt)-1] != '\n') ? "\n" : ""); \
+ } while(0)
+
+enum ts_fsm_state {
+ TS_ST_NOT_INITIALIZED,
+ TS_ST_UNUSED,
+ TS_ST_WAIT_PDCH_ACT,
+ TS_ST_PDCH,
+ TS_ST_WAIT_PDCH_DEACT,
+ TS_ST_IN_USE,
+ TS_ST_BORKEN,
+};
+
+enum ts_fsm_event {
+ TS_EV_OML_READY,
+ TS_EV_OML_DOWN,
+ TS_EV_RSL_READY,
+ TS_EV_RSL_DOWN,
+ TS_EV_LCHAN_REQUESTED,
+ TS_EV_LCHAN_UNUSED,
+ TS_EV_PDCH_ACT_ACK,
+ TS_EV_PDCH_ACT_NACK,
+ TS_EV_PDCH_DEACT_ACK,
+ TS_EV_PDCH_DEACT_NACK,
+};
+
+void ts_fsm_init();
+
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts);
+
+bool ts_is_capable_of_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan);
+bool ts_is_capable_of_lchant(struct gsm_bts_trx_ts *ts, enum gsm_chan_t type);
+bool ts_is_lchan_waiting_for_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config *target_pchan);
+bool ts_is_pchan_switching(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config *target_pchan);
+bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan);
diff --git a/include/osmocom/bsc/ussd.h b/include/osmocom/bsc/ussd.h
new file mode 100644
index 000000000..266546811
--- /dev/null
+++ b/include/osmocom/bsc/ussd.h
@@ -0,0 +1,10 @@
+#ifndef _USSD_H
+#define _USSD_H
+
+/* Handler function for mobile-originated USSD messages */
+
+#include <osmocom/core/msgb.h>
+
+int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+#endif
diff --git a/include/osmocom/bsc/vty.h b/include/osmocom/bsc/vty.h
new file mode 100644
index 000000000..e63275546
--- /dev/null
+++ b/include/osmocom/bsc/vty.h
@@ -0,0 +1,38 @@
+#ifndef OPENBSC_VTY_H
+#define OPENBSC_VTY_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/command.h>
+
+struct gsm_network;
+struct vty;
+
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *);
+
+struct buffer *vty_argv_to_buffer(int argc, const char *argv[], int base);
+
+extern struct cmd_element cfg_description_cmd;
+extern struct cmd_element cfg_no_description_cmd;
+
+enum bsc_vty_node {
+ GSMNET_NODE = _LAST_OSMOVTY_NODE + 1,
+ BTS_NODE,
+ TRX_NODE,
+ TS_NODE,
+ OML_NODE,
+ NAT_NODE,
+ NAT_BSC_NODE,
+ MSC_NODE,
+ OM2K_NODE,
+ OM2K_CON_GROUP_NODE,
+ BSC_NODE,
+};
+
+struct log_info;
+int bsc_vty_init(struct gsm_network *network);
+int bsc_vty_init_extra(void);
+
+struct gsm_network *gsmnet_from_vty(struct vty *vty);
+
+#endif
diff --git a/m4/README b/m4/README
new file mode 100644
index 000000000..92eb30ba4
--- /dev/null
+++ b/m4/README
@@ -0,0 +1,3 @@
+We want to avoid creating too many external build-time dependencies
+like this one to autoconf-archive. This directory provides a local
+copy of required m4 rules.
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
new file mode 100644
index 000000000..ca3639715
--- /dev/null
+++ b/m4/ax_check_compile_flag.m4
@@ -0,0 +1,74 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+# Check whether the given FLAG works with the current language's compiler
+# or gives an error. (Warnings, however, are ignored)
+#
+# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+# success/failure.
+#
+# If EXTRA-FLAGS is defined, it is added to the current language's default
+# flags (e.g. CFLAGS) when the check is done. The check is thus made with
+# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
+# force the compiler to issue an error when a bad flag is given.
+#
+# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
+# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+# 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 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 4
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+ _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+ AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+ [AS_VAR_SET(CACHEVAR,[yes])],
+ [AS_VAR_SET(CACHEVAR,[no])])
+ _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/osmoappdesc.py b/osmoappdesc.py
new file mode 100644
index 000000000..f5f18b235
--- /dev/null
+++ b/osmoappdesc.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
+# 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 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>
+
+app_configs = {
+ "osmo-bsc": ["doc/examples/osmo-bsc/osmo-bsc.cfg",
+ "doc/examples/osmo-bsc/osmo-bsc_custom-sccp.cfg"]
+}
+
+apps = [(4242, "src/osmo-bsc/osmo-bsc", "OsmoBSC", "osmo-bsc")
+ ]
+
+vty_command = ["./src/osmo-bsc/osmo-bsc", "-c",
+ "doc/examples/osmo-bsc/osmo-bsc.cfg"]
+
+vty_app = apps[0]
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 000000000..6c63eead8
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,27 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+SUBDIRS = \
+ libfilter \
+ osmo-bsc \
+ utils \
+ ipaccess \
+ $(NULL)
diff --git a/src/ipaccess/Makefile.am b/src/ipaccess/Makefile.am
new file mode 100644
index 000000000..d73aa4d77
--- /dev/null
+++ b/src/ipaccess/Makefile.am
@@ -0,0 +1,71 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+OSMO_LIBS = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOMGCPCLIENT_LIBS) \
+ $(LIBOSMOSIGTRAN_LIBS) \
+ $(NULL)
+
+bin_PROGRAMS = \
+ abisip-find \
+ ipaccess-config \
+ ipaccess-proxy \
+ $(NULL)
+
+abisip_find_LDADD = \
+ $(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/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/gsm_timers.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/osmo-bsc/gsm_timers.o \
+ $(OSMO_LIBS) \
+ $(NULL)
diff --git a/src/ipaccess/abisip-find.c b/src/ipaccess/abisip-find.c
new file mode 100644
index 000000000..21ed50e5a
--- /dev/null
+++ b/src/ipaccess/abisip-find.c
@@ -0,0 +1,476 @@
+/* ip.access nanoBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <getopt.h>
+#include <time.h>
+#include <talloc.h>
+#include <errno.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/bsc/gsm_data.h>
+
+static struct {
+ const char *ifname;
+ const char *bind_ip;
+ int send_interval;
+ bool list_view;
+ time_t list_view_timeout;
+ bool format_json;
+} cmdline_opts = {
+ .ifname = NULL,
+ .bind_ip = NULL,
+ .send_interval = 5,
+ .list_view = false,
+ .list_view_timeout = 10,
+ .format_json = false,
+};
+
+static void print_help()
+{
+ printf("\n");
+ printf("Usage: abisip-find [-l] [<interface-name>]\n");
+ printf(" <interface-name> Specify the outgoing network interface,\n"
+ " e.g. 'eth0'\n");
+ printf(" -b --bind-ip <ip> Specify the local IP to bind to,\n"
+ " e.g. '192.168.1.10'\n");
+ printf(" -i --interval <s> Send broadcast frames every <s> seconds.\n");
+ printf(" -l --list-view Instead of printing received responses,\n"
+ " output a sorted list of currently present\n"
+ " base stations and change events.\n");
+ printf(" -t --timeout <s> Drop base stations after <s> seconds of\n"
+ " receiving no more replies from it.\n"
+ " Implies --list-view.\n");
+ printf(" -j --format-json Print BTS information using json syntax.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"bind-ip", 1, 0, 'b'},
+ {"send-interval", 1, 0, 'i'},
+ {"list-view", 0, 0, 'l'},
+ {"timeout", 1, 0, 't'},
+ {"format-json", 0, 0, 'j'},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "hb:i:lt:j",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'b':
+ cmdline_opts.bind_ip = optarg;
+ break;
+ case 'i':
+ errno = 0;
+ cmdline_opts.send_interval = strtoul(optarg, NULL, 10);
+ if (errno || cmdline_opts.send_interval < 1) {
+ fprintf(stderr, "Invalid interval value: %s\n", optarg);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 't':
+ errno = 0;
+ cmdline_opts.list_view_timeout = strtoul(optarg, NULL, 10);
+ if (errno) {
+ fprintf(stderr, "Invalid timeout value: %s\n", optarg);
+ exit(EXIT_FAILURE);
+ }
+ /* fall through to imply list-view: */
+ case 'l':
+ cmdline_opts.list_view = true;
+ break;
+ case 'j':
+ cmdline_opts.format_json = true;
+ break;
+ default:
+ /* catch unknown options *as well as* missing arguments. */
+ fprintf(stderr, "Error in command line options. Exiting. Try --help.\n");
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+
+ if (argc - optind > 0)
+ cmdline_opts.ifname = argv[optind++];
+
+ if (argc - optind > 0) {
+ fprintf(stderr, "Error: too many arguments\n");
+ print_help();
+ exit(EXIT_FAILURE);
+ }
+}
+
+static int udp_sock(const char *ifname, const char *bind_ip)
+{
+ int fd, rc, bc = 1;
+ struct sockaddr_in sa;
+
+ fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0)
+ return fd;
+
+ if (ifname) {
+#ifdef __FreeBSD__
+ rc = setsockopt(fd, SOL_SOCKET, IP_RECVIF, ifname,
+ strlen(ifname));
+#else
+ rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
+ strlen(ifname));
+#endif
+ if (rc < 0)
+ goto err;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(3006);
+ if (bind_ip) {
+ rc = inet_pton(AF_INET, bind_ip, &sa.sin_addr);
+ if (rc != 1) {
+ fprintf(stderr, "bind ip addr: inet_pton failed, returned %d\n", rc);
+ goto err;
+ }
+ } else {
+ sa.sin_addr.s_addr = INADDR_ANY;
+ }
+
+ rc = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
+ if (rc < 0)
+ goto err;
+
+ rc = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc));
+ if (rc < 0)
+ goto err;
+
+#if 0
+ /* we cannot bind, since the response packets don't come from
+ * the broadcast address */
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(3006);
+ inet_aton("255.255.255.255", &sa.sin_addr);
+
+ rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
+ if (rc < 0)
+ goto err;
+#endif
+ return fd;
+
+err:
+ close(fd);
+ return rc;
+}
+
+const unsigned char find_pkt[] = { 0x00, 0x0b+8, IPAC_PROTO_IPACCESS, 0x00,
+ IPAC_MSGT_ID_GET,
+ 0x01, IPAC_IDTAG_MACADDR,
+ 0x01, IPAC_IDTAG_IPADDR,
+ 0x01, IPAC_IDTAG_UNIT,
+ 0x01, IPAC_IDTAG_LOCATION1,
+ 0x01, IPAC_IDTAG_LOCATION2,
+ 0x01, IPAC_IDTAG_EQUIPVERS,
+ 0x01, IPAC_IDTAG_SWVERSION,
+ 0x01, IPAC_IDTAG_UNITNAME,
+ 0x01, IPAC_IDTAG_SERNR,
+ };
+
+
+static int bcast_find(int fd)
+{
+ struct sockaddr_in sa;
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(3006);
+ inet_aton("255.255.255.255", &sa.sin_addr);
+
+ return sendto(fd, find_pkt, sizeof(find_pkt), 0, (struct sockaddr *) &sa, sizeof(sa));
+}
+
+static char *parse_response(void *ctx, unsigned char *buf, int len)
+{
+ unsigned int out_len;
+ uint8_t t_len;
+ uint8_t t_tag;
+ uint8_t *cur = buf;
+ char *out = talloc_zero_size(ctx, 512);
+
+ if (cmdline_opts.format_json)
+ out = talloc_asprintf_append(out,"{ ");
+
+ while (cur < buf + len) {
+ t_len = *cur++;
+ t_tag = *cur++;
+
+ if (cmdline_opts.format_json)
+ out = talloc_asprintf_append(out, "\"%s\": \"%s\", ", ipa_ccm_idtag_name(t_tag), cur);
+ else
+ out = talloc_asprintf_append(out, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur);
+
+ cur += t_len;
+ }
+
+ if (cmdline_opts.format_json) {
+ out_len = strlen(out);
+ if (out[out_len-2] == ',')
+ out[out_len-2] = ' ';
+ out[out_len-1] = '}';
+ }
+
+ return out;
+}
+
+struct base_station {
+ struct llist_head entry;
+ char *line;
+ time_t timestamp;
+};
+
+LLIST_HEAD(base_stations);
+
+void *ctx = NULL;
+
+void print_timestamp()
+{
+ time_t now = time(NULL);
+ printf("\n\n----- %s\n", ctime(&now));
+}
+
+struct base_station *base_station_parse(unsigned char *buf, int len)
+{
+ struct base_station *new_bs = talloc_zero(ctx, struct base_station);
+ new_bs->line = parse_response(new_bs, buf, len);
+ new_bs->timestamp = time(NULL);
+ return new_bs;
+}
+
+bool base_stations_add(struct base_station *new_bs)
+{
+ struct base_station *bs;
+
+ llist_for_each_entry(bs, &base_stations, entry) {
+ int c = strcmp(new_bs->line, bs->line);
+ if (!c) {
+ /* entry already exists. */
+ bs->timestamp = new_bs->timestamp;
+ return false;
+ }
+
+ if (c < 0) {
+ /* found the place to add the entry */
+ break;
+ }
+ }
+
+ print_timestamp();
+ printf("New:\n%s\n", new_bs->line);
+
+ llist_add_tail(&new_bs->entry, &bs->entry);
+ return true;
+}
+
+bool base_stations_timeout()
+{
+ struct base_station *bs, *next_bs;
+ time_t now = time(NULL);
+ bool changed = false;
+
+ llist_for_each_entry_safe(bs, next_bs, &base_stations, entry) {
+ if (now - bs->timestamp < cmdline_opts.list_view_timeout)
+ continue;
+ print_timestamp();
+ printf("LOST:\n%s\n", bs->line);
+
+ llist_del(&bs->entry);
+ talloc_free(bs);
+ changed = true;
+ }
+ return changed;
+}
+
+void base_stations_print()
+{
+ struct base_station *bs;
+ int count = 0;
+
+ print_timestamp();
+ if (cmdline_opts.format_json)
+ printf("[");
+
+ llist_for_each_entry(bs, &base_stations, entry) {
+ if (cmdline_opts.format_json) {
+ if (count)
+ printf(",");
+ printf("\n%s", bs->line);
+ } else {
+ printf("%3d: %s\n", count, bs->line);
+ }
+ count++;
+ }
+
+ if (cmdline_opts.format_json)
+ printf("%c]\n", count ? '\n': ' ');
+
+ printf("\nTotal: %d\n", count);
+}
+
+static void base_stations_bump(bool known_changed)
+{
+ bool changed = known_changed;
+ if (base_stations_timeout())
+ changed = true;
+
+ if (changed)
+ base_stations_print();
+}
+
+static void handle_response(unsigned char *buf, int len)
+{
+ static unsigned int responses = 0;
+ responses++;
+
+ if (cmdline_opts.list_view) {
+ bool changed = false;
+ struct base_station *bs = base_station_parse(buf, len);
+ if (base_stations_add(bs))
+ changed = true;
+ else
+ talloc_free(bs);
+ base_stations_bump(changed);
+ printf("RX: %u \r", responses);
+ } else {
+ printf("%s\n", parse_response(ctx, buf, len));
+ }
+ fflush(stdout);
+}
+
+static int read_response(int fd)
+{
+ unsigned char buf[255];
+ struct sockaddr_in sa;
+ int len;
+ socklen_t sa_len = sizeof(sa);
+
+ len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &sa_len);
+ if (len < 0)
+ return len;
+
+ /* 2 bytes length, 1 byte protocol */
+ if (buf[2] != IPAC_PROTO_IPACCESS)
+ return 0;
+
+ if (buf[4] != IPAC_MSGT_ID_RESP)
+ return 0;
+
+ handle_response(buf+6, len-6);
+ return 0;
+}
+
+static int bfd_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+ if (flags & BSC_FD_READ)
+ return read_response(bfd->fd);
+ if (flags & BSC_FD_WRITE) {
+ bfd->when &= ~BSC_FD_WRITE;
+ return bcast_find(bfd->fd);
+ }
+ return 0;
+}
+
+static struct osmo_timer_list timer;
+
+static void timer_cb(void *_data)
+{
+ struct osmo_fd *bfd = _data;
+
+ bfd->when |= BSC_FD_WRITE;
+
+ base_stations_bump(false);
+
+ osmo_timer_schedule(&timer, cmdline_opts.send_interval, 0);
+}
+
+int main(int argc, char **argv)
+{
+ struct osmo_fd bfd;
+ int rc;
+
+ printf("abisip-find (C) 2009-2010 by Harald Welte\n");
+ printf(" (C) 2017 by sysmocom - s.f.m.c. GmbH\n");
+ printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+ handle_options(argc, argv);
+
+ if (!cmdline_opts.ifname && !cmdline_opts.bind_ip)
+ fprintf(stdout, "- You might need to specify the outgoing network interface,\n"
+ " e.g. ``%s eth0'' (requires root permissions),\n"
+ " or alternatively use -b to bind to the source address\n"
+ " assigned to that interface\n", argv[0]);
+ if (!cmdline_opts.list_view)
+ fprintf(stdout, "- You may find the --list-view option convenient.\n");
+ else if (cmdline_opts.send_interval >= cmdline_opts.list_view_timeout)
+ fprintf(stdout, "\nWARNING: the --timeout should be larger than --interval.\n\n");
+
+ bfd.cb = bfd_cb;
+ bfd.when = BSC_FD_READ | BSC_FD_WRITE;
+ bfd.fd = udp_sock(cmdline_opts.ifname, cmdline_opts.bind_ip);
+ if (bfd.fd < 0) {
+ perror("Cannot create local socket for broadcast udp");
+ exit(1);
+ }
+
+ rc = osmo_fd_register(&bfd);
+ if (rc < 0) {
+ fprintf(stderr, "Cannot register FD\n");
+ exit(1);
+ }
+
+ osmo_timer_setup(&timer, timer_cb, &bfd);
+ osmo_timer_schedule(&timer, cmdline_opts.send_interval, 0);
+
+ printf("Trying to find ip.access BTS by broadcast UDP...\n");
+
+ while (1) {
+ rc = osmo_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ exit(0);
+}
+
diff --git a/src/ipaccess/ipaccess-config.c b/src/ipaccess/ipaccess-config.c
new file mode 100644
index 000000000..da19ce20a
--- /dev/null
+++ b/src/ipaccess/ipaccess-config.c
@@ -0,0 +1,1155 @@
+/* ip.access nanoBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/network_listen.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/network_listen.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/abis/abis.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/bsc/bss.h>
+
+struct gsm_network *bsc_gsmnet;
+
+static int net_listen_testnr;
+static int restart;
+static bool get_attr;
+static char *prim_oml_ip;
+static char *bts_ip_addr, *bts_ip_mask, *bts_ip_gw;
+static char *unit_id;
+static uint16_t nv_flags;
+static uint16_t nv_mask;
+static char *software = NULL;
+static int sw_load_state = 0;
+static int oml_state = 0;
+static int dump_files = 0;
+static char *firmware_analysis = NULL;
+static int found_trx = 0;
+static int loop_tests = 0;
+static bool quiet = false;
+
+static void *tall_ctx_config = NULL;
+static struct abis_nm_sw_desc *sw_load1 = NULL;
+static struct abis_nm_sw_desc *sw_load2 = NULL;
+
+extern int ipaccess_fd_cb(struct osmo_fd *bfd, unsigned int what);
+extern struct e1inp_line_ops ipaccess_e1inp_line_ops;
+
+/* Actively connect to a BTS. Currently used by ipaccess-config.c */
+static int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa)
+{
+ struct e1inp_ts *e1i_ts = &line->ts[0];
+ struct osmo_fd *bfd = &e1i_ts->driver.ipaccess.fd;
+ int ret, on = 1;
+
+ bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ bfd->cb = ipaccess_fd_cb;
+ bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+ bfd->data = line;
+ bfd->priv_nr = E1INP_SIGN_OML;
+
+ if (bfd->fd < 0) {
+ LOGP(DLINP, LOGL_ERROR, "could not create TCP socket.\n");
+ return -EIO;
+ }
+
+ ret = setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "could not set socket option\n");
+ close(bfd->fd);
+ return -EIO;
+ }
+
+ ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "could not connect socket\n");
+ close(bfd->fd);
+ return ret;
+ }
+
+ ret = osmo_fd_register(bfd);
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "unable to register socket fd\n");
+ close(bfd->fd);
+ return ret;
+ }
+ return ret;
+ //return e1inp_line_register(line);
+}
+
+/* configure pseudo E1 line in ip.access style and connect to BTS */
+static int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin)
+{
+ struct e1inp_line *line;
+ struct e1inp_ts *sign_ts, *rsl_ts;
+ struct e1inp_sign_link *oml_link, *rsl_link;
+
+ line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+ if (!line)
+ return -ENOMEM;
+
+ line->driver = e1inp_driver_find("ipa");
+ if (!line->driver) {
+ fprintf(stderr, "cannot `ipa' driver, giving up.\n");
+ return -EINVAL;
+ }
+ line->ops = &ipaccess_e1inp_line_ops;
+
+ /* create E1 timeslots for signalling and TRAU frames */
+ e1inp_ts_config_sign(&line->ts[1-1], line);
+ e1inp_ts_config_sign(&line->ts[2-1], line);
+
+ /* create signalling links for TS1 */
+ sign_ts = &line->ts[1-1];
+ rsl_ts = &line->ts[2-1];
+ oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+ bts->c0, 0xff, 0);
+ rsl_link = e1inp_sign_link_create(rsl_ts, E1INP_SIGN_RSL,
+ bts->c0, 0, 0);
+
+ /* create back-links from bts/trx */
+ bts->oml_link = oml_link;
+ bts->c0->rsl_link = rsl_link;
+
+ /* default port at BTS for incoming connections is 3006 */
+ if (sin->sin_port == 0)
+ sin->sin_port = htons(3006);
+
+ return ipaccess_connect(line, sin);
+}
+
+/*
+ * Callback function for NACK on the OML NM
+ *
+ * Currently we send the config requests but don't check the
+ * result. The nanoBTS will send us a NACK when we did something the
+ * BTS didn't like.
+ */
+static int ipacc_msg_nack(uint8_t mt)
+{
+ fprintf(stderr, "Failure to set attribute. This seems fatal\n");
+ exit(-1);
+ return 0;
+}
+
+static void check_restart_or_exit(struct gsm_bts_trx *trx)
+{
+ if (restart) {
+ abis_nm_ipaccess_restart(trx);
+ } else {
+ exit(0);
+ }
+}
+
+static int ipacc_msg_ack(uint8_t mt, struct gsm_bts_trx *trx)
+{
+ if (sw_load_state == 1) {
+ fprintf(stderr, "The new software is activaed.\n");
+ check_restart_or_exit(trx);
+ } else if (oml_state == 1) {
+ fprintf(stderr, "Set the NV Attributes.\n");
+ check_restart_or_exit(trx);
+ }
+
+ return 0;
+}
+
+static const uint8_t phys_conf_min[] = { 0x02 };
+
+static uint16_t build_physconf(uint8_t *physconf_buf, const struct rxlev_stats *st)
+{
+ uint16_t *whitelist = (uint16_t *) (physconf_buf + 4);
+ int num_arfcn;
+ unsigned int arfcnlist_size;
+
+ /* Create whitelist from rxlevels */
+ physconf_buf[0] = phys_conf_min[0];
+ physconf_buf[1] = NM_IPAC_EIE_ARFCN_WHITE;
+ num_arfcn = ipac_rxlevstat2whitelist(whitelist, st, 0, 100);
+ arfcnlist_size = num_arfcn * 2;
+ *((uint16_t *) (physconf_buf+2)) = htons(arfcnlist_size);
+ DEBUGP(DNM, "physconf_buf (%s)\n", osmo_hexdump(physconf_buf, arfcnlist_size+4));
+ return arfcnlist_size+4;
+}
+
+static int nwl_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t physconf_buf[2*NUM_ARFCNS+16];
+ uint16_t physconf_len;
+
+ switch (signal) {
+ case S_IPAC_NWL_COMPLETE:
+ trx = signal_data;
+ DEBUGP(DNM, "received S_IPAC_NWL_COMPLETE signal\n");
+ switch (trx->ipaccess.test_nr) {
+ case NM_IPACC_TESTNO_CHAN_USAGE:
+ /* Dump RxLev results */
+ //rxlev_stat_dump(&trx->ipaccess.rxlev_stat);
+ /* Create whitelist from results */
+ physconf_len = build_physconf(physconf_buf,
+ &trx->ipaccess.rxlev_stat);
+ /* Start next test abbout BCCH channel usage */
+ ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_CHAN_USAGE,
+ physconf_buf, physconf_len);
+ break;
+ case NM_IPACC_TESTNO_BCCH_CHAN_USAGE:
+ /* Dump BCCH RxLev results */
+ //rxlev_stat_dump(&trx->ipaccess.rxlev_stat);
+ /* Create whitelist from results */
+ physconf_len = build_physconf(physconf_buf,
+ &trx->ipaccess.rxlev_stat);
+ /* Start next test about BCCH info */
+ ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_INFO,
+ physconf_buf, physconf_len);
+ break;
+ case NM_IPACC_TESTNO_BCCH_INFO:
+ /* re-start full process with CHAN_USAGE */
+ if (loop_tests) {
+ DEBUGP(DNM, "starting next test cycle\n");
+ ipac_nwl_test_start(trx, net_listen_testnr, phys_conf_min,
+ sizeof(phys_conf_min));
+ } else {
+ exit(0);
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int print_attr_rep(struct msgb *mb)
+{
+ /* Parse using nanoBTS own formatting for Get Attribute Response */
+ 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_trx *trx = sign_link->trx;
+ struct gsm_bts *bts = trx->bts;
+ struct tlv_parsed tp;
+ struct in_addr ia = {0};
+ char oml_ip[20] = {0};
+ uint16_t oml_port = 0;
+ char unit_id[40] = {0};
+
+
+ abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+
+ abis_nm_tlv_attr_primary_oml(&tp, &ia, &oml_port);
+ osmo_strlcpy(oml_ip, inet_ntoa(ia), sizeof(oml_ip));
+
+ abis_nm_tlv_attr_unit_id(&tp, unit_id, sizeof(unit_id));
+
+ fprintf(stdout, "{ \"primary_oml_ip\": \"%s\", \"primary_oml_port\": %" PRIu16 ", \"unit_id\": \"%s\" }\n",
+ oml_ip, oml_port, unit_id);
+ return 0;
+}
+
+static int nm_state_event(int evt, uint8_t obj_class, void *obj,
+ struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+ struct abis_om_obj_inst *obj_inst);
+
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct ipacc_ack_signal_data *ipacc_data;
+ struct nm_statechg_signal_data *nsd;
+ struct msgb *oml_msg;
+
+ switch (signal) {
+ case S_NM_IPACC_NACK:
+ ipacc_data = signal_data;
+ return ipacc_msg_nack(ipacc_data->msg_type);
+ case S_NM_IPACC_ACK:
+ ipacc_data = signal_data;
+ return ipacc_msg_ack(ipacc_data->msg_type, ipacc_data->trx);
+ case S_NM_IPACC_RESTART_ACK:
+ if (!quiet)
+ printf("The BTS has acked the restart. Exiting.\n");
+ exit(0);
+ break;
+ case S_NM_IPACC_RESTART_NACK:
+ if (!quiet)
+ printf("The BTS has nacked the restart. Exiting.\n");
+ exit(0);
+ break;
+ case S_NM_STATECHG_OPER:
+ case S_NM_STATECHG_ADM:
+ nsd = signal_data;
+ nm_state_event(signal, nsd->obj_class, nsd->obj, nsd->old_state,
+ nsd->new_state, nsd->obj_inst);
+ break;
+ case S_NM_GET_ATTR_REP:
+ fprintf(stderr, "Received SIGNAL S_NM_GET_ATTR_REP\n");
+ oml_msg = signal_data;
+ print_attr_rep(oml_msg);
+ exit(0);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* callback function passed to the ABIS OML code */
+static int percent;
+static int percent_old;
+static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *_msg,
+ void *data, void *param)
+{
+ struct msgb *msg;
+ struct gsm_bts_trx *trx;
+
+ if (hook != GSM_HOOK_NM_SWLOAD)
+ return 0;
+
+ trx = (struct gsm_bts_trx *) data;
+
+ switch (event) {
+ case NM_MT_LOAD_INIT_ACK:
+ if (!quiet)
+ fprintf(stdout, "Software Load Initiate ACK\n");
+ break;
+ case NM_MT_LOAD_INIT_NACK:
+ fprintf(stderr, "ERROR: Software Load Initiate NACK\n");
+ exit(5);
+ break;
+ case NM_MT_LOAD_END_ACK:
+ fprintf(stderr, "LOAD END ACK...");
+ /* now make it the default */
+ sw_load_state = 1;
+
+ msg = msgb_alloc(1024, "sw: nvattr");
+ msg->l2h = msgb_put(msg, 3);
+ msg->l3h = &msg->l2h[3];
+
+ /* activate software */
+ if (sw_load1)
+ abis_nm_put_sw_desc(msg, sw_load1, true);
+
+ if (sw_load2)
+ abis_nm_put_sw_desc(msg, sw_load2, true);
+
+ /* fill in the data */
+ msg->l2h[0] = NM_ATT_IPACC_CUR_SW_CFG;
+ msg->l2h[1] = msgb_l3len(msg) >> 8;
+ msg->l2h[2] = msgb_l3len(msg) & 0xff;
+ if (!quiet)
+ printf("Foo l2h: %p l3h: %p... length l2: %u l3: %u\n",
+ msg->l2h, msg->l3h, msgb_l2len(msg), msgb_l3len(msg));
+ abis_nm_ipaccess_set_nvattr(trx, msg->l2h, msgb_l2len(msg));
+ msgb_free(msg);
+ break;
+ case NM_MT_LOAD_END_NACK:
+ fprintf(stderr, "ERROR: Software Load End NACK\n");
+ exit(3);
+ break;
+ case NM_MT_ACTIVATE_SW_NACK:
+ fprintf(stderr, "ERROR: Activate Software NACK\n");
+ exit(4);
+ break;
+ case NM_MT_ACTIVATE_SW_ACK:
+ break;
+ case NM_MT_LOAD_SEG_ACK:
+ percent = abis_nm_software_load_status(trx->bts);
+ if (!quiet && percent > percent_old)
+ printf("Software Download Progress: %d%%\n", percent);
+ percent_old = percent;
+ break;
+ case NM_MT_LOAD_ABORT:
+ fprintf(stderr, "ERROR: Load aborted by the BTS.\n");
+ exit(6);
+ break;
+ }
+ return 0;
+}
+
+static void nv_put_ip_if_cfg(struct msgb *nmsg, uint32_t ip, uint32_t mask)
+{
+ msgb_put_u8(nmsg, NM_ATT_IPACC_IP_IF_CFG);
+
+ msgb_put_u32(nmsg, ip);
+ msgb_put_u32(nmsg, mask);
+}
+
+static void nv_put_gw_cfg(struct msgb *nmsg, uint32_t addr, uint32_t mask, uint32_t gw)
+{
+ msgb_put_u8(nmsg, NM_ATT_IPACC_IP_GW_CFG);
+ msgb_put_u32(nmsg, addr);
+ msgb_put_u32(nmsg, mask);
+ msgb_put_u32(nmsg, gw);
+}
+
+static void nv_put_unit_id(struct msgb *nmsg, const char *unit_id)
+{
+ msgb_tl16v_put(nmsg, NM_ATT_IPACC_UNIT_ID, strlen(unit_id)+1,
+ (const uint8_t *)unit_id);
+}
+
+static void nv_put_prim_oml(struct msgb *nmsg, uint32_t ip, uint16_t port)
+{
+ int len;
+
+ /* 0x88 + IP + port */
+ len = 1 + sizeof(ip) + sizeof(port);
+
+ msgb_put_u8(nmsg, NM_ATT_IPACC_PRIM_OML_CFG_LIST);
+ msgb_put_u16(nmsg, len);
+
+ msgb_put_u8(nmsg, NM_ATT_IPACC_PRIM_OML_CFG);
+
+ /* IP address */
+ msgb_put_u32(nmsg, ip);
+
+ /* port number */
+ msgb_put_u16(nmsg, port);
+}
+
+static void nv_put_flags(struct msgb *nmsg, uint16_t nv_flags, uint16_t nv_mask)
+{
+ msgb_put_u8(nmsg, NM_ATT_IPACC_NV_FLAGS);
+ msgb_put_u16(nmsg, sizeof(nv_flags) + sizeof(nv_mask));
+ msgb_put_u8(nmsg, nv_flags & 0xff);
+ msgb_put_u8(nmsg, nv_mask & 0xff);
+ msgb_put_u8(nmsg, nv_flags >> 8);
+ msgb_put_u8(nmsg, nv_mask >> 8);
+}
+
+/* human-readable test names for the ip.access tests */
+static const struct value_string ipa_test_strs[] = {
+ { 64, "ccch-usage" },
+ { 65, "bcch-usage" },
+ { 66, "freq-sync" },
+ { 67, "rtp-usage" },
+ { 68, "rtp-perf" },
+ { 69, "gprs-ccch" },
+ { 70, "pccch-usage" },
+ { 71, "gprs-usage" },
+ { 72, "esta-mf" },
+ { 73, "uplink-mf" },
+ { 74, "dolink-mf" },
+ { 75, "tbf-details" },
+ { 76, "tbf-usage" },
+ { 77, "llc-data" },
+ { 78, "pdch-usage" },
+ { 79, "power-control" },
+ { 80, "link-adaption" },
+ { 81, "tch-usage" },
+ { 82, "amr-mf" },
+ { 83, "rtp-multiplex-perf" },
+ { 84, "rtp-multiplex-usage" },
+ { 85, "srtp-multiplex-usage" },
+ { 86, "abis-traffic" },
+ { 89, "gprs-multiplex-perf" },
+ { 90, "gprs-multiplex-usage" },
+ { 0, NULL },
+};
+
+/* human-readable names for the ip.access nanoBTS NVRAM Flags */
+static const struct value_string ipa_nvflag_strs[] = {
+ { 0x0001, "static-ip" },
+ { 0x0002, "static-gw" },
+ { 0x0004, "no-dhcp-vsi" },
+ { 0x0008, "dhcp-enabled" },
+ { 0x0040, "led-disabled" },
+ { 0x0100, "secondary-oml-enabled" },
+ { 0x0200, "diag-enabled" },
+ { 0x0400, "cli-enabled" },
+ { 0x0800, "http-enabled" },
+ { 0x1000, "post-enabled" },
+ { 0x2000, "snmp-enabled" },
+ { 0, NULL }
+};
+
+/* set the flags in flags/mask according to a string-identified flag and 'enable' */
+static int ipa_nvflag_set(uint16_t *flags, uint16_t *mask, const char *name, int en)
+{
+ int rc;
+ rc = get_string_value(ipa_nvflag_strs, name);
+ if (rc < 0)
+ return rc;
+
+ *mask |= rc;
+ if (en)
+ *flags |= rc;
+ else
+ *flags &= ~rc;
+
+ return 0;
+}
+
+static void bootstrap_om(struct gsm_bts_trx *trx)
+{
+ struct msgb *nmsg_get = msgb_alloc(1024, "nested get msgb");
+ struct msgb *nmsg_set = msgb_alloc(1024, "nested set msgb");
+ int need_to_set_attr = 0;
+ int len;
+
+ if (!quiet)
+ printf("OML link established using TRX %d\n", trx->nr);
+
+ if (get_attr) {
+ msgb_put_u8(nmsg_get, NM_ATT_IPACC_PRIM_OML_CFG);
+ msgb_put_u8(nmsg_get, NM_ATT_IPACC_UNIT_ID);
+ }
+ if (unit_id) {
+ len = strlen(unit_id);
+ if (len > nmsg_set->data_len-10)
+ goto out_err;
+ if (!quiet)
+ printf("setting Unit ID to '%s'\n", unit_id);
+ nv_put_unit_id(nmsg_set, unit_id);
+ need_to_set_attr = 1;
+ }
+ if (prim_oml_ip) {
+ struct in_addr ia;
+
+ if (!inet_aton(prim_oml_ip, &ia)) {
+ fprintf(stderr, "invalid IP address: %s\n",
+ prim_oml_ip);
+ goto out_err;
+ }
+
+ if (!quiet)
+ printf("setting primary OML link IP to '%s'\n", inet_ntoa(ia));
+ nv_put_prim_oml(nmsg_set, ntohl(ia.s_addr), 0);
+ need_to_set_attr = 1;
+ }
+ if (nv_mask) {
+ if (!quiet)
+ printf("setting NV Flags/Mask to 0x%04x/0x%04x\n",
+ nv_flags, nv_mask);
+ nv_put_flags(nmsg_set, nv_flags, nv_mask);
+ need_to_set_attr = 1;
+ }
+ if (bts_ip_addr && bts_ip_mask) {
+ struct in_addr ia_addr, ia_mask;
+
+ if (!inet_aton(bts_ip_addr, &ia_addr)) {
+ fprintf(stderr, "invalid IP address: %s\n",
+ bts_ip_addr);
+ goto out_err;
+ }
+
+ if (!inet_aton(bts_ip_mask, &ia_mask)) {
+ fprintf(stderr, "invalid IP address: %s\n",
+ bts_ip_mask);
+ goto out_err;
+ }
+
+ if (!quiet)
+ printf("setting static IP Address/Mask\n");
+ nv_put_ip_if_cfg(nmsg_set, ntohl(ia_addr.s_addr), ntohl(ia_mask.s_addr));
+ need_to_set_attr = 1;
+ }
+ if (bts_ip_gw) {
+ struct in_addr ia_gw;
+
+ if (!inet_aton(bts_ip_gw, &ia_gw)) {
+ fprintf(stderr, "invalid IP address: %s\n",
+ bts_ip_gw);
+ goto out_err;
+ }
+
+ if (!quiet)
+ printf("setting static IP Gateway\n");
+ /* we only set the default gateway with zero addr/mask */
+ nv_put_gw_cfg(nmsg_set, 0, 0, ntohl(ia_gw.s_addr));
+ need_to_set_attr = 1;
+ }
+
+ if (get_attr) {
+ fprintf(stderr, "getting Attributes (%d): %s\n", nmsg_get->len, osmo_hexdump(msgb_data(nmsg_get), msgb_length(nmsg_get)));
+ abis_nm_get_attr(trx->bts, NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, nmsg_get->head, nmsg_get->len);
+ oml_state = 1;
+ }
+
+ if (need_to_set_attr) {
+ abis_nm_ipaccess_set_nvattr(trx, nmsg_set->head, nmsg_set->len);
+ oml_state = 1;
+ }
+
+ if (restart && !prim_oml_ip && !software) {
+ if (!quiet)
+ printf("restarting BTS\n");
+ abis_nm_ipaccess_restart(trx);
+ }
+
+out_err:
+ msgb_free(nmsg_get);
+ msgb_free(nmsg_set);
+}
+
+static int nm_state_event(int evt, uint8_t obj_class, void *obj,
+ struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+ struct abis_om_obj_inst *obj_inst)
+{
+ if (obj_class == NM_OC_BASEB_TRANSC) {
+ if (!found_trx && obj_inst->trx_nr != 0xff) {
+ struct gsm_bts_trx *trx = container_of(obj, struct gsm_bts_trx, bb_transc);
+ bootstrap_om(trx);
+ found_trx = 1;
+ }
+ } else if (evt == S_NM_STATECHG_OPER &&
+ obj_class == NM_OC_RADIO_CARRIER &&
+ new_state->availability == 3) {
+ struct gsm_bts_trx *trx = obj;
+
+ if (net_listen_testnr)
+ ipac_nwl_test_start(trx, net_listen_testnr,
+ phys_conf_min, sizeof(phys_conf_min));
+ else if (software) {
+ int rc;
+ if (!quiet)
+ printf("Attempting software upload with '%s'\n", software);
+ rc = abis_nm_software_load(trx->bts, trx->nr, software, 19, 0, swload_cbfn, trx);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to start software load\n");
+ exit(-3);
+ }
+ }
+ }
+ return 0;
+}
+
+static struct abis_nm_sw_desc *create_swload(struct sdp_header *header)
+{
+ struct abis_nm_sw_desc *load;
+
+ load = talloc_zero(tall_ctx_config, struct abis_nm_sw_desc);
+
+ osmo_strlcpy((char *)load->file_id, header->firmware_info.sw_part,
+ sizeof(load->file_id));
+ load->file_id_len = strlen((char*)load->file_id) + 1;
+
+ osmo_strlcpy((char *)load->file_version, header->firmware_info.version,
+ sizeof(load->file_version));
+ load->file_version_len = strlen((char*)load->file_version) + 1;
+
+ return load;
+}
+
+static int find_sw_load_params(const char *filename)
+{
+ struct stat stat;
+ struct sdp_header *header;
+ struct llist_head *entry;
+ int fd;
+ void *tall_firm_ctx = 0;
+
+ entry = talloc_zero(tall_firm_ctx, struct llist_head);
+ INIT_LLIST_HEAD(entry);
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ perror("nada");
+ return -1;
+ }
+
+ /* verify the file */
+ if (fstat(fd, &stat) == -1) {
+ perror("Can not stat the file");
+ close(fd);
+ return -1;
+ }
+
+ ipaccess_analyze_file(fd, stat.st_size, 0, entry);
+ if (close(fd) != 0) {
+ perror("Close failed.\n");
+ return -1;
+ }
+
+ /* try to find what we are looking for */
+ llist_for_each_entry(header, entry, entry) {
+ if (ntohs(header->firmware_info.more_more_magic) == 0x1000) {
+ sw_load1 = create_swload(header);
+ } else if (ntohs(header->firmware_info.more_more_magic) == 0x2001) {
+ sw_load2 = create_swload(header);
+ }
+ }
+
+ if (!sw_load1 || !sw_load2) {
+ fprintf(stderr, "Did not find data.\n");
+ talloc_free(tall_firm_ctx);
+ return -1;
+ }
+
+ talloc_free(tall_firm_ctx);
+ return 0;
+}
+
+static void dump_entry(struct sdp_header_item *sub_entry, int part, int fd)
+{
+ int out_fd;
+ int copied;
+ char filename[4096];
+ off_t target;
+
+ if (!dump_files)
+ return;
+
+ if (sub_entry->header_entry.something1 == 0)
+ return;
+
+ snprintf(filename, sizeof(filename), "part.%d", part++);
+ out_fd = open(filename, O_WRONLY | O_CREAT, 0660);
+ if (out_fd < 0) {
+ perror("Can not dump firmware");
+ return;
+ }
+
+ target = sub_entry->absolute_offset + ntohl(sub_entry->header_entry.start) + 4;
+ if (lseek(fd, target, SEEK_SET) != target) {
+ perror("seek failed");
+ close(out_fd);
+ return;
+ }
+
+ for (copied = 0; copied < ntohl(sub_entry->header_entry.length); ++copied) {
+ char c;
+ if (read(fd, &c, sizeof(c)) != sizeof(c)) {
+ perror("copy failed");
+ break;
+ }
+
+ if (write(out_fd, &c, sizeof(c)) != sizeof(c)) {
+ perror("write failed");
+ break;
+ }
+ }
+
+ close(out_fd);
+}
+
+static void analyze_firmware(const char *filename)
+{
+ struct stat stat;
+ struct sdp_header *header;
+ struct sdp_header_item *sub_entry;
+ struct llist_head *entry;
+ int fd;
+ void *tall_firm_ctx = 0;
+ int part = 0;
+
+ entry = talloc_zero(tall_firm_ctx, struct llist_head);
+ INIT_LLIST_HEAD(entry);
+
+ if (!quiet)
+ printf("Opening possible firmware '%s'\n", filename);
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ perror("nada");
+ return;
+ }
+
+ /* verify the file */
+ if (fstat(fd, &stat) == -1) {
+ perror("Can not stat the file");
+ close(fd);
+ return;
+ }
+
+ ipaccess_analyze_file(fd, stat.st_size, 0, entry);
+
+ llist_for_each_entry(header, entry, entry) {
+ printf("Printing header information:\n");
+ printf("more_more_magic: 0x%x\n", ntohs(header->firmware_info.more_more_magic));
+ printf("header_length: %u\n", ntohl(header->firmware_info.header_length));
+ printf("file_length: %u\n", ntohl(header->firmware_info.file_length));
+ printf("sw_part: %.20s\n", header->firmware_info.sw_part);
+ printf("text1: %.64s\n", header->firmware_info.text1);
+ printf("time: %.12s\n", header->firmware_info.time);
+ printf("date: %.14s\n", header->firmware_info.date);
+ printf("text2: %.10s\n", header->firmware_info.text2);
+ printf("version: %.20s\n", header->firmware_info.version);
+ printf("subitems...\n");
+
+ llist_for_each_entry(sub_entry, &header->header_list, entry) {
+ printf("\tsomething1: %u\n", sub_entry->header_entry.something1);
+ printf("\ttext1: %.64s\n", sub_entry->header_entry.text1);
+ printf("\ttime: %.12s\n", sub_entry->header_entry.time);
+ printf("\tdate: %.14s\n", sub_entry->header_entry.date);
+ printf("\ttext2: %.10s\n", sub_entry->header_entry.text2);
+ printf("\tversion: %.20s\n", sub_entry->header_entry.version);
+ printf("\tlength: %u\n", ntohl(sub_entry->header_entry.length));
+ printf("\taddr1: 0x%x\n", ntohl(sub_entry->header_entry.addr1));
+ printf("\taddr2: 0x%x\n", ntohl(sub_entry->header_entry.addr2));
+ printf("\tstart: 0x%x\n", ntohl(sub_entry->header_entry.start));
+ printf("\tabs. offset: 0x%lx\n", sub_entry->absolute_offset);
+ printf("\n\n");
+
+ dump_entry(sub_entry, part++, fd);
+ }
+ printf("\n\n");
+ }
+
+ if (close(fd) != 0) {
+ perror("Close failed.\n");
+ return;
+ }
+
+ talloc_free(tall_firm_ctx);
+}
+
+static bool check_unitid_fmt(const char* unit_id)
+{
+ const char *p = unit_id;
+ bool must_digit = true;
+ uint8_t remain_slash = 2;
+
+ if (strlen(unit_id) < 5)
+ goto wrong_fmt;
+
+ while (*p != '\0') {
+ if (*p != '/' && !isdigit(*p))
+ goto wrong_fmt;
+ if (*p == '/' && must_digit)
+ goto wrong_fmt;
+ if (*p == '/') {
+ must_digit = true;
+ remain_slash--;
+ if (remain_slash < 0)
+ goto wrong_fmt;
+ } else {
+ must_digit = false;
+ }
+ p++;
+ }
+
+ if (*(p-1) == '/')
+ goto wrong_fmt;
+
+ return true;
+
+wrong_fmt:
+ fprintf(stderr, "ERROR: unit-id wrong format. Must be '\\d+/\\d+/\\d+'\n");
+ return false;
+}
+
+static void print_usage(void)
+{
+ printf("Usage: ipaccess-config IP_OF_BTS\n");
+}
+
+static void print_help(void)
+{
+#if 0
+ printf("Commands for reading from the BTS:\n");
+ printf(" -D --dump\t\t\tDump the BTS configuration\n");
+ printf("\n");
+#endif
+ printf("Commands for writing to the BTS:\n");
+ printf(" -u --unit-id UNIT_ID\t\tSet the Unit ID of the BTS\n");
+ printf(" -o --oml-ip IP\t\tSet primary OML IP (IP of your BSC)\n");
+ printf(" -G --get-attr\t\t\tGet several attributes from BTS\n");
+ printf(" -i --ip-address IP/MASK\tSet static IP address + netmask of BTS\n");
+ printf(" -g --ip-gateway IP\t\tSet static IP gateway of BTS\n");
+ printf(" -r --restart\t\t\tRestart the BTS (after other operations)\n");
+ printf(" -n --nvram-flags FLAGS/MASK\tSet NVRAM attributes\n");
+ printf(" -S --nvattr-set FLAG\t\tSet one additional NVRAM attribute\n");
+ printf(" -U --nvattr-unset FLAG\tSet one additional NVRAM attribute\n");
+ printf(" -l --listen TESTNR\t\tPerform specified test number\n");
+ printf(" -L --Listen TEST_NAME\t\tPerform specified test\n");
+ printf(" -s --stream-id ID\t\tSet the IPA Stream Identifier for OML\n");
+ printf(" -d --software FIRMWARE\tDownload firmware into BTS\n");
+ printf("\n");
+ printf("Miscellaneous commands:\n");
+ printf(" -h --help\t\t\tthis text\n");
+ printf(" -H --HELP\t\t\tPrint parameter details.\n");
+ printf(" -f --firmware FIRMWARE\tProvide firmware information\n");
+ printf(" -w --write-firmware\t\tThis will dump the firmware parts to the filesystem. Use with -f.\n");
+ printf(" -p --loop\t\t\tLoop the tests executed with the --listen command.\n");
+ printf(" -q --quiet\t\t\tAvoid printing unformatted logging to stdout, useful with -G.\n");
+}
+
+static void print_value_string(const struct value_string *val, int size)
+{
+ int i;
+
+ for (i = 0; i < size - 1; ++i) {
+ char sep = val[i + 1].str == NULL ? '.' : ',';
+ printf("%s%c ", val[i].str, sep);
+ }
+ printf("\n");
+}
+
+static void print_options(void)
+{
+
+ printf("Options for NVRAM (-S,-U):\n ");
+ print_value_string(&ipa_nvflag_strs[0], ARRAY_SIZE(ipa_nvflag_strs));
+
+ printf("Options for Tests (-L):\n ");
+ print_value_string(&ipa_test_strs[0], ARRAY_SIZE(ipa_test_strs));
+}
+
+static const struct log_info_cat log_categories[] = {
+ [DNM] = {
+ .name = "DNM",
+ .description = "A-bis Network Management / O&M (NM/OML)",
+ .color = "\033[1;36m",
+ .loglevel = LOGL_DEBUG,
+ .enabled = 1,
+ },
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ struct gsm_bts *bts;
+ struct sockaddr_in sin;
+ char *bts_ip;
+ 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);
+ bts_model_nanobts_init();
+
+ while (1) {
+ int c;
+ unsigned long ul;
+ char *slash;
+ static struct option long_options[] = {
+ { "get-attr", 0, 0, 'G' },
+ { "unit-id", 1, 0, 'u' },
+ { "oml-ip", 1, 0, 'o' },
+ { "ip-address", 1, 0, 'i' },
+ { "ip-gateway", 1, 0, 'g' },
+ { "restart", 0, 0, 'r' },
+ { "nvram-flags", 1, 0, 'n' },
+ { "nvattr-set", 1, 0, 'S' },
+ { "nvattr-unset", 1, 0, 'U' },
+ { "help", 0, 0, 'h' },
+ { "HELP", 0, 0, 'H' },
+ { "listen", 1, 0, 'l' },
+ { "Listen", 1, 0, 'L' },
+ { "stream-id", 1, 0, 's' },
+ { "software", 1, 0, 'd' },
+ { "firmware", 1, 0, 'f' },
+ { "write-firmware", 0, 0, 'w' },
+ { "disable-color", 0, 0, 'c'},
+ { "loop", 0, 0, 'p' },
+ { "quiet", 0, 0, 'q' },
+ { 0, 0, 0, 0 },
+ };
+
+ c = getopt_long(argc, argv, "Gu:o:i:g:rn:S:U:l:L:hs:d:f:wcpqH", long_options,
+ &option_index);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'G':
+ get_attr = true;
+ break;
+ case 'u':
+ if (!check_unitid_fmt(optarg))
+ exit(2);
+ unit_id = optarg;
+ break;
+ case 'o':
+ prim_oml_ip = optarg;
+ break;
+ case 'i':
+ slash = strchr(optarg, '/');
+ if (!slash)
+ exit(2);
+ bts_ip_addr = optarg;
+ *slash = 0;
+ bts_ip_mask = slash+1;
+ break;
+ case 'g':
+ bts_ip_gw = optarg;
+ break;
+ case 'r':
+ restart = 1;
+ break;
+ case 'n':
+ slash = strchr(optarg, '/');
+ if (!slash)
+ exit(2);
+ ul = strtoul(optarg, NULL, 16);
+ nv_flags = ul & 0xffff;
+ ul = strtoul(slash+1, NULL, 16);
+ nv_mask = ul & 0xffff;
+ break;
+ case 'S':
+ if (ipa_nvflag_set(&nv_flags, &nv_mask, optarg, 1) < 0)
+ exit(2);
+ break;
+ case 'U':
+ if (ipa_nvflag_set(&nv_flags, &nv_mask, optarg, 0) < 0)
+ exit(2);
+ break;
+ case 'l':
+ net_listen_testnr = atoi(optarg);
+ break;
+ case 'L':
+ net_listen_testnr = get_string_value(ipa_test_strs,
+ optarg);
+ if (net_listen_testnr < 0) {
+ fprintf(stderr,
+ "The test '%s' is not known. Use -H to"
+ " see available tests.\n", optarg);
+ exit(2);
+ }
+ break;
+ case 's':
+ stream_id = atoi(optarg);
+ break;
+ case 'd':
+ software = strdup(optarg);
+ if (find_sw_load_params(optarg) != 0)
+ exit(0);
+ break;
+ case 'f':
+ firmware_analysis = optarg;
+ break;
+ case 'w':
+ dump_files = 1;
+ break;
+ case 'c':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'p':
+ loop_tests = 1;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'h':
+ print_usage();
+ print_help();
+ exit(0);
+ case 'H':
+ print_options();
+ exit(0);
+ }
+ };
+
+ if (!quiet) {
+ printf("ipaccess-config (C) 2009-2010 by Harald Welte and others\n");
+ printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+ }
+
+ if (firmware_analysis) {
+ analyze_firmware(firmware_analysis);
+ if (argc == optind) /* Nothing more to do, exit successfully */
+ exit(EXIT_SUCCESS);
+ }
+ if (argc - optind != 1) {
+ fprintf(stderr, "you have to specify the IP address of the BTS. Use --help for more information\n");
+ exit(2);
+ }
+ bts_ip = argv[optind++];
+
+ libosmo_abis_init(tall_ctx_config);
+
+ bsc_gsmnet = gsm_network_init(tall_ctx_config);
+ if (!bsc_gsmnet)
+ exit(1);
+
+ bts = gsm_bts_alloc_register(bsc_gsmnet, GSM_BTS_TYPE_NANOBTS,
+ HARDCODED_BSIC);
+ /* ip.access supports up to 4 chained TRX */
+ gsm_bts_trx_alloc(bts);
+ gsm_bts_trx_alloc(bts);
+ gsm_bts_trx_alloc(bts);
+ bts->oml_tei = stream_id;
+
+ osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
+ osmo_signal_register_handler(SS_IPAC_NWL, nwl_sig_cb, NULL);
+
+ ipac_nwl_init();
+
+ if (!quiet)
+ printf("Trying to connect to ip.access BTS %s...\n", bts_ip);
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ inet_aton(bts_ip, &sin.sin_addr);
+ rc = ia_config_connect(bts, &sin);
+ if (rc < 0) {
+ perror("Error connecting to the BTS");
+ exit(1);
+ }
+
+ bts->oml_link->ts->sign.delay = 10;
+ bts->c0->rsl_link->ts->sign.delay = 10;
+ while (1) {
+ rc = osmo_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+
+ exit(0);
+}
+
+/* Stub */
+int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ return 0;
+}
+
+/* Stub */
+int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ return 0;
+}
diff --git a/src/ipaccess/ipaccess-firmware.c b/src/ipaccess/ipaccess-firmware.c
new file mode 100644
index 000000000..515cc75f4
--- /dev/null
+++ b/src/ipaccess/ipaccess-firmware.c
@@ -0,0 +1,135 @@
+/* Routines for parsing an ipacces SDP firmware file */
+
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/core/talloc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PART_LENGTH 138
+
+osmo_static_assert(sizeof(struct sdp_header_entry) == 138, right_entry);
+osmo_static_assert(sizeof(struct sdp_firmware) == 158, _right_header_length);
+
+/* more magic, the second "int" in the header */
+static char more_magic[] = { 0x10, 0x02 };
+
+int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned int base_offset, struct llist_head *list)
+{
+ struct sdp_firmware *firmware_header = 0;
+ struct sdp_header *header;
+ char buf[4096];
+ int rc, i;
+ uint16_t table_size;
+ uint16_t table_offset;
+ off_t table_start;
+
+
+ rc = read(fd, buf, sizeof(*firmware_header));
+ if (rc < 0) {
+ perror("Can not read header start.");
+ return -1;
+ }
+
+ firmware_header = (struct sdp_firmware *) &buf[0];
+ if (strncmp(firmware_header->magic, " SDP", 4) != 0) {
+ fprintf(stderr, "Wrong magic.\n");
+ return -1;
+ }
+
+ if (memcmp(firmware_header->more_magic, more_magic, 2) != 0) {
+ fprintf(stderr, "Wrong more magic. Got: 0x%x 0x%x vs. 0x%x 0x%x\n",
+ firmware_header->more_magic[0] & 0xff, firmware_header->more_magic[1] & 0xff,
+ more_magic[0], more_magic[1]);
+ return -1;
+ }
+
+
+ if (ntohl(firmware_header->file_length) != st_size) {
+ fprintf(stderr, "The filesize and the header do not match.\n");
+ return -1;
+ }
+
+ /* add the firmware */
+ header = talloc_zero(list, struct sdp_header);
+ header->firmware_info = *firmware_header;
+ INIT_LLIST_HEAD(&header->header_list);
+ llist_add(&header->entry, list);
+
+ table_offset = ntohs(firmware_header->table_offset);
+ table_start = lseek(fd, table_offset, SEEK_CUR);
+ if (table_start == -1) {
+ fprintf(stderr, "Failed to seek to the rel position: 0x%x\n", table_offset);
+ return -1;
+ }
+
+ if (read(fd, &table_size, sizeof(table_size)) != sizeof(table_size)) {
+ fprintf(stderr, "The table size could not be read.\n");
+ return -1;
+ }
+
+ table_size = ntohs(table_size);
+
+ if (table_size % PART_LENGTH != 0) {
+ fprintf(stderr, "The part length seems to be wrong: 0x%x\n", table_size);
+ return -1;
+ }
+
+ /* look into each firmware now */
+ for (i = 0; i < table_size / PART_LENGTH; ++i) {
+ struct sdp_header_entry entry;
+ struct sdp_header_item *header_entry;
+ unsigned int offset = table_start + 2;
+ offset += i * 138;
+
+ if (lseek(fd, offset, SEEK_SET) != offset) {
+ fprintf(stderr, "Can not seek to the offset: %u.\n", offset);
+ return -1;
+ }
+
+ rc = read(fd, &entry, sizeof(entry));
+ if (rc != sizeof(entry)) {
+ fprintf(stderr, "Can not read the header entry.\n");
+ return -1;
+ }
+
+ header_entry = talloc_zero(header, struct sdp_header_item);
+ header_entry->header_entry = entry;
+ header_entry->absolute_offset = base_offset;
+ llist_add(&header_entry->entry, &header->header_list);
+
+ /* now we need to find the SDP file... */
+ offset = ntohl(entry.start) + 4 + base_offset;
+ if (lseek(fd, offset, SEEK_SET) != offset) {
+ perror("can't seek to sdp");
+ return -1;
+ }
+
+
+ ipaccess_analyze_file(fd, ntohl(entry.length), offset, list);
+ }
+
+ return 0;
+}
+
diff --git a/src/ipaccess/ipaccess-proxy.c b/src/ipaccess/ipaccess-proxy.c
new file mode 100644
index 000000000..26c5bcd92
--- /dev/null
+++ b/src/ipaccess/ipaccess-proxy.c
@@ -0,0 +1,1250 @@
+/* OpenBSC Abis/IP proxy ip.access nanoBTS */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/abis/ipa.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+
+/* one instance of an ip.access protocol proxy */
+struct ipa_proxy {
+ /* socket where we listen for incoming OML from BTS */
+ struct osmo_fd oml_listen_fd;
+ /* socket where we listen for incoming RSL from BTS */
+ struct osmo_fd rsl_listen_fd;
+ /* list of BTS's (struct ipa_bts_conn */
+ struct llist_head bts_list;
+ /* the BSC reconnect timer */
+ struct osmo_timer_list reconn_timer;
+ /* global GPRS NS data */
+ struct in_addr gprs_addr;
+ struct in_addr listen_addr;
+};
+
+/* global pointer to the proxy structure */
+static struct ipa_proxy *ipp;
+
+struct ipa_proxy_conn {
+ struct osmo_fd fd;
+ struct llist_head tx_queue;
+ struct ipa_bts_conn *bts_conn;
+};
+#define MAX_TRX 4
+
+/* represents a particular BTS in our proxy */
+struct ipa_bts_conn {
+ /* list of BTS's (ipa_proxy->bts_list) */
+ struct llist_head list;
+ /* back pointer to the proxy which we belong to */
+ struct ipa_proxy *ipp;
+ /* the unit ID as determined by CCM */
+ struct {
+ uint16_t site_id;
+ uint16_t bts_id;
+ } unit_id;
+
+ /* incoming connections from BTS */
+ struct ipa_proxy_conn *oml_conn;
+ struct ipa_proxy_conn *rsl_conn[MAX_TRX];
+
+ /* outgoing connections to BSC */
+ struct ipa_proxy_conn *bsc_oml_conn;
+ struct ipa_proxy_conn *bsc_rsl_conn[MAX_TRX];
+
+ /* UDP sockets for BTS and BSC injection */
+ struct osmo_fd udp_bts_fd;
+ struct osmo_fd udp_bsc_fd;
+
+ /* NS data */
+ struct in_addr bts_addr;
+ struct osmo_fd gprs_ns_fd;
+ int gprs_local_port;
+ uint16_t gprs_orig_port;
+ uint32_t gprs_orig_ip;
+
+ char *id_tags[256];
+ uint8_t *id_resp;
+ unsigned int id_resp_len;
+};
+
+enum ipp_fd_type {
+ OML_FROM_BTS = 1,
+ RSL_FROM_BTS = 2,
+ OML_TO_BSC = 3,
+ RSL_TO_BSC = 4,
+ UDP_TO_BTS = 5,
+ UDP_TO_BSC = 6,
+};
+
+extern void *tall_bsc_ctx;
+
+static char *listen_ipaddr;
+static char *bsc_ipaddr;
+static char *gprs_ns_ipaddr;
+
+static int gprs_ns_cb(struct osmo_fd *bfd, unsigned int what);
+
+#define PROXY_ALLOC_SIZE 1200
+
+static struct ipa_bts_conn *find_bts_by_unitid(struct ipa_proxy *ipp,
+ uint16_t site_id,
+ uint16_t bts_id)
+{
+ struct ipa_bts_conn *ipbc;
+
+ llist_for_each_entry(ipbc, &ipp->bts_list, list) {
+ if (ipbc->unit_id.site_id == site_id &&
+ ipbc->unit_id.bts_id == bts_id)
+ return ipbc;
+ }
+
+ return NULL;
+}
+
+struct ipa_proxy_conn *alloc_conn(void)
+{
+ struct ipa_proxy_conn *ipc;
+
+ ipc = talloc_zero(tall_bsc_ctx, struct ipa_proxy_conn);
+ if (!ipc)
+ return NULL;
+
+ INIT_LLIST_HEAD(&ipc->tx_queue);
+
+ return ipc;
+}
+
+static int store_idtags(struct ipa_bts_conn *ipbc, struct tlv_parsed *tlvp)
+{
+ unsigned int i, len;
+
+ for (i = 0; i <= 0xff; i++) {
+ if (!TLVP_PRESENT(tlvp, i))
+ continue;
+
+ len = TLVP_LEN(tlvp, i);
+#if 0
+ if (!ipbc->id_tags[i])
+ ipbc->id_tags[i] = talloc_size(tall_bsc_ctx, len);
+ else
+#endif
+ ipbc->id_tags[i] = talloc_realloc_size(ipbc,
+ ipbc->id_tags[i], len);
+ if (!ipbc->id_tags[i])
+ return -ENOMEM;
+
+ memset(ipbc->id_tags[i], 0, len);
+ //memcpy(ipbc->id_tags[i], TLVP_VAL(tlvp, i), len);
+ }
+ return 0;
+}
+
+
+static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data);
+
+#define logp_ipbc_uid(ss, lvl, ipbc, trx_id) _logp_ipbc_uid(ss, lvl, __FILE__, __LINE__, ipbc, trx_id)
+
+static void _logp_ipbc_uid(unsigned int ss, unsigned int lvl, char *file, int line,
+ struct ipa_bts_conn *ipbc, uint8_t trx_id)
+{
+ if (ipbc)
+ logp2(ss, lvl, file, line, 0, "(%u/%u/%u) ", ipbc->unit_id.site_id,
+ ipbc->unit_id.bts_id, trx_id);
+ else
+ logp2(ss, lvl, file, line, 0, "unknown ");
+}
+
+static int handle_udp_read(struct osmo_fd *bfd)
+{
+ struct ipa_bts_conn *ipbc = bfd->data;
+ struct ipa_proxy_conn *other_conn = NULL;
+ struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP UDP");
+ struct ipaccess_head *hh;
+ int ret;
+
+ /* with UDP sockets, we cannot read partial packets but have to read
+ * all of it in one go */
+ hh = (struct ipaccess_head *) msg->data;
+ ret = recv(bfd->fd, msg->data, msg->data_len, 0);
+ if (ret < 0) {
+ if (errno != EAGAIN)
+ LOGP(DLINP, LOGL_ERROR, "recv error %s\n", strerror(errno));
+ msgb_free(msg);
+ return ret;
+ }
+ if (ret == 0) {
+ DEBUGP(DLINP, "UDP peer disappeared, dead socket\n");
+ osmo_fd_unregister(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+ msgb_free(msg);
+ return -EIO;
+ }
+ if (ret < sizeof(*hh)) {
+ DEBUGP(DLINP, "could not even read header!?!\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msgb_put(msg, ret);
+ msg->l2h = msg->data + sizeof(*hh);
+ DEBUGP(DLMI, "UDP RX: %s\n", osmo_hexdump(msg->data, msg->len));
+
+ if (hh->len != msg->len - sizeof(*hh)) {
+ DEBUGP(DLINP, "length (%u/%u) disagrees with header(%u)\n",
+ msg->len, msg->len - 3, hh->len);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ switch (bfd->priv_nr & 0xff) {
+ case UDP_TO_BTS:
+ /* injection towards BTS */
+ switch (hh->proto) {
+ case IPAC_PROTO_RSL:
+ /* FIXME: what to do about TRX > 0 */
+ other_conn = ipbc->rsl_conn[0];
+ break;
+ default:
+ DEBUGP(DLINP, "Unknown protocol 0x%02x, sending to "
+ "OML FD\n", hh->proto);
+ /* fall through */
+ case IPAC_PROTO_IPACCESS:
+ case IPAC_PROTO_OML:
+ other_conn = ipbc->oml_conn;
+ break;
+ }
+ break;
+ case UDP_TO_BSC:
+ /* injection towards BSC */
+ switch (hh->proto) {
+ case IPAC_PROTO_RSL:
+ /* FIXME: what to do about TRX > 0 */
+ other_conn = ipbc->bsc_rsl_conn[0];
+ break;
+ default:
+ DEBUGP(DLINP, "Unknown protocol 0x%02x, sending to "
+ "OML FD\n", hh->proto);
+ /* fall through */
+ case IPAC_PROTO_IPACCESS:
+ case IPAC_PROTO_OML:
+ other_conn = ipbc->bsc_oml_conn;
+ break;
+ }
+ break;
+ default:
+ DEBUGP(DLINP, "Unknown filedescriptor priv_nr=%04x\n", bfd->priv_nr);
+ break;
+ }
+
+ if (other_conn) {
+ /* enqueue the message for TX on the respective FD */
+ msgb_enqueue(&other_conn->tx_queue, msg);
+ other_conn->fd.when |= BSC_FD_WRITE;
+ } else
+ msgb_free(msg);
+
+ return 0;
+}
+
+static int handle_udp_write(struct osmo_fd *bfd)
+{
+ /* not implemented yet */
+ bfd->when &= ~BSC_FD_WRITE;
+
+ return -EIO;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int udp_fd_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ int rc = 0;
+
+ if (what & BSC_FD_READ)
+ rc = handle_udp_read(bfd);
+ if (what & BSC_FD_WRITE)
+ rc = handle_udp_write(bfd);
+
+ return rc;
+}
+
+
+static int ipbc_alloc_connect(struct ipa_proxy_conn *ipc, struct osmo_fd *bfd,
+ uint16_t site_id, uint16_t bts_id,
+ uint16_t trx_id, struct tlv_parsed *tlvp,
+ struct msgb *msg)
+{
+ struct ipa_bts_conn *ipbc;
+ uint16_t udp_port;
+ int ret = 0;
+ struct sockaddr_in sin;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+ DEBUGP(DLINP, "(%u/%u/%u) New BTS connection: ",
+ site_id, bts_id, trx_id);
+
+ /* OML needs to be established before RSL */
+ if ((bfd->priv_nr & 0xff) != OML_FROM_BTS) {
+ DEBUGPC(DLINP, "Not a OML connection ?!?\n");
+ return -EIO;
+ }
+
+ /* allocate new BTS connection data structure */
+ ipbc = talloc_zero(tall_bsc_ctx, struct ipa_bts_conn);
+ if (!ipbc) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ DEBUGPC(DLINP, "Created BTS Conn data structure\n");
+ ipbc->ipp = ipp;
+ ipbc->unit_id.site_id = site_id;
+ ipbc->unit_id.bts_id = bts_id;
+ ipbc->oml_conn = ipc;
+ ipc->bts_conn = ipbc;
+
+ /* store the content of the ID TAGS for later reference */
+ store_idtags(ipbc, tlvp);
+ ipbc->id_resp_len = msg->len;
+ ipbc->id_resp = talloc_size(tall_bsc_ctx, ipbc->id_resp_len);
+ memcpy(ipbc->id_resp, msg->data, ipbc->id_resp_len);
+
+ /* Create OML TCP connection towards BSC */
+ sin.sin_port = htons(IPA_TCP_PORT_OML);
+ ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc);
+ if (!ipbc->bsc_oml_conn) {
+ ret = -EIO;
+ goto err_bsc_conn;
+ }
+
+ DEBUGP(DLINP, "(%u/%u/%u) OML Connected to BSC\n",
+ site_id, bts_id, trx_id);
+
+ /* Create UDP socket for BTS packet injection */
+ udp_port = 10000 + (site_id % 1000)*100 + (bts_id % 100);
+ ipbc->udp_bts_fd.priv_nr = UDP_TO_BTS;
+ ipbc->udp_bts_fd.cb = udp_fd_cb;
+ ipbc->udp_bts_fd.data = ipbc;
+ ret = osmo_sock_init_ofd(&ipbc->udp_bts_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ NULL, udp_port, OSMO_SOCK_F_BIND);
+ if (ret < 0)
+ goto err_udp_bts;
+ DEBUGP(DLINP, "(%u/%u/%u) Created UDP socket for injection "
+ "towards BTS at port %u\n", site_id, bts_id, trx_id, udp_port);
+
+ /* Create UDP socket for BSC packet injection */
+ udp_port = 20000 + (site_id % 1000)*100 + (bts_id % 100);
+ ipbc->udp_bsc_fd.priv_nr = UDP_TO_BSC;
+ ipbc->udp_bsc_fd.cb = udp_fd_cb;
+ ipbc->udp_bsc_fd.data = ipbc;
+ ret = osmo_sock_init_ofd(&ipbc->udp_bsc_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ NULL, udp_port, OSMO_SOCK_F_BIND);
+ if (ret < 0)
+ goto err_udp_bsc;
+ DEBUGP(DLINP, "(%u/%u/%u) Created UDP socket for injection "
+ "towards BSC at port %u\n", site_id, bts_id, trx_id, udp_port);
+
+
+ /* GPRS NS related code */
+ if (gprs_ns_ipaddr) {
+ struct sockaddr_in sock;
+ socklen_t len = sizeof(sock);
+
+ ipbc->gprs_ns_fd.priv_nr = 0;
+ ipbc->gprs_ns_fd.cb = gprs_ns_cb;
+ ipbc->gprs_ns_fd.data = ipbc;
+ ret = osmo_sock_init_ofd(&ipbc->gprs_ns_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ listen_ipaddr, 0, OSMO_SOCK_F_BIND);
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Creating the GPRS socket failed.\n");
+ goto err_udp_bsc;
+ }
+
+ ret = getsockname(ipbc->gprs_ns_fd.fd, (struct sockaddr* ) &sock, &len);
+ ipbc->gprs_local_port = ntohs(sock.sin_port);
+ LOGP(DLINP, LOGL_NOTICE,
+ "Created GPRS NS Socket. Listening on: %s:%d\n",
+ inet_ntoa(sock.sin_addr), ipbc->gprs_local_port);
+
+ ret = getpeername(bfd->fd, (struct sockaddr* ) &sock, &len);
+ ipbc->bts_addr = sock.sin_addr;
+ }
+
+ llist_add(&ipbc->list, &ipp->bts_list);
+
+ return 0;
+
+err_udp_bsc:
+ osmo_fd_unregister(&ipbc->udp_bts_fd);
+err_udp_bts:
+ osmo_fd_unregister(&ipbc->bsc_oml_conn->fd);
+ close(ipbc->bsc_oml_conn->fd.fd);
+ talloc_free(ipbc->bsc_oml_conn);
+ ipbc->bsc_oml_conn = NULL;
+err_bsc_conn:
+ talloc_free(ipbc->id_resp);
+ talloc_free(ipbc);
+#if 0
+ osmo_fd_unregister(bfd);
+ close(bfd->fd);
+ talloc_free(bfd);
+#endif
+err_out:
+ return ret;
+}
+
+static int ipaccess_rcvmsg(struct ipa_proxy_conn *ipc, struct msgb *msg,
+ struct osmo_fd *bfd)
+{
+ struct tlv_parsed tlvp;
+ uint8_t msg_type = *(msg->l2h);
+ struct ipaccess_unit unit_data;
+ struct ipa_bts_conn *ipbc;
+ int ret = 0;
+
+ switch (msg_type) {
+ case IPAC_MSGT_PING:
+ ret = ipa_ccm_send_pong(bfd->fd);
+ break;
+ case IPAC_MSGT_PONG:
+ DEBUGP(DLMI, "PONG!\n");
+ break;
+ case IPAC_MSGT_ID_RESP:
+ DEBUGP(DLMI, "ID_RESP ");
+ /* parse tags, search for Unit ID */
+ ret = ipa_ccm_id_resp_parse(&tlvp, (uint8_t *)msg->l2h+1, msgb_l2len(msg)-1);
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Error parsing CCM ID RESP !?!\n");
+ return -EIO;
+ }
+ DEBUGP(DLMI, "\n");
+
+ if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT)) {
+ LOGP(DLINP, LOGL_ERROR, "No Unit ID in ID RESPONSE !?!\n");
+ return -EIO;
+ }
+
+ /* lookup BTS, create sign_link, ... */
+ memset(&unit_data, 0, sizeof(unit_data));
+ ipa_parse_unitid((char *)TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT),
+ &unit_data);
+ ipbc = find_bts_by_unitid(ipp, unit_data.site_id, unit_data.bts_id);
+ if (!ipbc) {
+ /* We have not found an ipbc (per-bts proxy instance)
+ * for this BTS yet. The first connection of a new BTS must
+ * be a OML connection. We allocate the associated data structures,
+ * and try to connect to the remote end */
+
+ return ipbc_alloc_connect(ipc, bfd, unit_data.site_id,
+ unit_data.bts_id,
+ unit_data.trx_id, &tlvp, msg);
+ /* if this fails, the caller will clean up bfd */
+ } else {
+ struct sockaddr_in sin;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+ DEBUGP(DLINP, "Identified BTS %u/%u/%u\n",
+ unit_data.site_id, unit_data.bts_id, unit_data.trx_id);
+
+ if ((bfd->priv_nr & 0xff) != RSL_FROM_BTS) {
+ LOGP(DLINP, LOGL_ERROR, "Second OML connection from "
+ "same BTS ?!?\n");
+ return 0;
+ }
+
+ if (unit_data.trx_id >= MAX_TRX) {
+ LOGP(DLINP, LOGL_ERROR, "We don't support more "
+ "than %u TRX\n", MAX_TRX);
+ return -EINVAL;
+ }
+
+ ipc->bts_conn = ipbc;
+ /* store TRX number in higher 8 bit of the bfd private number */
+ bfd->priv_nr |= unit_data.trx_id << 8;
+ ipbc->rsl_conn[unit_data.trx_id] = ipc;
+
+ /* Create RSL TCP connection towards BSC */
+ sin.sin_port = htons(IPA_TCP_PORT_RSL);
+ ipbc->bsc_rsl_conn[unit_data.trx_id] =
+ connect_bsc(&sin, RSL_TO_BSC | (unit_data.trx_id << 8), ipbc);
+ if (!ipbc->bsc_oml_conn)
+ return -EIO;
+ DEBUGP(DLINP, "(%u/%u/%u) Connected RSL to BSC\n",
+ unit_data.site_id, unit_data.bts_id, unit_data.trx_id);
+ }
+ break;
+ case IPAC_MSGT_ID_GET:
+ DEBUGP(DLMI, "ID_GET\n");
+ if ((bfd->priv_nr & 0xff) != OML_TO_BSC &&
+ (bfd->priv_nr & 0xff) != RSL_TO_BSC) {
+ DEBUGP(DLINP, "IDentity REQuest from BTS ?!?\n");
+ return -EIO;
+ }
+ ipbc = ipc->bts_conn;
+ if (!ipbc) {
+ DEBUGP(DLINP, "ID_GET from BSC before we have ID_RESP from BTS\n");
+ return -EIO;
+ }
+ ret = write(bfd->fd, ipbc->id_resp, ipbc->id_resp_len);
+ if (ret != ipbc->id_resp_len) {
+ LOGP(DLINP, LOGL_ERROR, "Partial write: %d of %d\n",
+ ret, ipbc->id_resp_len);
+ return -EIO;
+ }
+ ret = 0;
+ break;
+ case IPAC_MSGT_ID_ACK:
+ DEBUGP(DLMI, "ID_ACK? -> ACK!\n");
+ ret = ipa_ccm_send_id_ack(bfd->fd);
+ break;
+ default:
+ LOGP(DLMI, LOGL_ERROR, "Unhandled IPA type; %d\n", msg_type);
+ return 1;
+ break;
+ }
+ return ret;
+}
+
+struct msgb *ipaccess_proxy_read_msg(struct osmo_fd *bfd, int *error)
+{
+ struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP");
+ struct ipaccess_head *hh;
+ int len, ret = 0;
+
+ if (!msg) {
+ *error = -ENOMEM;
+ return NULL;
+ }
+
+ /* first read our 3-byte header */
+ hh = (struct ipaccess_head *) msg->data;
+ ret = recv(bfd->fd, msg->data, 3, 0);
+ if (ret < 0) {
+ if (errno != EAGAIN)
+ LOGP(DLINP, LOGL_ERROR, "recv error: %s\n", strerror(errno));
+ msgb_free(msg);
+ *error = ret;
+ return NULL;
+ } else if (ret == 0) {
+ msgb_free(msg);
+ *error = ret;
+ return NULL;
+ }
+
+ msgb_put(msg, ret);
+
+ /* then read te length as specified in header */
+ msg->l2h = msg->data + sizeof(*hh);
+ len = ntohs(hh->len);
+ ret = recv(bfd->fd, msg->l2h, len, 0);
+ if (ret < len) {
+ LOGP(DLINP, LOGL_ERROR, "short read!\n");
+ msgb_free(msg);
+ *error = -EIO;
+ return NULL;
+ }
+ msgb_put(msg, ret);
+
+ return msg;
+}
+
+static struct ipa_proxy_conn *ipc_by_priv_nr(struct ipa_bts_conn *ipbc,
+ unsigned int priv_nr)
+{
+ struct ipa_proxy_conn *bsc_conn;
+ unsigned int trx_id = priv_nr >> 8;
+
+ switch (priv_nr & 0xff) {
+ case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */
+ bsc_conn = ipbc->bsc_oml_conn;
+ break;
+ case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */
+ bsc_conn = ipbc->bsc_rsl_conn[trx_id];
+ break;
+ case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */
+ bsc_conn = ipbc->oml_conn;
+ break;
+ case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */
+ bsc_conn = ipbc->rsl_conn[trx_id];
+ break;
+ default:
+ bsc_conn = NULL;
+ break;
+ }
+ return bsc_conn;
+}
+
+static void reconn_tmr_cb(void *data)
+{
+ struct ipa_proxy *ipp = data;
+ struct ipa_bts_conn *ipbc;
+ struct sockaddr_in sin;
+ int i;
+
+ DEBUGP(DLINP, "Running reconnect timer\n");
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+ llist_for_each_entry(ipbc, &ipp->bts_list, list) {
+ /* if OML to BSC is dead, try to restore it */
+ if (ipbc->oml_conn && !ipbc->bsc_oml_conn) {
+ sin.sin_port = htons(IPA_TCP_PORT_OML);
+ logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, 0);
+ LOGPC(DLINP, LOGL_NOTICE, "OML Trying to reconnect\n");
+ ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc);
+ if (!ipbc->bsc_oml_conn)
+ goto reschedule;
+ logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, 0);
+ LOGPC(DLINP, LOGL_NOTICE, "OML Reconnected\n");
+ }
+ /* if we (still) don't have a OML connection, skip RSL */
+ if (!ipbc->oml_conn || !ipbc->bsc_oml_conn)
+ continue;
+
+ for (i = 0; i < ARRAY_SIZE(ipbc->rsl_conn); i++) {
+ unsigned int priv_nr;
+ /* don't establish RSL links which we don't have */
+ if (!ipbc->rsl_conn[i])
+ continue;
+ if (ipbc->bsc_rsl_conn[i])
+ continue;
+ priv_nr = ipbc->rsl_conn[i]->fd.priv_nr;
+ priv_nr &= ~0xff;
+ priv_nr |= RSL_TO_BSC;
+ sin.sin_port = htons(IPA_TCP_PORT_RSL);
+ logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+ LOGPC(DLINP, LOGL_NOTICE, "RSL Trying to reconnect\n");
+ ipbc->bsc_rsl_conn[i] = connect_bsc(&sin, priv_nr, ipbc);
+ if (!ipbc->bsc_rsl_conn[i])
+ goto reschedule;
+ logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+ LOGPC(DLINP, LOGL_NOTICE, "RSL Reconnected\n");
+ }
+ }
+ return;
+
+reschedule:
+ osmo_timer_schedule(&ipp->reconn_timer, 5, 0);
+}
+
+static void handle_dead_socket(struct osmo_fd *bfd)
+{
+ struct ipa_proxy_conn *ipc = bfd->data; /* local conn */
+ struct ipa_proxy_conn *bsc_conn; /* remote conn */
+ struct ipa_bts_conn *ipbc = ipc->bts_conn;
+ unsigned int trx_id = bfd->priv_nr >> 8;
+ struct msgb *msg, *msg2;
+
+ osmo_fd_unregister(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+
+ /* FIXME: clear tx_queue, remove all references, etc. */
+ llist_for_each_entry_safe(msg, msg2, &ipc->tx_queue, list)
+ msgb_free(msg);
+
+ switch (bfd->priv_nr & 0xff) {
+ case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */
+ /* The BTS started a connection with us but we got no
+ * IPAC_MSGT_ID_RESP message yet, in that scenario we did not
+ * allocate the ipa_bts_conn structure. */
+ if (ipbc == NULL)
+ break;
+ ipbc->oml_conn = NULL;
+ bsc_conn = ipbc->bsc_oml_conn;
+ /* close the connection to the BSC */
+ osmo_fd_unregister(&bsc_conn->fd);
+ close(bsc_conn->fd.fd);
+ llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list)
+ msgb_free(msg);
+ talloc_free(bsc_conn);
+ ipbc->bsc_oml_conn = NULL;
+ /* FIXME: do we need to delete the entire ipbc ? */
+ break;
+ case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */
+ ipbc->rsl_conn[trx_id] = NULL;
+ bsc_conn = ipbc->bsc_rsl_conn[trx_id];
+ /* close the connection to the BSC */
+ osmo_fd_unregister(&bsc_conn->fd);
+ close(bsc_conn->fd.fd);
+ llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list)
+ msgb_free(msg);
+ talloc_free(bsc_conn);
+ ipbc->bsc_rsl_conn[trx_id] = NULL;
+ break;
+ case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */
+ ipbc->bsc_oml_conn = NULL;
+ bsc_conn = ipbc->oml_conn;
+ /* start reconnect timer */
+ osmo_timer_schedule(&ipp->reconn_timer, 5, 0);
+ break;
+ case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */
+ ipbc->bsc_rsl_conn[trx_id] = NULL;
+ bsc_conn = ipbc->rsl_conn[trx_id];
+ /* start reconnect timer */
+ osmo_timer_schedule(&ipp->reconn_timer, 5, 0);
+ break;
+ default:
+ bsc_conn = NULL;
+ break;
+ }
+
+ talloc_free(ipc);
+}
+
+static void patch_gprs_msg(struct ipa_bts_conn *ipbc, int priv_nr, struct msgb *msg)
+{
+ uint8_t *nsvci;
+
+ if ((priv_nr & 0xff) != OML_FROM_BTS && (priv_nr & 0xff) != OML_TO_BSC)
+ return;
+
+ if (msgb_l2len(msg) != 39)
+ return;
+
+ /*
+ * Check if this is a IPA Set Attribute or IPA Set Attribute ACK
+ * and if the FOM Class is GPRS NSVC0 and then we will patch it.
+ *
+ * The patch assumes the message looks like the one from the trace
+ * but we only match messages with a specific size anyway... So
+ * this hack should work just fine.
+ */
+
+ if (msg->l2h[0] == 0x10 && msg->l2h[1] == 0x80 &&
+ msg->l2h[2] == 0x00 && msg->l2h[3] == 0x15 &&
+ msg->l2h[18] == 0xf5 && msg->l2h[19] == 0xf2) {
+ nsvci = &msg->l2h[23];
+ ipbc->gprs_orig_port = *(uint16_t *)(nsvci+8);
+ ipbc->gprs_orig_ip = *(uint32_t *)(nsvci+10);
+ *(uint16_t *)(nsvci+8) = htons(ipbc->gprs_local_port);
+ *(uint32_t *)(nsvci+10) = ipbc->ipp->listen_addr.s_addr;
+ } else if (msg->l2h[0] == 0x10 && msg->l2h[1] == 0x80 &&
+ msg->l2h[2] == 0x00 && msg->l2h[3] == 0x15 &&
+ msg->l2h[18] == 0xf6 && msg->l2h[19] == 0xf2) {
+ nsvci = &msg->l2h[23];
+ *(uint16_t *)(nsvci+8) = ipbc->gprs_orig_port;
+ *(uint32_t *)(nsvci+10) = ipbc->gprs_orig_ip;
+ }
+}
+
+static int handle_tcp_read(struct osmo_fd *bfd)
+{
+ struct ipa_proxy_conn *ipc = bfd->data;
+ struct ipa_bts_conn *ipbc = ipc->bts_conn;
+ struct ipa_proxy_conn *bsc_conn;
+ struct msgb *msg;
+ struct ipaccess_head *hh;
+ int ret = 0;
+ char *btsbsc;
+
+ if ((bfd->priv_nr & 0xff) <= 2)
+ btsbsc = "BTS";
+ else
+ btsbsc = "BSC";
+
+ msg = ipaccess_proxy_read_msg(bfd, &ret);
+ if (!msg) {
+ if (ret == 0) {
+ logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+ LOGPC(DLINP, LOGL_NOTICE, "%s disappeared, "
+ "dead socket\n", btsbsc);
+ handle_dead_socket(bfd);
+ }
+ return ret;
+ }
+
+ msgb_put(msg, ret);
+ logp_ipbc_uid(DLMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+ DEBUGPC(DLMI, "RX<-%s: %s\n", btsbsc, osmo_hexdump(msg->data, msg->len));
+
+ hh = (struct ipaccess_head *) msg->data;
+ if (hh->proto == IPAC_PROTO_IPACCESS) {
+ ret = ipaccess_rcvmsg(ipc, msg, bfd);
+ if (ret < 0) {
+ osmo_fd_unregister(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+ talloc_free(bfd);
+ msgb_free(msg);
+ return ret;
+ } else if (ret == 0) {
+ /* we do not forward parts of the CCM protocol
+ * through the proxy but rather terminate it ourselves. */
+ msgb_free(msg);
+ return ret;
+ }
+ }
+
+ if (!ipbc) {
+ LOGP(DLINP, LOGL_ERROR,
+ "received %s packet but no ipc->bts_conn?!?\n", btsbsc);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ bsc_conn = ipc_by_priv_nr(ipbc, bfd->priv_nr);
+ if (bsc_conn) {
+ if (gprs_ns_ipaddr)
+ patch_gprs_msg(ipbc, bfd->priv_nr, msg);
+ /* enqueue packet towards BSC */
+ msgb_enqueue(&bsc_conn->tx_queue, msg);
+ /* mark respective filedescriptor as 'we want to write' */
+ bsc_conn->fd.when |= BSC_FD_WRITE;
+ } else {
+ logp_ipbc_uid(DLINP, LOGL_INFO, ipbc, bfd->priv_nr >> 8);
+ LOGPC(DLINP, LOGL_INFO, "Dropping packet from %s, "
+ "since remote connection is dead\n", btsbsc);
+ msgb_free(msg);
+ }
+
+ return ret;
+}
+
+/* a TCP socket is ready to be written to */
+static int handle_tcp_write(struct osmo_fd *bfd)
+{
+ struct ipa_proxy_conn *ipc = bfd->data;
+ struct ipa_bts_conn *ipbc = ipc->bts_conn;
+ struct llist_head *lh;
+ struct msgb *msg;
+ char *btsbsc;
+ int ret;
+
+ if ((bfd->priv_nr & 0xff) <= 2)
+ btsbsc = "BTS";
+ else
+ btsbsc = "BSC";
+
+
+ /* get the next msg for this timeslot */
+ if (llist_empty(&ipc->tx_queue)) {
+ bfd->when &= ~BSC_FD_WRITE;
+ return 0;
+ }
+ lh = ipc->tx_queue.next;
+ llist_del(lh);
+ msg = llist_entry(lh, struct msgb, list);
+
+ logp_ipbc_uid(DLMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+ DEBUGPC(DLMI, "TX %04x: %s\n", bfd->priv_nr,
+ osmo_hexdump(msg->data, msg->len));
+
+ ret = send(bfd->fd, msg->data, msg->len, 0);
+ msgb_free(msg);
+
+ if (ret == 0) {
+ logp_ipbc_uid(DLINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+ LOGP(DLINP, LOGL_NOTICE, "%s disappeared, dead socket\n", btsbsc);
+ handle_dead_socket(bfd);
+ }
+
+ return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int proxy_ipaccess_fd_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ int rc = 0;
+
+ if (what & BSC_FD_READ) {
+ rc = handle_tcp_read(bfd);
+ if (rc < 0)
+ return rc;
+ }
+ if (what & BSC_FD_WRITE)
+ rc = handle_tcp_write(bfd);
+
+ return rc;
+}
+
+/* callback of the listening filedescriptor */
+static int listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what)
+{
+ int ret;
+ struct ipa_proxy_conn *ipc;
+ struct osmo_fd *bfd;
+ struct sockaddr_in sa;
+ socklen_t sa_len = sizeof(sa);
+
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+ if (ret < 0) {
+ perror("accept");
+ return ret;
+ }
+ DEBUGP(DLINP, "accept()ed new %s link from %s\n",
+ (listen_bfd->priv_nr & 0xff) == OML_FROM_BTS ? "OML" : "RSL",
+ inet_ntoa(sa.sin_addr));
+
+ ipc = alloc_conn();
+ if (!ipc) {
+ close(ret);
+ return -ENOMEM;
+ }
+
+ bfd = &ipc->fd;
+ bfd->fd = ret;
+ bfd->data = ipc;
+ bfd->priv_nr = listen_bfd->priv_nr;
+ bfd->cb = proxy_ipaccess_fd_cb;
+ bfd->when = BSC_FD_READ;
+ ret = osmo_fd_register(bfd);
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "could not register FD\n");
+ close(bfd->fd);
+ talloc_free(ipc);
+ return ret;
+ }
+
+ /* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+ ret = ipa_ccm_send_id_req(bfd->fd);
+
+ return 0;
+}
+
+static void send_ns(int fd, const char *buf, int size, struct in_addr ip, int port)
+{
+ int ret;
+ struct sockaddr_in addr;
+ socklen_t len = sizeof(addr);
+ memset(&addr, 0, sizeof(addr));
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr = ip;
+
+ ret = sendto(fd, buf, size, 0, (struct sockaddr *) &addr, len);
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to forward GPRS message.\n");
+ }
+}
+
+static int gprs_ns_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ struct ipa_bts_conn *bts;
+ char buf[4096];
+ int ret;
+ struct sockaddr_in sock;
+ socklen_t len = sizeof(sock);
+
+ /* 1. get the data... */
+ ret = recvfrom(bfd->fd, buf, sizeof(buf), 0, (struct sockaddr *) &sock, &len);
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to recv GPRS NS msg: %s.\n", strerror(errno));
+ return -1;
+ }
+
+ bts = bfd->data;
+
+ /* 2. figure out where to send it to */
+ if (memcmp(&sock.sin_addr, &ipp->gprs_addr, sizeof(sock.sin_addr)) == 0) {
+ LOGP(DLINP, LOGL_DEBUG, "GPRS NS msg from network.\n");
+ send_ns(bfd->fd, buf, ret, bts->bts_addr, 23000);
+ } else if (memcmp(&sock.sin_addr, &bts->bts_addr, sizeof(sock.sin_addr)) == 0) {
+ LOGP(DLINP, LOGL_DEBUG, "GPRS NS msg from BTS.\n");
+ send_ns(bfd->fd, buf, ret, ipp->gprs_addr, 23000);
+ } else {
+ LOGP(DLINP, LOGL_ERROR, "Unknown GPRS source: %s\n", inet_ntoa(sock.sin_addr));
+ }
+
+ return 0;
+}
+
+/* Actively connect to a BSC. */
+static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data)
+{
+ struct ipa_proxy_conn *ipc;
+ struct osmo_fd *bfd;
+ int ret, on = 1;
+
+ ipc = alloc_conn();
+ if (!ipc)
+ return NULL;
+
+ ipc->bts_conn = data;
+
+ bfd = &ipc->fd;
+ bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ bfd->cb = ipaccess_fd_cb;
+ bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+ bfd->data = ipc;
+ bfd->priv_nr = priv_nr;
+
+ if (bfd->fd < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Could not create socket: %s\n",
+ strerror(errno));
+ talloc_free(ipc);
+ return NULL;
+ }
+
+ ret = setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Could not set socket option\n");
+ close(bfd->fd);
+ talloc_free(ipc);
+ return NULL;
+ }
+
+ ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Could not connect socket: %s\n",
+ inet_ntoa(sa->sin_addr));
+ close(bfd->fd);
+ talloc_free(ipc);
+ return NULL;
+ }
+
+ /* pre-fill tx_queue with identity request */
+ ret = osmo_fd_register(bfd);
+ if (ret < 0) {
+ close(bfd->fd);
+ talloc_free(ipc);
+ return NULL;
+ }
+
+ return ipc;
+}
+
+static int ipaccess_proxy_setup(void)
+{
+ int ret;
+
+ ipp = talloc_zero(tall_bsc_ctx, struct ipa_proxy);
+ if (!ipp)
+ return -ENOMEM;
+ INIT_LLIST_HEAD(&ipp->bts_list);
+ osmo_timer_setup(&ipp->reconn_timer, reconn_tmr_cb, ipp);
+
+ /* Listen for OML connections */
+ ipp->oml_listen_fd.priv_nr = OML_FROM_BTS;
+ ipp->oml_listen_fd.cb = listen_fd_cb;
+ ret = osmo_sock_init_ofd(&ipp->oml_listen_fd, AF_INET, SOCK_STREAM, IPPROTO_TCP,
+ NULL, IPA_TCP_PORT_OML, OSMO_SOCK_F_BIND);
+ if (ret < 0)
+ return ret;
+
+ /* Listen for RSL connections */
+ ipp->rsl_listen_fd.priv_nr = RSL_FROM_BTS;
+ ipp->rsl_listen_fd.cb = listen_fd_cb;
+ ret = osmo_sock_init_ofd(&ipp->rsl_listen_fd, AF_INET, SOCK_STREAM, IPPROTO_TCP,
+ NULL, IPA_TCP_PORT_RSL, OSMO_SOCK_F_BIND);
+ if (ret < 0)
+ return ret;
+
+ /* Connect the GPRS NS Socket */
+ if (gprs_ns_ipaddr) {
+ inet_aton(gprs_ns_ipaddr, &ipp->gprs_addr);
+ inet_aton(listen_ipaddr, &ipp->listen_addr);
+ }
+
+ return ret;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report
+ * and then return to the caller, who will abort the process */
+ case SIGUSR1:
+ talloc_report_full(tall_bsc_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static void print_help(void)
+{
+ printf(" ipaccess-proxy is a proxy BTS.\n");
+ printf(" -h --help. This help text.\n");
+ printf(" -l --listen IP. The ip to listen to.\n");
+ printf(" -b --bsc IP. The BSC IP address.\n");
+ printf(" -g --gprs IP. Take GPRS NS from that IP.\n");
+ printf("\n");
+ printf(" -s --disable-color. Disable the color inside the logging message.\n");
+ printf(" -e --log-level number. Set the global loglevel.\n");
+ printf(" -T --timestamp. Prefix every log message with a timestamp.\n");
+ printf(" -V --version. Print the version of OpenBSC.\n");
+}
+
+static void print_usage(void)
+{
+ printf("Usage: ipaccess-proxy [options]\n");
+}
+
+enum {
+ IPA_PROXY_OPT_LISTEN_NONE = 0,
+ IPA_PROXY_OPT_LISTEN_IP = (1 << 0),
+ IPA_PROXY_OPT_BSC_IP = (1 << 1),
+};
+
+static void handle_options(int argc, char** argv)
+{
+ int options_mask = 0;
+
+ /* disable explicit missing arguments error output from getopt_long */
+ opterr = 0;
+
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"disable-color", 0, 0, 's'},
+ {"timestamp", 0, 0, 'T'},
+ {"log-level", 1, 0, 'e'},
+ {"listen", 1, 0, 'l'},
+ {"bsc", 1, 0, 'b'},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "hsTe:l:b:g:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_usage();
+ print_help();
+ exit(0);
+ case 'l':
+ listen_ipaddr = optarg;
+ options_mask |= IPA_PROXY_OPT_LISTEN_IP;
+ break;
+ case 'b':
+ bsc_ipaddr = optarg;
+ options_mask |= IPA_PROXY_OPT_BSC_IP;
+ break;
+ case 'g':
+ gprs_ns_ipaddr = optarg;
+ break;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'T':
+ log_set_print_timestamp(osmo_stderr_target, 1);
+ break;
+ case 'e':
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case '?':
+ if (optopt) {
+ printf("ERROR: missing mandatory argument "
+ "for `%s' option\n", argv[optind-1]);
+ } else {
+ printf("ERROR: unknown option `%s'\n",
+ argv[optind-1]);
+ }
+ print_usage();
+ print_help();
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+ if ((options_mask & (IPA_PROXY_OPT_LISTEN_IP | IPA_PROXY_OPT_BSC_IP))
+ != (IPA_PROXY_OPT_LISTEN_IP | IPA_PROXY_OPT_BSC_IP)) {
+ printf("ERROR: You have to specify `--listen' and `--bsc' "
+ "options at least.\n");
+ print_usage();
+ print_help();
+ exit(EXIT_FAILURE);
+ }
+}
+
+static const struct log_info_cat log_categories[] = {
+ [DMM] = {
+ .name = "DMM",
+ .description = "Layer3 Mobility Management (MM)",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+};
+
+const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ tall_bsc_ctx = talloc_named_const(NULL, 1, "ipaccess-proxy");
+ msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+
+ osmo_init_logging2(tall_bsc_ctx, &log_info);
+ log_parse_category_mask(osmo_stderr_target, "DLINP:DLMI");
+
+ handle_options(argc, argv);
+
+ rc = ipaccess_proxy_setup();
+ if (rc < 0)
+ exit(1);
+
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ osmo_init_ignore_signals();
+
+ while (1) {
+ osmo_select_main(0);
+ }
+}
diff --git a/src/ipaccess/network_listen.c b/src/ipaccess/network_listen.c
new file mode 100644
index 000000000..bbaf79810
--- /dev/null
+++ b/src/ipaccess/network_listen.c
@@ -0,0 +1,257 @@
+/* ip.access nanoBTS network listen mode */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/rxlev_stat.h>
+#include <osmocom/gsm/gsm48_ie.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/abis/e1_input.h>
+
+#define WHITELIST_MAX_SIZE ((NUM_ARFCNS*2)+2+1)
+
+int ipac_rxlevstat2whitelist(uint16_t *buf, const struct rxlev_stats *st, uint8_t min_rxlev,
+ uint16_t max_num_arfcns)
+{
+ int i;
+ unsigned int num_arfcn = 0;
+
+ for (i = NUM_RXLEVS-1; i >= min_rxlev; i--) {
+ int16_t arfcn = -1;
+
+ while ((arfcn = rxlev_stat_get_next(st, i, arfcn)) >= 0) {
+ *buf++ = htons(arfcn);
+ num_arfcn++;
+
+ }
+
+ if (num_arfcn > max_num_arfcns)
+ break;
+ }
+
+ return num_arfcn;
+}
+
+enum ipac_test_state {
+ IPAC_TEST_S_IDLE,
+ IPAC_TEST_S_RQD,
+ IPAC_TEST_S_EXEC,
+ IPAC_TEST_S_PARTIAL,
+};
+
+int ipac_nwl_test_start(struct gsm_bts_trx *trx, uint8_t testnr,
+ const uint8_t *phys_conf, unsigned int phys_conf_len)
+{
+ struct msgb *msg;
+
+ if (trx->ipaccess.test_state != IPAC_TEST_S_IDLE) {
+ fprintf(stderr, "Cannot start test in state %u\n", trx->ipaccess.test_state);
+ return -EINVAL;
+ }
+
+ switch (testnr) {
+ case NM_IPACC_TESTNO_CHAN_USAGE:
+ case NM_IPACC_TESTNO_BCCH_CHAN_USAGE:
+ rxlev_stat_reset(&trx->ipaccess.rxlev_stat);
+ break;
+ }
+
+ msg = msgb_alloc_headroom(phys_conf_len+256, 128, "OML");
+
+ if (phys_conf && phys_conf_len) {
+ uint8_t *payload;
+ /* first put the phys conf header */
+ msgb_tv16_put(msg, NM_ATT_PHYS_CONF, phys_conf_len);
+ payload = msgb_put(msg, phys_conf_len);
+ memcpy(payload, phys_conf, phys_conf_len);
+ }
+
+ abis_nm_perform_test(trx->bts, NM_OC_RADIO_CARRIER, 0, trx->nr, 0xff,
+ testnr, 1, msg);
+ trx->ipaccess.test_nr = testnr;
+
+ /* FIXME: start safety timer until when test is supposed to complete */
+
+ return 0;
+}
+
+static uint16_t last_arfcn;
+static struct gsm_sysinfo_freq nwl_si_freq[1024];
+#define FREQ_TYPE_NCELL_2 0x04 /* sub channel of SI 2 */
+#define FREQ_TYPE_NCELL_2bis 0x08 /* sub channel of SI 2bis */
+#define FREQ_TYPE_NCELL_2ter 0x10 /* sub channel of SI 2ter */
+
+struct ipacc_ferr_elem {
+ int16_t freq_err;
+ uint8_t freq_qual;
+ uint8_t arfcn;
+} __attribute__((packed));
+
+struct ipacc_cusage_elem {
+ uint16_t arfcn:10,
+ rxlev:6;
+} __attribute__ ((packed));
+
+static int test_rep(void *_msg)
+{
+ struct msgb *msg = _msg;
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ uint16_t test_rep_len, ferr_list_len;
+ struct ipacc_ferr_elem *ife;
+ struct ipac_bcch_info binfo;
+ struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst;
+ int i, rc;
+
+ DEBUGP(DNM, "TEST REPORT: ");
+
+ if (foh->data[0] != NM_ATT_TEST_NO ||
+ foh->data[2] != NM_ATT_TEST_REPORT)
+ return -EINVAL;
+
+ DEBUGPC(DNM, "test_no=0x%02x ", foh->data[1]);
+ /* data[2] == NM_ATT_TEST_REPORT */
+ /* data[3..4]: test_rep_len */
+ memcpy(&test_rep_len, &foh->data[3], sizeof(uint16_t));
+ test_rep_len = ntohs(test_rep_len);
+ /* data[5]: ip.access test result */
+ DEBUGPC(DNM, "tst_res=%s\n", ipacc_testres_name(foh->data[5]));
+
+ /* data[6]: ip.access nested IE. 3 == freq_err_list */
+ switch (foh->data[6]) {
+ case NM_IPAC_EIE_FREQ_ERR_LIST:
+ /* data[7..8]: length of ferr_list */
+ memcpy(&ferr_list_len, &foh->data[7], sizeof(uint16_t));
+ ferr_list_len = ntohs(ferr_list_len);
+
+ /* data[9...]: frequency error list elements */
+ for (i = 0; i < ferr_list_len; i+= sizeof(*ife)) {
+ ife = (struct ipacc_ferr_elem *) (foh->data + 9 + i);
+ DEBUGP(DNM, "==> ARFCN %4u, Frequency Error %6hd\n",
+ ife->arfcn, ntohs(ife->freq_err));
+ }
+ break;
+ case NM_IPAC_EIE_CHAN_USE_LIST:
+ /* data[7..8]: length of ferr_list */
+ memcpy(&ferr_list_len, &foh->data[7], sizeof(uint16_t));
+ ferr_list_len = ntohs(ferr_list_len);
+
+ /* data[9...]: channel usage list elements */
+ for (i = 0; i < ferr_list_len; i+= 2) {
+ uint16_t *cu_ptr = (uint16_t *)(foh->data + 9 + i);
+ uint16_t cu = ntohs(*cu_ptr);
+ uint16_t arfcn = cu & 0x3ff;
+ uint8_t rxlev = cu >> 10;
+ DEBUGP(DNM, "==> ARFCN %4u, RxLev %2u\n", arfcn, rxlev);
+ rxlev_stat_input(&sign_link->trx->ipaccess.rxlev_stat,
+ arfcn, rxlev);
+ }
+ break;
+ case NM_IPAC_EIE_BCCH_INFO_TYPE:
+ break;
+ case NM_IPAC_EIE_BCCH_INFO:
+ rc = ipac_parse_bcch_info(&binfo, foh->data+6);
+ if (rc < 0) {
+ DEBUGP(DNM, "BCCH Info parsing failed\n");
+ break;
+ }
+ DEBUGP(DNM, "==> ARFCN %u, RxLev %2u, RxQual %2u: %s, LAC %d CI %d BSIC %u\n",
+ binfo.arfcn, binfo.rx_lev, binfo.rx_qual,
+ osmo_plmn_name(&binfo.cgi.lai.plmn),
+ binfo.cgi.lai.lac, binfo.cgi.cell_identity, binfo.bsic);
+
+ if (binfo.arfcn != last_arfcn) {
+ /* report is on a new arfcn, need to clear channel list */
+ memset(nwl_si_freq, 0, sizeof(nwl_si_freq));
+ last_arfcn = binfo.arfcn;
+ }
+ if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2) {
+ DEBUGP(DNM, "BA SI2: %s\n", osmo_hexdump(binfo.ba_list_si2, sizeof(binfo.ba_list_si2)));
+ gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2, sizeof(binfo.ba_list_si2),
+ 0x8c, FREQ_TYPE_NCELL_2);
+ }
+ if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2bis) {
+ DEBUGP(DNM, "BA SI2bis: %s\n", osmo_hexdump(binfo.ba_list_si2bis, sizeof(binfo.ba_list_si2bis)));
+ gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2bis, sizeof(binfo.ba_list_si2bis),
+ 0x8e, FREQ_TYPE_NCELL_2bis);
+ }
+ if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2ter) {
+ DEBUGP(DNM, "BA SI2ter: %s\n", osmo_hexdump(binfo.ba_list_si2ter, sizeof(binfo.ba_list_si2ter)));
+ gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2ter, sizeof(binfo.ba_list_si2ter),
+ 0x8e, FREQ_TYPE_NCELL_2ter);
+ }
+ for (i = 0; i < ARRAY_SIZE(nwl_si_freq); i++) {
+ if (nwl_si_freq[i].mask)
+ DEBUGP(DNM, "Neighbor Cell on ARFCN %u\n", i);
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (foh->data[5]) {
+ case NM_IPACC_TESTRES_SUCCESS:
+ case NM_IPACC_TESTRES_STOPPED:
+ case NM_IPACC_TESTRES_TIMEOUT:
+ case NM_IPACC_TESTRES_NO_CHANS:
+ sign_link->trx->ipaccess.test_state = IPAC_TEST_S_IDLE;
+ /* Send signal to notify higher layers of test completion */
+ DEBUGP(DNM, "dispatching S_IPAC_NWL_COMPLETE signal\n");
+ osmo_signal_dispatch(SS_IPAC_NWL, S_IPAC_NWL_COMPLETE,
+ sign_link->trx);
+ break;
+ case NM_IPACC_TESTRES_PARTIAL:
+ sign_link->trx->ipaccess.test_state = IPAC_TEST_S_PARTIAL;
+ break;
+ }
+
+ return 0;
+}
+
+static int nwl_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ switch (signal) {
+ case S_NM_TEST_REP:
+ return test_rep(signal_data);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void ipac_nwl_init(void)
+{
+ osmo_signal_register_handler(SS_NM, nwl_sig_cb, NULL);
+}
diff --git a/src/ipaccess/stubs.c b/src/ipaccess/stubs.c
new file mode 100644
index 000000000..cb561051a
--- /dev/null
+++ b/src/ipaccess/stubs.c
@@ -0,0 +1,46 @@
+/* Stubs required for linking */
+
+/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdbool.h>
+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 */
+}
+
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts)
+{}
diff --git a/src/libfilter/Makefile.am b/src/libfilter/Makefile.am
new file mode 100644
index 000000000..8b0597bc6
--- /dev/null
+++ b/src/libfilter/Makefile.am
@@ -0,0 +1,27 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(LIBOSMOLEGACYMGCP_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+noinst_LIBRARIES = \
+ libfilter.a \
+ $(NULL)
+
+libfilter_a_SOURCES = \
+ bsc_msg_filter.c \
+ bsc_msg_acc.c \
+ bsc_msg_vty.c \
+ $(NULL)
+
diff --git a/src/libfilter/bsc_msg_acc.c b/src/libfilter/bsc_msg_acc.c
new file mode 100644
index 000000000..de6c4d933
--- /dev/null
+++ b/src/libfilter/bsc_msg_acc.c
@@ -0,0 +1,136 @@
+/*
+ * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/bsc_msg_filter.h>
+#include <osmocom/bsc/debug.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
+
+#include <string.h>
+
+static const struct rate_ctr_desc acc_list_ctr_description[] = {
+ [ACC_LIST_LOCAL_FILTER] = { "access-list:local-filter", "Rejected by rule for local"},
+ [ACC_LIST_GLOBAL_FILTER]= { "access-list:global-filter", "Rejected by rule for global"},
+};
+
+static const struct rate_ctr_group_desc bsc_cfg_acc_list_desc = {
+ .group_name_prefix = "nat:filter",
+ .group_description = "NAT Access-List Statistics",
+ .num_ctr = ARRAY_SIZE(acc_list_ctr_description),
+ .ctr_desc = acc_list_ctr_description,
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+};
+
+/*! Find an unused index for this rate counter group.
+ * \param[in] head List of allocated ctr groups of the same type
+ * \returns the largest used index number + 1, or 0 if none exist yet. */
+static unsigned int rate_ctr_get_unused_idx(struct llist_head *head)
+{
+ unsigned int idx = 0;
+ struct bsc_msg_acc_lst *lst;
+
+ llist_for_each_entry(lst, head, list) {
+ if (idx <= lst->stats->idx)
+ idx = lst->stats->idx + 1;
+ }
+ return idx;
+}
+
+
+int bsc_msg_acc_lst_check_allow(struct bsc_msg_acc_lst *lst, const char *mi_string)
+{
+ struct bsc_msg_acc_lst_entry *entry;
+
+ llist_for_each_entry(entry, &lst->fltr_list, list) {
+ if (!entry->imsi_allow)
+ continue;
+ if (regexec(&entry->imsi_allow_re, mi_string, 0, NULL, 0) == 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+struct bsc_msg_acc_lst *bsc_msg_acc_lst_find(struct llist_head *head, const char *name)
+{
+ struct bsc_msg_acc_lst *lst;
+
+ if (!name)
+ return NULL;
+
+ llist_for_each_entry(lst, head, list)
+ if (strcmp(lst->name, name) == 0)
+ return lst;
+
+ return NULL;
+}
+
+struct bsc_msg_acc_lst *bsc_msg_acc_lst_get(void *ctx, struct llist_head *head, const char *name)
+{
+ struct bsc_msg_acc_lst *lst;
+ unsigned int new_idx;
+
+ lst = bsc_msg_acc_lst_find(head, name);
+ if (lst)
+ return lst;
+
+ lst = talloc_zero(ctx, struct bsc_msg_acc_lst);
+ if (!lst) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate access list\n");
+ return NULL;
+ }
+
+ new_idx = rate_ctr_get_unused_idx(head);
+ lst->stats = rate_ctr_group_alloc(lst, &bsc_cfg_acc_list_desc, new_idx);
+ if (!lst->stats) {
+ talloc_free(lst);
+ return NULL;
+ }
+
+ INIT_LLIST_HEAD(&lst->fltr_list);
+ lst->name = talloc_strdup(lst, name);
+ llist_add_tail(&lst->list, head);
+ return lst;
+}
+
+void bsc_msg_acc_lst_delete(struct bsc_msg_acc_lst *lst)
+{
+ llist_del(&lst->list);
+ rate_ctr_group_free(lst->stats);
+ talloc_free(lst);
+}
+
+struct bsc_msg_acc_lst_entry *bsc_msg_acc_lst_entry_create(struct bsc_msg_acc_lst *lst)
+{
+ struct bsc_msg_acc_lst_entry *entry;
+
+ entry = talloc_zero(lst, struct bsc_msg_acc_lst_entry);
+ if (!entry)
+ return NULL;
+
+ entry->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+ entry->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+ llist_add_tail(&entry->list, &lst->fltr_list);
+ return entry;
+}
+
diff --git a/src/libfilter/bsc_msg_filter.c b/src/libfilter/bsc_msg_filter.c
new file mode 100644
index 000000000..1318689fa
--- /dev/null
+++ b/src/libfilter/bsc_msg_filter.c
@@ -0,0 +1,339 @@
+/*
+ * Access filtering
+ */
+/*
+ * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/bsc_msg_filter.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+#include <osmocom/gsm/gsm48.h>
+
+static int bsc_filter_barr_find(struct rb_root *root, const char *imsi, int *cm, int *lu)
+{
+ struct bsc_filter_barr_entry *n;
+ n = rb_entry(root->rb_node, struct bsc_filter_barr_entry, node);
+
+ while (n) {
+ int rc = strcmp(imsi, n->imsi);
+ if (rc == 0) {
+ *cm = n->cm_reject_cause;
+ *lu = n->lu_reject_cause;
+ return 1;
+ }
+
+ n = rb_entry(
+ (rc < 0) ? n->node.rb_left : n->node.rb_right,
+ struct bsc_filter_barr_entry, node);
+ };
+
+ return 0;
+}
+
+
+static int lst_check_deny(struct bsc_msg_acc_lst *lst, const char *mi_string,
+ int *cm_cause, int *lu_cause)
+{
+ struct bsc_msg_acc_lst_entry *entry;
+
+ llist_for_each_entry(entry, &lst->fltr_list, list) {
+ if (!entry->imsi_deny)
+ continue;
+ if (regexec(&entry->imsi_deny_re, mi_string, 0, NULL, 0) == 0) {
+ *cm_cause = entry->cm_reject_cause;
+ *lu_cause = entry->lu_reject_cause;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* apply white/black list */
+static int auth_imsi(struct bsc_filter_request *req,
+ const char *imsi,
+ struct bsc_filter_reject_cause *cause)
+{
+ /*
+ * Now apply blacklist/whitelist of the BSC and the NAT.
+ * 1.) Check the global IMSI barr list
+ * 2.) Allow directly if the IMSI is allowed at the BSC
+ * 3.) Reject if the IMSI is not allowed at the BSC
+ * 4.) Allow directly if the IMSI is allowed at the global level
+ * 5.) Reject if the IMSI not allowed at the global level.
+ */
+ int cm, lu;
+ struct bsc_msg_acc_lst *nat_lst = NULL;
+ struct bsc_msg_acc_lst *bsc_lst = NULL;
+
+ /* 1. global check for barred imsis */
+ if (req->black_list && bsc_filter_barr_find(req->black_list, imsi, &cm, &lu)) {
+ cause->cm_reject_cause = cm;
+ cause->lu_reject_cause = lu;
+ LOGP(DFILTER, LOGL_DEBUG,
+ "Blocking subscriber IMSI %s with CM: %d LU: %d\n",
+ imsi, cm, lu);
+ return -4;
+ }
+
+
+ bsc_lst = bsc_msg_acc_lst_find(req->access_lists, req->local_lst_name);
+ nat_lst = bsc_msg_acc_lst_find(req->access_lists, req->global_lst_name);
+
+
+ if (bsc_lst) {
+ /* 2. BSC allow */
+ if (bsc_msg_acc_lst_check_allow(bsc_lst, imsi) == 0)
+ return 1;
+
+ /* 3. BSC deny */
+ if (lst_check_deny(bsc_lst, imsi, &cm, &lu) == 0) {
+ LOGP(DFILTER, LOGL_ERROR,
+ "Filtering %s by imsi_deny on config nr: %d.\n", imsi, req->bsc_nr);
+ rate_ctr_inc(&bsc_lst->stats->ctr[ACC_LIST_LOCAL_FILTER]);
+ cause->cm_reject_cause = cm;
+ cause->lu_reject_cause = lu;
+ return -2;
+ }
+
+ }
+
+ if (nat_lst) {
+ /* 4. global allow */
+ if (bsc_msg_acc_lst_check_allow(nat_lst, imsi) == 0)
+ return 1;
+
+ /* 5. global deny */
+ if (lst_check_deny(nat_lst, imsi, &cm, &lu) == 0) {
+ LOGP(DFILTER, LOGL_ERROR,
+ "Filtering %s global imsi_deny on bsc nr: %d.\n", imsi, req->bsc_nr);
+ rate_ctr_inc(&nat_lst->stats->ctr[ACC_LIST_GLOBAL_FILTER]);
+ cause->cm_reject_cause = cm;
+ cause->lu_reject_cause = lu;
+ return -3;
+ }
+ }
+
+ return 1;
+}
+
+static int _cr_check_loc_upd(void *ctx,
+ uint8_t *data, unsigned int length,
+ char **imsi)
+{
+ uint8_t mi_type;
+ struct gsm48_loc_upd_req *lu;
+ char mi_string[GSM48_MI_SIZE];
+
+ if (length < sizeof(*lu)) {
+ LOGP(DFILTER, LOGL_ERROR,
+ "LU does not fit. Length is %d \n", length);
+ return -1;
+ }
+
+ lu = (struct gsm48_loc_upd_req *) data;
+ mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
+
+ /*
+ * We can only deal with the IMSI. This will fail for a phone that
+ * will send the TMSI of a previous network to us.
+ */
+ if (mi_type != GSM_MI_TYPE_IMSI)
+ return 0;
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
+ *imsi = talloc_strdup(ctx, mi_string);
+ return 1;
+}
+
+static int _cr_check_cm_serv_req(void *ctx,
+ uint8_t *data, unsigned int length,
+ int *con_type, char **imsi)
+{
+ static const uint32_t classmark_offset =
+ offsetof(struct gsm48_service_request, classmark);
+
+ char mi_string[GSM48_MI_SIZE];
+ uint8_t mi_type;
+ int rc;
+ struct gsm48_service_request *req;
+
+ /* unfortunately in Phase1 the classmark2 length is variable */
+
+ if (length < sizeof(*req)) {
+ LOGP(DFILTER, LOGL_ERROR,
+ "CM Serv Req does not fit. Length is %d\n", length);
+ return -1;
+ }
+
+ req = (struct gsm48_service_request *) data;
+ if (req->cm_service_type == 0x8)
+ *con_type = FLT_CON_TYPE_SSA;
+ rc = gsm48_extract_mi((uint8_t *) &req->classmark,
+ length - classmark_offset, mi_string, &mi_type);
+ if (rc < 0) {
+ LOGP(DFILTER, LOGL_ERROR, "Failed to parse the classmark2/mi. error: %d\n", rc);
+ return -1;
+ }
+
+ /* we have to let the TMSI or such pass */
+ if (mi_type != GSM_MI_TYPE_IMSI)
+ return 0;
+
+ *imsi = talloc_strdup(ctx, mi_string);
+ return 1;
+}
+
+static int _cr_check_pag_resp(void *ctx,
+ uint8_t *data, unsigned int length, char **imsi)
+{
+ struct gsm48_pag_resp *resp;
+ char mi_string[GSM48_MI_SIZE];
+ uint8_t mi_type;
+
+ if (length < sizeof(*resp)) {
+ LOGP(DFILTER, LOGL_ERROR, "PAG RESP does not fit. Length was %d.\n", length);
+ return -1;
+ }
+
+ resp = (struct gsm48_pag_resp *) data;
+ if (gsm48_paging_extract_mi(resp, length, mi_string, &mi_type) < 0) {
+ LOGP(DFILTER, LOGL_ERROR, "Failed to extract the MI.\n");
+ return -1;
+ }
+
+ /* we need to let it pass for now */
+ if (mi_type != GSM_MI_TYPE_IMSI)
+ return 0;
+
+ *imsi = talloc_strdup(ctx, mi_string);
+ return 1;
+}
+
+static int _dt_check_id_resp(struct bsc_filter_request *req,
+ uint8_t *data, unsigned int length,
+ struct bsc_filter_state *state,
+ struct bsc_filter_reject_cause *cause)
+{
+ char mi_string[GSM48_MI_SIZE];
+ uint8_t mi_type;
+
+ if (length < 2) {
+ LOGP(DFILTER, LOGL_ERROR, "mi does not fit.\n");
+ return -1;
+ }
+
+ if (data[0] < length - 1) {
+ LOGP(DFILTER, LOGL_ERROR, "mi length too big.\n");
+ return -2;
+ }
+
+ mi_type = data[1] & GSM_MI_TYPE_MASK;
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), &data[1], data[0]);
+
+ if (mi_type != GSM_MI_TYPE_IMSI)
+ return 0;
+
+ state->imsi_checked = 1;
+ state->imsi = talloc_strdup(req->ctx, mi_string);
+ return auth_imsi(req, mi_string, cause);
+}
+
+
+/* Filter out CR data... */
+int bsc_msg_filter_initial(struct gsm48_hdr *hdr48, size_t hdr48_len,
+ struct bsc_filter_request *req,
+ int *con_type,
+ char **imsi, struct bsc_filter_reject_cause *cause)
+{
+ int ret = 0;
+ uint8_t msg_type, proto;
+
+ *con_type = FLT_CON_TYPE_NONE;
+ cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+ cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+ *imsi = NULL;
+
+ proto = gsm48_hdr_pdisc(hdr48);
+ msg_type = gsm48_hdr_msg_type(hdr48);
+ if (proto == GSM48_PDISC_MM &&
+ msg_type == GSM48_MT_MM_LOC_UPD_REQUEST) {
+ *con_type = FLT_CON_TYPE_LU;
+ ret = _cr_check_loc_upd(req->ctx, &hdr48->data[0],
+ hdr48_len - sizeof(*hdr48), imsi);
+ } else if (proto == GSM48_PDISC_MM &&
+ msg_type == GSM48_MT_MM_CM_SERV_REQ) {
+ *con_type = FLT_CON_TYPE_CM_SERV_REQ;
+ ret = _cr_check_cm_serv_req(req->ctx, &hdr48->data[0],
+ hdr48_len - sizeof(*hdr48),
+ con_type, imsi);
+ } else if (proto == GSM48_PDISC_RR &&
+ msg_type == GSM48_MT_RR_PAG_RESP) {
+ *con_type = FLT_CON_TYPE_PAG_RESP;
+ ret = _cr_check_pag_resp(req->ctx, &hdr48->data[0],
+ hdr48_len - sizeof(*hdr48), imsi);
+ } else {
+ /* We only want to filter the above, let other things pass */
+ *con_type = FLT_CON_TYPE_OTHER;
+ return 0;
+ }
+
+ /* check if we are done */
+ if (ret != 1)
+ return ret;
+
+ /* the memory allocation failed */
+ if (!*imsi)
+ return -1;
+
+ /* now check the imsi */
+ return auth_imsi(req, *imsi, cause);
+}
+
+int bsc_msg_filter_data(struct gsm48_hdr *hdr48, size_t len,
+ struct bsc_filter_request *req,
+ struct bsc_filter_state *state,
+ struct bsc_filter_reject_cause *cause)
+{
+ uint8_t msg_type, proto;
+
+ cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+ cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+
+ if (state->imsi_checked)
+ return 0;
+
+ proto = gsm48_hdr_pdisc(hdr48);
+ msg_type = gsm48_hdr_msg_type(hdr48);
+ if (proto != GSM48_PDISC_MM || msg_type != GSM48_MT_MM_ID_RESP)
+ return 0;
+
+ return _dt_check_id_resp(req, &hdr48->data[0],
+ len - sizeof(*hdr48), state, cause);
+}
diff --git a/src/libfilter/bsc_msg_vty.c b/src/libfilter/bsc_msg_vty.c
new file mode 100644
index 000000000..b26f4f1a6
--- /dev/null
+++ b/src/libfilter/bsc_msg_vty.c
@@ -0,0 +1,149 @@
+/* (C) 2010-2015 by Holger Hans Peter Freyther
+ * (C) 2010-2013 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/bsc_msg_filter.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/vty.h>
+
+#include <osmocom/vty/misc.h>
+
+static struct llist_head *_acc_lst;
+static void *_ctx;
+
+static void bsc_msg_acc_lst_write_one(struct vty *vty, struct bsc_msg_acc_lst *lst)
+{
+ struct bsc_msg_acc_lst_entry *entry;
+
+ llist_for_each_entry(entry, &lst->fltr_list, list) {
+ if (entry->imsi_allow)
+ vty_out(vty, " access-list %s imsi-allow %s%s",
+ lst->name, entry->imsi_allow, VTY_NEWLINE);
+ if (entry->imsi_deny)
+ vty_out(vty, " access-list %s imsi-deny %s %d %d%s",
+ lst->name, entry->imsi_deny,
+ entry->cm_reject_cause, entry->lu_reject_cause,
+ VTY_NEWLINE);
+ }
+}
+
+DEFUN(cfg_lst_no,
+ cfg_lst_no_cmd,
+ "no access-list NAME",
+ NO_STR "Remove an access-list by name\n"
+ "The access-list to remove\n")
+{
+ struct bsc_msg_acc_lst *acc;
+ acc = bsc_msg_acc_lst_find(_acc_lst, argv[0]);
+ if (!acc)
+ return CMD_WARNING;
+
+ bsc_msg_acc_lst_delete(acc);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_acc_lst,
+ show_acc_lst_cmd,
+ "show access-list NAME",
+ SHOW_STR "IMSI access list\n" "Name of the access list\n")
+{
+ struct bsc_msg_acc_lst *acc;
+ acc = bsc_msg_acc_lst_find(_acc_lst, argv[0]);
+ if (!acc)
+ return CMD_WARNING;
+
+ vty_out(vty, "access-list %s%s", acc->name, VTY_NEWLINE);
+ bsc_msg_acc_lst_write_one(vty, acc);
+ vty_out_rate_ctr_group(vty, " ", acc->stats);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_lst_imsi_allow,
+ cfg_lst_imsi_allow_cmd,
+ "access-list NAME imsi-allow [REGEXP]",
+ "Access list commands\n"
+ "Name of the access list\n"
+ "Add allowed IMSI to the list\n"
+ "Regexp for IMSIs\n")
+{
+ struct bsc_msg_acc_lst *acc;
+ struct bsc_msg_acc_lst_entry *entry;
+
+ acc = bsc_msg_acc_lst_get(_ctx, _acc_lst, argv[0]);
+ if (!acc)
+ return CMD_WARNING;
+
+ entry = bsc_msg_acc_lst_entry_create(acc);
+ if (!entry)
+ return CMD_WARNING;
+
+ if (gsm_parse_reg(acc, &entry->imsi_allow_re, &entry->imsi_allow, argc - 1, &argv[1]) != 0)
+ return CMD_WARNING;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_lst_imsi_deny,
+ cfg_lst_imsi_deny_cmd,
+ "access-list NAME imsi-deny [REGEXP] (<0-256>) (<0-256>)",
+ "Access list commands\n"
+ "Name of the access list\n"
+ "Add denied IMSI to the list\n"
+ "Regexp for IMSIs\n"
+ "CM Service Reject reason\n"
+ "LU Reject reason\n")
+{
+ struct bsc_msg_acc_lst *acc;
+ struct bsc_msg_acc_lst_entry *entry;
+
+ acc = bsc_msg_acc_lst_get(_ctx, _acc_lst, argv[0]);
+ if (!acc)
+ return CMD_WARNING;
+
+ entry = bsc_msg_acc_lst_entry_create(acc);
+ if (!entry)
+ return CMD_WARNING;
+
+ if (gsm_parse_reg(acc, &entry->imsi_deny_re, &entry->imsi_deny, argc - 1, &argv[1]) != 0)
+ return CMD_WARNING;
+ if (argc >= 3)
+ entry->cm_reject_cause = atoi(argv[2]);
+ if (argc >= 4)
+ entry->lu_reject_cause = atoi(argv[3]);
+ return CMD_SUCCESS;
+}
+
+void bsc_msg_acc_lst_write(struct vty *vty)
+{
+ struct bsc_msg_acc_lst *lst;
+ llist_for_each_entry(lst, _acc_lst, list) {
+ bsc_msg_acc_lst_write_one(vty, lst);
+ }
+}
+
+void bsc_msg_acc_lst_vty_init(void *ctx, struct llist_head *lst, int node)
+{
+ _ctx = ctx;
+ _acc_lst = lst;
+ install_element_ve(&show_acc_lst_cmd);
+
+ /* access-list */
+ install_element(node, &cfg_lst_imsi_allow_cmd);
+ install_element(node, &cfg_lst_imsi_deny_cmd);
+ install_element(node, &cfg_lst_no_cmd);
+}
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
new file mode 100644
index 000000000..364228d8c
--- /dev/null
+++ b/src/osmo-bsc/Makefile.am
@@ -0,0 +1,105 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+bin_PROGRAMS = \
+ osmo-bsc \
+ $(NULL)
+
+osmo_bsc_SOURCES = \
+ 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 \
+ assignment_fsm.c \
+ bsc_ctrl_commands.c \
+ bsc_ctrl_lookup.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 \
+ codec_pref.c \
+ e1_config.c \
+ gsm_04_08_rr.c \
+ gsm_04_80_utils.c \
+ gsm_data.c \
+ gsm_timers.c \
+ gsm_timers_vty.c \
+ handover_cfg.c \
+ handover_decision.c \
+ handover_decision_2.c \
+ handover_fsm.c \
+ handover_logic.c \
+ handover_vty.c \
+ lchan_fsm.c \
+ lchan_rtp_fsm.c \
+ lchan_select.c \
+ meas_feed.c \
+ meas_rep.c \
+ mgw_endpoint_fsm.c \
+ neighbor_ident.c \
+ neighbor_ident_vty.c \
+ net_init.c \
+ gsm_08_08.c \
+ osmo_bsc_bssap.c \
+ osmo_bsc_ctrl.c \
+ osmo_bsc_filter.c \
+ osmo_bsc_grace.c \
+ osmo_bsc_lcls.c \
+ osmo_bsc_main.c \
+ osmo_bsc_msc.c \
+ osmo_bsc_sigtran.c \
+ osmo_bsc_vty.c \
+ paging.c \
+ pcu_sock.c \
+ penalty_timers.c \
+ rest_octets.c \
+ system_information.c \
+ timeslot_fsm.c \
+ $(NULL)
+
+osmo_bsc_LDADD = \
+ $(top_builddir)/src/libfilter/libfilter.a \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(COVERAGE_LDFLAGS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOSIGTRAN_LIBS) \
+ $(LIBOSMOMGCPCLIENT_LIBS) \
+ $(NULL)
diff --git a/src/osmo-bsc/a_reset.c b/src/osmo-bsc/a_reset.c
new file mode 100644
index 000000000..3c2114219
--- /dev/null
+++ b/src/osmo-bsc/a_reset.c
@@ -0,0 +1,220 @@
+/* (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/fsm.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+
+#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 */
+void a_reset_alloc(struct bsc_msc_data *msc, const char *name, void *cb)
+{
+ struct reset_ctx *reset_ctx;
+ struct osmo_fsm_inst *reset_fsm;
+
+ OSMO_ASSERT(msc);
+ OSMO_ASSERT(name);
+ OSMO_ASSERT(cb);
+
+ /* There must not be any double allocation! */
+ OSMO_ASSERT(msc->a.reset_fsm == NULL);
+
+ /* 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(msc, struct reset_ctx);
+ OSMO_ASSERT(reset_ctx);
+ reset_ctx->priv = msc;
+ reset_ctx->cb = cb;
+ reset_ctx->conn_loss_counter = 0;
+ reset_fsm = osmo_fsm_inst_alloc(&fsm, msc, 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);
+
+ msc->a.reset_fsm = reset_fsm;
+}
+
+/* Confirm that we sucessfully received a reset acknowlege message */
+void a_reset_ack_confirm(struct bsc_msc_data *msc)
+{
+ if (!msc)
+ return;
+
+ if (!msc->a.reset_fsm)
+ return;
+
+ osmo_fsm_inst_dispatch(msc->a.reset_fsm, EV_RESET_ACK, NULL);
+}
+
+/* Report a failed connection */
+void a_reset_conn_fail(struct bsc_msc_data *msc)
+{
+ if (!msc)
+ return;
+
+ if (!msc->a.reset_fsm)
+ return;
+
+ osmo_fsm_inst_dispatch(msc->a.reset_fsm, EV_N_DISCONNECT, NULL);
+}
+
+/* Report a successful connection */
+void a_reset_conn_success(struct bsc_msc_data *msc)
+{
+ if (!msc)
+ return;
+
+ if (!msc->a.reset_fsm)
+ return;
+
+ osmo_fsm_inst_dispatch(msc->a.reset_fsm, EV_N_CONNECT, NULL);
+}
+
+/* Check if we have a connection to a specified msc */
+bool a_reset_conn_ready(struct bsc_msc_data *msc)
+{
+ if (!msc)
+ return false;
+
+ if (!msc->a.reset_fsm)
+ return false;
+
+ if (msc->a.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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
diff --git a/src/osmo-bsc/abis_nm.c b/src/osmo-bsc/abis_nm.c
new file mode 100644
index 000000000..f1306fcfb
--- /dev/null
+++ b/src/osmo-bsc/abis_nm.c
@@ -0,0 +1,3057 @@
+/* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <time.h>
+#include <limits.h>
+#include <inttypes.h>
+
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/misdn.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/gsm/bts_features.h>
+
+#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);
+}
+
+/* Parse OML Primary IP and port from tlv_parsed containing list of Reported Attributes */
+int abis_nm_tlv_attr_primary_oml(struct tlv_parsed *tp, struct in_addr *ia, uint16_t *oml_port)
+{
+ const uint8_t* data;
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_PRIM_OML_CFG_LIST, 7)) {
+ data = TLVP_VAL(tp, NM_ATT_IPACC_PRIM_OML_CFG_LIST);
+ if (NM_ATT_IPACC_PRIM_OML_CFG == *data) {
+ ia->s_addr = htonl(osmo_load32be(data+1));
+ *oml_port = osmo_load16be(data+5);
+ return 0;
+ }else {
+ LOGP(DNM, LOGL_ERROR,
+ "Get Attributes Response: PRIM_OML_CFG_LIST has unexpected format: %s\n",
+ osmo_hexdump(data, TLVP_LEN(tp, NM_ATT_IPACC_PRIM_OML_CFG_LIST)));
+ }
+ }
+ return -1;
+}
+
+/* Parse OML Primary IP and port from tlv_parsed containing list of Reported Attributes */
+int abis_nm_tlv_attr_unit_id(struct tlv_parsed *tp, char* unit_id, size_t buf_len)
+{
+ const uint8_t* data;
+ uint16_t len;
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_UNIT_ID, 1)) {
+ data = TLVP_VAL(tp, NM_ATT_IPACC_UNIT_ID);
+ len = TLVP_LEN(tp, NM_ATT_IPACC_UNIT_ID);
+ osmo_strlcpy(unit_id, (char*)data, OSMO_MIN(len, buf_len));
+ return 0;
+ }
+ return -1;
+}
+
+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 */
+ if (out_len)
+ *out_len = ari_len - (num_unreported + 1);
+
+ return ari + num_unreported + 1; /* we have to account for 1st byte with number of unreported attributes */
+}
+
+/* Handle 3GPP TS 52.021 §8.11.3 Get Attribute Response (with nanoBTS specific attribute formatting) */
+static int parse_attr_resp_info_attr(struct gsm_bts *bts, const struct gsm_bts_trx *trx, struct abis_om_fom_hdr *foh, struct tlv_parsed *tp)
+{
+ const uint8_t* data;
+ uint16_t len;
+ int i;
+ int rc;
+ uint16_t port;
+ struct in_addr ia = {0};
+ char unit_id[40];
+ struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR];
+
+ /* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.30 Manufacturer Id */
+ if (TLVP_PRES_LEN(tp, NM_ATT_MANUF_ID, 2)) {
+ len = TLVP_LEN(tp, NM_ATT_MANUF_ID);
+
+ /* log potential BTS feature vector overflow */
+ if (len > sizeof(bts->_features_data))
+ LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: feature vector is truncated to %u bytes\n",
+ bts->nr, MAX_BTS_FEATURES/8);
+
+ /* check that max. expected BTS attribute is above given feature vector length */
+ if (len > OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT))
+ LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: reported unexpectedly long (%u bytes) "
+ "feature vector - most likely it was compiled against newer BSC headers. "
+ "Consider upgrading your BSC to later version.\n",
+ bts->nr, len);
+
+ memcpy(bts->_features_data, TLVP_VAL(tp, NM_ATT_MANUF_ID), sizeof(bts->_features_data));
+
+ 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));
+ }
+
+ /* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.28 Manufacturer Dependent State */
+ /* this attribute does not make sense on BTS level, only on TRX level */
+ if (trx && TLVP_PRES_LEN(tp, NM_ATT_MANUF_STATE, 1)) {
+ data = TLVP_VAL(tp, NM_ATT_MANUF_STATE);
+ LOGPFOH(DNM, LOGL_NOTICE, foh, "%s Get Attributes Response: nominal power is %u\n", gsm_trx_name(trx), *data);
+ }
+
+ /* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.61 SW Configuration */
+ if (TLVP_PRESENT(tp, NM_ATT_SW_CONFIG)) {
+ data = TLVP_VAL(tp, NM_ATT_SW_CONFIG);
+ len = TLVP_LEN(tp, NM_ATT_SW_CONFIG);
+ /* after parsing manufacturer-specific attributes there's list of replies in form of sw-conf structure: */
+ rc = abis_nm_get_sw_conf(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));
+ }
+ }
+
+ if (abis_nm_tlv_attr_primary_oml(tp, &ia, &port) == 0) {
+ LOGPFOH(DNM, LOGL_NOTICE, foh,
+ "BTS%u Get Attributes Response: Primary OML IP is %s:%u\n",
+ bts->nr, inet_ntoa(ia), port);
+ }
+
+ if (abis_nm_tlv_attr_unit_id(tp, unit_id, sizeof(unit_id)) == 0) {
+ LOGPFOH(DNM, LOGL_NOTICE, foh, "BTS%u Get Attributes Response: Unit ID is %s\n",
+ bts->nr, unit_id);
+ }
+
+ /* nanoBTS provides Get Attribute Response Info at random position and only the unreported part of it. */
+ if (TLVP_PRES_LEN(tp, NM_ATT_GET_ARI, 1)) {
+ data = TLVP_VAL(tp, NM_ATT_GET_ARI);
+ len = TLVP_LEN(tp, NM_ATT_GET_ARI);
+ parse_attr_resp_info_unreported(bts->nr, data, len, NULL);
+ }
+
+ return 0;
+}
+
+/* Handle 3GPP TS 52.021 §9.4.64 Get Attribute Response Info */
+static int parse_attr_resp_info(struct gsm_bts *bts, const struct gsm_bts_trx *trx, struct abis_om_fom_hdr *foh, struct tlv_parsed *tp)
+{
+ const uint8_t *data;
+ uint16_t data_len;
+
+ 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);
+
+ /* After parsing unreported attribute id list inside Response info,
+ there's a list of reported attribute ids and their values, in a TLV
+ list form. */
+ abis_nm_tlv_parse(tp, bts, data, data_len);
+ return parse_attr_resp_info_attr(bts, trx, foh, tp);
+}
+
+/* Handle 3GPP TS 52.021 §8.11.3 Get Attribute Response */
+static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *trx)
+{
+ 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;
+ int rc;
+
+ DEBUGPFOH(DNM, foh, "Get Attributes Response for BTS%u\n", bts->nr);
+
+ abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+
+ /* nanoBTS doesn't send Get Attribute Response Info, uses its own format */
+ if (bts->type != GSM_BTS_TYPE_NANOBTS)
+ rc = parse_attr_resp_info(bts, trx, foh, &tp);
+ else
+ rc = parse_attr_resp_info_attr(bts, trx, foh, &tp);
+
+ osmo_signal_dispatch(SS_NM, S_NM_GET_ATTR_REP, mb);
+
+ return rc;
+}
+
+/* 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;
+}
+
+/* From a received OML message, determine the matching struct gsm_bts_trx_ts instance.
+ * Note that the BTS-TRX-TS numbers received in the FOM header do not correspond
+ * to the local bts->nr. Rather, the BTS is identified by the e1inp_sign_link*
+ * found in msg->dst which points to OML connection and thus to its 1st TRX, and the
+ * TRX and TS is then obtained by the FOM header's TS number. */
+struct gsm_bts_trx_ts *abis_nm_get_ts(const struct msgb *oml_msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
+ struct e1inp_sign_link *sign_link = oml_msg->dst;
+ struct gsm_bts_trx *trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr);
+ uint8_t ts_nr = foh->obj_inst.ts_nr;
+ if (!trx) {
+ LOGP(DNM, LOGL_ERROR, "%s Channel OPSTART ACK for sign_link without trx\n",
+ abis_nm_dump_foh(foh));
+ return NULL;
+ }
+ if (ts_nr >= ARRAY_SIZE(trx->ts)) {
+ LOGP(DNM, LOGL_ERROR, "bts%u-trx%u %s Channel OPSTART ACK for non-existent TS\n",
+ trx->bts->nr, trx->nr, abis_nm_dump_foh(foh));
+ return NULL;
+ }
+ return &trx->ts[ts_nr];
+}
+
+static int abis_nm_rx_opstart_ack(struct msgb *mb)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ struct e1inp_sign_link *sign_link = mb->dst;
+ DEBUGPFOH(DNM, foh, "bts=%u Opstart ACK\n", sign_link->trx->bts->nr);
+ osmo_signal_dispatch(SS_NM, S_NM_OPSTART_ACK, mb);
+ 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."
+ " There might be checksums in the file that are not"
+ " verified and incomplete firmware might be flashed."
+ " There is absolutely no WARRANTY that flashing will"
+ " work.\n");
+ return 0;
+}
+
+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;
+ }
+ 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;
+ }
+ 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 && bts->type != GSM_BTS_TYPE_NANOBTS) {
+ 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_BCCH_CBCH:
+ 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) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Invalid Channel Combination %d on %s. Reason: %s\n",
+ chan_comb, gsm_ts_name(ts), reason);
+ msgb_free(msg);
+ 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=%" SCNu16 " ", osmo_load16be(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, "(bts=%d,trx=%d) IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+ trx->bts->nr, trx->nr, 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+
+/* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/gsm/abis_nm.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+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..9715dfc17
--- /dev/null
+++ b/src/osmo-bsc/abis_om2000.c
@@ -0,0 +1,2771 @@
+/* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/abis_om2000.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/abis/e1_input.h>
+
+/* 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)
+{
+ if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH) {
+ LOGP(DNM, LOGL_ERROR, "%s pchan %s not intended for use"
+ " with OM2000, use %s instead\n",
+ gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(GSM_PCHAN_TCH_F_PDCH),
+ gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH));
+ /* 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;
+ }
+ return pchan2comb(ts->pchan_is);
+}
+
+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_is) {
+ 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,
+ T_def_get(ts->trx->bts->network->T_defs, 3105, T_MS, -1) / 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,
+ T_def_get(ts->trx->bts->network->T_defs, 3105, T_MS, -1) / 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,
+ T_def_get(ts->trx->bts->network->T_defs, 3105, T_MS, -1) / 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_is == 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);
+ OSMO_ASSERT(ts->fi);
+ osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL);
+ }
+}
+
+/* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_om2000.h>
+#include <osmocom/bsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+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..954fb0fe7
--- /dev/null
+++ b/src/osmo-bsc/abis_rsl.c
@@ -0,0 +1,2248 @@
+/* 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 <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bsc_rll.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/pcu_if.h>
+#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/netif/rtp.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
+#include <osmocom/bsc/handover_fsm.h>
+
+#define RSL_ALLOC_SIZE 1024
+#define RSL_ALLOC_HEADROOM 128
+
+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 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;
+}
+
+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;
+}
+
+/* If the TLV contain an RSL Cause IE, return pointer to the cause value. If there is no Cause IE, return
+ * NULL. Implementation choice: presence of a Cause IE cannot be indicated by a zero cause, because that
+ * would mean RSL_ERR_RADIO_IF_FAIL; a pointer can return NULL or point to a cause value. */
+static const uint8_t *rsl_cause(struct tlv_parsed *tp)
+{
+ if (TLVP_PRESENT(tp, RSL_IE_CAUSE))
+ return (const uint8_t *)TLVP_VAL(tp, RSL_IE_CAUSE);
+ return NULL;
+}
+
+/* If the TLV contain an RSL Cause IE, return the RSL cause name; otherwise return "". */
+static const char *rsl_cause_name(struct tlv_parsed *tp)
+{
+ static char buf[128];
+ if (TLVP_PRESENT(tp, RSL_IE_CAUSE)) {
+ const uint8_t *cause = TLVP_VAL(tp, RSL_IE_CAUSE);
+ snprintf(buf, sizeof(buf), " (cause=%s [ %s])",
+ rsl_err_name(*cause),
+ osmo_hexdump(cause, TLVP_LEN(tp, RSL_IE_CAUSE)));
+ return buf;
+ } else
+ return "";
+}
+
+/* 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)
+{
+ uint8_t len;
+
+ if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR)
+ return;
+
+ len = lchan->mr_bts_lv[0];
+ if (!len) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Missing Multirate Config (len is zero)\n");
+ return;
+ }
+ msgb_tlv_put(msg, RSL_IE_MR_CONFIG, lchan->mr_bts_lv[0],
+ lchan->mr_bts_lv + 1);
+}
+
+/* Chapter 8.4.1 */
+int rsl_tx_chan_activ(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;
+
+ 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));
+
+ /* PDCH activation is a job for rsl_tx_dyn_ts_pdch_act_deact(); */
+ OSMO_ASSERT(act_type != RSL_ACT_OSMO_PDCH);
+
+ rc = channel_mode_from_lchan(&cm, lchan);
+ 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;
+ }
+
+ 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);
+
+ 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]);
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* 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);
+}
+
+/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */
+int rsl_tx_rf_chan_release(struct gsm_lchan *lchan)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg;
+
+ 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;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+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 rsl_forward_layer3_info(struct gsm_lchan *lchan, const uint8_t *l3_info, uint8_t l3_info_len)
+{
+ struct msgb *msg;
+
+ if (!l3_info || !l3_info_len)
+ return -EINVAL;
+
+ msg = rsl_msgb_alloc();
+ msg->l3h = msgb_put(msg, l3_info_len);
+ memcpy(msg->l3h, l3_info, l3_info_len);
+
+ msg->lchan = lchan;
+ return rsl_data_request(msg, 0);
+}
+
+int imsi_str2bcd(uint8_t *bcd_out, const char *str_in)
+{
+ int i, len = strlen(str_in);
+
+ for (i = 0; i < len; i++) {
+ int num = str_in[i] - 0x30;
+ if (num < 0 || num > 9)
+ return -1;
+ if (i % 2 == 0)
+ bcd_out[i/2] = num;
+ else
+ bcd_out[i/2] |= (num << 4);
+ }
+
+ return 0;
+}
+
+/* Chapter 8.5.6 */
+struct msgb *rsl_imm_assign_cmd_common(struct gsm_bts *bts, uint8_t len, uint8_t *val)
+{
+ struct msgb *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);
+}
+
+/* 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);
+
+ 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);
+
+ return 0;
+}
+
+static bool msg_for_osmocom_dyn_ts(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+ if (msg->lchan->ts->pchan_on_init != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ return false;
+ /* dyn TS messages always come in on the first lchan of a timeslot */
+ if (msg->lchan->nr != 0)
+ return false;
+ return (rslh->chan_nr & RSL_CHAN_OSMO_PDCH) == RSL_CHAN_OSMO_PDCH;
+}
+
+/* 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;
+ struct gsm_lchan *lchan = msg->lchan;
+ const uint8_t *cause_p;
+
+ rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]);
+
+ if (dh->ie_chan != RSL_IE_CHAN_NR) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Invalid IE: expected CHAN_NR IE (0x%x), got 0x%x\n",
+ RSL_IE_CHAN_NR, dh->ie_chan);
+ return -EINVAL;
+ }
+
+ rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ cause_p = rsl_cause(&tp);
+ LOG_LCHAN(lchan, LOGL_ERROR, "CHANNEL ACTIVATE NACK%s\n", rsl_cause_name(&tp));
+
+ if (msg_for_osmocom_dyn_ts(msg))
+ osmo_fsm_inst_dispatch(lchan->ts->fi, TS_EV_PDCH_ACT_NACK, (void*)cause_p);
+ else
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RSL_CHAN_ACTIV_NACK, (void*)cause_p);
+ 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;
+ const uint8_t *cause_p;
+
+ rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ cause_p = rsl_cause(&tp);
+
+ LOG_LCHAN(lchan, LOGL_ERROR, "CONNECTION FAIL%s\n", rsl_cause_name(&tp));
+
+ rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]);
+
+ /* If the lchan is associated with a conn, we shall notify the MSC of the RSL Conn Failure, and
+ * the connection will presumably be torn down and lead to an lchan release. During initial
+ * Channel Request from the MS, an lchan has no conn yet, so in that case release now. */
+ if (!lchan->conn)
+ lchan_release(lchan, false, true, *cause_p);
+ else
+ osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_RSL_CONN_FAIL, (void*)cause_p);
+
+ return 0;
+}
+
+static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru,
+ const char *prefix)
+{
+ 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;
+
+ if (!lchan_may_receive_data(msg->lchan)) {
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "MEAS RES for inactive channel\n");
+ 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;
+ struct handover_rr_detect_data d = {
+ .msg = msg,
+ };
+
+ rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+ if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY))
+ d.access_delay = TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY);
+
+ if (!msg->lchan->conn || !msg->lchan->conn->ho.fi) {
+ LOGP(DRSL, LOGL_ERROR, "%s HANDOVER DETECT but no handover is ongoing\n",
+ gsm_lchan_name(msg->lchan));
+ return 0;
+ }
+
+ osmo_fsm_inst_dispatch(msg->lchan->conn->ho.fi, HO_EV_RR_HO_DETECT, &d);
+
+ return 0;
+}
+
+static int rsl_rx_ipacc_pdch(struct msgb *msg, char *name, uint32_t ts_ev)
+{
+ struct gsm_bts_trx_ts *ts = msg->lchan->ts;
+
+ if (ts->pchan_on_init != GSM_PCHAN_TCH_F_PDCH) {
+ LOG_TS(ts, LOGL_ERROR, "Rx RSL ip.access PDCH %s acceptable only for %s\n",
+ name, gsm_pchan_name(GSM_PCHAN_TCH_F_PDCH));
+ return -EINVAL;
+ }
+
+ osmo_fsm_inst_dispatch(ts->fi, ts_ev, NULL);
+ return 0;
+}
+
+static int abis_rsl_rx_dchan(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+ int rc = 0;
+ struct e1inp_sign_link *sign_link = msg->dst;
+
+ if (rslh->ie_chan != RSL_IE_CHAN_NR) {
+ LOGP(DRSL, LOGL_ERROR,
+ "Rx RSL DCHAN: invalid RSL header, expecting Channel Number IE tag, got 0x%x\n",
+ rslh->ie_chan);
+ return -EINVAL;
+ }
+
+ msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr,
+ "Abis RSL rx DCHAN: ");
+ if (!msg->lchan) {
+ LOGP(DRSL, LOGL_ERROR,
+ "Rx RSL DCHAN: unable to match RSL message to an lchan: chan_nr=0x%x\n",
+ rslh->chan_nr);
+ return -EINVAL;
+ }
+
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "Rx %s\n", rsl_or_ipac_msg_name(rslh->c.msg_type));
+
+ if (!msg->lchan->fi) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL DCHAN: RSL message for unconfigured lchan\n");
+ return -EINVAL;
+ }
+
+ switch (rslh->c.msg_type) {
+ case RSL_MT_CHAN_ACTIV_ACK:
+ if (msg_for_osmocom_dyn_ts(msg))
+ osmo_fsm_inst_dispatch(msg->lchan->ts->fi, TS_EV_PDCH_ACT_ACK, NULL);
+ else {
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RSL_CHAN_ACTIV_ACK, NULL);
+ 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:
+ if (msg_for_osmocom_dyn_ts(msg))
+ osmo_fsm_inst_dispatch(msg->lchan->ts->fi, TS_EV_PDCH_DEACT_ACK, NULL);
+ else
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RSL_RF_CHAN_REL_ACK, NULL);
+ break;
+ case RSL_MT_MODE_MODIFY_ACK:
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CHANNEL MODE MODIFY ACK\n");
+ count_codecs(sign_link->trx->bts, msg->lchan);
+ break;
+ case RSL_MT_MODE_MODIFY_NACK:
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CHANNEL MODE MODIFY NACK\n");
+ rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_MODE_MODIFY_NACK]);
+ break;
+ case RSL_MT_IPAC_PDCH_ACT_ACK:
+ rc = rsl_rx_ipacc_pdch(msg, "ACT ACK", TS_EV_PDCH_ACT_ACK);
+ break;
+ case RSL_MT_IPAC_PDCH_ACT_NACK:
+ rc = rsl_rx_ipacc_pdch(msg, "ACT NACK", TS_EV_PDCH_ACT_NACK);
+ break;
+ case RSL_MT_IPAC_PDCH_DEACT_ACK:
+ rc = rsl_rx_ipacc_pdch(msg, "DEACT ACK", TS_EV_PDCH_DEACT_ACK);
+ break;
+ case RSL_MT_IPAC_PDCH_DEACT_NACK:
+ rc = rsl_rx_ipacc_pdch(msg, "DEACT NACK", TS_EV_PDCH_DEACT_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:
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unimplemented Abis RSL DChan msg 0x%02x\n",
+ rslh->c.msg_type);
+ rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ break;
+ default:
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown Abis RSL DChan msg 0x%02x\n",
+ rslh->c.msg_type);
+ rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ 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;
+
+ rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh));
+
+ LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT%s\n",
+ gsm_trx_name(sign_link->trx), rsl_cause_name(&tp));
+
+ 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;
+}
+
+/* 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);
+}
+
+int rsl_tx_imm_ass_rej(struct gsm_bts *bts, struct gsm48_req_ref *rqd_ref)
+{
+ uint8_t wait_ind;
+ wait_ind = bts->T3122;
+ if (!wait_ind)
+ wait_ind = T_def_get(bts->network->T_defs, 3122, T_S, -1);
+ if (!wait_ind)
+ wait_ind = GSM_T3122_DEFAULT;
+ /* The BTS will gather multiple CHAN RQD and reject up to 4 MS at the same time. */
+ return rsl_send_imm_ass_rej(bts, rqd_ref, wait_ind);
+}
+
+/* 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 lchan_activate_info info;
+ 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;
+
+ /* 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_select_by_type(bts, GSM_LCHAN_SDCCH);
+ 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_select_by_type(bts, lctype);
+ }
+ 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(GSM_LCHAN_TCH_H));
+ lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_H);
+ }
+ 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(GSM_LCHAN_TCH_F));
+ lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_F);
+ }
+ if (!lchan) {
+ 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]);
+ rsl_tx_imm_ass_rej(bts, rqd_ref);
+ return 0;
+ }
+
+ /* save the RACH data as we need it after the CHAN ACT ACK */
+ lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref);
+ OSMO_ASSERT(lchan->rqd_ref);
+
+ *(lchan->rqd_ref) = *rqd_ref;
+ lchan->rqd_ta = rqd_ta;
+
+ LOG_LCHAN(lchan, LOGL_DEBUG, "MS: Channel Request: reason=%s ra=0x%02x ta=%d\n",
+ gsm_chreq_name(chreq_reason), rqd_ref->ra, rqd_ta);
+ info = (struct lchan_activate_info){
+ .activ_for = FOR_MS_CHANNEL_REQUEST,
+ .chan_mode = GSM48_CMODE_SIGN,
+ };
+
+ lchan_activate(lchan, &info);
+ return 0;
+}
+
+int rsl_tx_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);
+
+ /* 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)) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "ERROR INDICATION without mandantory cause.\n");
+ return -1;
+ }
+
+ rlm_cause = *TLVP_VAL(&tp, RSL_IE_RLM_CAUSE);
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "ERROR INDICATION cause=%s\n", rsl_rlm_cause_name(rlm_cause));
+
+ rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND);
+
+ rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR]);
+
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_ERR_IND, &rlm_cause);
+
+ return 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;
+ uint8_t sapi = rllh->link_id & 0x7;
+
+ msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, "Abis RSL rx RLL: ");
+
+ switch (rllh->c.msg_type) {
+ case RSL_MT_DATA_IND:
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "SAPI=%u DATA INDICATION\n", sapi);
+ if (msgb_l2len(msg) >
+ sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+ 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:
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "SAPI=%u ESTABLISH INDICATION\n", sapi);
+ /* 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) {
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE,
+ "MS attempted to establish DCCH on SAPI=%d (expected SAPI=0)\n",
+ sapi);
+ /* 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) {
+ LOG_LCHAN(msg->lchan, 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[sapi] = LCHAN_SAPI_MS;
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_ESTABLISH_IND, msg);
+
+ if (msgb_l2len(msg) >
+ sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+ 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:
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "SAPI=%u ESTABLISH CONFIRM\n", sapi);
+ msg->lchan->sapis[sapi] = 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 */
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_REL_IND, &rllh->link_id);
+ break;
+ case RSL_MT_REL_CONF:
+ /* BTS informs us of having received UA from MS,
+ * in response to DISC that we've sent earlier */
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_REL_CONF, &rllh->link_id);
+ break;
+ case RSL_MT_ERROR_IND:
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "SAPI=%u ERROR INDICATION\n", sapi);
+ rc = rsl_rx_rll_err_ind(msg);
+ break;
+ case RSL_MT_UNIT_DATA_IND:
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "SAPI=%u UNIT DATA INDICATION:"
+ " unimplemented Abis RLL message type 0x%02x\n", sapi, rllh->c.msg_type);
+ break;
+ default:
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "SAPI=%u Unknown Abis RLL message type 0x%02x\n",
+ sapi, rllh->c.msg_type);
+ rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ }
+ return rc;
+}
+
+/* Return an ip.access BTS speech mode value (uint8_t) or negative on error. */
+int ipacc_speech_mode(enum gsm48_chan_mode tch_mode, enum gsm_chan_t type)
+{
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ return 0x00;
+ case GSM_LCHAN_TCH_H:
+ return 0x03;
+ default:
+ break;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ return 0x01;
+ /* there's no half-rate EFR */
+ default:
+ break;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ return 0x02;
+ case GSM_LCHAN_TCH_H:
+ return 0x05;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+void ipacc_speech_mode_set_direction(uint8_t *speech_mode, bool send)
+{
+ const uint8_t recv_only_flag = 0x10;
+ if (send)
+ *speech_mode = *speech_mode & ~recv_only_flag;
+ else
+ *speech_mode = *speech_mode | recv_only_flag;
+}
+
+/* Return an ip.access BTS payload type value (uint8_t) or negative on error. */
+int ipacc_payload_type(enum gsm48_chan_mode tch_mode, enum gsm_chan_t type)
+{
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ switch (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 (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 (type) {
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ return RTP_PT_AMR;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+const char *ip_to_a(uint32_t ip)
+{
+ struct in_addr ia;
+ ia.s_addr = htonl(ip);
+ return inet_ntoa(ia);
+}
+
+/* ip.access specific RSL extensions */
+static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv, const char *label)
+{
+ 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);
+ 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);
+ 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);
+ 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);
+ }
+
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) {
+ lchan->abis_ip.speech_mode =
+ *TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE);
+ }
+
+ /* Why would we receive the MGW IP and port back from the BTS, and why would we care?? */
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) {
+ ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_REMOTE_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);
+ lchan->abis_ip.connect_port = port;
+ }
+
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Rx IPACC %s ACK:"
+ " BTS=%s:%u conn_id=%u rtp_payload2=0x%02x speech_mode=0x%02x\n",
+ label, ip_to_a(lchan->abis_ip.bound_ip), lchan->abis_ip.bound_port,
+ lchan->abis_ip.conn_id, lchan->abis_ip.rtp_payload2, lchan->abis_ip.speech_mode);
+}
+
+/*! Send Issue IPA RSL CRCX to configure the RTP port of the BTS.
+ * \param[in] lchan Logical Channel for which we issue CRCX
+ */
+int rsl_tx_ipacc_crcx(const 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 */
+ 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);
+
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC CRCX to BTS: speech_mode=0x%02x RTP_PAYLOAD=%d\n",
+ lchan->abis_ip.speech_mode, lchan->abis_ip.rtp_payload);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/*! Allocate buffer for IPA RSL MDCX and populate it with given parameters.
+ * \param[in] lchan Logical Channel for which we make MDCX
+ * \param[in] dest_ip The IP address to connect to
+ * \param[in] dest_port The port to connect to
+ */
+struct msgb *rsl_make_ipacc_mdcx(const struct gsm_lchan *lchan, uint32_t dest_ip, uint16_t dest_port)
+{
+ struct msgb *msg = rsl_msgb_alloc();
+ struct abis_rsl_dchan_hdr *dh;
+ uint32_t *att_ip;
+
+ 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);
+
+ msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
+ msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP);
+ att_ip = (uint32_t *)msgb_put(msg, sizeof(uint32_t));
+ *att_ip = htonl(dest_ip);
+ msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, dest_port);
+ msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+ msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
+ if (lchan->abis_ip.rtp_payload2)
+ msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, lchan->abis_ip.rtp_payload2);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return msg;
+}
+
+/*! Send IPA RSL MDCX to configure the RTP port the BTS sends to (MGW).
+ * \param[in] lchan Logical Channel for which we issue MDCX
+ * Remote (MGW) IP address, port and payload types for RTP are determined from lchan->abis_ip.
+ */
+int rsl_tx_ipacc_mdcx(const struct gsm_lchan *lchan)
+{
+ struct msgb *msg = rsl_make_ipacc_mdcx(lchan, lchan->abis_ip.connect_ip, lchan->abis_ip.connect_port);
+
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC MDCX to BTS:"
+ " %s:%u rtp_payload=%u rtp_payload2=%u conn_id=%u speech_mode=0x%02x\n",
+ ip_to_a(lchan->abis_ip.connect_ip),
+ lchan->abis_ip.connect_port,
+ lchan->abis_ip.rtp_payload,
+ lchan->abis_ip.rtp_payload2,
+ lchan->abis_ip.conn_id,
+ lchan->abis_ip.speech_mode);
+
+ 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;
+
+ if (!lchan->fi_rtp) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX ACK message for unconfigured lchan");
+ return -EINVAL;
+ }
+
+ /* 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\n");
+ return -EINVAL;
+ }
+
+ ipac_parse_rtp(lchan, &tv, "CRCX");
+
+ osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_IPACC_CRCX_ACK, 0);
+
+ return 0;
+}
+
+static int abis_rsl_rx_ipacc_crcx_nack(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = msg->dst;
+ struct gsm_lchan *lchan = msg->lchan;
+
+ rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+
+ if (!lchan->fi_rtp) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX NACK message for unconfigured lchan");
+ return -EINVAL;
+ }
+ osmo_fsm_inst_dispatch(msg->lchan->fi_rtp, LCHAN_RTP_EV_IPACC_CRCX_NACK, 0);
+ 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;
+
+ if (!lchan->fi_rtp) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX ACK message for unconfigured lchan");
+ return -EINVAL;
+ }
+
+ /* 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, "MDCX");
+
+ osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_IPACC_MDCX_ACK, 0);
+
+ return 0;
+}
+
+static int abis_rsl_rx_ipacc_mdcx_nack(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = msg->dst;
+ struct gsm_lchan *lchan = msg->lchan;
+
+ rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+
+ if (!lchan->fi_rtp) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX NACK message for unconfigured lchan");
+ return -EINVAL;
+ }
+ osmo_fsm_inst_dispatch(msg->lchan->fi_rtp, LCHAN_RTP_EV_IPACC_MDCX_NACK, 0);
+ 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));
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Rx IPACC DLCX IND%s\n",
+ rsl_cause_name(&tv));
+
+ 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);
+ int rc = 0;
+
+ msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr,
+ "Abis RSL rx IPACC: ");
+
+ if (!msg->lchan) {
+ LOGP(DRSL, LOGL_ERROR,
+ "Rx RSL IPACC: unable to match RSL message to an lchan: chan_nr=0x%x\n",
+ rllh->chan_nr);
+ return -EINVAL;
+ }
+
+ if (!msg->lchan->fi) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: RSL message for unconfigured lchan\n");
+ return -EINVAL;
+ }
+
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "Rx %s\n", rsl_or_ipac_msg_name(rllh->c.msg_type));
+
+ switch (rllh->c.msg_type) {
+ case RSL_MT_IPAC_CRCX_ACK:
+ 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?!? */
+ rc = abis_rsl_rx_ipacc_crcx_nack(msg);
+ break;
+ case RSL_MT_IPAC_MDCX_ACK:
+ /* the BTS tells us that a connect operation was successful */
+ 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 */
+ rc = abis_rsl_rx_ipacc_mdcx_nack(msg);
+ break;
+ case RSL_MT_IPAC_DLCX_IND:
+ rc = abis_rsl_rx_ipacc_dlcx_ind(msg);
+ break;
+ default:
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n",
+ rllh->c.msg_type);
+ rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ break;
+ }
+
+ return rc;
+}
+
+/*! Tx simplified channel (de-)activation message for non-standard Osmocom dyn TS PDCH type. */
+static int send_osmocom_style_pdch_chan_act(struct gsm_bts_trx_ts *ts, bool activate)
+{
+ struct msgb *msg;
+ struct abis_rsl_dchan_hdr *dh;
+
+ msg = rsl_msgb_alloc();
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ init_dchan_hdr(dh, activate ? RSL_MT_CHAN_ACTIV : RSL_MT_RF_CHAN_REL);
+
+ dh->chan_nr = RSL_CHAN_OSMO_PDCH | (ts->nr & ~RSL_CHAN_NR_MASK);
+
+ if (activate) {
+ msgb_tv_put(msg, RSL_IE_ACT_TYPE, RSL_ACT_OSMO_PDCH);
+
+ if (ts->trx->bts->type == GSM_BTS_TYPE_RBS2000
+ && 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 = ts->trx->rsl_link;
+ return abis_rsl_sendmsg(msg);
+}
+
+/*! Tx simplified channel (de-)activation message for non-standard ip.access dyn TS PDCH type. */
+static int send_ipacc_style_pdch_act(struct gsm_bts_trx_ts *ts, bool activate)
+{
+ struct msgb *msg = rsl_msgb_alloc();
+ struct abis_rsl_dchan_hdr *dh;
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ init_dchan_hdr(dh, activate ? RSL_MT_IPAC_PDCH_ACT : RSL_MT_IPAC_PDCH_DEACT);
+ dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ dh->chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0);
+
+ msg->dst = ts->trx->rsl_link;
+ return abis_rsl_sendmsg(msg);
+}
+
+int rsl_tx_dyn_ts_pdch_act_deact(struct gsm_bts_trx_ts *ts, bool activate)
+{
+ int rc;
+ const char *what;
+ const char *act;
+
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ what = "Osmocom dyn TS";
+ act = activate? "PDCH Chan Activ" : "PDCH Chan RF Release";
+
+ rc = send_osmocom_style_pdch_chan_act(ts, activate);
+ break;
+
+ case GSM_PCHAN_TCH_F_PDCH:
+ what = "ip.access dyn TS";
+ act = activate? "PDCH ACT" : "PDCH DEACT";
+
+ rc = send_ipacc_style_pdch_act(ts, activate);
+ break;
+
+ default:
+ what = "static timeslot";
+ act = activate? "dynamic PDCH activation" : "dynamic PDCH deactivation";
+ rc = -EINVAL;
+ break;
+ }
+
+ if (rc)
+ LOG_TS(ts, LOGL_ERROR, "Tx FAILED: %s: %s: %d (%s)\n",
+ what, act, rc, strerror(-rc));
+ else
+ LOG_TS(ts, LOGL_DEBUG, "Tx: %s: %s\n", what, act);
+ return rc;
+}
+
+/* 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);
+}
diff --git a/src/osmo-bsc/acc_ramp.c b/src/osmo-bsc/acc_ramp.c
new file mode 100644
index 000000000..bc2e3fb73
--- /dev/null
+++ b/src/osmo-bsc/acc_ramp.c
@@ -0,0 +1,363 @@
+/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Stefan Sperling <ssperling@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <strings.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/acc_ramp.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+
+/*
+ * 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 <= 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 <= 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 <= 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..54d98a967
--- /dev/null
+++ b/src/osmo-bsc/arfcn_range_encode.c
@@ -0,0 +1,340 @@
+/* gsm 04.08 system information (si) encoding and decoding
+ * 3gpp ts 04.08 version 7.21.0 release 1998 / etsi ts 100 940 v7.21.0 */
+
+/*
+ * (C) 2012 Holger Hans Peter Freyther
+ * (C) 2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <osmocom/bsc/arfcn_range_encode.h>
+#include <osmocom/bsc/debug.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <osmocom/core/utils.h>
+
+#include <errno.h>
+
+static inline int greatest_power_of_2_lesser_or_equal_to(int index)
+{
+ int power_of_2 = 1;
+
+ do {
+ power_of_2 *= 2;
+ } while (power_of_2 <= index);
+
+ /* now go back one step */
+ return power_of_2 / 2;
+}
+
+static inline int mod(int data, int range)
+{
+ int res = data % range;
+ while (res < 0)
+ res += range;
+ return res;
+}
+
+/**
+ * Determine at which index to split the ARFCNs to create an
+ * equally size partition for the given range. Return -1 if
+ * no such partition exists.
+ */
+int range_enc_find_index(enum gsm48_range range, const int *freqs, const int size)
+{
+ int i, j, n;
+
+ const int RANGE_DELTA = (range - 1) / 2;
+
+ for (i = 0; i < size; ++i) {
+ n = 0;
+ for (j = 0; j < size; ++j) {
+ if (mod(freqs[j] - freqs[i], range) <= RANGE_DELTA)
+ n += 1;
+ }
+
+ if (n - 1 == (size - 1) / 2)
+ return i;
+ }
+
+ return -1;
+}
+
+/* Worker for range_enc_arfcns(), do not call directly. */
+int _range_enc_arfcns(enum gsm48_range range,
+ const int *arfcns, int size, int *out,
+ const int index)
+{
+ int split_at;
+ int i;
+
+ /*
+ * The below is a GNU extension and we can remove it when
+ * we move to a quicksort like in-situ swap with the pivot.
+ */
+ int arfcns_left[size / 2];
+ int arfcns_right[size / 2];
+ int l_size;
+ int r_size;
+ int l_origin;
+ int r_origin;
+
+ /* Now do the processing */
+ split_at = range_enc_find_index(range, arfcns, size);
+ if (split_at < 0)
+ return -EINVAL;
+
+ /* we now know where to split */
+ out[index] = 1 + arfcns[split_at];
+
+ /* calculate the work that needs to be done for the leafs */
+ l_origin = mod(arfcns[split_at] + ((range - 1) / 2) + 1, range);
+ r_origin = mod(arfcns[split_at] + 1, range);
+ for (i = 0, l_size = 0, r_size = 0; i < size; ++i) {
+ if (mod(arfcns[i] - l_origin, range) < range / 2)
+ arfcns_left[l_size++] = mod(arfcns[i] - l_origin, range);
+ if (mod(arfcns[i] - r_origin, range) < range / 2)
+ arfcns_right[r_size++] = mod(arfcns[i] - r_origin, range);
+ }
+
+ /*
+ * Now recurse and we need to make this iterative... but as the
+ * tree is balanced the stack will not be too deep.
+ */
+ if (l_size)
+ range_enc_arfcns(range / 2, arfcns_left, l_size,
+ out, index + greatest_power_of_2_lesser_or_equal_to(index + 1));
+ if (r_size)
+ range_enc_arfcns((range - 1) / 2, arfcns_right, r_size,
+ out, index + (2 * greatest_power_of_2_lesser_or_equal_to(index + 1)));
+ return 0;
+}
+
+/**
+ * Range encode the ARFCN list.
+ * \param range The range to use.
+ * \param arfcns The list of ARFCNs
+ * \param size The size of the list of ARFCNs
+ * \param out Place to store the W(i) output.
+ */
+int range_enc_arfcns(enum gsm48_range range,
+ const int *arfcns, int size, int *out,
+ const int index)
+{
+ if (size <= 0)
+ return 0;
+
+ if (size == 1) {
+ out[index] = 1 + arfcns[0];
+ return 0;
+ }
+
+ return _range_enc_arfcns(range, arfcns, size, out, index);
+}
+
+/*
+ * The easiest is to use f0 == arfcns[0]. This means that under certain
+ * circumstances we can encode less ARFCNs than possible with an optimal f0.
+ *
+ * TODO: Solve the optimisation problem and pick f0 so that the max distance
+ * is the smallest. Taking into account the modulo operation. I think picking
+ * size/2 will be the optimal arfcn.
+ */
+/**
+ * This implements the range determination as described in GSM 04.08 J4. The
+ * result will be a base frequency f0 and the range to use. Note that for range
+ * 1024 encoding f0 always refers to ARFCN 0 even if it is not an element of
+ * the arfcns list.
+ *
+ * \param[in] arfcns The input frequencies, they must be sorted, lowest number first
+ * \param[in] size The length of the array
+ * \param[out] f0 The selected F0 base frequency. It might not be inside the list
+ */
+int range_enc_determine_range(const int *arfcns, const int size, int *f0)
+{
+ int max = 0;
+
+ /* don't dereference arfcns[] array if size is 0 */
+ if (size == 0)
+ return ARFCN_RANGE_128;
+
+ /*
+ * Go for the easiest. And pick arfcns[0] == f0.
+ */
+ max = arfcns[size - 1] - arfcns[0];
+ *f0 = arfcns[0];
+
+ if (max < 128 && size <= 29)
+ return ARFCN_RANGE_128;
+ if (max < 256 && size <= 22)
+ return ARFCN_RANGE_256;
+ if (max < 512 && size <= 18)
+ return ARFCN_RANGE_512;
+ if (max < 1024 && size <= 17) {
+ *f0 = 0;
+ return ARFCN_RANGE_1024;
+ }
+
+ return ARFCN_RANGE_INVALID;
+}
+
+static void write_orig_arfcn(uint8_t *chan_list, int f0)
+{
+ chan_list[0] |= (f0 >> 9) & 1;
+ chan_list[1] = (f0 >> 1);
+ chan_list[2] = (f0 & 1) << 7;
+}
+
+static void write_all_wn(uint8_t *chan_list, int bit_offs,
+ int *w, int w_size, int w1_len)
+{
+ int octet_offs = 0; /* offset into chan_list */
+ int wk_len = w1_len; /* encoding size in bits of w[k] */
+ int k; /* 1 based */
+ int level = 0; /* tree level, top level = 0 */
+ int lvl_left = 1; /* nodes per tree level */
+
+ /* W(2^i) to W(2^(i+1)-1) are on w1_len-i bits when present */
+
+ for (k = 1; k <= w_size; k++) {
+ int wk_left = wk_len;
+ DEBUGP(DRR,
+ "k=%d, wk_len=%d, offs=%d:%d, level=%d, "
+ "lvl_left=%d\n",
+ k, wk_len, octet_offs, bit_offs, level, lvl_left);
+
+ while (wk_left > 0) {
+ int cur_bits = 8 - bit_offs;
+ int cur_mask;
+ int wk_slice;
+
+ if (cur_bits > wk_left)
+ cur_bits = wk_left;
+
+ cur_mask = ((1 << cur_bits) - 1);
+
+ DEBUGP(DRR,
+ " wk_left=%d, cur_bits=%d, offs=%d:%d\n",
+ wk_left, cur_bits, octet_offs, bit_offs);
+
+ /* advance */
+ wk_left -= cur_bits;
+ bit_offs += cur_bits;
+
+ /* right aligned wk data for current out octet */
+ wk_slice = (w[k-1] >> wk_left) & cur_mask;
+
+ /* cur_bits now contains the number of bits
+ * that are to be copied from wk to the chan_list.
+ * wk_left is set to the number of bits that must
+ * not yet be copied.
+ * bit_offs points after the bit area that is going to
+ * be overwritten:
+ *
+ * wk_left
+ * |
+ * v
+ * wk: WWWWWWWWWWW
+ * |||||<-- wk_slice, cur_bits=5
+ * --WWWWW-
+ * ^
+ * |
+ * bit_offs
+ */
+
+ DEBUGP(DRR,
+ " wk=%02x, slice=%02x/%02x, cl=%02x\n",
+ w[k-1], wk_slice, cur_mask, wk_slice << (8 - bit_offs));
+
+ chan_list[octet_offs] &= ~(cur_mask << (8 - bit_offs));
+ chan_list[octet_offs] |= wk_slice << (8 - bit_offs);
+
+ /* adjust output */
+ if (bit_offs == 8) {
+ bit_offs = 0;
+ octet_offs += 1;
+ }
+ }
+
+ /* adjust bit sizes */
+ lvl_left -= 1;
+ if (!lvl_left) {
+ /* completed tree level, advance to next */
+ level += 1;
+ lvl_left = 1 << level;
+ wk_len -= 1;
+ }
+ }
+}
+
+int range_enc_range128(uint8_t *chan_list, int f0, int *w)
+{
+ chan_list[0] = 0x8C;
+ write_orig_arfcn(chan_list, f0);
+
+ write_all_wn(&chan_list[2], 1, w, 28, 7);
+ return 0;
+}
+
+int range_enc_range256(uint8_t *chan_list, int f0, int *w)
+{
+ chan_list[0] = 0x8A;
+ write_orig_arfcn(chan_list, f0);
+
+ write_all_wn(&chan_list[2], 1, w, 21, 8);
+ return 0;
+}
+
+int range_enc_range512(uint8_t *chan_list, int f0, int *w)
+{
+ chan_list[0] = 0x88;
+ write_orig_arfcn(chan_list, f0);
+
+ write_all_wn(&chan_list[2], 1, w, 17, 9);
+ return 0;
+}
+
+int range_enc_range1024(uint8_t *chan_list, int f0, int f0_included, int *w)
+{
+ chan_list[0] = 0x80 | (f0_included << 2);
+
+ write_all_wn(&chan_list[0], 6, w, 16, 10);
+ return 0;
+}
+
+int range_enc_filter_arfcns(int *arfcns,
+ const int size, const int f0, int *f0_included)
+{
+ int i, j = 0;
+ *f0_included = 0;
+
+ for (i = 0; i < size; ++i) {
+ /*
+ * Appendix J.4 says the following:
+ * All frequencies except F(0), minus F(0) + 1.
+ * I assume we need to exclude it here.
+ */
+ if (arfcns[i] == f0) {
+ *f0_included = 1;
+ continue;
+ }
+
+ arfcns[j++] = mod(arfcns[i] - (f0 + 1), 1024);
+ }
+
+ return j;
+}
diff --git a/src/osmo-bsc/assignment_fsm.c b/src/osmo-bsc/assignment_fsm.c
new file mode 100644
index 000000000..93362f85b
--- /dev/null
+++ b/src/osmo-bsc/assignment_fsm.c
@@ -0,0 +1,661 @@
+/* osmo-bsc BSSMAP Assignment procedure implementation.
+ *
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/osmo_bsc_lcls.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/abis_rsl.h>
+
+#include <osmocom/bsc/assignment_fsm.h>
+
+static struct osmo_fsm assignment_fsm;
+
+struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &assignment_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+static const struct state_timeout assignment_fsm_timeouts[32] = {
+ [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = { .T=10 },
+ [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { .keep_timer=true },
+ [ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED] = { .keep_timer=true },
+ [ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T=23042 },
+};
+
+/* Transition to a state, using the T timer defined in assignment_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define assignment_fsm_state_chg(state) \
+ fsm_inst_state_chg_T(fi, state, \
+ assignment_fsm_timeouts, \
+ ((struct gsm_subscriber_connection*)(fi->priv))->network->T_defs, \
+ 5)
+
+/* Log failure and transition to ASSIGNMENT_ST_FAILURE, which triggers the appropriate actions. */
+#define assignment_fail(cause, fmt, args...) do { \
+ struct gsm_subscriber_connection *_conn = fi->priv; \
+ _conn->assignment.failure_cause = cause; \
+ LOG_ASSIGNMENT(_conn, LOGL_ERROR, "Assignment failed in state %s, cause %s: " fmt "\n", \
+ osmo_fsm_inst_state_name(fi), gsm0808_cause_name(cause), ## args); \
+ assignment_count_result(BSC_CTR_ASSIGNMENT_ERROR); \
+ on_assignment_failure(_conn); \
+ } while(0)
+
+/* Assume presence of local var 'conn' as struct gsm_subscriber_connection */
+#define assignment_count(counter) do { \
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: %s %s\n", \
+ bsc_ctr_description[counter].name, \
+ bsc_ctr_description[counter].description); \
+ rate_ctr_inc(&conn->network->bsc_ctrs->ctr[counter]); \
+ } while(0)
+
+#define assignment_count_result(counter) do { \
+ if (!conn->assignment.result_rate_ctr_done) { \
+ assignment_count(counter); \
+ conn->assignment.result_rate_ctr_done = true; \
+ } else \
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, \
+ "result rate counter already recorded, NOT counting as: %s %s", \
+ bsc_ctr_description[counter].name, \
+ bsc_ctr_description[counter].description); \
+ } while(0)
+
+void assignment_reset(struct gsm_subscriber_connection *conn)
+{
+ if (conn->assignment.new_lchan) {
+ struct gsm_lchan *lchan = conn->assignment.new_lchan;
+ conn->assignment.new_lchan = NULL;
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ }
+
+ if (conn->assignment.created_ci_for_msc) {
+ gscon_forget_mgw_endpoint_ci(conn, conn->assignment.created_ci_for_msc);
+ /* If this is the last endpoint released, the mgw_endpoint_fsm will terminate and tell
+ * the gscon about it. */
+ mgw_endpoint_ci_dlcx(conn->assignment.created_ci_for_msc);
+ }
+
+ conn->assignment = (struct assignment_fsm_data){
+ .fi = conn->assignment.fi, /* The FSM shall clear itself when it's done. */
+ };
+}
+
+static void on_assignment_failure(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *resp = gsm0808_create_assignment_failure(conn->assignment.failure_cause, NULL);
+
+ if (!resp)
+ LOG_ASSIGNMENT(conn, LOGL_ERROR, "Unable to compose BSSMAP Assignment Failure message\n");
+ else
+ gscon_sigtran_send(conn, resp);
+
+ /* If assignment failed as early as in assignment_fsm_start(), there may not be an fi yet. */
+ if (conn->assignment.fi) {
+ LOG_ASSIGNMENT(conn, LOGL_ERROR, "Assignment failed\n");
+ osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_ERROR, 0);
+ }
+}
+
+static void send_assignment_complete(struct gsm_subscriber_connection *conn)
+{
+ int rc;
+ struct gsm0808_speech_codec sc;
+ struct gsm0808_speech_codec *sc_ptr = NULL;
+ struct sockaddr_storage addr_local;
+ struct sockaddr_storage *addr_local_p = NULL;
+ int perm_spch = 0;
+ uint8_t chosen_channel;
+ struct msgb *resp;
+ struct gsm_lchan *lchan = conn->lchan;
+ struct osmo_fsm_inst *fi = conn->fi;
+
+ chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode);
+ if (!chosen_channel) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable to compose Chosen Channel for mode=%s type=%s",
+ get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
+ gsm_lchant_name(lchan->type));
+ return;
+ }
+
+ /* Generate voice related fields */
+ if (conn->assignment.requires_voice_stream) {
+ perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode);
+
+ if (gscon_is_aoip(conn)) {
+ if (!mgwep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc,
+ &addr_local)) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable to compose RTP address of MGW -> MSC");
+ return;
+ }
+ addr_local_p = &addr_local;
+ }
+
+ /* Only AoIP networks include a speech codec (choosen) in the
+ * assignment complete message. */
+ if (gscon_is_aoip(conn)) {
+ /* Extrapolate speech codec from speech mode */
+ gsm0808_speech_codec_from_chan_type(&sc, perm_spch);
+ sc.cfg = conn->assignment.req.s15_s0;
+ sc_ptr = &sc;
+ }
+ }
+
+ resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause,
+ chosen_channel,
+ lchan->encr.alg_id, perm_spch,
+ addr_local_p, sc_ptr, NULL);
+
+ if (!resp) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable to compose Assignment Complete message");
+ return;
+ }
+
+ /* Add LCLS BSS-Status IE in case there is any LCLS status for this connection */
+ bssmap_add_lcls_status_if_needed(conn, resp);
+
+ rc = gscon_sigtran_send(conn, resp);
+ if (rc) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable send Assignment Complete message: rc=%d %s",
+ rc, strerror(-rc));
+ return;
+ }
+}
+
+static void assignment_success(struct gsm_subscriber_connection *conn)
+{
+ /* Take on the new lchan */
+ gscon_change_primary_lchan(conn, conn->assignment.new_lchan);
+ conn->assignment.new_lchan = NULL;
+
+ /* apply LCLS configuration (if any) */
+ lcls_apply_config(conn);
+
+ send_assignment_complete(conn);
+ /* If something went wrong during send_assignment_complete(), the fi will be gone from
+ * error handling in there. Almost a success, but then again the whole thing failed. */
+ if (!conn->assignment.fi) {
+ /* The lchan was ready, and we failed to tell the MSC about it. By releasing this lchan,
+ * the conn will notice that its primary lchan is gone and should clean itself up. */
+ lchan_release(conn->lchan, true, true, RSL_ERR_EQUIPMENT_FAIL);
+ return;
+ }
+
+ /* Rembered this only for error handling: should assignment fail, assignment_reset() will release
+ * the MGW endpoint right away. If successful, the conn continues to use the endpoint. */
+ conn->assignment.created_ci_for_msc = NULL;
+
+ /* New RTP information is now accepted */
+ conn->user_plane.msc_assigned_cic = conn->assignment.req.msc_assigned_cic;
+ osmo_strlcpy(conn->user_plane.msc_assigned_rtp_addr, conn->assignment.req.msc_rtp_addr,
+ sizeof(conn->user_plane.msc_assigned_rtp_addr));
+ conn->user_plane.msc_assigned_rtp_port = conn->assignment.req.msc_rtp_port;
+
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Assignment successful\n");
+ osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_REGULAR, 0);
+
+ assignment_count_result(BSC_CTR_ASSIGNMENT_COMPLETED);
+}
+
+static void assignment_fsm_update_id(struct gsm_subscriber_connection *conn)
+{
+ struct gsm_lchan *new_lchan = conn->assignment.new_lchan;
+ if (!new_lchan) {
+ osmo_fsm_inst_update_id(conn->assignment.fi, conn->fi->id);
+ return;
+ }
+
+ osmo_fsm_inst_update_id_f(conn->assignment.fi, "%s_%u-%u-%u-%s%s%s-%u",
+ conn->fi->id,
+ new_lchan->ts->trx->bts->nr, new_lchan->ts->trx->nr, new_lchan->ts->nr,
+ gsm_pchan_id(new_lchan->ts->pchan_on_init),
+ (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is)? "" : "as",
+ (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is)? ""
+ : gsm_pchan_id(new_lchan->ts->pchan_is),
+ new_lchan->nr);
+}
+
+static bool lchan_type_compat_with_mode(enum gsm_chan_t type,
+ enum gsm48_chan_mode chan_mode, int full_rate)
+{
+ switch (chan_mode) {
+ case GSM48_CMODE_SIGN:
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ case GSM_LCHAN_SDCCH:
+ return true;
+ default:
+ return false;
+ }
+
+ 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 (type) {
+ case GSM_LCHAN_TCH_F:
+ return full_rate;
+ case GSM_LCHAN_TCH_H:
+ return !full_rate;
+ default:
+ return false;
+ }
+
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_SPEECH_EFR:
+ /* these services all explicitly require a TCH/F */
+ return type == GSM_LCHAN_TCH_F;
+
+ default:
+ return false;
+ }
+}
+
+void assignment_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&assignment_fsm) == 0);
+}
+
+void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts *bts,
+ struct assignment_request *req)
+{
+ struct osmo_fsm_inst *fi;
+ struct lchan_activate_info info;
+
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(conn->fi);
+ OSMO_ASSERT(!conn->assignment.fi);
+ OSMO_ASSERT(!conn->assignment.new_lchan);
+
+ assignment_count(BSC_CTR_ASSIGNMENT_ATTEMPTED);
+
+ fi = osmo_fsm_inst_alloc_child(&assignment_fsm, conn->fi, GSCON_EV_ASSIGNMENT_END);
+ OSMO_ASSERT(fi);
+ conn->assignment.fi = fi;
+ fi->priv = conn;
+
+ conn->assignment.req = *req;
+
+ switch (req->chan_mode) {
+
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_AMR:
+ conn->assignment.requires_voice_stream = true;
+ /* Select an lchan below. */
+ break;
+
+ case GSM48_CMODE_SIGN:
+ conn->assignment.requires_voice_stream = false;
+ /* Select an lchan below. */
+ break;
+
+ default:
+ assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP,
+ "Channel mode not supported: %s",
+ gsm48_chan_mode_name(req->chan_mode));
+ return;
+ }
+
+ if (conn->lchan
+ && lchan_type_compat_with_mode(conn->lchan->type, req->chan_mode, req->full_rate)) {
+
+ if (conn->lchan->tch_mode == req->chan_mode) {
+ /* current lchan suffices and already is in the right mode. We're done. */
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG,
+ "Current lchan is compatible with requested chan_mode,"
+ " sending BSSMAP Assignment Complete directly."
+ " requested chan_mode=%s; current lchan is %s\n",
+ gsm48_chan_mode_name(req->chan_mode),
+ gsm_lchan_name(conn->lchan));
+ send_assignment_complete(conn);
+ return;
+ }
+
+ /* FIXME: send Channel Mode Modify to put the current lchan in the right mode, and kick
+ * off its RTP stream setup code path. See gsm48_lchan_modify() and
+ * gsm48_rx_rr_modif_ack(), and see lchan_fsm.h LCHAN_EV_CHAN_MODE_MODIF_* */
+ LOG_ASSIGNMENT(conn, LOGL_ERROR,
+ "NOT IMPLEMENTED:"
+ " Current lchan would be compatible, we should send Channel Mode Modify\n");
+ }
+
+ conn->assignment.new_lchan = lchan_select_by_chan_mode(bts, req->chan_mode, req->full_rate);
+
+ if (!conn->assignment.new_lchan) {
+ assignment_count_result(BSC_CTR_ASSIGNMENT_NO_CHANNEL);
+ assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "BSSMAP Assignment Command:"
+ " No lchan available for: chan_mode=%s, full_rate=%i\n",
+ get_value_string(gsm48_chan_mode_names, req->chan_mode), req->full_rate);
+ return;
+ }
+
+ assignment_fsm_update_id(conn);
+ LOG_ASSIGNMENT(conn, LOGL_INFO, "Starting Assignment: chan_mode=%s, full_rate=%d,"
+ " aoip=%s MSC-rtp=%s:%u\n",
+ gsm48_chan_mode_name(req->chan_mode), req->full_rate,
+ req->aoip ? "yes" : "no", req->msc_rtp_addr, req->msc_rtp_port);
+
+ assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE);
+ info = (struct lchan_activate_info){
+ .activ_for = FOR_ASSIGNMENT,
+ .for_conn = conn,
+ .chan_mode = req->chan_mode,
+ .s15_s0 = req->s15_s0,
+ .requires_voice_stream = conn->assignment.requires_voice_stream,
+ .msc_assigned_cic = req->msc_assigned_cic,
+ .old_lchan = conn->lchan,
+ };
+ lchan_activate(conn->assignment.new_lchan, &info);
+}
+
+static void assignment_fsm_wait_lchan(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ switch (event) {
+
+ case ASSIGNMENT_EV_LCHAN_ACTIVE:
+ if (data != conn->assignment.new_lchan)
+ return;
+
+ /* The TS may have changed its pchan_is */
+ assignment_fsm_update_id(conn);
+
+ assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void assignment_fsm_wait_rr_ass_complete_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+
+ rc = gsm48_send_rr_ass_cmd(conn->lchan, conn->assignment.new_lchan,
+ conn->lchan->ms_power);
+
+ if (rc)
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, "Unable to send RR Assignment Command");
+}
+
+static uint8_t get_cause(void *data)
+{
+ if (data)
+ return *(uint8_t*)data;
+ return GSM0808_CAUSE_EQUIPMENT_FAILURE;
+}
+
+static void assignment_fsm_wait_rr_ass_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ switch (event) {
+
+ case ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE:
+ assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED);
+ return;
+
+ case ASSIGNMENT_EV_LCHAN_ESTABLISHED:
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "lchan established, still waiting for RR Assignment Complete\n");
+ /* The lchan is already done with all of its RTP setup. We will notice the lchan state
+ * being LCHAN_ST_ESTABLISHED in assignment_fsm_wait_lchan_established_onenter(). */
+ return;
+
+ case ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL:
+ assignment_count_result(BSC_CTR_ASSIGNMENT_FAILED);
+ assignment_fail(get_cause(data), "Rx RR Assignment Failure");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void assignment_fsm_post_lchan_established(struct osmo_fsm_inst *fi);
+
+static void assignment_fsm_wait_lchan_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ /* Do we still need to wait for the RTP stream at all? */
+ if (lchan_state_is(conn->assignment.new_lchan, LCHAN_ST_ESTABLISHED)) {
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "lchan fully established, no need to wait");
+ assignment_fsm_post_lchan_established(fi);
+ }
+}
+
+static void assignment_fsm_wait_lchan_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case ASSIGNMENT_EV_LCHAN_ESTABLISHED:
+ assignment_fsm_post_lchan_established(fi);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void assignment_fsm_post_lchan_established(struct osmo_fsm_inst *fi)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ if (conn->assignment.requires_voice_stream)
+ assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC);
+ else
+ assignment_success(conn);
+}
+
+static void assignment_fsm_wait_mgw_endpoint_to_msc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+
+ OSMO_ASSERT(conn->assignment.requires_voice_stream);
+
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG,
+ "Connecting MGW endpoint to the MSC's RTP port: %s:%u\n",
+ conn->assignment.req.msc_rtp_addr,
+ conn->assignment.req.msc_rtp_port);
+
+ if (!gscon_connect_mgw_to_msc(conn,
+ conn->assignment.new_lchan,
+ conn->assignment.req.msc_rtp_addr,
+ conn->assignment.req.msc_rtp_port,
+ fi,
+ ASSIGNMENT_EV_MSC_MGW_OK,
+ ASSIGNMENT_EV_MSC_MGW_FAIL,
+ NULL,
+ &conn->assignment.created_ci_for_msc)) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable to connect MGW endpoint to the MSC side");
+ return;
+ }
+}
+
+static void assignment_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ switch (event) {
+
+ case ASSIGNMENT_EV_MSC_MGW_OK:
+ /* For AoIP, we created the MGW endpoint. Ensure it is really there, and log it. */
+ if (gscon_is_aoip(conn)) {
+ const struct mgcp_conn_peer *mgw_info;
+ mgw_info = mgwep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
+ if (!mgw_info) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable to retrieve RTP port info allocated by MGW for"
+ " the MSC side.");
+ return;
+ }
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "MGW's MSC side CI: %s:%u\n",
+ mgw_info->addr, mgw_info->port);
+ }
+ assignment_success(conn);
+ return;
+
+ case ASSIGNMENT_EV_MSC_MGW_FAIL:
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable to connect MGW endpoint to the MSC side");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state assignment_fsm_states[] = {
+ [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = {
+ .name = "WAIT_LCHAN_ACTIVE",
+ .action = assignment_fsm_wait_lchan,
+ .in_event_mask = 0
+ | S(ASSIGNMENT_EV_LCHAN_ACTIVE)
+ ,
+ .out_state_mask = 0
+ | S(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE)
+ | S(ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE)
+ ,
+ },
+ [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = {
+ .name = "WAIT_RR_ASS_COMPLETE",
+ .onenter = assignment_fsm_wait_rr_ass_complete_onenter,
+ .action = assignment_fsm_wait_rr_ass_complete,
+ .in_event_mask = 0
+ | S(ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE)
+ | S(ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL)
+ | S(ASSIGNMENT_EV_LCHAN_ESTABLISHED)
+ ,
+ .out_state_mask = 0
+ | S(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED)
+ ,
+ },
+ [ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED] = {
+ .name = "WAIT_LCHAN_ESTABLISHED",
+ .onenter = assignment_fsm_wait_lchan_established_onenter,
+ .action = assignment_fsm_wait_lchan_established,
+ .in_event_mask = 0
+ | S(ASSIGNMENT_EV_LCHAN_ESTABLISHED)
+ ,
+ .out_state_mask = 0
+ | S(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC)
+ ,
+ },
+ [ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC] = {
+ .name = "WAIT_MGW_ENDPOINT_TO_MSC",
+ .onenter = assignment_fsm_wait_mgw_endpoint_to_msc_onenter,
+ .action = assignment_fsm_wait_mgw_endpoint_to_msc,
+ .in_event_mask = 0
+ | S(ASSIGNMENT_EV_MSC_MGW_OK)
+ | S(ASSIGNMENT_EV_MSC_MGW_FAIL)
+ ,
+ },
+};
+
+static const struct value_string assignment_fsm_event_names[] = {
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ACTIVE),
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ESTABLISHED),
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ERROR),
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_OK),
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_FAIL),
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE),
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL),
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_CONN_RELEASING),
+ {}
+};
+
+void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ switch (event) {
+
+ case ASSIGNMENT_EV_CONN_RELEASING:
+ assignment_count_result(BSC_CTR_ASSIGNMENT_STOPPED);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+ return;
+
+ case ASSIGNMENT_EV_LCHAN_ERROR:
+ if (data != conn->assignment.new_lchan)
+ return;
+ assignment_fail(conn->assignment.new_lchan->activate.gsm0808_error_cause,
+ "Failed to activate lchan %s",
+ gsm_lchan_name(conn->assignment.new_lchan));
+ return;
+
+ default:
+ return;
+ }
+}
+
+int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ assignment_count_result(BSC_CTR_ASSIGNMENT_TIMEOUT);
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, "Timeout");
+ return 0;
+}
+
+void assignment_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ assignment_reset(conn);
+ conn->assignment.fi = NULL;
+}
+
+static struct osmo_fsm assignment_fsm = {
+ .name = "assignment",
+ .states = assignment_fsm_states,
+ .num_states = ARRAY_SIZE(assignment_fsm_states),
+ .log_subsys = DAS,
+ .event_names = assignment_fsm_event_names,
+ .allstate_action = assignment_fsm_allstate_action,
+ .allstate_event_mask = 0
+ | S(ASSIGNMENT_EV_CONN_RELEASING)
+ | S(ASSIGNMENT_EV_LCHAN_ERROR)
+ ,
+ .timer_cb = assignment_fsm_timer_cb,
+ .cleanup = assignment_fsm_cleanup,
+};
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 <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <errno.h>
+#include <time.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+
+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 <daniel@totalueberwachung.de>
+ * (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 <errno.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/bsc/ctrl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+
+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_init.c b/src/osmo-bsc/bsc_init.c
new file mode 100644
index 000000000..2f44b200a
--- /dev/null
+++ b/src/osmo-bsc/bsc_init.c
@@ -0,0 +1,291 @@
+/* A hackish minimal BSC (+MSC +HLR) implementation */
+
+/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/misdn.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <osmocom/bsc/pcu_if.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/neighbor_ident.h>
+
+#include <time.h>
+#include <limits.h>
+#include <stdbool.h>
+
+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;
+ net->neighbor_bss_cells = neighbor_ident_init(net);
+
+ /* init statistics */
+ net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0);
+ if (!net->bsc_ctrs) {
+ talloc_free(net);
+ return NULL;
+ }
+
+ INIT_LLIST_HEAD(&net->bts_rejected);
+ 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..791abf6cb
--- /dev/null
+++ b/src/osmo-bsc/bsc_rf_ctrl.c
@@ -0,0 +1,542 @@
+/* RF Ctl handling socket */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2014 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/ipaccess.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#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;
+ if (osmo_strlcpy(local.sun_path, path, sizeof(local.sun_path)) >= sizeof(local.sun_path)) {
+ LOGP(DLINP, LOGL_ERROR, "Socket path exceeds maximum length of %zd bytes: %s\n",
+ sizeof(local.sun_path), path);
+ return -1;
+ }
+ if (unlink(local.sun_path) < 0 && errno != ENOENT) {
+ LOGP(DLINP, LOGL_ERROR, "Could not unlink socket path %s: %d/%s\n",
+ path, errno, strerror(errno));
+ return -1;
+ }
+
+ /* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/bsc/bsc_rll.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/signal.h>
+
+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..074c238df
--- /dev/null
+++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c
@@ -0,0 +1,1071 @@
+/* (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/a_reset.h>
+#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/osmo_bsc_lcls.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/penalty_timers.h>
+#include <osmocom/bsc/bsc_rll.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/assignment_fsm.h>
+#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+#include <osmocom/core/byteswap.h>
+
+#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
+
+enum gscon_fsm_states {
+ ST_INIT,
+ /* waiting for CC from MSC */
+ ST_WAIT_CC,
+ /* active connection */
+ ST_ACTIVE,
+ ST_ASSIGNMENT,
+ ST_HANDOVER,
+ /* BSSMAP CLEAR has been received */
+ ST_CLEARING,
+};
+
+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_CLEAR_CMD, "CLEAR_CMD"},
+ {GSCON_EV_A_DISC_IND, "DISCONNET.ind"},
+ {GSCON_EV_ASSIGNMENT_START, "ASSIGNMENT_START"},
+ {GSCON_EV_ASSIGNMENT_END, "ASSIGNMENT_END"},
+ {GSCON_EV_HANDOVER_START, "HANDOVER_START"},
+ {GSCON_EV_HANDOVER_END, "HANDOVER_END"},
+ {GSCON_EV_RSL_CONN_FAIL, "RSL_CONN_FAIL"},
+ {GSCON_EV_MO_DTAP, "MO_DTAP"},
+ {GSCON_EV_MT_DTAP, "MT_DTAP"},
+ {GSCON_EV_TX_SCCP, "TX_SCCP"},
+ {GSCON_EV_MGW_MDCX_RESP_MSC, "MGW_MDCX_RESP_MSC"},
+ {GSCON_EV_LCLS_FAIL, "LCLS_FAIL"},
+ {GSCON_EV_FORGET_LCHAN, "FORGET_LCHAN"},
+ {GSCON_EV_FORGET_MGW_ENDPOINT, "FORGET_MGW_ENDPOINT"},
+ {}
+};
+
+struct state_timeout conn_fsm_timeouts[32] = {
+ [ST_WAIT_CC] = { .T = 993210 },
+ [ST_CLEARING] = { .T = 999 },
+};
+
+/* Transition to a state, using the T timer defined in conn_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable 'conn' exists. */
+#define conn_fsm_state_chg(state) \
+ fsm_inst_state_chg_T(conn->fi, state, \
+ conn_fsm_timeouts, \
+ conn->network->T_defs, \
+ -1)
+
+/* forward MT DTAP from BSSAP side to RSL side */
+static inline void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(conn);
+ gscon_submit_rsl_dtap(conn, msg, OBSC_LINKID_CB(msg), 1);
+}
+
+static void gscon_dtap_queue_flush(struct gsm_subscriber_connection *conn, int send);
+
+int gscon_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ int rc;
+
+ if (!msg)
+ return -ENOMEM;
+
+ /* Make sure that we only attempt to send SCCP messages if we have
+ * a life SCCP connection. Otherwise drop the message. */
+ if (conn->fi->state == ST_INIT || conn->fi->state == ST_WAIT_CC) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "No active SCCP connection, dropping message\n");
+ msgb_free(msg);
+ return -ENODEV;
+ }
+
+ rc = osmo_bsc_sigtran_send(conn, msg);
+ if (rc < 0)
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver SCCP message\n");
+ return rc;
+}
+
+static void gscon_bssmap_clear(struct gsm_subscriber_connection *conn,
+ enum gsm0808_cause cause)
+{
+ struct msgb *resp = gsm0808_create_clear_rqst(cause);
+ int rc;
+ if (!resp) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to compose BSSMAP Clear Request message\n");
+ return;
+ }
+ rc = osmo_bsc_sigtran_send(conn, resp);
+ if (rc < 0)
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message\n");
+}
+
+/* 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));
+ gscon_sigtran_send(conn, resp);
+}
+
+
+/* Release an lchan in such a way that it doesn't fire events back to the conn. */
+static void gscon_release_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan,
+ bool do_rr_release, bool err, enum gsm48_rr_cause cause_rr)
+{
+ if (!lchan || !conn)
+ return;
+ if (lchan->conn == conn)
+ lchan_forget_conn(lchan);
+ if (conn->lchan == lchan)
+ conn->lchan = NULL;
+ if (conn->ho.fi && conn->ho.new_lchan == lchan)
+ conn->ho.new_lchan = NULL;
+ if (conn->assignment.new_lchan == lchan)
+ conn->assignment.new_lchan = NULL;
+ lchan_release(lchan, do_rr_release, err, cause_rr);
+}
+
+void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_release)
+{
+ if (conn->ho.fi)
+ handover_end(conn, HO_RESULT_CONN_RELEASE);
+
+ assignment_reset(conn);
+
+ gscon_release_lchan(conn, conn->lchan, do_rr_release, false, 0);
+}
+
+static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ struct msgb *msg = scu_prim->oph.msg;
+ struct bssmap_header *bs;
+ uint8_t bssmap_type;
+
+ msg->l3h = msgb_l2(msg);
+ if (!msgb_l3(msg)) {
+ LOGPFSML(fi, LOGL_ERROR, "internal error: no l3 in msg\n");
+ goto refuse;
+ }
+
+ if (msgb_l3len(msg) < sizeof(*bs)) {
+ LOGPFSML(fi, LOGL_NOTICE, "message too short for BSSMAP header (%u < %zu)\n",
+ msgb_l3len(msg), sizeof(*bs));
+ goto refuse;
+ }
+
+ bs = (struct bssmap_header*)msgb_l3(msg);
+ if (msgb_l3len(msg) < (bs->length + sizeof(*bs))) {
+ LOGPFSML(fi, LOGL_NOTICE,
+ "message too short for length indicated in BSSMAP header (%u < %u)\n",
+ msgb_l3len(msg), bs->length);
+ goto refuse;
+ }
+
+ switch (bs->type) {
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ break;
+ default:
+ LOGPFSML(fi, LOGL_NOTICE,
+ "message type not allowed for N-CONNECT: %s\n", gsm0808_bssap_name(bs->type));
+ goto refuse;
+ }
+
+ msg->l4h = &msg->l3h[sizeof(*bs)];
+ bssmap_type = msg->l4h[0];
+
+ LOGPFSML(fi, LOGL_DEBUG, "Rx N-CONNECT: %s: %s\n", gsm0808_bssap_name(bs->type),
+ gsm0808_bssmap_name(bssmap_type));
+
+ switch (bssmap_type) {
+ case BSS_MAP_MSG_HANDOVER_RQST:
+ /* First off, accept the new conn. */
+ osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
+ &scu_prim->u.connect.called_addr, NULL, 0);
+
+ /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */
+ conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
+
+ /* Inter-BSC MT Handover Request, another BSS is handovering to us. */
+ handover_start_inter_bsc_in(conn, msg);
+ return;
+ default:
+ break;
+ }
+
+ LOGPFSML(fi, LOGL_NOTICE, "No support for N-CONNECT: %s: %s\n",
+ gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type));
+refuse:
+ osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
+ &scu_prim->u.connect.called_addr, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+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;
+ enum handover_result ho_result;
+
+ 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) */
+ conn_fsm_state_chg(ST_WAIT_CC);
+ }
+ gscon_update_id(conn);
+ break;
+ case GSCON_EV_A_CONN_IND:
+ gscon_update_id(conn);
+ 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));
+ /* We cannot find a way to the sccp_user without the MSC, so we cannot
+ * use osmo_sccp_tx_disconn() :( */
+ //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);
+ return;
+ }
+ /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id()
+ * related: OS2969 (same as above) */
+
+ handle_bssap_n_connect(fi, scu_prim);
+ break;
+ case GSCON_EV_HANDOVER_END:
+ ho_result = HO_RESULT_ERROR;
+ if (data)
+ ho_result = *(enum handover_result*)data;
+ LOGPFSML(fi, LOGL_DEBUG, "Handover result: %s\n", handover_result_name(ho_result));
+ if (ho_result == HO_RESULT_OK) {
+ /* In this case the ho struct should still be populated. */
+ if (conn->ho.scope & HO_INTER_BSC_IN) {
+ /* Done with establishing a conn where we accept another BSC's MS via
+ * inter-BSC handover */
+
+ osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+ gscon_dtap_queue_flush(conn, 1);
+ return;
+ }
+ LOG_HO(conn, LOGL_ERROR,
+ "Conn is in state %s, the only accepted handover kind is inter-BSC MT\n",
+ osmo_fsm_inst_state_name(conn->fi));
+ }
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ if (conn->fi->state != ST_CLEARING)
+ osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, 999);
+ return;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+/* 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)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ 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 */
+ conn_fsm_state_chg(ST_ACTIVE);
+ /* 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);
+ }
+}
+
+static void gscon_fsm_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ if (!conn->lchan)
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+}
+
+/* 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 gsm_bts *bts;
+
+ switch (event) {
+
+ case GSCON_EV_ASSIGNMENT_START:
+ bts = conn->lchan? conn->lchan->ts->trx->bts : NULL;
+
+ if (!bts) {
+ LOGPFSML(fi, LOGL_ERROR, "Cannot do assignment, no active BTS\n");
+ return;
+ }
+
+ /* Rely on assignment_fsm timeout */
+ osmo_fsm_inst_state_chg(fi, ST_ASSIGNMENT, 0, 0);
+ assignment_fsm_start(conn, bts, data);
+ return;
+
+ case GSCON_EV_HANDOVER_START:
+ rate_ctr_inc(&conn->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]);
+ /* Rely on handover_fsm timeout */
+ if (osmo_fsm_inst_state_chg(fi, ST_HANDOVER, 0, 0))
+ LOGPFSML(fi, LOGL_ERROR, "Cannot transition to HANDOVER state, discarding\n");
+ else
+ handover_start(data);
+ 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);
+ break;
+ case GSCON_EV_TX_SCCP:
+ gscon_sigtran_send(conn, (struct msgb *)data);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void gscon_fsm_assignment(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ switch (event) {
+ case GSCON_EV_ASSIGNMENT_END:
+ osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+ gscon_dtap_queue_flush(conn, 1);
+ return;
+
+ case GSCON_EV_MO_DTAP:
+ forward_dtap(conn, (struct msgb *)data, fi);
+ break;
+ case GSCON_EV_MT_DTAP:
+ submit_dtap(conn, (struct msgb *)data);
+ break;
+ case GSCON_EV_TX_SCCP:
+ gscon_sigtran_send(conn, (struct msgb *)data);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void gscon_fsm_handover(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ switch (event) {
+ case GSCON_EV_HANDOVER_END:
+ osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0);
+ gscon_dtap_queue_flush(conn, 1);
+ return;
+
+ case GSCON_EV_MO_DTAP:
+ forward_dtap(conn, (struct msgb *)data, fi);
+ break;
+ case GSCON_EV_MT_DTAP:
+ /* cache until handover is done */
+ submit_dtap(conn, (struct msgb *)data);
+ break;
+ case GSCON_EV_TX_SCCP:
+ gscon_sigtran_send(conn, (struct msgb *)data);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static bool same_mgw_info(const struct mgcp_conn_peer *a, const struct mgcp_conn_peer *b)
+{
+ if (!a || !b)
+ return false;
+ if (a == b)
+ return true;
+ if (strcmp(a->addr, b->addr))
+ return false;
+ if (a->port != b->port)
+ return false;
+ if (a->call_id != b->call_id)
+ return false;
+ return true;
+}
+
+/* Make sure a conn->user_plane.mgw_endpoint is allocated with the proper mgw endpoint name. For
+ * SCCPlite, pass in msc_assigned_cic the CIC received upon BSSMAP Assignment Command or BSSMAP Handover
+ * Request form the MSC (which is only stored in conn->user_plane after success). Ignored for AoIP. */
+struct mgw_endpoint *gscon_ensure_mgw_endpoint(struct gsm_subscriber_connection *conn,
+ uint16_t msc_assigned_cic)
+{
+ if (conn->user_plane.mgw_endpoint)
+ return conn->user_plane.mgw_endpoint;
+
+ if (gscon_is_sccplite(conn)) {
+ /* derive endpoint name from CIC on A interface side */
+ conn->user_plane.mgw_endpoint =
+ mgw_endpoint_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT,
+ conn->network->mgw.client, conn->fi->id,
+ "%x@mgw", msc_assigned_cic);
+ LOGPFSML(conn->fi, LOGL_DEBUG, "MGW endpoint name derived from CIC 0x%x: %s\n",
+ msc_assigned_cic, mgw_endpoint_name(conn->user_plane.mgw_endpoint));
+
+ } else if (gscon_is_aoip(conn)) {
+ /* use dynamic RTPBRIDGE endpoint allocation in MGW */
+ conn->user_plane.mgw_endpoint =
+ mgw_endpoint_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT,
+ conn->network->mgw.client, conn->fi->id,
+ "rtpbridge/*@mgw");
+ } else {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Conn is neither SCCPlite nor AoIP!?\n");
+ return NULL;
+ }
+
+ return conn->user_plane.mgw_endpoint;
+}
+
+bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
+ struct gsm_lchan *for_lchan,
+ const char *addr, uint16_t port,
+ struct osmo_fsm_inst *notify,
+ uint32_t event_success, uint32_t event_failure,
+ void *notify_data,
+ struct mgwep_ci **created_ci)
+{
+ int rc;
+ struct mgwep_ci *ci;
+ struct mgcp_conn_peer mgw_info;
+ enum mgcp_verb verb;
+
+ if (created_ci)
+ *created_ci = NULL;
+
+ if (gscon_is_sccplite(conn)) {
+ /* SCCPlite connection uses an MGW endpoint created by the MSC, so there is nothing to do
+ * here. */
+ if (notify)
+ osmo_fsm_inst_dispatch(notify, event_success, notify_data);
+ return true;
+ }
+
+ mgw_info = (struct mgcp_conn_peer){
+ .port = port,
+ .call_id = conn->sccp.conn_id,
+ .ptime = 20,
+ };
+ mgcp_pick_codec(&mgw_info, for_lchan, false);
+
+ rc = osmo_strlcpy(mgw_info.addr, addr, sizeof(mgw_info.addr));
+ if (rc <= 0 || rc >= sizeof(mgw_info.addr)) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Failed to compose MGW endpoint address for MGW -> MSC\n");
+ return false;
+ }
+
+ ci = conn->user_plane.mgw_endpoint_ci_msc;
+ if (ci) {
+ const struct mgcp_conn_peer *prev_crcx_info = mgwep_ci_get_rtp_info(ci);
+
+ if (!conn->user_plane.mgw_endpoint) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Internal error: conn has a CI but no endoint\n");
+ return false;
+ }
+
+ if (!prev_crcx_info) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "There already is an MGW connection for the MSC side,"
+ " but it seems to be broken. Will not CRCX another one (%s)\n",
+ mgwep_ci_name(ci));
+ return false;
+ }
+
+ if (same_mgw_info(&mgw_info, prev_crcx_info)) {
+ LOGPFSML(conn->fi, LOGL_DEBUG,
+ "MSC side MGW endpoint ci is already configured to %s\n",
+ mgwep_ci_name(ci));
+ return true;
+ }
+
+ verb = MGCP_VERB_MDCX;
+ } else
+ verb = MGCP_VERB_CRCX;
+
+ gscon_ensure_mgw_endpoint(conn, for_lchan->activate.msc_assigned_cic);
+
+ if (!conn->user_plane.mgw_endpoint) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to allocate endpoint info\n");
+ return false;
+ }
+
+ if (!ci) {
+ ci = mgw_endpoint_ci_add(conn->user_plane.mgw_endpoint, "to-MSC");
+ if (created_ci)
+ *created_ci = ci;
+ conn->user_plane.mgw_endpoint_ci_msc = ci;
+ }
+ if (!ci) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to allocate endpoint CI info\n");
+ return false;
+ }
+
+ mgw_endpoint_ci_request(ci, verb, &mgw_info, notify, event_success, event_failure, notify_data);
+ return true;
+}
+
+#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 = "INIT",
+ .in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND)
+ | S(GSCON_EV_HANDOVER_END),
+ .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_CLEARING),
+ .action = gscon_fsm_init,
+ },
+ [ST_WAIT_CC] = {
+ .name = "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 = "ACTIVE",
+ .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_START) |
+ S(GSCON_EV_HANDOVER_START),
+ .out_state_mask = S(ST_CLEARING) | S(ST_ASSIGNMENT) |
+ S(ST_HANDOVER),
+ .onenter = gscon_fsm_active_onenter,
+ .action = gscon_fsm_active,
+ },
+ [ST_ASSIGNMENT] = {
+ .name = "ASSIGNMENT",
+ .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_END),
+ .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING),
+ .action = gscon_fsm_assignment,
+ },
+ [ST_HANDOVER] = {
+ .name = "HANDOVER",
+ .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_HANDOVER_END),
+ .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING),
+ .action = gscon_fsm_handover,
+ },
+ [ST_CLEARING] = {
+ .name = "CLEARING",
+ /* dead end state */
+ },
+};
+
+void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *new_lchan)
+{
+ /* On release, do not receive release events that look like the primary lchan is gone. */
+ struct gsm_lchan *old_lchan = conn->lchan;
+
+ conn->lchan = new_lchan;
+ conn->lchan->conn = conn;
+
+ if (conn->lchan->fi_rtp)
+ osmo_fsm_inst_dispatch(conn->lchan->fi_rtp, LCHAN_RTP_EV_ESTABLISHED, 0);
+
+ if (old_lchan && (old_lchan != new_lchan))
+ gscon_release_lchan(conn, old_lchan, false, false, 0);
+}
+
+void gscon_lchan_releasing(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan)
+{
+ if (!lchan)
+ return;
+ if (conn->assignment.new_lchan == lchan) {
+ if (conn->assignment.fi)
+ osmo_fsm_inst_dispatch(conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, lchan);
+ lchan_forget_conn(conn->assignment.new_lchan);
+ conn->assignment.new_lchan = NULL;
+ }
+ if (conn->ho.new_lchan == lchan) {
+ if (conn->ho.fi)
+ osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_LCHAN_ERROR, lchan);
+ }
+ if (conn->lchan == lchan) {
+ lchan_forget_conn(conn->lchan);
+ conn->lchan = NULL;
+ }
+ if (!conn->lchan) {
+ if (conn->fi->state != ST_CLEARING)
+ osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, 999);
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ }
+}
+
+/* An lchan was deallocated. */
+void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan)
+{
+ const char *detach_label = NULL;
+ if (!conn)
+ return;
+ if (!lchan)
+ return;
+
+ if (conn->assignment.new_lchan == lchan) {
+ conn->assignment.new_lchan = NULL;
+ detach_label = "assignment.new_lchan";
+ }
+ if (conn->ho.new_lchan == lchan) {
+ conn->ho.new_lchan = NULL;
+ detach_label = "ho.new_lchan";
+ }
+ if (conn->lchan == lchan) {
+ conn->lchan = NULL;
+ detach_label = "primary lchan";
+ }
+
+ /* Log for both lchan FSM and conn FSM to ease reading the log in case of problems */
+ if (detach_label) {
+ LOGPFSML(conn->fi, LOGL_DEBUG, "conn detaches lchan %s\n",
+ lchan->fi? osmo_fsm_inst_name(lchan->fi) : gsm_lchan_name(lchan));
+
+ if (lchan->fi)
+ LOGPFSML(lchan->fi, LOGL_DEBUG, "conn %s detaches lchan (%s)\n",
+ osmo_fsm_inst_name(conn->fi), detach_label);
+ }
+
+ if (conn->fi->state != ST_CLEARING
+ && !conn->lchan
+ && !conn->ho.new_lchan
+ && !conn->assignment.new_lchan)
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+}
+
+static void gscon_forget_mgw_endpoint(struct gsm_subscriber_connection *conn)
+{
+ conn->user_plane.mgw_endpoint = NULL;
+ conn->user_plane.mgw_endpoint_ci_msc = NULL;
+ lchan_forget_mgw_endpoint(conn->lchan);
+ lchan_forget_mgw_endpoint(conn->assignment.new_lchan);
+ lchan_forget_mgw_endpoint(conn->ho.new_lchan);
+}
+
+void gscon_forget_mgw_endpoint_ci(struct gsm_subscriber_connection *conn, struct mgwep_ci *ci)
+{
+ if (ci != conn->user_plane.mgw_endpoint_ci_msc)
+ return;
+ conn->user_plane.mgw_endpoint_ci_msc = NULL;
+}
+
+static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ /* Regular allstate event processing */
+ switch (event) {
+ case GSCON_EV_A_CLEAR_CMD:
+ /* MSC tells us to cleanly shut down */
+ if (conn->fi->state != ST_CLEARING)
+ osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, 999);
+ LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) after BSSMAP Clear Command\n");
+ gscon_release_lchans(conn, true);
+ /* 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 */
+ mgw_endpoint_clear(conn->user_plane.mgw_endpoint);
+
+ gscon_sigtran_send(conn, gsm0808_create_clear_complete());
+ 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_FORGET_MGW_ENDPOINT:
+ gscon_forget_mgw_endpoint(conn);
+ break;
+ case GSCON_EV_RSL_CONN_FAIL:
+ if (conn->lchan) {
+ conn->lchan->release.in_error = true;
+ conn->lchan->release.rsl_error_cause = data ? *(uint8_t*)data : RSL_ERR_IE_ERROR;
+ }
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
+ break;
+ case GSCON_EV_MGW_MDCX_RESP_MSC:
+ LOGPFSML(fi, LOGL_DEBUG, "Rx MDCX of MSC side (LCLS?)\n");
+ break;
+ case GSCON_EV_LCLS_FAIL:
+ break;
+ default:
+ OSMO_ASSERT(false);
+ break;
+ }
+}
+
+static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ lchan_forget_conn(conn->lchan);
+ lchan_forget_conn(conn->assignment.new_lchan);
+ lchan_forget_conn(conn->ho.new_lchan);
+
+ 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;
+ }
+
+ if (conn->bsub) {
+ LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n");
+ bsc_subscr_put(conn->bsub);
+ conn->bsub = NULL;
+ }
+
+ llist_del(&conn->entry);
+ talloc_free(conn);
+}
+
+static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ mgw_endpoint_clear(conn->user_plane.mgw_endpoint);
+
+ if (conn->lcls.fi) {
+ /* request termination of LCLS FSM */
+ osmo_fsm_inst_term(conn->lcls.fi, cause, NULL);
+ conn->lcls.fi = NULL;
+ }
+
+ LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) because this conn is terminating\n");
+ gscon_release_lchans(conn, true);
+
+ /* drop pending messages */
+ gscon_dtap_queue_flush(conn, 0);
+
+ penalty_timers_free(&conn->hodec2.penalty_timers);
+}
+
+static int gscon_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ switch (fi->T) {
+ case 993210:
+ gscon_release_lchan(conn, conn->lchan, true, true, RSL_ERR_INTERWORKING);
+
+ /* 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);
+
+ /* 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 999:
+ /* The MSC has sent a BSSMAP Clear Command, we acknowledged that, but the conn was never
+ * disconnected. */
+ LOGPFSML(fi, LOGL_ERROR, "Long after a BSSMAP Clear Command, the conn is still not"
+ " released. For sanity, discarding this conn now.\n");
+ a_reset_conn_fail(conn->sccp.msc);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "Unknown timer %d expired\n", fi->T);
+ 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_LCLS_FAIL) |
+ S(GSCON_EV_FORGET_LCHAN) |
+ S(GSCON_EV_FORGET_MGW_ENDPOINT),
+ .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,
+};
+
+void bsc_subscr_conn_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&gscon_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&lcls_fsm) == 0);
+}
+
+/* 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;
+
+ conn = talloc_zero(net, struct gsm_subscriber_connection);
+ if (!conn)
+ return NULL;
+
+ conn->network = net;
+ INIT_LLIST_HEAD(&conn->dtap_queue);
+ /* 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;
+}
+
+static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int link_id, int allow_sacch);
+
+#define GSCON_DTAP_QUEUE_MSGB_CB_LINK_ID 0
+#define GSCON_DTAP_QUEUE_MSGB_CB_ALLOW_SACCH 1
+
+static void gscon_dtap_queue_add(struct gsm_subscriber_connection *conn, struct msgb *msg,
+ int link_id, bool allow_sacch)
+{
+ if (conn->dtap_queue_len >= 8) {
+ LOGP(DMSC, LOGL_ERROR, "%s: Cannot queue more DTAP messages,"
+ " already reached sane maximum of %u queued messages\n",
+ bsc_subscr_name(conn->bsub), conn->dtap_queue_len);
+ msgb_free(msg);
+ return;
+ }
+ conn->dtap_queue_len ++;
+ LOGP(DMSC, LOGL_DEBUG, "%s: Queueing DTAP message during handover/assignment (%u)\n",
+ bsc_subscr_name(conn->bsub), conn->dtap_queue_len);
+ msg->cb[GSCON_DTAP_QUEUE_MSGB_CB_LINK_ID] = (unsigned long)link_id;
+ msg->cb[GSCON_DTAP_QUEUE_MSGB_CB_ALLOW_SACCH] = allow_sacch ? 1 : 0;
+ msgb_enqueue(&conn->dtap_queue, msg);
+}
+
+static void gscon_dtap_queue_flush(struct gsm_subscriber_connection *conn, int send)
+{
+ struct msgb *msg;
+ unsigned int flushed_count = 0;
+
+ while ((msg = msgb_dequeue(&conn->dtap_queue))) {
+ conn->dtap_queue_len --;
+ flushed_count ++;
+ if (send) {
+ int link_id = (int)msg->cb[GSCON_DTAP_QUEUE_MSGB_CB_LINK_ID];
+ bool allow_sacch = !!msg->cb[GSCON_DTAP_QUEUE_MSGB_CB_ALLOW_SACCH];
+ LOGPFSML(conn->fi, LOGL_DEBUG,
+ "%s: Sending queued DTAP message after handover/assignment (%u/%u)\n",
+ bsc_subscr_name(conn->bsub), flushed_count, conn->dtap_queue_len);
+ gsm0808_send_rsl_dtap(conn, msg, link_id, allow_sacch);
+ } else
+ msgb_free(msg);
+ }
+}
+
+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:
+ bsc_sapi_n_reject(lchan->conn, OBSC_LINKID_CB(msg));
+ msgb_free(msg);
+ break;
+ }
+}
+
+/*! \brief process incoming 08.08 DTAP from MSC (send via BTS to MS) */
+static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int link_id, int allow_sacch)
+{
+ uint8_t sapi;
+ int rc;
+ struct msgb *resp = NULL;
+
+ if (!conn->lchan) {
+ LOGP(DMSC, LOGL_ERROR,
+ "%s Called submit dtap without an lchan.\n",
+ bsc_subscr_name(conn->bsub));
+ msgb_free(msg);
+ rc = -EINVAL;
+ goto failed_to_send;
+ }
+
+ 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;
+ rc = rll_establish(msg->lchan, sapi, rll_ind_cb, msg);
+ if (rc) {
+ msgb_free(msg);
+ bsc_sapi_n_reject(conn, link_id);
+ goto failed_to_send;
+ }
+ return;
+ } else {
+ /* Directly forward via RLL/RSL to BTS */
+ rc = rsl_data_request(msg, link_id);
+ if (rc)
+ goto failed_to_send;
+ }
+ return;
+
+failed_to_send:
+ LOGPFSML(conn->fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n");
+ resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ gscon_sigtran_send(conn, resp);
+ osmo_fsm_inst_state_chg(conn->fi, ST_ACTIVE, 0, 0);
+}
+
+void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int link_id, int allow_sacch)
+{
+ /* buffer message during assignment / handover */
+ if (conn->fi->state != ST_ACTIVE) {
+ gscon_dtap_queue_add(conn, msg, link_id, !! allow_sacch);
+ return;
+ }
+
+ gsm0808_send_rsl_dtap(conn, msg, link_id, allow_sacch);
+}
+
+/* Compose an FSM ID, if possible from the current subscriber information */
+void gscon_update_id(struct gsm_subscriber_connection *conn)
+{
+ osmo_fsm_inst_update_id_f(conn->fi, "conn%u%s%s",
+ conn->sccp.conn_id,
+ conn->bsub? "_" : "",
+ conn->bsub? bsc_subscr_id(conn->bsub) : "");
+}
+
+bool gscon_is_aoip(struct gsm_subscriber_connection *conn)
+{
+ if (!conn || !conn->sccp.msc)
+ return false;
+
+ switch (conn->sccp.msc->a.asp_proto) {
+ case OSMO_SS7_ASP_PROT_SUA:
+ case OSMO_SS7_ASP_PROT_M3UA:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool gscon_is_sccplite(struct gsm_subscriber_connection *conn)
+{
+ if (!conn || !conn->sccp.msc)
+ return false;
+
+ switch (conn->sccp.msc->a.asp_proto) {
+ case OSMO_SS7_ASP_PROT_IPA:
+ return true;
+
+ default:
+ return false;
+ }
+}
diff --git a/src/osmo-bsc/bsc_subscriber.c b/src/osmo-bsc/bsc_subscriber.c
new file mode 100644
index 000000000..2541883d5
--- /dev/null
+++ b/src/osmo-bsc/bsc_subscriber.c
@@ -0,0 +1,181 @@
+/* GSM subscriber details for use in BSC land */
+
+/*
+ * (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <talloc.h>
+#include <string.h>
+#include <limits.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/debug.h>
+
+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);
+
+ 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 bsc_subscr_get(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 bsc_subscr_get(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;
+}
+
+/* Like bsc_subscr_name() but returns only characters approved by osmo_identifier_valid(), useful for
+ * osmo_fsm_inst IDs. */
+const char *bsc_subscr_id(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%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..983dcb9b3
--- /dev/null
+++ b/src/osmo-bsc/bsc_vty.c
@@ -0,0 +1,5179 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0502.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/abis_om2000.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/pcu_if.h>
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/handover_vty.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/acc_ramp.h>
+#include <osmocom/bsc/meas_feed.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+
+#include <inttypes.h>
+
+#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);
+}
+
+/*! Dump the IP addresses and ports of the input signal link's timeslot.
+ * This only makes sense for links connected with ipaccess.
+ * Example output: "(r=10.1.42.1:55416<->l=10.1.42.123:3003)" */
+static void e1isl_dump_vty_tcp(struct vty *vty, const struct e1inp_sign_link *e1l)
+{
+ if (e1l) {
+ char *name = osmo_sock_get_name(NULL, e1l->ts->driver.ipaccess.fd.fd);
+ vty_out(vty, "%s", name);
+ talloc_free(name);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv)
+{
+ int count = 0;
+ int i;
+ for (i = 0; i < 1024; i++) {
+ if (!bitvec_get_bit_pos(bv, i))
+ continue;
+ vty_out(vty, " %u", i);
+ count ++;
+ }
+ if (!count)
+ vty_out(vty, " (none)");
+ else
+ vty_out(vty, " (%d)", count);
+}
+
+static void bts_dump_vty_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);
+
+ if (bts->gprs.mode != BTS_GPRS_NONE) {
+ vty_out(vty, " GPRS NSE: ");
+ net_dump_nmstate(vty, &bts->gprs.nse.mo.nm_state);
+ vty_out(vty, " GPRS CELL: ");
+ net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state);
+ vty_out(vty, " GPRS NSVC0: ");
+ net_dump_nmstate(vty, &bts->gprs.nsvc[0].mo.nm_state);
+ vty_out(vty, " GPRS NSVC1: ");
+ net_dump_nmstate(vty, &bts->gprs.nsvc[1].mo.nm_state);
+ } else
+ vty_out(vty, " GPRS: not configured%s", VTY_NEWLINE);
+
+ vty_out(vty, " Paging: %u pending requests, %u free slots%s",
+ paging_pending_requests_nr(bts),
+ bts->paging.available_slots, VTY_NEWLINE);
+ if (is_ipaccess_bts(bts)) {
+ vty_out(vty, " OML Link: ");
+ e1isl_dump_vty_tcp(vty, bts->oml_link);
+ vty_out(vty, " OML Link state: %s", get_model_oml_status(bts));
+ sec = bts_uptime(bts);
+ if (sec)
+ vty_out(vty, " %llu days %llu hours %llu min. %llu sec.",
+ OSMO_SEC2DAY(sec), OSMO_SEC2HRS(sec), OSMO_SEC2MIN(sec), sec % 60);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ } else {
+ vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE);
+ e1isl_dump_vty(vty, bts->oml_link);
+ }
+
+ vty_out(vty, " Neighbor Cells: ");
+ switch (bts->neigh_list_manual_mode) {
+ default:
+ case NL_MODE_AUTOMATIC:
+ vty_out(vty, "Automatic");
+ /* generate_bcch_chan_list() should populate si_common.neigh_list */
+ break;
+ case NL_MODE_MANUAL:
+ vty_out(vty, "Manual");
+ break;
+ case NL_MODE_MANUAL_SI5SEP:
+ vty_out(vty, "Manual/separate SI5");
+ break;
+ }
+ vty_out(vty, ", ARFCNs:");
+ vty_out_neigh_list(vty, &bts->si_common.neigh_list);
+ if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) {
+ vty_out(vty, " SI5:");
+ vty_out_neigh_list(vty, &bts->si_common.si5_neigh_list);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ /* FIXME: chan_desc */
+ memset(&pl, 0, sizeof(pl));
+ bts_chan_load(&pl, bts);
+ vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE);
+ dump_pchan_load_vty(vty, " ", &pl);
+
+ 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;
+}
+
+DEFUN(show_rejected_bts, show_rejected_bts_cmd, "show rejected-bts",
+ SHOW_STR "Display recently rejected BTS devices\n")
+{
+ struct gsm_bts_rejected *pos;
+
+ /* empty list */
+ struct llist_head *rejected = &gsmnet_from_vty(vty)->bts_rejected;
+ if (llist_empty(rejected)) {
+ vty_out(vty, "No BTS has been rejected.%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+ }
+
+ /* table head */
+ vty_out(vty, "Date Site ID BTS ID IP%s", VTY_NEWLINE);
+ vty_out(vty, "------------------- ------- ------ ---------------%s", VTY_NEWLINE);
+
+ /* table body */
+ llist_for_each_entry(pos, rejected, list) {
+ /* timestamp formatted like: "2018-10-24 15:04:52" */
+ char buf[20];
+ strftime(buf, sizeof(buf), "%F %T", localtime(&pos->time));
+
+ vty_out(vty, "%s %7u %6u %15s%s", buf, pos->site_id, pos->bts_id, pos->ip, VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+/* utility functions */
+static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line,
+ const char *ts, const char *ss)
+{
+ e1_link->e1_nr = atoi(line);
+ e1_link->e1_ts = atoi(ts);
+ if (!strcmp(ss, "full"))
+ e1_link->e1_ts_ss = 255;
+ else
+ e1_link->e1_ts_ss = atoi(ss);
+}
+
+static void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link,
+ const char *prefix)
+{
+ if (!e1_link->e1_ts)
+ return;
+
+ if (e1_link->e1_ts_ss == 255)
+ vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s",
+ prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE);
+ else
+ vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s",
+ prefix, e1_link->e1_nr, e1_link->e1_ts,
+ e1_link->e1_ts_ss, VTY_NEWLINE);
+}
+
+
+static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ vty_out(vty, " timeslot %u%s", ts->nr, VTY_NEWLINE);
+ if (ts->tsc != -1)
+ vty_out(vty, " training_sequence_code %u%s", ts->tsc, VTY_NEWLINE);
+ if (ts->pchan_from_config != GSM_PCHAN_NONE)
+ vty_out(vty, " phys_chan_config %s%s",
+ gsm_pchan_name(ts->pchan_from_config), VTY_NEWLINE);
+ vty_out(vty, " hopping enabled %u%s",
+ ts->hopping.enabled, VTY_NEWLINE);
+ if (ts->hopping.enabled) {
+ unsigned int i;
+ vty_out(vty, " hopping sequence-number %u%s",
+ ts->hopping.hsn, VTY_NEWLINE);
+ vty_out(vty, " hopping maio %u%s",
+ ts->hopping.maio, VTY_NEWLINE);
+ for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+ if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i))
+ continue;
+ vty_out(vty, " hopping arfcn add %u%s",
+ i, VTY_NEWLINE);
+ }
+ }
+ config_write_e1_link(vty, &ts->e1_link, " ");
+
+ if (ts->trx->bts->model->config_write_ts)
+ ts->trx->bts->model->config_write_ts(vty, ts);
+}
+
+static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ int i;
+
+ vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE);
+ if (trx->description)
+ vty_out(vty, " description %s%s", trx->description,
+ VTY_NEWLINE);
+ vty_out(vty, " rf_locked %u%s",
+ trx->mo.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);
+ }
+
+ neighbor_ident_vty_write(vty, " ", bts);
+
+ vty_out(vty, " codec-support fr");
+ if (bts->codec.hr)
+ vty_out(vty, " hr");
+ if (bts->codec.efr)
+ vty_out(vty, " efr");
+ if (bts->codec.amr)
+ vty_out(vty, " amr");
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ config_write_bts_amr(vty, bts, &bts->mr_full, 1);
+ config_write_bts_amr(vty, bts, &bts->mr_half, 0);
+
+ config_write_bts_gprs(vty, bts);
+
+ if (bts->excl_from_rf_lock)
+ vty_out(vty, " rf-lock-exclude%s", VTY_NEWLINE);
+
+ if (bts->force_combined_si_set)
+ vty_out(vty, " %sforce-combined-si%s",
+ bts->force_combined_si ? "" : "no ", VTY_NEWLINE);
+
+ for (i = 0; i < ARRAY_SIZE(bts->depends_on); ++i) {
+ int j;
+
+ if (bts->depends_on[i] == 0)
+ continue;
+
+ for (j = 0; j < sizeof(bts->depends_on[i]) * 8; ++j) {
+ int bts_nr;
+
+ if ((bts->depends_on[i] & (1<<j)) == 0)
+ continue;
+
+ bts_nr = (i * sizeof(bts->depends_on[i]) * 8) + j;
+ vty_out(vty, " depends-on-bts %d%s", bts_nr, VTY_NEWLINE);
+ }
+ }
+ if (bts->pcu_sock_path)
+ vty_out(vty, " pcu-socket %s%s", bts->pcu_sock_path, VTY_NEWLINE);
+
+ ho_vty_write_bts(vty, bts);
+
+ config_write_bts_model(vty, bts);
+}
+
+static int config_write_bts(struct vty *v)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(v);
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &gsmnet->bts_list, list)
+ config_write_bts_single(v, bts);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_net(struct vty *vty)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ 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);
+
+ T_defs_vty_write(vty, " ");
+
+ 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);
+ }
+
+ /* writing T3212 from the common T_defs_vty_write() instead. */
+
+ {
+ 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, bool print_rsl, bool show_connected)
+{
+ if (show_connected && !trx->rsl_link)
+ return;
+
+ if (!show_connected && trx->rsl_link)
+ return;
+
+ vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
+ trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
+ vty_out(vty, "Description: %s%s",
+ trx->description ? trx->description : "(null)", VTY_NEWLINE);
+ vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, "
+ "resulting BS power: %d dBm%s",
+ trx->nominal_power, trx->max_power_red,
+ trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ net_dump_nmstate(vty, &trx->mo.nm_state);
+ if (print_rsl)
+ vty_out(vty, " RSL State: %s%s", trx->rsl_link? "connected" : "disconnected", VTY_NEWLINE);
+ vty_out(vty, " Baseband Transceiver NM State: ");
+ net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state);
+ if (is_ipaccess_bts(trx->bts)) {
+ vty_out(vty, " ip.access stream ID: 0x%02x ", trx->rsl_tei);
+ e1isl_dump_vty_tcp(vty, trx->rsl_link);
+ } else {
+ vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE);
+ e1isl_dump_vty(vty, trx->rsl_link);
+ }
+}
+
+static void trx_dump_vty_all(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ trx_dump_vty(vty, trx, true, true);
+ trx_dump_vty(vty, trx, true, false);
+}
+
+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_all(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_all(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;
+}
+
+/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots.
+ * Don't do anything if the ts is not dynamic. */
+static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config target;
+ if (ts_is_pchan_switching(ts, &target)) {
+ vty_out(vty, " switching %s -> %s", gsm_pchan_name(ts->pchan_is),
+ gsm_pchan_name(target));
+ } else if (ts->pchan_is != ts->pchan_on_init) {
+ vty_out(vty, " as %s", gsm_pchan_name(ts->pchan_is));
+ }
+}
+
+static void vty_out_dyn_ts_details(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ /* show dyn TS details, if applicable */
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ vty_out(vty, " Osmocom Dyn TS:");
+ vty_out_dyn_ts_status(vty, ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ vty_out(vty, " IPACC Dyn PDCH TS:");
+ vty_out_dyn_ts_status(vty, ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ default:
+ /* no dyn ts */
+ break;
+ }
+}
+
+static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan_on_init));
+ if (ts->pchan_is != ts->pchan_on_init)
+ vty_out(vty, " (%s mode)", gsm_pchan_name(ts->pchan_is));
+ vty_out(vty, ", TSC %u%s NM State: ", gsm_ts_tsc(ts), VTY_NEWLINE);
+ vty_out_dyn_ts_details(vty, ts);
+ net_dump_nmstate(vty, &ts->mo.nm_state);
+ if (!is_ipaccess_bts(ts->trx->bts))
+ vty_out(vty, " E1 Line %u, Timeslot %u, Subslot %u%s",
+ ts->e1_link.e1_nr, ts->e1_link.e1_ts,
+ ts->e1_link.e1_ts_ss, VTY_NEWLINE);
+}
+
+DEFUN(show_ts,
+ show_ts_cmd,
+ "show timeslot [<0-255>] [<0-255>] [<0-7>]",
+ 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");
+}
+
+static inline void print_all_trx_ext(struct vty *vty, bool show_connected)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts = NULL;
+ uint8_t bts_nr;
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ uint8_t trx_nr;
+ bts = gsm_bts_num(net, bts_nr);
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++)
+ trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr), false, show_connected);
+ }
+}
+
+DEFUN(show_trx_con,
+ show_trx_con_cmd,
+ "show trx (connected|disconnected)",
+ SHOW_STR "Display information about a TRX\n"
+ "Show TRX with RSL connected\n"
+ "Show TRX with RSL disconnected\n")
+{
+ if (!strcmp(argv[0], "connected"))
+ print_all_trx_ext(vty, true);
+ else
+ print_all_trx_ext(vty, false);
+
+ return CMD_SUCCESS;
+}
+
+static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+ int idx;
+
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE);
+ vty_out_dyn_ts_details(vty, lchan->ts);
+ vty_out(vty, " Connection: %u, State: %s%s%s%s",
+ lchan->conn ? 1: 0, lchan_state_name(lchan),
+ lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? " Error reason: " : "",
+ lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? lchan->last_error : "",
+ VTY_NEWLINE);
+ vty_out(vty, " BS Power: %u dBm, MS Power: %u dBm%s",
+ lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red
+ - lchan->bs_power*2,
+ ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+ VTY_NEWLINE);
+ vty_out(vty, " Channel Mode / Codec: %s%s",
+ gsm48_chan_mode_name(lchan->tch_mode),
+ VTY_NEWLINE);
+ if (lchan->conn && lchan->conn->bsub) {
+ vty_out(vty, " Subscriber:%s", VTY_NEWLINE);
+ bsc_subscr_dump_vty(vty, lchan->conn->bsub);
+ } else
+ vty_out(vty, " No Subscriber%s", VTY_NEWLINE);
+ if (is_ipaccess_bts(lchan->ts->trx->bts)) {
+ struct in_addr ia;
+ if (lchan->abis_ip.bound_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+ vty_out(vty, " Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s",
+ inet_ntoa(ia), lchan->abis_ip.bound_port,
+ lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id,
+ VTY_NEWLINE);
+ }
+ if (lchan->abis_ip.connect_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.connect_ip);
+ vty_out(vty, " Conn. IP: %s Port %u RTP_TYPE=%u SPEECH_MODE=0x%02x%s",
+ inet_ntoa(ia), lchan->abis_ip.connect_port,
+ lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode,
+ VTY_NEWLINE);
+ }
+
+ }
+
+ /* we want to report the last measurement report */
+ idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+ lchan->meas_rep_idx, 1);
+ meas_rep_dump_vty(vty, &lchan->meas_rep[idx], " ");
+}
+
+static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+ struct gsm_meas_rep *mr;
+ int idx;
+
+ /* we want to report the last measurement report */
+ idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+ lchan->meas_rep_idx, 1);
+ mr = &lchan->meas_rep[idx];
+
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ gsm_pchan_name(lchan->ts->pchan_on_init));
+ vty_out_dyn_ts_status(vty, lchan->ts);
+ vty_out(vty, ", Lchan %u, Type %s, State %s - "
+ "L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s",
+ lchan->nr,
+ gsm_lchant_name(lchan->type), lchan_state_name(lchan),
+ mr->ms_l1.pwr,
+ rxlev2dbm(mr->dl.full.rx_lev),
+ rxlev2dbm(mr->ul.full.rx_lev),
+ VTY_NEWLINE);
+}
+
+
+static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ struct gsm_lchan *lchan;
+ ts_for_each_lchan(lchan, ts) {
+ if (lchan_state_is(lchan, LCHAN_ST_UNUSED))
+ 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, mgw_ep=%s%s",
+ conn->sccp.conn_id, conn->sccp.msc->nr, conn->hodec2.failures,
+ mgw_endpoint_name(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: %s, LCLS Control: %s, LCLS BSS Status: %s%s",
+ gsm0808_lcls_config_name(conn->lcls.config),
+ gsm0808_lcls_control_name(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->assignment.new_lchan)
+ lchan_dump_full_vty(vty, conn->assignment.new_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)
+{
+ if (!to_bts || from_lchan->ts->trx->bts == to_bts) {
+ LOGP(DHO, LOGL_NOTICE, "%s Manually triggering Assignment from VTY\n",
+ gsm_lchan_name(from_lchan));
+ to_bts = from_lchan->ts->trx->bts;
+ } else
+ LOGP(DHO, LOGL_NOTICE, "%s (ARFCN %u) --> BTS %u Manually triggering Handover from VTY\n",
+ gsm_lchan_name(from_lchan), from_lchan->ts->trx->arfcn, to_bts->nr);
+ {
+ struct handover_out_req req = {
+ .from_hodec_id = HODEC_USER,
+ .old_lchan = from_lchan,
+ .target_nik = *bts_ident_key(to_bts),
+ };
+ handover_request(&req);
+ }
+ 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, int random_idx)
+{
+ struct gsm_bts *bts;
+ struct gsm_network *network = gsmnet_from_vty(vty);
+
+ while (1) {
+ int count = 0;
+ 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];
+ struct gsm_lchan *lchan;
+
+ if (ts->fi->state != TS_ST_IN_USE)
+ continue;
+
+ ts_for_each_lchan(lchan, ts) {
+ if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED)
+ && (lchan->type == GSM_LCHAN_TCH_F
+ || lchan->type == GSM_LCHAN_TCH_H)) {
+
+ if (count == random_idx) {
+ vty_out(vty, "Found voice call: %s%s",
+ gsm_lchan_name(lchan),
+ VTY_NEWLINE);
+ lchan_dump_full_vty(vty, lchan);
+ return lchan;
+ }
+ count ++;
+ }
+ }
+ }
+ }
+ }
+
+ if (!count)
+ break;
+ /* there are used lchans, but random_idx is > count. Iterate again. */
+ random_idx %= count;
+ }
+
+ 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_chan_t 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) {
+ struct gsm_lchan *lchan = lchan_select_by_type(bts, free_type);
+ if (!lchan)
+ continue;
+
+ vty_out(vty, "Found unused %s slot: %s%s",
+ gsm_lchant_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, random());
+ if (!from_lchan)
+ return CMD_WARNING;
+
+ to_bts = find_other_bts_with_free_slots(vty, from_lchan->ts->trx->bts, from_lchan->type);
+ 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, random());
+ if (!from_lchan)
+ return CMD_WARNING;
+
+ return trigger_ho_or_as(vty, from_lchan, NULL);
+}
+
+DEFUN(handover_any_to_arfcn_bsic, handover_any_to_arfcn_bsic_cmd,
+ "handover any to " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
+ MANUAL_HANDOVER_STR
+ "Pick any actively used TCH/F or TCH/H lchan to handover to another cell."
+ " This is likely to fail outside of a lab setup where you are certain that"
+ " all MS are able to see the target cell.\n"
+ "'to'\n"
+ NEIGHBOR_IDENT_VTY_KEY_DOC)
+{
+ struct handover_out_req req;
+ struct gsm_lchan *from_lchan;
+
+ from_lchan = find_used_voice_lchan(vty, random());
+ if (!from_lchan)
+ return CMD_WARNING;
+
+ req = (struct handover_out_req){
+ .from_hodec_id = HODEC_USER,
+ .old_lchan = from_lchan,
+ };
+
+ if (!neighbor_ident_bts_parse_key_params(vty, from_lchan->ts->trx->bts,
+ argv, &req.target_nik)) {
+ vty_out(vty, "%% BTS %u does not know about this neighbor%s",
+ from_lchan->ts->trx->bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ handover_request(&req);
+ return CMD_SUCCESS;
+}
+
+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 requests 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;
+}
+
+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]);
+ enum gsm_band unused;
+
+ if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) {
+ vty_out(vty, "%% Cannot configure neighbor list in "
+ "automatic mode%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "add"))
+ bitvec_set_bit_pos(bv, arfcn, 1);
+ else
+ bitvec_set_bit_pos(bv, arfcn, 0);
+
+ return CMD_SUCCESS;
+}
+
+/* help text should be kept in sync with EARFCN_*_INVALID defines */
+DEFUN(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")
+{
+ enum gsm_band unused;
+ struct gsm_bts *bts = vty->index;
+ struct bitvec *bv = &bts->si_common.si5_neigh_list;
+ uint16_t arfcn = atoi(argv[1]);
+
+ if (!bts->neigh_list_manual_mode) {
+ vty_out(vty, "%% Cannot configure neighbor list in "
+ "automatic mode%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "add"))
+ bitvec_set_bit_pos(bv, arfcn, 1);
+ else
+ bitvec_set_bit_pos(bv, arfcn, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(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;
+ bts->force_combined_si_set = true;
+ 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;
+ bts->force_combined_si_set = true;
+ return CMD_SUCCESS;
+}
+
+static void _get_codec_from_arg(struct vty *vty, int argc, const char *argv[])
+{
+ struct gsm_bts *bts = vty->index;
+ struct bts_codec_conf *codec = &bts->codec;
+ int i;
+
+ codec->hr = 0;
+ codec->efr = 0;
+ codec->amr = 0;
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "hr"))
+ codec->hr = 1;
+ if (!strcmp(argv[i], "efr"))
+ codec->efr = 1;
+ if (!strcmp(argv[i], "amr"))
+ codec->amr = 1;
+ }
+}
+
+#define CODEC_PAR_STR " (hr|efr|amr)"
+#define CODEC_HELP_STR "Half Rate\n" \
+ "Enhanced Full Rate\nAdaptive Multirate\n"
+
+DEFUN(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;
+ int mode;
+ int mode_prev = -1;
+
+ /* Check if mode parameters are in order */
+ for (i = 0; i < argc; i++) {
+ mode = atoi(argv[i]);
+ if (mode_prev > mode) {
+ vty_out(vty, "Modes must be listed in order%s",
+ VTY_NEWLINE);
+ return;
+ }
+
+ if (mode_prev == mode) {
+ vty_out(vty, "Modes must be unique %s", VTY_NEWLINE);
+ return;
+ }
+ mode_prev = mode;
+ }
+
+ /* Prepare the multirate configuration IE */
+ mr->gsm48_ie[1] = 0;
+ for (i = 0; i < argc; i++)
+ mr->gsm48_ie[1] |= 1 << atoi(argv[i]);
+ mr_conf->icmi = 0;
+
+ /* Store actual mode identifier values */
+ for (i = 0; i < argc; i++) {
+ mr->ms_mode[i].mode = atoi(argv[i]);
+ mr->bts_mode[i].mode = atoi(argv[i]);
+ }
+ mr->num_modes = argc;
+
+ /* Trim excess threshold and hysteresis values from previous config */
+ for (i = argc - 1; i < 4; i++) {
+ mr->ms_mode[i].threshold = 0;
+ mr->bts_mode[i].threshold = 0;
+ mr->ms_mode[i].hysteresis = 0;
+ mr->bts_mode[i].hysteresis = 0;
+ }
+}
+
+static void get_amr_th_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+{
+ struct gsm_bts *bts = vty->index;
+ struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+ struct amr_mode *modes;
+ int i;
+
+ modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode;
+ for (i = 0; i < argc - 1; i++)
+ modes[i].threshold = atoi(argv[i + 1]);
+}
+
+static void get_amr_hy_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+{
+ struct gsm_bts *bts = vty->index;
+ struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+ struct amr_mode *modes;
+ int i;
+
+ modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode;
+ for (i = 0; i < argc - 1; i++)
+ modes[i].hysteresis = atoi(argv[i + 1]);
+}
+
+static void get_amr_start_from_arg(struct vty *vty, const char *argv[], int full)
+{
+ struct gsm_bts *bts = vty->index;
+ struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+ int num = 0, i;
+
+ for (i = 0; i < ((full) ? 8 : 6); i++) {
+ if ((mr->gsm48_ie[1] & (1 << i))) {
+ num++;
+ }
+ }
+
+ if (argv[0][0] == 'a' || num == 0)
+ mr_conf->icmi = 0;
+ else {
+ mr_conf->icmi = 1;
+ if (num < atoi(argv[0]))
+ mr_conf->smod = num - 1;
+ else
+ mr_conf->smod = atoi(argv[0]) - 1;
+ }
+}
+
+/* Give the current amr configuration a final consistency chack by feeding the
+ * the configuration into the gsm48 multirate IE generator function */
+static int check_amr_config(struct vty *vty)
+{
+ int rc = 0;
+ struct amr_multirate_conf *mr;
+ const struct gsm48_multi_rate_conf *mr_conf;
+ struct gsm_bts *bts = vty->index;
+ int vty_rc = CMD_SUCCESS;
+
+ mr = &bts->mr_full;
+ mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie;
+ rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "Invalid AMR multirate configuration (tch-f, ms) - check parameters%s",
+ VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "Invalid AMR multirate configuration (tch-f, bts) - check parameters%s",
+ VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ mr = &bts->mr_half;
+ mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie;
+ rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "Invalid AMR multirate configuration (tch-h, ms) - check parameters%s",
+ VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "Invalid AMR multirate configuration (tch-h, bts) - check parameters%s",
+ VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ return vty_rc;
+}
+
+#define AMR_TCHF_PAR_STR " (0|1|2|3|4|5|6|7)"
+#define AMR_TCHF_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" \
+ "10,2k\n12,2k\n"
+
+#define AMR_TCHH_PAR_STR " (0|1|2|3|4|5)"
+#define AMR_TCHH_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n"
+
+#define AMR_TH_HELP_STR "Threshold between codec 1 and 2\n"
+#define AMR_HY_HELP_STR "Hysteresis between codec 1 and 2\n"
+
+DEFUN(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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+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 check_amr_config(vty);
+}
+
+#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")
+{
+ enum gsm_band unused;
+ struct gsm_bts_trx *trx = vty->index;
+ int arfcn = atoi(argv[0]);
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* FIXME: check if this ARFCN is supported by this TRX */
+
+ trx->arfcn = arfcn;
+
+ /* FIXME: patch ARFCN into SYSTEM INFORMATION */
+ /* FIXME: use OML layer to update the ARFCN */
+ /* FIXME: use RSL layer to update SYSTEM INFORMATION */
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(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_from_config = pchanc;
+
+ return CMD_SUCCESS;
+}
+
+/* used for backwards compatibility with old config files that still
+ * have uppercase pchan type names */
+DEFUN_HIDDEN(cfg_ts_pchan_compat,
+ cfg_ts_pchan_compat_cmd,
+ "phys_chan_config PCHAN",
+ "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int pchanc;
+
+ pchanc = gsm_pchan_parse(argv[0]);
+ if (pchanc < 0) {
+ vty_out(vty, "Unknown physical channel name '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_ERR_NO_MATCH;
+ }
+
+ ts->pchan_from_config = pchanc;
+
+ return CMD_SUCCESS;
+}
+
+
+
+DEFUN(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")
+{
+ enum gsm_band unused;
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int arfcn = atoi(argv[0]);
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(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")
+{
+ enum gsm_band unused;
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int arfcn = atoi(argv[0]);
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(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;
+ }
+ if (!gsm_bts_get_cbch(bts)) {
+ vty_out(vty, "%% BTS %d doesn't have a CBCH%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 || !ts->fi || ts->fi->state == TS_ST_NOT_INITIALIZED || ts->fi->state == TS_ST_BORKEN) {
+ vty_out(vty, "%% Timeslot is not usable%s", VTY_NEWLINE);
+ 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_on_init != GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && ts->pchan_on_init != GSM_PCHAN_TCH_F_PDCH) {
+ vty_out(vty, "%% Timeslot %u is not dynamic TCH/F_TCH/H_PDCH or TCH/F_PDCH%s",
+ ts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[3], "activate"))
+ activate = 1;
+ else
+ activate = 0;
+
+ if (activate && ts->fi->state != TS_ST_UNUSED) {
+ vty_out(vty, "%% Timeslot %u is still in use%s",
+ ts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else if (!activate && ts->fi->state != TS_ST_PDCH) {
+ vty_out(vty, "%% Timeslot %u is not in PDCH mode%s",
+ ts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ LOG_TS(ts, LOGL_NOTICE, "telnet VTY user asks to %s\n", activate ? "PDCH ACT" : "PDCH DEACT");
+ ts->pdch_act_allowed = activate;
+ osmo_fsm_inst_state_chg(ts->fi, activate ? TS_ST_WAIT_PDCH_ACT : TS_ST_WAIT_PDCH_DEACT, 4, 0);
+
+ return CMD_SUCCESS;
+
+}
+
+/* configure the lchan for a single AMR mode (as specified) */
+static int lchan_set_single_amr_mode(struct vty *vty, struct gsm_lchan *lchan, uint8_t amr_mode)
+{
+ struct amr_multirate_conf mr;
+ struct gsm48_multi_rate_conf *mr_conf;
+ int rc, vty_rc = CMD_SUCCESS;
+ 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;
+ mr.num_modes = 1;
+
+ /* encode this configuration into the lchan for both uplink and
+ * downlink direction */
+ rc = gsm48_multirate_config(lchan->mr_ms_lv, mr_conf, mr.ms_mode, mr.num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "Invalid AMR multirate configuration (%s, amr mode %d, ms) - check parameters%s",
+ gsm_lchant_name(lchan->type), amr_mode, VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+ rc = gsm48_multirate_config(lchan->mr_bts_lv, mr_conf, mr.bts_mode, mr.num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "Invalid AMR multirate configuration (%s, amr mode %d, bts) - check parameters%s",
+ gsm_lchant_name(lchan->type), amr_mode, VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ return vty_rc;
+}
+
+/* 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;
+
+ /* FIXME: allow dynamic channels with switchover, lchan_activate(lchan, FOR_VTY) */
+ if (ss_nr >= pchan_subslots(ts->pchan_is)) {
+ vty_out(vty, "%% subslot index %d too large for physical channel %s (%u slots)%s",
+ ss_nr, gsm_pchan_name(ts->pchan_is), pchan_subslots(ts->pchan_is),
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (activate) {
+ int lchan_t;
+ if (lchan->fi->state != LCHAN_ST_UNUSED) {
+ vty_out(vty, "%% Cannot activate: Channel busy!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan_t = gsm_lchan_type_by_pchan(ts->pchan_is);
+ 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, vty_rc;
+ 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;
+ vty_rc = lchan_set_single_amr_mode(vty, lchan, amr_mode);
+ if (vty_rc != CMD_SUCCESS)
+ return vty_rc;
+ }
+ vty_out(vty, "%% activating lchan %s%s", gsm_lchan_name(lchan), VTY_NEWLINE);
+ rsl_tx_chan_activ(lchan, RSL_ACT_TYPE_INITIAL, 0);
+ rsl_tx_ipacc_crcx(lchan);
+ } else {
+ if (!lchan->fi) {
+ vty_out(vty, "%% Cannot release: Channel not initialized%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ vty_out(vty, "%% Asking for release of %s in state %s%s", gsm_lchan_name(lchan),
+ osmo_fsm_inst_state_name(lchan->fi), VTY_NEWLINE);
+ lchan_release(lchan, !!(lchan->conn), false, 0);
+ }
+
+ 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 >= pchan_subslots(ts->pchan_is)) {
+ vty_out(vty, "%% subslot index %d too large for physical channel %s (%u slots)%s",
+ ss_nr, gsm_pchan_name(ts->pchan_is), pchan_subslots(ts->pchan_is),
+ 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);
+ lchan->abis_ip.connect_ip = ia.s_addr;
+ lchan->abis_ip.connect_port = port;
+ rsl_tx_ipacc_mdcx(lchan);
+ 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;
+ struct T_def *d = T_def_get_entry(net->T_defs, 3212);
+
+ OSMO_ASSERT(d);
+ d->val = atoi(argv[0]) / 6;
+ vty_out(vty, "T%d = %u %s (%s)%s", d->T, d->val, "* 6min", d->desc, VTY_NEWLINE);
+ 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;
+ struct T_def *d = T_def_get_entry(net->T_defs, 3212);
+
+ OSMO_ASSERT(d);
+ d->val = 0;
+ vty_out(vty, "T%d = %u %s (%s)%s", d->T, d->val, "* 6min", d->desc, VTY_NEWLINE);
+ 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_rejected_bts_cmd);
+ install_element_ve(&show_trx_cmd);
+ install_element_ve(&show_trx_con_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(&show_paging_cmd);
+ install_element_ve(&show_paging_group_cmd);
+
+ install_element(ENABLE_NODE, &handover_any_cmd);
+ install_element(ENABLE_NODE, &assignment_any_cmd);
+ install_element(ENABLE_NODE, &handover_any_to_arfcn_bsic_cmd);
+
+ logging_vty_add_cmds(NULL);
+ osmo_talloc_vty_add_cmds();
+
+ T_defs_vty_init(network->T_defs, GSMNET_NODE);
+
+ install_element(GSMNET_NODE, &cfg_net_neci_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);
+ neighbor_ident_vty_init(network, network->neighbor_bss_cells);
+ /* See also handover commands added on bts level from handover_vty.c */
+
+ install_element(BTS_NODE, &cfg_trx_cmd);
+ install_node(&trx_node, dummy_config_write);
+ install_element(TRX_NODE, &cfg_trx_arfcn_cmd);
+ install_element(TRX_NODE, &cfg_description_cmd);
+ install_element(TRX_NODE, &cfg_no_description_cmd);
+ install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+ install_element(TRX_NODE, &cfg_trx_max_power_red_cmd);
+ install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd);
+ install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd);
+ install_element(TRX_NODE, &cfg_trx_rf_locked_cmd);
+
+ install_element(TRX_NODE, &cfg_ts_cmd);
+ install_node(&ts_node, dummy_config_write);
+ install_element(TS_NODE, &cfg_ts_pchan_cmd);
+ install_element(TS_NODE, &cfg_ts_pchan_compat_cmd);
+ install_element(TS_NODE, &cfg_ts_tsc_cmd);
+ install_element(TS_NODE, &cfg_ts_hopping_cmd);
+ install_element(TS_NODE, &cfg_ts_hsn_cmd);
+ install_element(TS_NODE, &cfg_ts_maio_cmd);
+ install_element(TS_NODE, &cfg_ts_arfcn_add_cmd);
+ install_element(TS_NODE, &cfg_ts_arfcn_del_cmd);
+ install_element(TS_NODE, &cfg_ts_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..4d1e91b0f
--- /dev/null
+++ b/src/osmo-bsc/bts_ericsson_rbs2000.c
@@ -0,0 +1,209 @@
+/* Ericsson RBS-2xxx specific code */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_om2000.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+
+#include <osmocom/abis/lapd.h>
+
+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_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL);
+
+ /* 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;
+ default:
+ 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 struct gsm_bts_model model_rbs2k = {
+ .type = GSM_BTS_TYPE_RBS2000,
+ .name = "rbs2000",
+ .start = bts_model_rbs2k_start,
+ .oml_rcvmsg = &abis_om2k_rcvmsg,
+ .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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <osmocom/bsc/bss.h>
+
+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..cf81a22c6
--- /dev/null
+++ b/src/osmo-bsc/bts_ipaccess_nanobts.c
@@ -0,0 +1,653 @@
+/* ip.access nanoBTS specific code */
+
+/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <time.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/abis/subchan_demux.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+
+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";
+}
+
+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,
+ .e1line_bind_ops = bts_model_nanobts_e1line_bind_ops,
+ /* Some nanoBTS firmwares (if not all) don't support SI2ter and cause
+ * problems on some MS if it is enabled, see OS#3063. Disable it by
+ * default, can still be enabled through VTY cmd with same name.
+ */
+ .force_combined_si = true,
+ .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_from_config);
+ if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) {
+ ipaccess_drop_oml_deferred(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 void nm_rx_opstart_ack_chan(struct msgb *oml_msg)
+{
+ struct gsm_bts_trx_ts *ts;
+ ts = abis_nm_get_ts(oml_msg);
+ if (!ts)
+ /* error already logged in abis_nm_get_ts() */
+ return;
+ if (!ts->fi) {
+ LOG_TS(ts, LOGL_ERROR, "Channel OPSTART ACK for uninitialized TS\n");
+ return;
+ }
+
+ osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL);
+}
+
+static void nm_rx_opstart_ack(struct msgb *oml_msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
+ switch (foh->obj_class) {
+ case NM_OC_CHANNEL:
+ nm_rx_opstart_ack_chan(oml_msg);
+ 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;
+
+ /* First of all, remove deferred drop if enabled */
+ osmo_timer_del(&bts->oml_drop_link_timer);
+
+ 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_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL);
+
+ 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);
+ }
+}
+
+/*! Callback for \ref ipaccess_drop_oml_deferred_cb.
+ */
+static void ipaccess_drop_oml_deferred_cb(void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *) data;
+ ipaccess_drop_oml(bts);
+}
+/*! Deferr \ref ipacces_drop_oml through a timer to avoid dropping structures in
+ * current code context. This may be needed if we want to destroy the OML link
+ * while being called from a lower layer "struct osmo_fd" cb, were it is
+ * mandatory to return -EBADF if the osmo_fd has been destroyed. In case code
+ * destroying an OML link is called through an osmo_signal, it becomes
+ * impossible to return any value, thus deferring the destruction is required.
+ */
+void ipaccess_drop_oml_deferred(struct gsm_bts *bts)
+{
+ if (!osmo_timer_pending(&bts->oml_drop_link_timer) && bts->oml_link) {
+ LOGP(DLINP, LOGL_NOTICE, "(bts=%d) Deferring Drop of OML link.\n", bts->nr);
+ osmo_timer_setup(&bts->oml_drop_link_timer, ipaccess_drop_oml_deferred_cb, bts);
+ osmo_timer_schedule(&bts->oml_drop_link_timer, 0, 0);
+ }
+}
+
+/* Reject BTS because of an unknown unit ID */
+static void ipaccess_sign_link_reject(const struct ipaccess_unit *dev, const struct e1inp_ts* ts)
+{
+ uint16_t site_id = dev->site_id;
+ uint16_t bts_id = dev->bts_id;
+ uint16_t trx_id = dev->trx_id;
+ char ip[INET6_ADDRSTRLEN];
+ struct gsm_bts_rejected *entry = NULL;
+ struct gsm_bts_rejected *pos;
+
+ /* Write to log and increase counter */
+ LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for %u/%u/%u, disconnecting\n", site_id, bts_id,
+ trx_id);
+ rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_UNKNOWN_UNIT_ID]);
+
+ /* Get remote IP */
+ if (osmo_sock_get_remote_ip(ts->driver.ipaccess.fd.fd, ip, sizeof(ip)))
+ return;
+
+ /* Rejected list: unlink existing entry */
+ llist_for_each_entry(pos, &bsc_gsmnet->bts_rejected, list) {
+ if (pos->site_id == site_id && pos->bts_id == bts_id && !strcmp(pos->ip, ip)) {
+ entry = pos;
+ llist_del(&entry->list);
+ break;
+ }
+ }
+
+ /* Allocate new entry */
+ if (!entry) {
+ entry = talloc_zero(tall_bsc_ctx, struct gsm_bts_rejected);
+ if (!entry)
+ return;
+ entry->site_id = site_id;
+ entry->bts_id = bts_id;
+ osmo_strlcpy(entry->ip, ip, sizeof(entry->ip));
+ }
+
+ /* Add to beginning with current timestamp */
+ llist_add(&entry->list, &bsc_gsmnet->bts_rejected);
+ entry->time = time(NULL);
+
+ /* Cut off last (oldest) element if we have too many */
+ if (llist_count(&bsc_gsmnet->bts_rejected) > 25) {
+ pos = llist_last_entry(&bsc_gsmnet->bts_rejected, struct gsm_bts_rejected, list);
+ llist_del(&pos->list);
+ talloc_free(pos);
+ }
+}
+
+/* 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) {
+ ipaccess_sign_link_reject(dev, &line->ts[E1INP_SIGN_OML - 1]);
+ 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..d674c1891
--- /dev/null
+++ b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c
@@ -0,0 +1,241 @@
+/* ip.access nanoBTS specific code, OML attribute table generator */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/gsm_timers.h>
+
+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, T_def_get(bts->network->T_defs, 3105, T_MS, -1));
+
+ /* 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..66972c2b5
--- /dev/null
+++ b/src/osmo-bsc/bts_nokia_site.c
@@ -0,0 +1,1755 @@
+/* Nokia XXXsite family specific code */
+
+/* (C) 2011 by Dieter Spaar <spaar@mirider.augusta.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ 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 <time.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+
+#include <osmocom/core/timer.h>
+
+#include <osmocom/abis/lapd.h>
+
+/* 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);
+
+ if (!bts->nokia.skip_reset) {
+ if (!bts->nokia.did_reset)
+ abis_nm_reset(bts, 1);
+ } else
+ bts->nokia.did_reset = 1;
+
+ gsm_bts_all_ts_dispatch(bts, TS_EV_OML_READY, NULL);
+}
+
+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_all_ts_dispatch(trx, TS_EV_OML_READY, NULL);
+}
+
+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;
+ default:
+ 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
+ */
+
+ switch (ts->pchan_from_config) {
+ case GSM_PCHAN_NONE:
+ chan_config = 0xFF;
+ break;
+ case GSM_PCHAN_CCCH:
+ chan_config = 0;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ chan_config = 1;
+ break;
+ case GSM_PCHAN_TCH_F:
+ chan_config = 6; /* 9 should work too */
+ break;
+ case GSM_PCHAN_TCH_H:
+ chan_config = 9;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ chan_config = 4;
+ break;
+ case GSM_PCHAN_PDCH:
+ chan_config = 11;
+ break;
+ default:
+ fprintf(stderr,
+ "unsupported channel config %s for timeslot %d\n",
+ gsm_pchan_name(ts->pchan_from_config), 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..2cb676c93
--- /dev/null
+++ b/src/osmo-bsc/bts_siemens_bs11.c
@@ -0,0 +1,608 @@
+/* Siemens BS-11 specific code */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+
+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] = T_def_get(bts->network->T_defs, 3105, T_MS, -1) / 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_from_config);
+ 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_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL);
+
+ 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;
+ default:
+ 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..253786428
--- /dev/null
+++ b/src/osmo-bsc/bts_sysmobts.c
@@ -0,0 +1,63 @@
+/* sysmocom sysmoBTS specific code */
+
+/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/abis/subchan_demux.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/logging.h>
+
+extern struct gsm_bts_model bts_model_nanobts;
+
+static struct gsm_bts_model model_sysmobts;
+
+int bts_model_sysmobts_init(void)
+{
+ model_sysmobts = bts_model_nanobts;
+ model_sysmobts.name = "sysmobts";
+ model_sysmobts.type = GSM_BTS_TYPE_OSMOBTS;
+
+ /* Unlike nanoBTS, sysmoBTS supports SI2bis and SI2ter fine */
+ model_sysmobts.force_combined_si = false;
+
+ model_sysmobts.features.data = &model_sysmobts._features_data[0];
+ model_sysmobts.features.data_len =
+ sizeof(model_sysmobts._features_data);
+ memset(model_sysmobts.features.data, 0, 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 <daniel@totalueberwachung.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/bsc/abis_nm.h>
+
+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..9c434145f
--- /dev/null
+++ b/src/osmo-bsc/chan_alloc.c
@@ -0,0 +1,169 @@
+/* GSM Channel allocation routines
+ *
+ * (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+
+#include <osmocom/core/talloc.h>
+
+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_on_init];
+ struct gsm_lchan *lchan;
+
+ /* skip administratively deactivated timeslots */
+ if (!nm_is_running(&ts->mo.nm_state))
+ continue;
+
+ ts_for_each_lchan(lchan, ts) {
+ pl->total++;
+
+ switch (lchan->fi->state) {
+ case LCHAN_ST_UNUSED:
+ 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/codec_pref.c b/src/osmo-bsc/codec_pref.c
new file mode 100644
index 000000000..c99c38335
--- /dev/null
+++ b/src/osmo-bsc/codec_pref.c
@@ -0,0 +1,470 @@
+/*
+ * (C) 2017-2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/codec_pref.h>
+#include <osmocom/bsc/gsm_data.h>
+
+/* Determine whether a permitted speech value is specifies a half rate or full
+ * rate codec */
+static int full_rate_from_perm_spch(bool * full_rate,
+ enum gsm0808_permitted_speech perm_spch)
+{
+ /* Check if the result is a half or full rate codec */
+ switch (perm_spch) {
+ case GSM0808_PERM_HR1:
+ case GSM0808_PERM_HR2:
+ case GSM0808_PERM_HR3:
+ case GSM0808_PERM_HR4:
+ case GSM0808_PERM_HR6:
+ *full_rate = false;
+ break;
+
+ case GSM0808_PERM_FR1:
+ case GSM0808_PERM_FR2:
+ case GSM0808_PERM_FR3:
+ case GSM0808_PERM_FR4:
+ case GSM0808_PERM_FR5:
+ *full_rate = true;
+ break;
+
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Invalid permitted-speech value: %u\n",
+ perm_spch);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Helper function for match_codec_pref(), looks up a matching chan mode for
+ * a given permitted speech value */
+static enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech speech)
+{
+ switch (speech) {
+ case GSM0808_PERM_HR1:
+ case GSM0808_PERM_FR1:
+ return GSM48_CMODE_SPEECH_V1;
+ break;
+ case GSM0808_PERM_FR2:
+ return GSM48_CMODE_SPEECH_EFR;
+ break;
+ case GSM0808_PERM_HR3:
+ case GSM0808_PERM_FR3:
+ return GSM48_CMODE_SPEECH_AMR;
+ break;
+ case GSM0808_PERM_HR2:
+ LOGP(DMSC, LOGL_FATAL, "Speech HR2 was never specified!\n");
+ /* fall through */
+ default:
+ LOGP(DMSC, LOGL_FATAL, "Unsupported permitted speech %s selected, "
+ "assuming AMR as channel mode...\n",
+ gsm0808_permitted_speech_name(speech));
+ return GSM48_CMODE_SPEECH_AMR;
+ }
+}
+
+/* Helper function for match_codec_pref(), looks up a matching permitted speech
+ * value for a given msc audio codec pref */
+static enum gsm0808_permitted_speech audio_support_to_gsm88(const struct gsm_audio_support *audio)
+{
+ if (audio->hr) {
+ switch (audio->ver) {
+ case 1:
+ return GSM0808_PERM_HR1;
+ break;
+ case 2:
+ return GSM0808_PERM_HR2;
+ break;
+ case 3:
+ return GSM0808_PERM_HR3;
+ break;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: hr%d, using hr1 instead\n", audio->ver);
+ return GSM0808_PERM_HR1;
+ }
+ } else {
+ switch (audio->ver) {
+ case 1:
+ return GSM0808_PERM_FR1;
+ break;
+ case 2:
+ return GSM0808_PERM_FR2;
+ break;
+ case 3:
+ return GSM0808_PERM_FR3;
+ break;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: fr%d, using fr1 instead\n", audio->ver);
+ return GSM0808_PERM_FR1;
+ }
+ }
+}
+
+/* Helper function for match_codec_pref(), tests if a given audio support
+ * matches one of the permitted speech settings of the channel type element.
+ * The matched permitted speech value is then also compared against the
+ * speech codec list. (optional, only relevant for AoIP) */
+static bool test_codec_pref(const struct gsm0808_speech_codec **sc_match,
+ const struct gsm0808_speech_codec_list *scl,
+ const struct gsm0808_channel_type *ct,
+ uint8_t perm_spch)
+{
+ unsigned int i;
+ bool match = false;
+ struct gsm0808_speech_codec sc;
+ int rc;
+
+ *sc_match = NULL;
+
+ /* Try to find the given permitted speech value in the
+ * codec list of the channel type element */
+ for (i = 0; i < ct->perm_spch_len; i++) {
+ if (ct->perm_spch[i] == perm_spch) {
+ match = true;
+ break;
+ }
+ }
+
+ /* If we do not have a speech codec list to test against,
+ * we just exit early (will be always the case in non-AoIP networks) */
+ if (!scl || !scl->len)
+ return match;
+
+ /* If we failed to match until here, there is no
+ * point in testing further */
+ if (match == false)
+ return false;
+
+ /* Extrapolate speech codec data */
+ rc = gsm0808_speech_codec_from_chan_type(&sc, perm_spch);
+ if (rc < 0)
+ return false;
+
+ /* Try to find extrapolated speech codec data in
+ * the speech codec list */
+ for (i = 0; i < scl->len; i++) {
+ if (sc.type == scl->codec[i].type) {
+ *sc_match = &scl->codec[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Helper function to check if the given permitted speech value is supported
+ * by the BTS. (vty option bts->codec-support). */
+static bool test_codec_support_bts(const struct gsm_bts *bts, uint8_t perm_spch)
+{
+ struct gsm_bts_trx *trx;
+ const struct bts_codec_conf *bts_codec = &bts->codec;
+ unsigned int i;
+ bool full_rate;
+ int rc;
+ enum gsm_phys_chan_config pchan;
+ bool rate_match = false;
+
+ /* Check if the BTS provides a physical channel that matches the
+ * bandwith of the desired codec. */
+ rc = full_rate_from_perm_spch(&full_rate, perm_spch);
+ if (rc < 0)
+ return false;
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (i = 0; i < TRX_NR_TS; i++) {
+ pchan = trx->ts[i].pchan_from_config;
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ rate_match = true;
+ else if (full_rate && pchan == GSM_PCHAN_TCH_F)
+ rate_match = true;
+ else if (full_rate && pchan == GSM_PCHAN_TCH_F_PDCH)
+ rate_match = true;
+ else if (!full_rate && pchan == GSM_PCHAN_TCH_H)
+ rate_match = true;
+ }
+ }
+ if (!rate_match)
+ return false;
+
+ /* Check codec support */
+ switch (perm_spch) {
+ case GSM0808_PERM_FR1:
+ /* GSM-FR is always supported by all BTSs. There is also no way to
+ * selectively disable GSM-RF per BTS via VTY. */
+ return true;
+ case GSM0808_PERM_FR2:
+ if (bts_codec->efr)
+ return true;
+ break;
+ case GSM0808_PERM_FR3:
+ if (bts_codec->amr)
+ return true;
+ break;
+ case GSM0808_PERM_HR1:
+ if (bts_codec->hr)
+ return true;
+ break;
+ case GSM0808_PERM_HR3:
+ if (bts_codec->amr)
+ return true;
+ break;
+ default:
+ return false;
+ }
+
+ return false;
+}
+
+/* Generate the bss supported amr configuration bits (S0-S15) */
+static uint16_t gen_bss_supported_amr_s15_s0(const struct bsc_msc_data *msc, const struct gsm_bts *bts, bool hr)
+{
+ const struct gsm48_multi_rate_conf *amr_cfg_bts;
+ const struct gsm48_multi_rate_conf *amr_cfg_msc;
+ uint16_t amr_s15_s0_bts;
+ uint16_t amr_s15_s0_msc;
+
+ /* Lookup the BTS specific AMR rate configuration. This config is set
+ * via the VTY for each BTS individually. In cases where no configuration
+ * is set we will assume a safe default */
+ if (hr) {
+ amr_cfg_bts = (struct gsm48_multi_rate_conf *)&bts->mr_half.gsm48_ie;
+ amr_s15_s0_bts = gsm0808_sc_cfg_from_gsm48_mr_cfg(amr_cfg_bts, false);
+ } else {
+ amr_cfg_bts = (struct gsm48_multi_rate_conf *)&bts->mr_full.gsm48_ie;
+ amr_s15_s0_bts = gsm0808_sc_cfg_from_gsm48_mr_cfg(amr_cfg_bts, true);
+ }
+
+ /* Lookup the AMR rate configuration that is set for the MSC */
+ amr_cfg_msc = &msc->amr_conf;
+ amr_s15_s0_msc = gsm0808_sc_cfg_from_gsm48_mr_cfg(amr_cfg_msc, true);
+
+ /* Calculate the intersection of the two configurations and update S0-S15
+ * in the codec list. */
+ return amr_s15_s0_bts & amr_s15_s0_msc;
+}
+
+/*! Match the codec preferences from local config with a received codec preferences IEs received from the
+ * MSC and the BTS' codec configuration.
+ * \param[out] chan_mode GSM 04.08 channel mode.
+ * \param[out] full_rate true if full-rate.
+ * \param[out] s15_s0 codec configuration bits S15-S0 (AMR)
+ * \param[in] ct GSM 08.08 channel type received from MSC.
+ * \param[in] scl GSM 08.08 speech codec list received from MSC (optional).
+ * \param[in] msc associated msc (current codec settings).
+ * \param[in] bts associated bts (current codec settings).
+ * \returns 0 on success, -1 in case no match was found */
+int match_codec_pref(enum gsm48_chan_mode *chan_mode,
+ bool *full_rate,
+ uint16_t *s15_s0,
+ const struct gsm0808_channel_type *ct,
+ const struct gsm0808_speech_codec_list *scl,
+ const struct bsc_msc_data *msc,
+ const struct gsm_bts *bts)
+{
+ unsigned int i;
+ uint8_t perm_spch;
+ bool match = false;
+ const struct gsm0808_speech_codec *sc_match = NULL;
+ uint16_t amr_s15_s0_supported;
+ int rc;
+
+ /* Note: Normally the MSC should never try to advertise a codec that
+ * we did not advertise as supported before. In order to ensure that
+ * no unsupported codec is accepted, we make sure that the codec is
+ * indeed available with the current BTS and MSC configuration */
+ for (i = 0; i < msc->audio_length; i++) {
+ /* Pick a permitted speech value from the global codec configuration list */
+ perm_spch = audio_support_to_gsm88(msc->audio_support[i]);
+
+ /* Check this permitted speech value against the BTS specific parameters.
+ * if the BTS does not support the codec, try the next one */
+ if (!test_codec_support_bts(bts, perm_spch))
+ continue;
+
+ /* Match the permitted speech value against the codec lists that were
+ * advertised by the MS and the MSC */
+ if (test_codec_pref(&sc_match, scl, ct, perm_spch)) {
+ match = true;
+ break;
+ }
+ }
+
+ /* Exit without result, in case no match can be deteched */
+ if (!match) {
+ *full_rate = false;
+ *chan_mode = GSM48_CMODE_SIGN;
+ *s15_s0 = 0;
+ return -1;
+ }
+
+ /* Determine if the result is a half or full rate codec */
+ rc = full_rate_from_perm_spch(full_rate, perm_spch);
+ if (rc < 0)
+ return -EINVAL;
+
+ /* Lookup a channel mode for the selected codec */
+ *chan_mode = gsm88_to_chan_mode(perm_spch);
+
+ /* Special handling for AMR */
+ if (perm_spch == GSM0808_PERM_HR3 || perm_spch == GSM0808_PERM_FR3) {
+ /* Normally the MSC should never try to advertise an AMR codec
+ * configuration that we did not previously advertise as
+ * supported. However, to ensure that no unsupported AMR codec
+ * configuration enters the further processing steps we again
+ * lookup what we support and generate an intersection. All
+ * further processing is then done with this intersection
+ * result */
+ amr_s15_s0_supported = gen_bss_supported_amr_s15_s0(msc, bts, (perm_spch == GSM0808_PERM_HR3));
+ if (sc_match)
+ *s15_s0 = sc_match->cfg & amr_s15_s0_supported;
+ else
+ *s15_s0 = amr_s15_s0_supported;
+
+ /* NOTE: The function test_codec_pref() will populate the
+ * sc_match pointer from the searched speech codec list. For
+ * AoIP based networks, no speech codec list will be present
+ * and therefore no sc_match will be populated. For those
+ * cases only the local configuration will influence s15_s0.
+ * However s15_s0 is always populated with a meaningful value,
+ * regardless if AoIP is in use or not. */
+ } else
+ *s15_s0 = 0;
+
+ return 0;
+}
+
+/*! Determine the BSS supported speech codec list that is sent to the MSC with
+ * the COMPLETE LAYER 3 INFORMATION message.
+ * \param[out] scl GSM 08.08 speech codec list with BSS supported codecs.
+ * \param[in] msc associated msc (current codec settings).
+ * \param[in] bts associated bts (current codec settings). */
+void gen_bss_supported_codec_list(struct gsm0808_speech_codec_list *scl,
+ const struct bsc_msc_data *msc, const struct gsm_bts *bts)
+{
+ uint8_t perm_spch;
+ unsigned int i;
+ int rc;
+
+ memset(scl, 0, sizeof(*scl));
+
+ for (i = 0; i < msc->audio_length; i++) {
+
+ /* Pick a permitted speech value from the global codec configuration list */
+ perm_spch = audio_support_to_gsm88(msc->audio_support[i]);
+
+ /* Check this permitted speech value against the BTS specific parameters.
+ * if the BTS does not support the codec, try the next one */
+ if (!test_codec_support_bts(bts, perm_spch))
+ continue;
+
+ /* Write item into codec list */
+ rc = gsm0808_speech_codec_from_chan_type(&scl->codec[scl->len], perm_spch);
+ if (rc != 0)
+ continue;
+
+ /* AMR (HR/FR version 3) is the only codec that requires a codec
+ * configuration (S0-S15). Determine the current configuration and update
+ * the cfg flag. */
+ if (msc->audio_support[i]->ver == 3)
+ scl->codec[scl->len].cfg = gen_bss_supported_amr_s15_s0(msc, bts, msc->audio_support[i]->hr);
+
+ scl->len++;
+ }
+}
+
+/*! Calculate the intersection of the rate configuration of two multirate configuration
+ * IE structures. The result c will be a copy of a, but the rate configuration bits
+ * will be the intersection of the rate configuration bits in a and b.
+ * \param[out] c user provided memory to store the result.
+ * \param[in] a multi rate configuration a.
+ * \param[in] b multi rate configuration b.
+ * \returns 0 on success, -1 when the result contains an empty set of modes. */
+int calc_amr_rate_intersection(struct gsm48_multi_rate_conf *c,
+ const struct gsm48_multi_rate_conf *b,
+ const struct gsm48_multi_rate_conf *a)
+{
+ struct gsm48_multi_rate_conf res;
+ uint8_t *_a = (uint8_t *) a;
+ uint8_t *_b = (uint8_t *) b;
+ uint8_t *_res = (uint8_t *) & res;
+
+ memcpy(&res, a, sizeof(res));
+
+ _res[1] = _a[1] & _b[1];
+
+ if (_res[1] == 0x00)
+ return -1;
+
+ if (c)
+ memcpy(c, &res, sizeof(*c));
+
+ return 0;
+}
+
+/*! Visit the codec settings for the MSC and for each BTS in order to make sure
+ * that the configuration does not contain any combinations that lead into a
+ * mutually exclusive codec configuration (empty intersection).
+ * \param[in] mscs list head of the msc list.
+ * \returns 0 on success, -1 in case an invalid setting is found. */
+int check_codec_pref(struct llist_head *mscs)
+{
+ struct bsc_msc_data *msc;
+ struct gsm_bts *bts;
+ struct gsm0808_speech_codec_list scl;
+ int rc = 0;
+ int rc_rate;
+ const struct gsm48_multi_rate_conf *bts_gsm48_ie;
+
+ llist_for_each_entry(msc, mscs, entry) {
+ llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ gen_bss_supported_codec_list(&scl, msc, bts);
+ if (scl.len <= 0) {
+ LOGP(DMSC, LOGL_FATAL,
+ "codec-support/trx config of BTS %u does not intersect with codec-list of MSC %u\n",
+ bts->nr, msc->nr);
+ rc = -1;
+ }
+
+ bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_full.gsm48_ie;
+ rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie);
+ if (rc_rate < 0) {
+ LOGP(DMSC, LOGL_FATAL,
+ "network amr tch-f mode config of BTS %u does not intersect with amr-config of MSC %u\n",
+ bts->nr, msc->nr);
+ rc = -1;
+ }
+
+ bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_half.gsm48_ie;
+ rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie);
+ if (rc_rate < 0) {
+ LOGP(DMSC, LOGL_FATAL,
+ "network amr tch-h mode config of BTS %u does not intersect with amr-config of MSC %u\n",
+ bts->nr, msc->nr);
+ rc = -1;
+ }
+ }
+ }
+
+ return rc;
+}
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 <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/misdn.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/abis_rsl.h>
+
+#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_rr.c b/src/osmo-bsc/gsm_04_08_rr.c
new file mode 100644
index 000000000..4be51981f
--- /dev/null
+++ b/src/osmo-bsc/gsm_04_08_rr.c
@@ -0,0 +1,961 @@
+/* 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 <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/assignment_fsm.h>
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/gsm_08_08.h>
+
+/* 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;
+
+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);
+}
+
+/* 3GPP 44.018 9.1.12 Classmark Enquiry */
+int gsm48_send_rr_classmark_enquiry(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 44.018 Classmark Enquiry");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ msg->lchan = lchan;
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_CLSM_ENQ;
+
+ DEBUGP(DRR, "%s TX CLASSMARK ENQUIRY %u\n", gsm_lchan_name(lchan), msgb_length(msg));
+
+ return gsm48_sendmsg(msg);
+}
+
+/* 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_conf multi-rate configuration to encode (selected modes).
+ * \param[in] modes array describing the AMR modes.
+ * \param[in] num_modes length of the modes array.
+ * \returns 0 on success, -EINVAL on failure. */
+int gsm48_multirate_config(uint8_t *lv,
+ const struct gsm48_multi_rate_conf *mr_conf,
+ const struct amr_mode *modes, unsigned int num_modes)
+{
+ int num = 0;
+ unsigned int i;
+ unsigned int k;
+ unsigned int m = 0;
+ bool mode_valid;
+ uint8_t *gsm48_ie = (uint8_t *) mr_conf;
+ const struct amr_mode *modes_selected[4];
+
+ /* Check if modes for consistency (order and duplicates) */
+ for (i = 0; i < num_modes; i++) {
+ if (i > 0 && modes[i - 1].mode > modes[i].mode) {
+ LOGP(DRR, LOGL_ERROR,
+ "BUG: Multirate codec with inconsistant config (mode order).\n");
+ return -EINVAL;
+ }
+ if (i > 0 && modes[i - 1].mode == modes[i].mode) {
+ LOGP(DRR, LOGL_ERROR,
+ "BUG: Multirate codec with inconsistant config (duplicate modes).\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Check if the active set that is defined in mr_conf has at least one
+ * mode but not more than 4 modes set */
+ for (i = 0; i < 8; i++) {
+ if (((gsm48_ie[1] >> i) & 1))
+ num++;
+ }
+ if (num > 4) {
+ LOGP(DRR, LOGL_ERROR,
+ "BUG: Multirate codec with too many modes in config.\n");
+ return -EINVAL;
+ }
+ if (num < 1) {
+ LOGP(DRR, LOGL_ERROR,
+ "BUG: Multirate codec with no mode in config.\n");
+ return -EINVAL;
+ }
+
+ /* Do not accept excess hysteresis or threshold values */
+ for (i = 0; i < num_modes; i++) {
+ if (modes[i].threshold >= 64) {
+ LOGP(DRR, LOGL_ERROR,
+ "BUG: Multirate codec with excessive threshold values.\n");
+ return -EINVAL;
+ }
+ if (modes[i].hysteresis >= 16) {
+ LOGP(DRR, LOGL_ERROR,
+ "BUG: Multirate codec with excessive hysteresis values.\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Scan through the selected modes and find a matching threshold/
+ * hysteresis value for that mode. */
+ for (i = 0; i < 8; i++) {
+ if (((gsm48_ie[1] >> i) & 1)) {
+ mode_valid = false;
+ for (k = 0; k < num_modes; k++) {
+ if (modes[k].mode == i) {
+ mode_valid = true;
+ modes_selected[m] = &modes[k];
+ m++;
+ }
+ }
+ if (!mode_valid) {
+ LOGP(DRR, LOGL_ERROR,
+ "BUG: Multirate codec with inconsistant config (no mode defined).\n");
+ return -EINVAL;
+ }
+ }
+ }
+ OSMO_ASSERT(m <= 4);
+
+ /* When the caller is not interested in any result, skip the actual
+ * composition of the IE (dry run) */
+ if (!lv)
+ return 0;
+
+ /* Compose output buffer */
+ lv[0] = (num == 1) ? 2 : (num + 2);
+ memcpy(lv + 1, gsm48_ie, 2);
+ if (num == 1)
+ return 0;
+
+ lv[3] = modes_selected[0]->threshold & 0x3f;
+ lv[4] = modes_selected[0]->hysteresis << 4;
+ if (num == 2)
+ return 0;
+ lv[4] |= (modes_selected[1]->threshold & 0x3f) >> 2;
+ lv[5] = modes_selected[1]->threshold << 6;
+ lv[5] |= (modes_selected[1]->hysteresis & 0x0f) << 2;
+ if (num == 3)
+ return 0;
+ lv[5] |= (modes_selected[2]->threshold & 0x3f) >> 4;
+ lv[6] = modes_selected[2]->threshold << 4;
+ lv[6] |= modes_selected[2]->hysteresis & 0x0f;
+
+ return 0;
+}
+
+#define GSM48_HOCMD_CCHDESC_LEN 16
+
+/* Chapter 9.1.15: Handover Command */
+struct msgb *gsm48_make_ho_cmd(struct gsm_lchan *new_lchan, uint8_t power_command, uint8_t ho_ref)
+{
+ struct msgb *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));
+
+ 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 msg;
+}
+
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
+ uint8_t power_command, uint8_t ho_ref)
+{
+ struct msgb *msg = gsm48_make_ho_cmd(new_lchan, power_command, ho_ref);
+ if (!msg)
+ return -EINVAL;
+ msg->lchan = old_lchan;
+ return gsm48_sendmsg(msg);
+}
+
+/* Chapter 9.1.2: Assignment Command */
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_command)
+{
+ 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;
+
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CHANNEL MODE MODIFY ACK for %s\n",
+ gsm48_chan_mode_name(mod->mode));
+
+ if (mod->mode != msg->lchan->tch_mode) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR,
+ "CHANNEL MODE MODIFY ACK has wrong mode: Wanted: %s Got: %s\n",
+ gsm48_chan_mode_name(msg->lchan->tch_mode),
+ gsm48_chan_mode_name(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_tx_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.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;
+ gscon_submit_rsl_dtap(conn, msg, 0, 0);
+ return 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);
+}
+
+/* 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 void handle_classmark_chg(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ 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;
+
+
+ /* classmark 2 */
+ cm2_len = gh->data[0];
+ cm2 = &gh->data[1];
+
+ if (cm2_len > 3) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "CLASSMARK CHANGE: CM2 too long: %u\n", cm2_len);
+ return;
+ }
+
+ if (payload_len > cm2_len + 1) {
+ /* we must have a classmark3 */
+ if (gh->data[cm2_len+1] != 0x20) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "CLASSMARK CHANGE: invalid CM3 TAG\n");
+ return;
+ }
+
+ cm3_len = gh->data[cm2_len+2];
+ cm3 = &gh->data[cm2_len+3];
+ if (cm3_len > 14) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "CLASSMARK CHANGE: CM3 too long: %u\n",
+ cm3_len);
+ return;
+ }
+ }
+
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CLASSMARK CHANGE CM2(len=%u) CM3(len=%u)\n",
+ cm2_len, cm3_len);
+ bsc_cm_update(conn, cm2, cm2_len, cm3, cm3_len);
+}
+
+static void dispatch_dtap(struct gsm_subscriber_connection *conn,
+ uint8_t link_id, struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+ uint8_t pdisc;
+ uint8_t msg_type;
+ int rc;
+
+ if (msgb_l3len(msg) < sizeof(*gh)) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR,
+ "Message too short for a GSM48 header (%u)\n", msgb_l3len(msg));
+ 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:
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "Rx %s\n", gsm48_rr_msg_name(msg_type));
+ switch (msg_type) {
+ case GSM48_MT_RR_GPRS_SUSP_REQ:
+ /* do something? */
+ break;
+ case GSM48_MT_RR_STATUS:
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "RR Status: %s\n", rr_cause_name(gh->data[0]));
+ /* do something? */
+ 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 */
+ LOG_LCHAN(msg->lchan, 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:
+ /* Chapter 9.1.16 Handover complete */
+ if (!conn->ho.fi)
+ LOG_LCHAN(msg->lchan, LOGL_ERROR,
+ "Rx RR Handover Complete, but no handover is ongoing\n");
+ else
+ osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_RR_HO_COMPLETE, msg);
+ break;
+ case GSM48_MT_RR_HANDO_FAIL:
+ /* Chapter 9.1.17 Handover Failure */
+ if (!conn->ho.fi)
+ LOG_LCHAN(msg->lchan, LOGL_ERROR,
+ "Rx RR Handover Fail, but no handover is ongoing\n");
+ else
+ osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_RR_HO_FAIL, msg);
+ break;
+ case GSM48_MT_RR_CIPH_M_COMPL:
+ bsc_cipher_mode_compl(conn, msg, conn->lchan->encr.alg_id);
+ break;
+ case GSM48_MT_RR_ASS_COMPL:
+ if (conn->assignment.fi)
+ osmo_fsm_inst_dispatch(conn->assignment.fi,
+ ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE, msg);
+ else
+ LOGPLCHAN(msg->lchan, DRR, LOGL_ERROR,
+ "Rx RR Assignment Complete, but no assignment is ongoing\n");
+ break;
+ case GSM48_MT_RR_ASS_FAIL:
+ if (conn->assignment.fi)
+ osmo_fsm_inst_dispatch(conn->assignment.fi,
+ ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL, msg);
+ else
+ LOGPLCHAN(msg->lchan, DRR, LOGL_ERROR,
+ "Rx RR Assignment Failure, but no assignment is ongoing\n");
+ break;
+ case GSM48_MT_RR_CHAN_MODE_MODIF_ACK:
+ rc = gsm48_rx_rr_modif_ack(msg);
+ if (rc < 0)
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_CHAN_MODE_MODIF_ERROR, &rc);
+ else
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_CHAN_MODE_MODIF_ACK, msg);
+ 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 */
+ bsc_dtap(conn, link_id, msg);
+ break;
+ default:
+ /* Drop unknown RR message */
+ LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown RR message: %s\n",
+ gsm48_rr_msg_name(msg_type));
+ gsm48_tx_rr_status(conn, GSM48_RR_CAUSE_MSG_TYPE_N);
+ break;
+ }
+ break;
+ default:
+ bsc_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)
+{
+ struct gsm_lchan *lchan;
+ int rc;
+
+ lchan = msg->lchan;
+ if (!lchan_may_receive_data(lchan)) {
+ LOG_LCHAN(msg->lchan, 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 */
+ lchan->conn = bsc_subscr_con_allocate(msg->lchan->ts->trx->bts->network);
+ if (!lchan->conn) {
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ return -1;
+ }
+ lchan->conn->lchan = lchan;
+
+ /* fwd via bsc_api to send COMPLETE L3 INFO to MSC */
+ rc = bsc_compl_l3(lchan->conn, msg, 0);
+ if (rc < 0) {
+ gscon_release_lchans(lchan->conn, true);
+ return rc;
+ }
+ /* conn shall release lchan on teardown, also if this Layer 3 Complete is rejected. */
+ }
+
+ return 0;
+}
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..8de1262e9
--- /dev/null
+++ b/src/osmo-bsc/gsm_04_80_utils.c
@@ -0,0 +1,42 @@
+/* OpenBSC utility functions for 3GPP TS 04.80 */
+
+/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+
+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;
+ gscon_submit_rsl_dtap(conn, msg, 0, 0);
+ return 0;
+}
+
+int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *msg = gsm0480_create_ussd_release_complete();
+ if (!msg)
+ return -1;
+ gscon_submit_rsl_dtap(conn, msg, 0, 0);
+ return 0;
+}
diff --git a/src/osmo-bsc/gsm_08_08.c b/src/osmo-bsc/gsm_08_08.c
new file mode 100644
index 000000000..a3e8b3093
--- /dev/null
+++ b/src/osmo-bsc/gsm_08_08.c
@@ -0,0 +1,666 @@
+/* (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/bsc/codec_pref.h>
+
+#include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/a_reset.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/mncc.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+
+/* Check if we have a proper connection to the MSC */
+static bool msc_connected(struct gsm_subscriber_connection *conn)
+{
+ /* No subscriber conn at all */
+ if (!conn)
+ return false;
+
+ /* Connection to MSC not established */
+ if (!conn->sccp.msc)
+ return false;
+
+ /* Reset procedure not (yet) executed */
+ if (a_reset_conn_ready(conn->sccp.msc) == false)
+ return false;
+
+ return true;
+}
+
+static bool complete_layer3(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, struct bsc_msc_data *msc);
+
+static void bsc_maybe_lu_reject(struct gsm_subscriber_connection *conn, int con_type, int cause)
+{
+ struct msgb *msg;
+
+ /* ignore cm service request or such */
+ if (con_type != FLT_CON_TYPE_LU)
+ return;
+
+ msg = gsm48_create_loc_upd_rej(cause);
+ if (!msg) {
+ LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
+ return;
+ }
+
+ msg->lchan = conn->lchan;
+ gscon_submit_rsl_dtap(conn, msg, 0, 0);
+}
+
+static int bsc_filter_initial(struct osmo_bsc_data *bsc,
+ struct bsc_msc_data *msc,
+ struct gsm_subscriber_connection *conn,
+ struct msgb *msg, char **imsi, int *con_type,
+ int *lu_cause)
+{
+ struct bsc_filter_request req;
+ struct bsc_filter_reject_cause cause;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ int rc;
+
+ req.ctx = conn;
+ req.black_list = NULL;
+ req.access_lists = bsc_access_lists();
+ req.local_lst_name = msc->acc_lst_name;
+ req.global_lst_name = conn_get_bts(conn)->network->bsc_data->acc_lst_name;
+ req.bsc_nr = 0;
+
+ rc = bsc_msg_filter_initial(gh, msgb_l3len(msg), &req,
+ con_type, imsi, &cause);
+ *lu_cause = cause.lu_reject_cause;
+ return rc;
+}
+
+static int bsc_filter_data(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int *lu_cause)
+{
+ struct bsc_filter_request req;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct bsc_filter_reject_cause cause;
+ int rc;
+
+ req.ctx = conn;
+ req.black_list = NULL;
+ req.access_lists = bsc_access_lists();
+ req.local_lst_name = conn->sccp.msc->acc_lst_name;
+ req.global_lst_name = conn_get_bts(conn)->network->bsc_data->acc_lst_name;
+ req.bsc_nr = 0;
+
+ rc = bsc_msg_filter_data(gh, msgb_l3len(msg), &req,
+ &conn->filter_state,
+ &cause);
+ *lu_cause = cause.lu_reject_cause;
+ return rc;
+}
+
+/*! BTS->MSC: tell MSC a SAPI was not established. */
+void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+{
+ int rc;
+ struct msgb *resp;
+
+ if (!conn || !msc_connected(conn))
+ return;
+
+ LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT DLCI=0x%02x\n", dlci);
+ resp = gsm0808_create_sapi_reject(dlci);
+ rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+ if (rc != 0)
+ msgb_free(resp);
+}
+
+/*! MS->MSC: Tell MSC that ciphering has been enabled. */
+void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_encr)
+{
+ int rc;
+ struct msgb *resp;
+
+ if (!msc_connected(conn))
+ return;
+
+ LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n");
+ resp = gsm0808_create_cipher_complete(msg, chosen_encr);
+ rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+ if (rc != 0)
+ msgb_free(resp);
+}
+
+/* 9.2.5 CM service accept */
+int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERV ACK");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ msg->lchan = conn->lchan;
+
+ gh->proto_discr = GSM48_PDISC_MM;
+ gh->msg_type = GSM48_MT_MM_CM_SERV_ACC;
+
+ DEBUGP(DMM, "-> CM SERVICE ACK\n");
+
+ gscon_submit_rsl_dtap(conn, msg, 0, 0);
+ return 0;
+}
+
+static void bsc_send_ussd_no_srv(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, const char *text)
+{
+ struct gsm48_hdr *gh;
+ int8_t pdisc;
+ uint8_t mtype;
+ int drop_message = 1;
+
+ if (!text)
+ return;
+
+ if (!msg || msgb_l3len(msg) < sizeof(*gh))
+ return;
+
+ gh = msgb_l3(msg);
+ pdisc = gsm48_hdr_pdisc(gh);
+ mtype = gsm48_hdr_msg_type(gh);
+
+ /* Is CM service request? */
+ if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) {
+ struct gsm48_service_request *cm;
+
+ cm = (struct gsm48_service_request *) &gh->data[0];
+
+ /* Is type SMS or call? */
+ if (cm->cm_service_type == GSM48_CMSERV_SMS)
+ drop_message = 0;
+ else if (cm->cm_service_type == GSM48_CMSERV_MO_CALL_PACKET)
+ drop_message = 0;
+ }
+
+ if (drop_message) {
+ LOGP(DMSC, LOGL_DEBUG, "Skipping (not sending) USSD message: '%s'\n", text);
+ return;
+ }
+
+ LOGP(DMSC, LOGL_INFO, "Sending CM Service Accept\n");
+ gsm48_tx_mm_serv_ack(conn);
+
+ LOGP(DMSC, LOGL_INFO, "Sending USSD message: '%s'\n", text);
+ bsc_send_ussd_notify(conn, 1, text);
+ bsc_send_ussd_release_complete(conn);
+}
+
+static int is_cm_service_for_emerg(struct msgb *msg)
+{
+ struct gsm48_service_request *cm;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*cm)) {
+ LOGP(DMSC, LOGL_ERROR, "CM ServiceRequest does not fit.\n");
+ return 0;
+ }
+
+ cm = (struct gsm48_service_request *) &gh->data[0];
+ return cm->cm_service_type == GSM48_CMSERV_EMERGENCY;
+}
+
+/* extract a subscriber from the paging response */
+static struct bsc_subscr *extract_sub(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ uint8_t mi_type;
+ char mi_string[GSM48_MI_SIZE];
+ struct gsm48_hdr *gh;
+ struct gsm48_pag_resp *resp;
+ struct bsc_subscr *subscr;
+
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) {
+ LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg));
+ return NULL;
+ }
+
+ gh = msgb_l3(msg);
+ resp = (struct gsm48_pag_resp *) &gh->data[0];
+
+ gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
+ mi_string, &mi_type);
+ DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n",
+ gsm48_mi_type_name(mi_type), mi_string);
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_TMSI:
+ subscr = bsc_subscr_find_by_tmsi(conn->network->bsc_subscribers,
+ tmsi_from_string(mi_string));
+ break;
+ case GSM_MI_TYPE_IMSI:
+ subscr = bsc_subscr_find_by_imsi(conn->network->bsc_subscribers,
+ mi_string);
+ break;
+ default:
+ subscr = NULL;
+ break;
+ }
+
+ return subscr;
+}
+
+struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+ int8_t pdisc;
+ uint8_t mtype;
+ struct osmo_bsc_data *bsc;
+ struct bsc_msc_data *msc, *pag_msc;
+ struct bsc_subscr *subscr;
+ int is_emerg = 0;
+
+ bsc = conn->network->bsc_data;
+
+ if (msgb_l3len(msg) < sizeof(*gh)) {
+ LOGP(DMSC, LOGL_ERROR, "There is no GSM48 header here.\n");
+ return NULL;
+ }
+
+ gh = msgb_l3(msg);
+ pdisc = gsm48_hdr_pdisc(gh);
+ mtype = gsm48_hdr_msg_type(gh);
+
+ /*
+ * We are asked to select a MSC here but they are not equal. We
+ * want to respond to a paging request on the MSC where we got the
+ * request from. This is where we need to decide where this connection
+ * will go.
+ */
+ if (pdisc == GSM48_PDISC_RR && mtype == GSM48_MT_RR_PAG_RESP)
+ goto paging;
+ else if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) {
+ is_emerg = is_cm_service_for_emerg(msg);
+ goto round_robin;
+ } else
+ goto round_robin;
+
+round_robin:
+ llist_for_each_entry(msc, &bsc->mscs, entry) {
+ if (!msc->is_authenticated)
+ continue;
+ if (!is_emerg && msc->type != MSC_CON_TYPE_NORMAL)
+ continue;
+ if (is_emerg && !msc->allow_emerg)
+ continue;
+
+ /* force round robin by moving it to the end */
+ llist_move_tail(&msc->entry, &bsc->mscs);
+ return msc;
+ }
+
+ return NULL;
+
+paging:
+ subscr = extract_sub(conn, msg);
+
+ if (!subscr) {
+ LOGP(DMSC, LOGL_ERROR, "Got paged but no subscriber found.\n");
+ return NULL;
+ }
+
+ pag_msc = paging_get_msc(conn_get_bts(conn), subscr);
+ bsc_subscr_put(subscr);
+
+ llist_for_each_entry(msc, &bsc->mscs, entry) {
+ if (msc != pag_msc)
+ continue;
+
+ /*
+ * We don't check if the MSC is connected. In case it
+ * is not the connection will be dropped.
+ */
+
+ /* force round robin by moving it to the end */
+ llist_move_tail(&msc->entry, &bsc->mscs);
+ return msc;
+ }
+
+ LOGP(DMSC, LOGL_ERROR, "Got paged but no request found.\n");
+ return NULL;
+}
+
+/*! MS->MSC: New MM context with L3 payload. */
+int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, uint16_t chosen_channel)
+{
+ struct bsc_msc_data *msc;
+
+ LOGP(DMSC, LOGL_INFO, "Tx MSC COMPL L3\n");
+
+ /* find the MSC link we want to use */
+ msc = bsc_find_msc(conn, msg);
+ if (!msc) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to find a MSC for a connection.\n");
+ bsc_send_ussd_no_srv(conn, msg,
+ conn_get_bts(conn)->network->bsc_data->ussd_no_msc_txt);
+ return -1;
+ }
+
+ return complete_layer3(conn, msg, msc) ? 0 : -2;
+}
+
+static int handle_page_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct bsc_subscr *subscr = extract_sub(conn, msg);
+
+ if (!subscr) {
+ LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n");
+ return -1;
+ }
+
+ paging_request_stop(&conn->network->bts_list, conn_get_bts(conn), subscr, conn,
+ msg);
+ bsc_subscr_put(subscr);
+ return 0;
+}
+
+static void handle_lu_request(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+ struct gsm48_loc_upd_req *lu;
+ struct gsm48_loc_area_id lai;
+
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) {
+ LOGP(DMSC, LOGL_ERROR, "LU too small to look at: %u\n", msgb_l3len(msg));
+ return;
+ }
+
+ gh = msgb_l3(msg);
+ lu = (struct gsm48_loc_upd_req *) gh->data;
+
+ gsm48_generate_lai2(&lai, bts_lai(conn_get_bts(conn)));
+
+ if (memcmp(&lai, &lu->lai, sizeof(lai)) != 0) {
+ LOGP(DMSC, LOGL_DEBUG, "Marking con for welcome USSD.\n");
+ conn->new_subscriber = 1;
+ }
+}
+
+int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ uint8_t mtype = gsm48_hdr_msg_type(gh);
+
+ if (pdisc == GSM48_PDISC_MM) {
+ if (mtype == GSM48_MT_MM_LOC_UPD_REQUEST)
+ handle_lu_request(conn, msg);
+ } else if (pdisc == GSM48_PDISC_RR) {
+ if (mtype == GSM48_MT_RR_PAG_RESP)
+ handle_page_resp(conn, msg);
+ }
+
+ return 0;
+}
+
+static bool complete_layer3(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, struct bsc_msc_data *msc)
+{
+ int con_type, rc, lu_cause;
+ char *imsi = NULL;
+ struct msgb *resp;
+ enum bsc_con ret;
+ struct gsm0808_speech_codec_list scl;
+
+ /* Check the filter */
+ rc = bsc_filter_initial(msc->network->bsc_data, msc, conn, msg,
+ &imsi, &con_type, &lu_cause);
+ if (rc < 0) {
+ bsc_maybe_lu_reject(conn, con_type, lu_cause);
+ return false;
+ }
+
+ /* allocate resource for a new connection */
+ ret = osmo_bsc_sigtran_new_conn(conn, msc);
+
+ if (ret != BSC_CON_SUCCESS) {
+ /* allocation has failed */
+ if (ret == BSC_CON_REJECT_NO_LINK)
+ bsc_send_ussd_no_srv(conn, msg, msc->ussd_msc_lost_txt);
+ else if (ret == BSC_CON_REJECT_RF_GRACE)
+ bsc_send_ussd_no_srv(conn, msg, msc->ussd_grace_txt);
+
+ return false;
+ }
+
+ /* TODO: also extract TMSI. We get an IMSI only when an initial L3 Complete comes in that
+ * contains an IMSI. We filter by IMSI. A TMSI identity is never returned here, see e.g.
+ * _cr_check_loc_upd() and other similar functions called from bsc_msg_filter_initial(). */
+ if (imsi) {
+ conn->filter_state.imsi = talloc_steal(conn, imsi);
+ if (conn->bsub) {
+ /* Already a subscriber on L3 Complete? Should never happen... */
+ if (conn->bsub->imsi[0]
+ && strcmp(conn->bsub->imsi, imsi))
+ LOGP(DMSC, LOGL_ERROR, "Subscriber's IMSI changes from %s to %s\n",
+ conn->bsub->imsi, imsi);
+ bsc_subscr_set_imsi(conn->bsub, imsi);
+ } else
+ conn->bsub = bsc_subscr_find_or_create_by_imsi(msc->network->bsc_subscribers,
+ imsi);
+ gscon_update_id(conn);
+ }
+ conn->filter_state.con_type = con_type;
+
+ /* check return value, if failed check msg for and send USSD */
+
+ bsc_scan_bts_msg(conn, msg);
+
+ if (gscon_is_aoip(conn)) {
+ gen_bss_supported_codec_list(&scl, msc, conn_get_bts(conn));
+ if (scl.len > 0)
+ resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp.msc, conn_get_bts(conn)), &scl);
+ else {
+ /* Note: 3GPP TS 48.008 3.2.1.32, COMPLETE LAYER 3 INFORMATION clearly states that
+ * Codec List (BSS Supported) shall be included, if the radio access network
+ * supports an IP based user plane interface. It may be intentional that the
+ * current configuration does not support any voice codecs, in those cases the
+ * network does not support an IP based user plane interface, and therefore the
+ * Codec List (BSS Supported) IE can be left out in those situations. */
+ resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp.msc, conn_get_bts(conn)), NULL);
+ }
+ } else
+ resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp.msc, conn_get_bts(conn)), NULL);
+
+ if (!resp) {
+ LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n");
+ return false;
+ }
+
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_REQ, resp);
+
+ return true;
+}
+
+/*
+ * Plastic surgery... we want to give up the current connection
+ */
+static int move_to_msc(struct gsm_subscriber_connection *_conn,
+ struct msgb *msg, struct bsc_msc_data *msc)
+{
+ /*
+ * 1. Give up the old connection.
+ * This happens by sending a clear request to the MSC,
+ * it should end with the MSC releasing the connection.
+ */
+ bsc_clear_request(_conn, 0);
+
+ /*
+ * 2. Attempt to create a new connection to the local
+ * MSC. If it fails the caller will need to handle this
+ * properly.
+ */
+ if (!complete_layer3(_conn, msg, msc)) {
+ /* FIXME: I have not the slightest idea what move_to_msc() intends to do; during lchan
+ * FSM introduction, I changed this and hope it is the appropriate action. I actually
+ * assume this is unused legacy code for osmo-bsc_nat?? */
+ gscon_release_lchans(_conn, false);
+ return 1;
+ }
+
+ return 2;
+}
+
+static int handle_cc_setup(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ uint8_t mtype = gsm48_hdr_msg_type(gh);
+
+ struct bsc_msc_data *msc;
+ struct gsm_mncc_number called;
+ struct tlv_parsed tp;
+ unsigned payload_len;
+
+ char _dest_nr[35];
+
+ /*
+ * Do we have a setup message here? if not return fast.
+ */
+ if (pdisc != GSM48_PDISC_CC || mtype != GSM48_MT_CC_SETUP)
+ return 0;
+
+ payload_len = msgb_l3len(msg) - sizeof(*gh);
+
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+ LOGP(DMSC, LOGL_ERROR, "Called BCD not present in setup.\n");
+ return -1;
+ }
+
+ memset(&called, 0, sizeof(called));
+ gsm48_decode_called(&called,
+ TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1);
+
+ if (called.plan != 1 && called.plan != 0)
+ return 0;
+
+ if (called.plan == 1 && called.type == 1) {
+ _dest_nr[0] = _dest_nr[1] = '0';
+ memcpy(_dest_nr + 2, called.number, sizeof(called.number));
+ } else
+ memcpy(_dest_nr, called.number, sizeof(called.number));
+
+ /*
+ * Check if the connection should be moved...
+ */
+ llist_for_each_entry(msc, &conn_get_bts(conn)->network->bsc_data->mscs, entry) {
+ if (msc->type != MSC_CON_TYPE_LOCAL)
+ continue;
+ if (!msc->local_pref)
+ continue;
+ if (regexec(&msc->local_pref_reg, _dest_nr, 0, NULL, 0) != 0)
+ continue;
+
+ return move_to_msc(conn, msg, msc);
+ }
+
+ return 0;
+}
+
+
+/*! MS->BSC/MSC: Um L3 message. */
+void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+{
+ int lu_cause;
+
+ if (!msc_connected(conn))
+ return;
+
+ LOGP(DMSC, LOGL_INFO, "Tx MSC DTAP LINK_ID=0x%02x\n", link_id);
+
+ /*
+ * We might want to move this connection to a new MSC. Ask someone
+ * to handle it. If it was handled we will return.
+ */
+ if (handle_cc_setup(conn, msg) >= 1)
+ return;
+
+ /* Check the filter */
+ if (bsc_filter_data(conn, msg, &lu_cause) < 0) {
+ bsc_maybe_lu_reject(conn,
+ conn->filter_state.con_type,
+ lu_cause);
+ bsc_clear_request(conn, 0);
+ return;
+ }
+
+ bsc_scan_bts_msg(conn, msg);
+
+ /* Store link_id in msg->cb */
+ OBSC_LINKID_CB(msg) = link_id;
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MO_DTAP, msg);
+}
+
+/*! BSC->MSC: RR conn has been cleared. */
+int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+ int rc;
+ struct msgb *resp;
+
+ if (!msc_connected(conn))
+ return 1;
+
+ LOGP(DMSC, LOGL_INFO, "Tx MSC CLEAR REQUEST\n");
+
+ resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
+ if (!resp) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n");
+ return 1;
+ }
+
+ rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+ if (rc != 0)
+ msgb_free(resp);
+
+ return 1;
+}
+
+/*! BSC->MSC: Classmark Update. */
+void bsc_cm_update(struct gsm_subscriber_connection *conn,
+ const uint8_t *cm2, uint8_t cm2_len,
+ const uint8_t *cm3, uint8_t cm3_len)
+{
+ int rc;
+ struct msgb *resp;
+
+ if (!msc_connected(conn))
+ return;
+
+ resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len);
+ rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+ if (rc != 0)
+ msgb_free(resp);
+}
diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c
new file mode 100644
index 000000000..6d39642e2
--- /dev/null
+++ b/src/osmo-bsc/gsm_data.c
@@ -0,0 +1,1707 @@
+/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <netinet/in.h>
+#include <talloc.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+
+void *tall_bsc_ctx = NULL;
+
+const struct value_string bsc_lcls_mode_names[] = {
+ { BSC_LCLS_MODE_DISABLED, "disabled" },
+ { BSC_LCLS_MODE_MGW_LOOP, "mgw-loop" },
+ { BSC_LCLS_MODE_BTS_LOOP, "bts-loop" },
+ { 0, 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[] = {
+ { GSM_PCHAN_NONE, "NONE" },
+ { GSM_PCHAN_CCCH, "CCCH" },
+ { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" },
+ { GSM_PCHAN_TCH_F, "TCH/F" },
+ { GSM_PCHAN_TCH_H, "TCH/H" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
+ { GSM_PCHAN_PDCH, "PDCH" },
+ { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" },
+ { GSM_PCHAN_UNKNOWN, "UNKNOWN" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" },
+ { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" },
+ { 0, NULL }
+};
+
+const struct value_string gsm_pchan_ids[] = {
+ { GSM_PCHAN_NONE, "NONE" },
+ { GSM_PCHAN_CCCH, "CCCH" },
+ { GSM_PCHAN_CCCH_SDCCH4,"CCCH_SDCCH4" },
+ { GSM_PCHAN_TCH_F, "TCH_F" },
+ { GSM_PCHAN_TCH_H, "TCH_H" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
+ { GSM_PCHAN_PDCH, "PDCH" },
+ { GSM_PCHAN_TCH_F_PDCH, "TCH_F_PDCH" },
+ { GSM_PCHAN_UNKNOWN, "UNKNOWN" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH_SDCCH4_CBCH" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8_CBCH" },
+ { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH_F_TCH_H_PDCH" },
+ { 0, NULL }
+};
+
+const struct value_string gsm_pchant_descs[13] = {
+ { GSM_PCHAN_NONE, "Physical Channel not configured" },
+ { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" },
+ { GSM_PCHAN_CCCH_SDCCH4,
+ "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" },
+ { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" },
+ { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" },
+ { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" },
+ { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" },
+ { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" },
+ { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" },
+ { 0, NULL }
+};
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c)
+{
+ return get_value_string(gsm_pchant_names, c);
+}
+
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name)
+{
+ return get_string_value(gsm_pchant_names, name);
+}
+
+/* TODO: move to libosmocore, next to gsm_chan_t_names? */
+const char *gsm_lchant_name(enum gsm_chan_t c)
+{
+ return get_value_string(gsm_chan_t_names, c);
+}
+
+static const struct value_string chreq_names[] = {
+ { GSM_CHREQ_REASON_EMERG, "EMERGENCY" },
+ { GSM_CHREQ_REASON_PAG, "PAGING" },
+ { GSM_CHREQ_REASON_CALL, "CALL" },
+ { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" },
+ { GSM_CHREQ_REASON_OTHER, "OTHER" },
+ { 0, NULL }
+};
+
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c)
+{
+ return get_value_string(chreq_names, c);
+}
+
+struct gsm_bts *gsm_bts_num(const struct gsm_network *net, int num)
+{
+ struct gsm_bts *bts;
+
+ if (num >= net->num_bts)
+ return NULL;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (bts->nr == num)
+ return bts;
+ }
+
+ return NULL;
+}
+
+bool gsm_bts_matches_lai(const struct gsm_bts *bts, const struct osmo_location_area_id *lai)
+{
+ return osmo_plmn_cmp(&lai->plmn, &bts->network->plmn) == 0
+ && lai->lac == bts->location_area_code;
+}
+
+bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cell_id *cell_id)
+{
+ const union gsm0808_cell_id_u *id = &cell_id->id;
+ if (!bts || !cell_id)
+ return false;
+
+ switch (cell_id->id_discr) {
+ case CELL_IDENT_WHOLE_GLOBAL:
+ return gsm_bts_matches_lai(bts, &id->global.lai)
+ && id->global.cell_identity == bts->cell_identity;
+ case CELL_IDENT_LAC_AND_CI:
+ return id->lac_and_ci.lac == bts->location_area_code
+ && id->lac_and_ci.ci == bts->cell_identity;
+ case CELL_IDENT_CI:
+ return id->ci == bts->cell_identity;
+ case CELL_IDENT_NO_CELL:
+ return false;
+ case CELL_IDENT_LAI_AND_LAC:
+ return gsm_bts_matches_lai(bts, &id->lai_and_lac);
+ case CELL_IDENT_LAC:
+ return id->lac == bts->location_area_code;
+ case CELL_IDENT_BSS:
+ return true;
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
+ return false;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+/* From a list of local BTSes that match the cell_id, return the Nth one, or NULL if there is no such
+ * match. */
+struct gsm_bts *gsm_bts_by_cell_id(const struct gsm_network *net,
+ const struct gsm0808_cell_id *cell_id,
+ int match_idx)
+{
+ struct gsm_bts *bts;
+ int i = 0;
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (!gsm_bts_matches_cell_id(bts, cell_id))
+ continue;
+ if (i < match_idx) {
+ /* this is only the i'th match, we're looking for a later one... */
+ i++;
+ continue;
+ }
+ return bts;
+ }
+ return NULL;
+}
+
+struct gsm_bts_ref *gsm_bts_ref_find(const struct llist_head *list, const struct gsm_bts *bts)
+{
+ struct gsm_bts_ref *ref;
+ if (!bts)
+ return NULL;
+ llist_for_each_entry(ref, list, entry) {
+ if (ref->bts == bts)
+ return ref;
+ }
+ return NULL;
+}
+
+/* Add a BTS reference to the local_neighbors list.
+ * Return 1 if added, 0 if such an entry already existed, and negative on errors. */
+int gsm_bts_local_neighbor_add(struct gsm_bts *bts, struct gsm_bts *neighbor)
+{
+ struct gsm_bts_ref *ref;
+ if (!bts || !neighbor)
+ return -ENOMEM;
+
+ if (bts == neighbor)
+ return -EINVAL;
+
+ /* Already got this entry? */
+ ref = gsm_bts_ref_find(&bts->local_neighbors, neighbor);
+ if (ref)
+ return 0;
+
+ ref = talloc_zero(bts, struct gsm_bts_ref);
+ if (!ref)
+ return -ENOMEM;
+ ref->bts = neighbor;
+ llist_add_tail(&ref->entry, &bts->local_neighbors);
+ return 1;
+}
+
+/* Remove a BTS reference from the local_neighbors list.
+ * Return 1 if removed, 0 if no such entry existed, and negative on errors. */
+int gsm_bts_local_neighbor_del(struct gsm_bts *bts, const struct gsm_bts *neighbor)
+{
+ struct gsm_bts_ref *ref;
+ if (!bts || !neighbor)
+ return -ENOMEM;
+
+ ref = gsm_bts_ref_find(&bts->local_neighbors, neighbor);
+ if (!ref)
+ return 0;
+
+ llist_del(&ref->entry);
+ talloc_free(ref);
+ return 1;
+}
+
+struct 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_from_config = ts->pchan_on_init = ts->pchan_is = GSM_PCHAN_NONE;
+ ts->tsc = -1;
+
+ ts_fsm_alloc(ts);
+
+ gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL,
+ bts->nr, trx->nr, ts->nr);
+
+ 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);
+ struct gsm48_multi_rate_conf mr_cfg;
+ 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_from_config = GSM_PCHAN_CCCH_SDCCH4; /* TODO: really?? */
+
+ 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 = NL_MODE_AUTOMATIC;
+ 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 = T_def_get(net->T_defs, 3212, T_CUSTOM, -1);
+ gsm_bts_set_radio_link_timeout(bts, 32); /* Use RADIO LINK TIMEOUT of 32 */
+
+ INIT_LLIST_HEAD(&bts->abis_queue);
+ INIT_LLIST_HEAD(&bts->loc_list);
+ INIT_LLIST_HEAD(&bts->local_neighbors);
+
+ /* Enable all codecs by default. These get reset to a more fine grained selection IF a
+ * 'codec-support' config appears in the config file (see bsc_vty.c). */
+ bts->codec = (struct bts_codec_conf){
+ .hr = 1,
+ .efr = 1,
+ .amr = 1,
+ };
+
+ /* Set reasonable defaults for AMR-FR and AMR-HR rate configuration.
+ * It is possible to set up to 4 codecs per active set, while 5,15K must
+ * be selected. */
+ mr_cfg = (struct gsm48_multi_rate_conf) {
+ .m4_75 = 0,
+ .m5_15 = 1,
+ .m5_90 = 1,
+ .m6_70 = 0,
+ .m7_40 = 0,
+ .m7_95 = 0,
+ .m10_2 = 1,
+ .m12_2 = 1
+ };
+ memcpy(bts->mr_full.gsm48_ie, &mr_cfg, sizeof(bts->mr_full.gsm48_ie));
+ bts->mr_full.ms_mode[0].mode = 1;
+ bts->mr_full.ms_mode[1].mode = 2;
+ bts->mr_full.ms_mode[2].mode = 6;
+ bts->mr_full.ms_mode[3].mode = 7;
+ bts->mr_full.bts_mode[0].mode = 1;
+ bts->mr_full.bts_mode[1].mode = 2;
+ bts->mr_full.bts_mode[2].mode = 6;
+ bts->mr_full.bts_mode[3].mode = 7;
+ for (i = 0; i < 3; i++) {
+ bts->mr_full.ms_mode[i].hysteresis = 8;
+ bts->mr_full.ms_mode[i].threshold = 32;
+ bts->mr_full.bts_mode[i].hysteresis = 8;
+ bts->mr_full.bts_mode[i].threshold = 32;
+ }
+ bts->mr_full.num_modes = 4;
+
+ mr_cfg = (struct gsm48_multi_rate_conf) {
+ .m4_75 = 0,
+ .m5_15 = 1,
+ .m5_90 = 1,
+ .m6_70 = 0,
+ .m7_40 = 1,
+ .m7_95 = 1,
+ .m10_2 = 0,
+ .m12_2 = 0
+ };
+ memcpy(bts->mr_half.gsm48_ie, &mr_cfg, sizeof(bts->mr_half.gsm48_ie));
+ bts->mr_half.ms_mode[0].mode = 1;
+ bts->mr_half.ms_mode[1].mode = 2;
+ bts->mr_half.ms_mode[2].mode = 4;
+ bts->mr_half.ms_mode[3].mode = 5;
+ bts->mr_half.bts_mode[0].mode = 1;
+ bts->mr_half.bts_mode[1].mode = 2;
+ bts->mr_half.bts_mode[2].mode = 4;
+ bts->mr_half.bts_mode[3].mode = 5;
+ for (i = 0; i < 3; i++) {
+ bts->mr_half.ms_mode[i].hysteresis = 8;
+ bts->mr_half.ms_mode[i].threshold = 32;
+ bts->mr_half.bts_mode[i].hysteresis = 8;
+ bts->mr_half.bts_mode[i].threshold = 32;
+ }
+ bts->mr_half.num_modes = 4;
+
+ 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)
+{
+ if (!ts->fi)
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan_from_config=%s, not allocated)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan_from_config));
+ else if (ts->fi->state == TS_ST_NOT_INITIALIZED)
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan_from_config=%s,state=%s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan_from_config),
+ osmo_fsm_inst_state_name(ts->fi));
+ else if (ts->pchan_is == ts->pchan_on_init)
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s,state=%s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan_is),
+ osmo_fsm_inst_state_name(ts->fi));
+ else
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan_on_init=%s,pchan=%s,state=%s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan_on_init),
+ gsm_pchan_name(ts->pchan_is),
+ osmo_fsm_inst_state_name(ts->fi));
+ return ts2str;
+}
+
+char *gsm_lchan_name_compute(const struct gsm_lchan *lchan)
+{
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr);
+
+ return ts2str;
+}
+
+/* obtain the MO structure for a given object instance */
+static inline struct gsm_abis_mo *
+gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_abis_mo *mo = NULL;
+
+ switch (obj_class) {
+ case NM_OC_BTS:
+ mo = &bts->mo;
+ break;
+ case NM_OC_RADIO_CARRIER:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->mo;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->bb_transc.mo;
+ break;
+ case NM_OC_CHANNEL:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ return NULL;
+ mo = &trx->ts[obj_inst->ts_nr].mo;
+ break;
+ case NM_OC_SITE_MANAGER:
+ mo = &bts->site_mgr.mo;
+ break;
+ case NM_OC_BS11:
+ switch (obj_inst->bts_nr) {
+ case BS11_OBJ_CCLK:
+ mo = &bts->bs11.cclk.mo;
+ break;
+ case BS11_OBJ_BBSIG:
+ if (obj_inst->ts_nr > bts->num_trx)
+ return NULL;
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->bs11.bbsig.mo;
+ break;
+ case BS11_OBJ_PA:
+ if (obj_inst->ts_nr > bts->num_trx)
+ return NULL;
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->bs11.pa.mo;
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ case NM_OC_BS11_RACK:
+ mo = &bts->bs11.rack.mo;
+ break;
+ case NM_OC_BS11_ENVABTSE:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse))
+ return NULL;
+ mo = &bts->bs11.envabtse[obj_inst->trx_nr].mo;
+ break;
+ case NM_OC_GPRS_NSE:
+ mo = &bts->gprs.nse.mo;
+ break;
+ case NM_OC_GPRS_CELL:
+ mo = &bts->gprs.cell.mo;
+ break;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ return NULL;
+ mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo;
+ break;
+ }
+ return mo;
+}
+
+/* obtain the gsm_nm_state data structure for a given object instance */
+struct gsm_nm_state *
+gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_abis_mo *mo;
+
+ mo = gsm_objclass2mo(bts, obj_class, obj_inst);
+ if (!mo)
+ return NULL;
+
+ return &mo->nm_state;
+}
+
+/* obtain the in-memory data structure of a given object instance */
+void *
+gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_bts_trx *trx;
+ void *obj = NULL;
+
+ switch (obj_class) {
+ case NM_OC_BTS:
+ obj = bts;
+ break;
+ case NM_OC_RADIO_CARRIER:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ obj = trx;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ obj = &trx->bb_transc;
+ break;
+ case NM_OC_CHANNEL:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ return NULL;
+ obj = &trx->ts[obj_inst->ts_nr];
+ break;
+ case NM_OC_SITE_MANAGER:
+ obj = &bts->site_mgr;
+ break;
+ case NM_OC_GPRS_NSE:
+ obj = &bts->gprs.nse;
+ break;
+ case NM_OC_GPRS_CELL:
+ obj = &bts->gprs.cell;
+ break;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ return NULL;
+ obj = &bts->gprs.nsvc[obj_inst->trx_nr];
+ break;
+ }
+ return obj;
+}
+
+/* See Table 10.5.25 of GSM04.08 */
+uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+ uint8_t ts_nr, uint8_t lchan_nr)
+{
+ uint8_t cbits, chan_nr;
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_F_PDCH:
+ OSMO_ASSERT(lchan_nr == 0);
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_PDCH:
+ OSMO_ASSERT(lchan_nr == 0);
+ cbits = RSL_CHAN_OSMO_PDCH >> 3;
+ break;
+ case GSM_PCHAN_TCH_H:
+ OSMO_ASSERT(lchan_nr < 2);
+ cbits = 0x02;
+ cbits += lchan_nr;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ /*
+ * As a special hack for BCCH, lchan_nr == 4 may be passed
+ * here. This should never be sent in an RSL message.
+ * See osmo-bts-xxx/oml.c:opstart_compl().
+ */
+ if (lchan_nr == CCCH_LCHAN)
+ chan_nr = 0;
+ else
+ OSMO_ASSERT(lchan_nr < 4);
+ cbits = 0x04;
+ cbits += lchan_nr;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ OSMO_ASSERT(lchan_nr < 8);
+ cbits = 0x08;
+ cbits += lchan_nr;
+ break;
+ default:
+ case GSM_PCHAN_CCCH:
+ OSMO_ASSERT(lchan_nr == 0);
+ cbits = 0x10;
+ break;
+ }
+
+ chan_nr = (cbits << 3) | (ts_nr & 0x7);
+
+ return chan_nr;
+}
+
+uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan)
+{
+ /* Note: non-standard Osmocom style dyn TS PDCH mode chan_nr is only used within
+ * rsl_tx_dyn_ts_pdch_act_deact(). */
+ return gsm_pchan2chan_nr(lchan->ts->pchan_is, lchan->ts->nr, lchan->nr);
+}
+
+/* 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_from_config == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ lchan = &trx->ts[0].lchan[2];
+ else {
+ int i;
+ for (i = 0; i < 8; i++) {
+ if (trx->ts[i].pchan_from_config == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) {
+ lchan = &trx->ts[i].lchan[2];
+ break;
+ }
+ }
+ }
+
+ return 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;
+
+ if (rc)
+ *rc = -EINVAL;
+
+ if (cbits == 0x01) {
+ lch_idx = 0; /* TCH/F */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_F)
+ || ts->pchan_on_init == GSM_PCHAN_PDCH; /* PDCH? really? */
+ } else if ((cbits & 0x1e) == 0x02) {
+ lch_idx = cbits & 0x1; /* TCH/H */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_H);
+ } else if ((cbits & 0x1c) == 0x04) {
+ lch_idx = cbits & 0x3; /* SDCCH/4 */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH_SDCCH4);
+ } else if ((cbits & 0x18) == 0x08) {
+ lch_idx = cbits & 0x7; /* SDCCH/8 */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_SDCCH8_SACCH8C);
+ } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
+ lch_idx = 0; /* CCCH? */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH);
+ /* FIXME: we should not return first sdcch4 !!! */
+ } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) {
+ lch_idx = 0;
+ ok = (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH);
+ } 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,
+ /* Dyn TS: maximum allowed subslots */
+ [GSM_PCHAN_TCH_F_TCH_H_PDCH] = 2,
+ [GSM_PCHAN_TCH_F_PDCH] = 1,
+};
+
+/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of
+ * logical channels available in the timeslot. */
+uint8_t pchan_subslots(enum gsm_phys_chan_config pchan)
+{
+ if (pchan < 0 || pchan >= ARRAY_SIZE(subslots_per_pchan))
+ return 0;
+ return subslots_per_pchan[pchan];
+}
+
+static bool pchan_is_tch(enum gsm_phys_chan_config pchan)
+{
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool ts_is_tch(struct gsm_bts_trx_ts *ts)
+{
+ return pchan_is_tch(ts->pchan_is);
+}
+
+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_all_ts_dispatch(struct gsm_bts_trx *trx, uint32_t ts_ev, void *data)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ osmo_fsm_inst_dispatch(ts->fi, ts_ev, data);
+ }
+}
+
+void gsm_bts_all_ts_dispatch(struct gsm_bts *bts, uint32_t ts_ev, void *data)
+{
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ gsm_trx_all_ts_dispatch(trx, ts_ev, data);
+}
+
+static void _chan_desc_fill_tail(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan)
+{
+ if (!lchan->ts->hopping.enabled) {
+ uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff;
+ cd->h0.tsc = gsm_ts_tsc(lchan->ts);
+ cd->h0.h = 0;
+ cd->h0.arfcn_high = arfcn >> 8;
+ cd->h0.arfcn_low = arfcn & 0xff;
+ } else {
+ cd->h1.tsc = gsm_ts_tsc(lchan->ts);
+ cd->h1.h = 1;
+ cd->h1.maio_high = lchan->ts->hopping.maio >> 2;
+ cd->h1.maio_low = lchan->ts->hopping.maio & 0x03;
+ cd->h1.hsn = lchan->ts->hopping.hsn;
+ }
+}
+
+void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
+ const struct gsm_lchan *lchan)
+{
+ cd->chan_nr = gsm_lchan2chan_nr(lchan);
+ _chan_desc_fill_tail(cd, lchan);
+}
+
+/* like gsm48_lchan2chan_desc() above, but use ts->pchan_from_config to
+ * return a channel description based on what is configured, rather than
+ * what the current state of the pchan type is */
+void gsm48_lchan2chan_desc_as_configured(struct gsm48_chan_desc *cd,
+ const struct gsm_lchan *lchan)
+{
+ cd->chan_nr = gsm_pchan2chan_nr(lchan->ts->pchan_from_config, lchan->ts->nr, lchan->nr);
+ _chan_desc_fill_tail(cd, lchan);
+}
+
+bool nm_is_running(const struct gsm_nm_state *s) {
+ return (s->operational == NM_OPSTATE_ENABLED) && (
+ (s->availability == NM_AVSTATE_OK) ||
+ (s->availability == 0xff)
+ );
+}
+
+/* determine the logical channel type based on the physical channel type */
+int gsm_lchan_type_by_pchan(enum gsm_phys_chan_config pchan)
+{
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ return GSM_LCHAN_TCH_F;
+ case GSM_PCHAN_TCH_H:
+ return GSM_LCHAN_TCH_H;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ return GSM_LCHAN_SDCCH;
+ default:
+ return -1;
+ }
+}
+
+enum gsm_phys_chan_config gsm_pchan_by_lchan_type(enum gsm_chan_t type)
+{
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ return GSM_PCHAN_TCH_F;
+ case GSM_LCHAN_TCH_H:
+ return GSM_PCHAN_TCH_H;
+ case GSM_LCHAN_NONE:
+ case GSM_LCHAN_PDTCH:
+ /* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only
+ * used in osmo-bts. Maybe set PDTCH and drop the NONE case
+ * here. */
+ return GSM_PCHAN_PDCH;
+ default:
+ return GSM_PCHAN_UNKNOWN;
+ }
+}
+
+/* Can the timeslot in principle be used as this PCHAN kind? */
+bool ts_is_capable_of_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan)
+{
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_PDCH:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_PDCH:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_CCCH_SDCCH4:
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ switch (pchan) {
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ return true;
+ default:
+ return false;
+ }
+
+ default:
+ return ts->pchan_on_init == pchan;
+ }
+}
+
+bool ts_is_capable_of_lchant(struct gsm_bts_trx_ts *ts, enum gsm_chan_t type)
+{
+ switch (ts->pchan_on_init) {
+
+ case GSM_PCHAN_TCH_F:
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_TCH_H:
+ switch (type) {
+ case GSM_LCHAN_TCH_H:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_TCH_F_PDCH:
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_PDTCH:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ case GSM_LCHAN_PDTCH:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_PDCH:
+ switch (type) {
+ case GSM_LCHAN_PDTCH:
+ return true;
+ default:
+ return false;
+ }
+
+ case GSM_PCHAN_CCCH:
+ switch (type) {
+ case GSM_LCHAN_CCCH:
+ return true;
+ default:
+ return false;
+ }
+ break;
+
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ switch (type) {
+ case GSM_LCHAN_CCCH:
+ case GSM_LCHAN_SDCCH:
+ return true;
+ default:
+ return false;
+ }
+
+ default:
+ return false;
+ }
+}
+
+static int trx_count_free_ts(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan)
+{
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int j;
+ int count = 0;
+
+ if (!trx_is_usable(trx))
+ return 0;
+
+ for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
+ ts = &trx->ts[j];
+ if (!ts_is_usable(ts))
+ continue;
+
+ if (ts->pchan_is == GSM_PCHAN_PDCH) {
+ /* Dynamic timeslots in PDCH mode will become TCH if needed. */
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (pchan == GSM_PCHAN_TCH_F)
+ count++;
+ continue;
+
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (pchan == GSM_PCHAN_TCH_F)
+ count++;
+ else if (pchan == GSM_PCHAN_TCH_H)
+ count += 2;
+ continue;
+
+ default:
+ /* Not dynamic, not applicable. */
+ continue;
+ }
+ }
+
+ if (ts->pchan_is != pchan)
+ continue;
+
+ ts_for_each_lchan(lchan, ts) {
+ if (lchan_state_is(lchan, LCHAN_ST_UNUSED))
+ count++;
+ }
+ }
+
+ return count;
+}
+
+/* Count number of free TS of given pchan type */
+int bts_count_free_ts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
+{
+ struct gsm_bts_trx *trx;
+ int count = 0;
+
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ count += trx_count_free_ts(trx, pchan);
+
+ return count;
+}
+
+bool ts_is_usable(const struct gsm_bts_trx_ts *ts)
+{
+ if (!trx_is_usable(ts->trx)) {
+ LOGP(DRLL, LOGL_DEBUG, "%s not usable\n", gsm_trx_name(ts->trx));
+ return false;
+ }
+
+ if (!ts->fi)
+ return false;
+
+ switch (ts->fi->state) {
+ case TS_ST_NOT_INITIALIZED:
+ case TS_ST_BORKEN:
+ return false;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+const struct value_string lchan_activate_mode_names[] = {
+ OSMO_VALUE_STRING(FOR_NONE),
+ OSMO_VALUE_STRING(FOR_MS_CHANNEL_REQUEST),
+ OSMO_VALUE_STRING(FOR_ASSIGNMENT),
+ OSMO_VALUE_STRING(FOR_HANDOVER),
+ OSMO_VALUE_STRING(FOR_VTY),
+ {}
+};
+
+struct osmo_cell_global_id *cgi_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts)
+{
+ static struct osmo_cell_global_id cgi;
+ cgi.lai.plmn = msc->network->plmn;
+ if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
+ cgi.lai.plmn.mcc = msc->core_plmn.mcc;
+ if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID) {
+ cgi.lai.plmn.mnc = msc->core_plmn.mnc;
+ cgi.lai.plmn.mnc_3_digits = msc->core_plmn.mnc_3_digits;
+ }
+ cgi.lai.lac = (msc->core_lac != -1) ? msc->core_lac : bts->location_area_code;
+ cgi.cell_identity = (msc->core_ci != -1) ? msc->core_ci : bts->cell_identity;
+
+ return &cgi;
+}
diff --git a/src/osmo-bsc/gsm_timers.c b/src/osmo-bsc/gsm_timers.c
new file mode 100644
index 000000000..fc3ec246e
--- /dev/null
+++ b/src/osmo-bsc/gsm_timers.c
@@ -0,0 +1,207 @@
+/* Implementation to define Tnnn timers globally and use for FSM state changes. */
+/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/bsc/gsm_timers.h>
+
+/* a = return_val * b. Return 0 if factor is below 1. */
+static int T_factor(enum T_unit a, enum T_unit b)
+{
+ if (b == a
+ || b == T_CUSTOM || a == T_CUSTOM)
+ return 1;
+
+ switch (b) {
+ case T_MS:
+ switch (a) {
+ case T_S:
+ return 1000;
+ case T_M:
+ return 60*1000;
+ default:
+ return 0;
+ }
+ case T_S:
+ switch (a) {
+ case T_M:
+ return 60;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static int T_round(int val, enum T_unit from_unit, enum T_unit to_unit)
+{
+ int f;
+ if (!val)
+ return 0;
+
+ f = T_factor(from_unit, to_unit);
+ if (f < 1) {
+ f = T_factor(to_unit, from_unit);
+ return (val / f) + (val % f? 1 : 0);
+ }
+ return val * f;
+}
+
+/* Return the value of a T timer from a list of T_defs.
+ * Any value is rounded up to match as_unit: 1100 ms as T_S becomes 2 seconds, as T_M becomes one minute.
+ * If no such timer is defined, return the default value passed, or abort the program if default < 0.
+ *
+ * Usage examples:
+ *
+ * - Initialization:
+ *
+ * struct T_def global_T_defs[] = {
+ * { .T=7, .default_val=50, .desc="Water Boiling Timeout" }, // default is .unit=T_S == 0
+ * { .T=8, .default_val=300, .desc="Tea brewing" },
+ * { .T=9, .default_val=5, .unit=T_M, .desc="Let tea cool down before drinking" },
+ * { .T=10, .default_val=20, .unit=T_M, .desc="Forgot to drink tea while it's warm" },
+ * {} // <-- important! last entry shall be zero
+ * };
+ * T_defs_reset(global_T_defs); // make all values the default
+ * T_defs_vty_init(global_T_defs, CONFIG_NODE);
+ *
+ * val = T_def_get(global_T_defs, 7, T_S, -1); // -> 50
+ * sleep(val);
+ *
+ * val = T_def_get(global_T_defs, 7, T_M, -1); // 50 seconds becomes 1 minute -> 1
+ * sleep_minutes(val);
+ *
+ * val = T_def_get(global_T_defs, 99, T_S, -1); // not defined, program aborts!
+ *
+ * val = T_def_get(global_T_defs, 99, T_S, 3); // not defined, returns 3
+ */
+int T_def_get(const struct T_def *T_defs, int T, enum T_unit as_unit, int val_if_not_present)
+{
+ const struct T_def *d = T_def_get_entry((struct T_def*)T_defs, T);
+ if (!d) {
+ OSMO_ASSERT(val_if_not_present >= 0);
+ return val_if_not_present;
+ }
+ return T_round(d->val, d->unit, as_unit);
+}
+
+/* Set all T_def values to the default_val. */
+void T_defs_reset(struct T_def *T_defs)
+{
+ struct T_def *d;
+ for_each_T_def(d, T_defs)
+ d->val = d->default_val;
+}
+
+/* Return a pointer to a T_def from an array, or NULL. */
+struct T_def *T_def_get_entry(struct T_def *T_defs, int T)
+{
+ struct T_def *d;
+ for_each_T_def(d, T_defs) {
+ if (d->T == T)
+ return d;
+ }
+ return NULL;
+}
+
+/* Return a state_timeout entry from an array, or return NULL if the entry is zero.
+ *
+ * The timeouts_array shall contain exactly 32 elements, which corresponds to the number of states
+ * allowed by osmo_fsm_*. Lookup is by array index.
+ *
+ * For example:
+ * struct state_timeout my_fsm_timeouts[32] = {
+ * [MY_FSM_STATE_3] = { .T = 423 },
+ * [MY_FSM_STATE_7] = { .T = 235 },
+ * [MY_FSM_STATE_8] = { .keep_timer = true },
+ * // any state that is omitted will remain zero == no timeout
+ * };
+ * get_state_timeout(MY_FSM_STATE_0, &my_fsm_timeouts) -> NULL,
+ * get_state_timeout(MY_FSM_STATE_7, &my_fsm_timeouts) -> { .T = 235 }
+ *
+ * The intention is then to obtain the timer like T_def_get(global_T_defs, T=235); see also
+ * fsm_inst_state_chg_T() below.
+ */
+const struct state_timeout *get_state_timeout(uint32_t state,
+ const struct state_timeout *timeouts_array)
+{
+ const struct state_timeout *t;
+ OSMO_ASSERT(state < 32);
+ t = &timeouts_array[state];
+ if (!t->keep_timer && !t->T)
+ return NULL;
+ return t;
+}
+
+/* Call osmo_fsm_inst_state_chg() or osmo_fsm_inst_state_chg_keep_timer(), depending on the T value
+ * defined for this state in the timeouts_array, and obtaining the actual timeout value from T_defs.
+ * A T timer configured in sub-second precision is rounded up to the next full second.
+ *
+ * See get_state_timeout() and T_def_get().
+ *
+ * Should a T number be defined in timeouts_array that is not defined in T_defs, use default_timeout.
+ * This is best used by wrapping this function call in a macro suitable for a specific FSM
+ * implementation, which can become as short as: my_fsm_state_chg(fi, NEXT_STATE):
+ *
+ * #define my_fsm_state_chg(fi, NEXT_STATE) \
+ * fsm_inst_state_chg_T(fi, NEXT_STATE, my_fsm_timeouts, global_T_defs, 5)
+ *
+ * my_fsm_state_chg(fi, MY_FSM_STATE_1);
+ * // -> No timeout configured, will enter state without timeout.
+ *
+ * my_fsm_state_chg(fi, MY_FSM_STATE_3);
+ * // T423 configured for this state, will look up T423 in T_defs, or use 5 seconds if unset.
+ *
+ * my_fsm_state_chg(fi, MY_FSM_STATE_8);
+ * // keep_timer configured for this state, will invoke osmo_fsm_inst_state_chg_keep_timer().
+ *
+ */
+int _fsm_inst_state_chg_T(struct osmo_fsm_inst *fi, uint32_t state,
+ const struct state_timeout *timeouts_array,
+ const struct T_def *T_defs, int default_timeout,
+ const char *file, int line)
+{
+ const struct state_timeout *t = get_state_timeout(state, timeouts_array);
+ int val;
+
+ /* No timeout defined for this state? */
+ if (!t)
+ return _osmo_fsm_inst_state_chg(fi, state, 0, 0, file, line);
+
+ if (t->keep_timer) {
+ int rc = _osmo_fsm_inst_state_chg_keep_timer(fi, state, file, line);
+ if (t->T && !rc)
+ fi->T = t->T;
+ return rc;
+ }
+
+ val = T_def_get(T_defs, t->T, T_S, default_timeout);
+ return _osmo_fsm_inst_state_chg(fi, state, val, t->T, file, line);
+}
+
+const struct value_string T_unit_names[] = {
+ { T_S, "s" },
+ { T_MS, "ms" },
+ { T_CUSTOM, "(custom)" },
+ { 0, NULL }
+};
diff --git a/src/osmo-bsc/gsm_timers_vty.c b/src/osmo-bsc/gsm_timers_vty.c
new file mode 100644
index 000000000..de61e2453
--- /dev/null
+++ b/src/osmo-bsc/gsm_timers_vty.c
@@ -0,0 +1,118 @@
+/* Implementation to configure Tnnn timers in VTY */
+/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/bsc/gsm_timers.h>
+
+/* Global singleton list used for the VTY configuration. See T_defs_vty_init(). */
+static struct T_def *g_vty_T_defs = NULL;
+
+/* Parse an argument like "T1234", "t1234" or "1234" and return the corresponding T_def entry from
+ * g_vty_T_defs, if any. */
+static struct T_def *parse_T_arg(struct vty *vty, const char *T_str)
+{
+ int T;
+ struct T_def *d;
+
+ if (T_str[0] == 't' || T_str[0] == 'T')
+ T_str++;
+ T = atoi(T_str);
+
+ d = T_def_get_entry(g_vty_T_defs, T);
+ if (!d)
+ vty_out(vty, "No such timer: T%d%s", T, VTY_NEWLINE);
+ return d;
+}
+
+/* Installed in the VTY on T_defs_vty_init(). */
+DEFUN(cfg_timer, cfg_timer_cmd,
+ "timer TNNNN (default|<1-65535>)",
+ "Configure GSM Timers\n"
+ "T-number, optionally preceded by 't' or 'T'."
+ "See also 'show timer' for a list of available timers.\n"
+ "Set to default timer value\n" "Timer value\n")
+{
+ const char *val_str = argv[1];
+ struct T_def *d;
+
+ d = parse_T_arg(vty, argv[0]);
+ if (!d)
+ return CMD_WARNING;
+
+ if (!strcmp(val_str, "default"))
+ d->val = d->default_val;
+ else
+ d->val = atoi(val_str);
+ vty_out(vty, "T%d = %u %s (%s)%s", d->T, d->val, T_unit_name(d->unit), d->desc, VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+/* Print a T_def to the VTY. */
+static void show_one_timer(struct vty *vty, struct T_def *d)
+{
+ vty_out(vty, "T%d = %u %s (default = %u %s) \t%s%s",
+ d->T, d->val, T_unit_name(d->unit),
+ d->default_val, T_unit_name(d->unit), d->desc, VTY_NEWLINE);
+}
+
+/* Installed in the VTY on T_defs_vty_init(). */
+DEFUN(show_timer, show_timer_cmd,
+ "show timer [TNNNN]",
+ SHOW_STR "GSM Timers\n"
+ "Specific timer to show, or all timers if omitted.\n")
+{
+ struct T_def *d;
+
+ if (argc) {
+ d = parse_T_arg(vty, argv[0]);
+ if (!d)
+ return CMD_WARNING;
+ show_one_timer(vty, d);
+ return CMD_SUCCESS;
+ }
+
+ for_each_T_def(d, g_vty_T_defs)
+ show_one_timer(vty, d);
+ return CMD_SUCCESS;
+}
+
+/* Install GSM timer configuration commands in the VTY. */
+void T_defs_vty_init(struct T_def *T_defs, int cfg_parent_node)
+{
+ g_vty_T_defs = T_defs;
+ install_element_ve(&show_timer_cmd);
+ install_element(cfg_parent_node, &cfg_timer_cmd);
+}
+
+/* Write GSM timer configuration to the vty. */
+void T_defs_vty_write(struct vty *vty, const char *indent)
+{
+ struct T_def *d;
+ for_each_T_def(d, g_vty_T_defs) {
+ if (d->val != d->default_val)
+ vty_out(vty, "%stimer t%d %u%s", indent, d->T, d->val, VTY_NEWLINE);
+ }
+}
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 <jolly@eversberg.eu>
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Andreas Eversberg <jolly@eversberg.eu>
+ * Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdbool.h>
+#include <talloc.h>
+
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/gsm_data.h>
+
+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..0bfbce20d
--- /dev/null
+++ b/src/osmo-bsc/handover_decision.c
@@ -0,0 +1,297 @@
+/* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/handover_cfg.h>
+
+/* 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 void attempt_handover(struct gsm_meas_rep *mr)
+{
+ struct handover_out_req req;
+ struct gsm_bts *bts = mr->lchan->ts->trx->bts;
+ struct neigh_meas_proc *best_cell = NULL;
+ unsigned int best_better_db = 0;
+ int i;
+
+ if (!ho_get_ho_active(bts->ho))
+ return;
+
+ /* 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;
+
+ req = (struct handover_out_req){
+ .from_hodec_id = HODEC1,
+ .old_lchan = mr->lchan,
+ .target_nik = {
+ .from_bts = bts->nr,
+ .arfcn = best_cell->arfcn,
+ .bsic = best_cell->bsic,
+ },
+ };
+ handover_request(&req);
+}
+
+/* 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..a8fff6319
--- /dev/null
+++ b/src/osmo-bsc/handover_decision_2.c
@@ -0,0 +1,1949 @@
+/* Handover Decision Algorithm 2 for intra-BSC (inter-BTS) handover, public API for OsmoBSC. */
+
+/* (C) 2009 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Andreas Eversberg <jolly@eversberg.eu>
+ * Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdbool.h>
+#include <errno.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/handover_decision.h>
+#include <osmocom/bsc/handover_decision_2.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/penalty_timers.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+
+#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 %s) (subscr %s) " fmt, \
+ lchan->ts->trx->bts->nr, \
+ lchan->ts->trx->nr, \
+ lchan->ts->nr, \
+ lchan->nr, \
+ gsm_lchant_name(lchan->type), \
+ gsm48_chan_mode_name(lchan->tch_mode), \
+ 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 %s)->(BTS %u) (subscr %s) " fmt, \
+ lchan->ts->trx->bts->nr, \
+ lchan->ts->trx->nr, \
+ lchan->ts->nr, \
+ lchan->nr, \
+ gsm_lchant_name(lchan->type), \
+ gsm48_chan_mode_name(lchan->tch_mode), \
+ new_bts->nr, \
+ bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \
+ ## args)
+
+#define LOGPHOLCHANTOREMOTE(lchan, remote_cil, level, fmt, args...) \
+ LOGP(DHODEC, level, "(lchan %u.%u%u%u %s %s)->(remote-BSS %s) (subscr %s) " fmt, \
+ lchan->ts->trx->bts->nr, \
+ lchan->ts->trx->nr, \
+ lchan->ts->nr, \
+ lchan->nr, \
+ gsm_lchant_name(lchan->type), \
+ gsm48_chan_mode_name(lchan->tch_mode), \
+ gsm0808_cell_id_list_name(remote_cil), \
+ bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \
+ ## args)
+
+#define LOGPHOCAND(candidate, level, fmt, args...) do {\
+ if ((candidate)->bts) \
+ LOGPHOLCHANTOBTS((candidate)->lchan, (candidate)->bts, level, fmt, ## args); \
+ else if ((candidate)->cil) \
+ LOGPHOLCHANTOREMOTE((candidate)->lchan, (candidate)->cil, level, fmt, ## args); \
+ } while(0)
+
+
+#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 neighbor_ident_key nik; /* neighbor ARFCN+BSIC */
+ struct gsm_bts *bts; /* target BTS in local BSS */
+ const struct gsm0808_cell_id_list2 *cil; /* target cells in remote BSS */
+ uint8_t requirements; /* what is fulfilled */
+ int avg; /* average RX level */
+};
+
+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,
+ const void *for_object,
+ int penalty_time)
+{
+ if (!for_object) {
+ LOGP(DHODEC, LOGL_ERROR, "%s Unable to set Handover-2 penalty timer:"
+ " no target cell pointer\n",
+ bsc_subscr_name(conn->bsub));
+ return;
+ }
+
+ if (!conn->hodec2.penalty_timers) {
+ conn->hodec2.penalty_timers = penalty_timers_init(conn);
+ OSMO_ASSERT(conn->hodec2.penalty_timers);
+ }
+
+ penalty_timers_add(conn->hodec2.penalty_timers, for_object, penalty_time);
+}
+
+static void nik_penalty_time_add(struct gsm_subscriber_connection *conn,
+ struct neighbor_ident_key *nik,
+ int penalty_time)
+{
+ _conn_penalty_time_add(conn,
+ neighbor_ident_get(conn->network->neighbor_bss_cells, nik),
+ penalty_time);
+}
+
+static void bts_penalty_time_add(struct gsm_subscriber_connection *conn,
+ struct gsm_bts *bts,
+ int penalty_time)
+{
+ _conn_penalty_time_add(conn, bts, penalty_time);
+}
+
+static unsigned int conn_penalty_time_remaining(struct gsm_subscriber_connection *conn,
+ const void *for_object)
+{
+ if (!conn->hodec2.penalty_timers)
+ return 0;
+ return penalty_timers_remaining(conn->hodec2.penalty_timers, for_object);
+}
+
+/* did we get a RXLEV for a given cell in the given report? Mark matches as MRC_F_PROCESSED. */
+static struct gsm_meas_rep_cell *cell_in_rep(struct gsm_meas_rep *mr, uint16_t arfcn, uint8_t bsic)
+{
+ 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;
+ mrc->flags |= MRC_F_PROCESSED;
+ } else {
+ nmp->rxlev[idx] = 0;
+ }
+ 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.len) {
+ /* 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)
+ 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;
+ }
+
+ /* 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;
+ 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");
+ /* FIXME: should allow handover of non-speech lchans */
+ 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:
+ requirement &= ~(REQUIREMENT_A_TCHF);
+ break;
+ case GSM_LCHAN_TCH_H:
+ 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(&current_bts->mr_full, &bts->mr_full,
+ sizeof(struct amr_multirate_conf)))
+ requirement &= ~(REQUIREMENT_A_TCHF);
+ if ((requirement & REQUIREMENT_A_TCHH)
+ && !!memcmp(&current_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(&current_bts->mr_half, &bts->mr_full,
+ sizeof(struct amr_multirate_conf)))
+ requirement &= ~(REQUIREMENT_A_TCHF);
+ if ((requirement & REQUIREMENT_A_TCHH)
+ && !!memcmp(&current_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 unsynchronized handovers must no be exceeded */
+ if (current_bts != bts
+ && bts_handover_count(bts, HO_SCOPE_ALL) >= ho_get_hodec2_ho_max(bts->ho)) {
+ LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ "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))
+ requirement |= REQUIREMENT_B_TCHF;
+ }
+ if (requirement & REQUIREMENT_A_TCHH) {
+ if (tchh_count - 1 >= ho_get_hodec2_tchh_min_slots(bts->ho))
+ requirement |= REQUIREMENT_B_TCHH;
+ }
+
+ /* Requirement C */
+
+ /* the nr of free timeslots of the target cell must be >= the
+ * free slots of the current cell _after_ handover/assignment */
+ count = bts_count_free_ts(current_bts,
+ (lchan->type == GSM_LCHAN_TCH_H) ?
+ GSM_PCHAN_TCH_H : GSM_PCHAN_TCH_F);
+ if (requirement & REQUIREMENT_A_TCHF) {
+ if (tchf_count - 1 >= count + 1)
+ requirement |= REQUIREMENT_C_TCHF;
+ }
+ if (requirement & REQUIREMENT_A_TCHH) {
+ if (tchh_count - 1 >= count + 1)
+ requirement |= REQUIREMENT_C_TCHH;
+ }
+
+ /* return mask of fulfilled requirements */
+ return requirement;
+}
+
+static uint8_t check_requirements_remote_bss(struct gsm_lchan *lchan,
+ const struct gsm0808_cell_id_list2 *cil)
+{
+ uint8_t requirement = 0;
+ unsigned int penalty_time;
+
+ /* Requirement A */
+
+ /* the handover penalty timer must not run for this bts */
+ penalty_time = conn_penalty_time_remaining(lchan->conn, cil);
+ if (penalty_time) {
+ LOGPHOLCHANTOREMOTE(lchan, cil, LOGL_DEBUG,
+ "not a candidate, target BSS still in penalty time"
+ " (%u seconds left)\n", penalty_time);
+ return 0;
+ }
+
+ /* compatibility check for codecs -- we have no notion of what the remote BSS supports. We can
+ * only assume that a handover would work, and use only the local requirements. */
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F: /* mandatory */
+ requirement |= REQUIREMENT_A_TCHF;
+ break;
+ case GSM_LCHAN_TCH_H:
+ if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR1))
+ 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 (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR2))
+ requirement |= REQUIREMENT_A_TCHF;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3))
+ requirement |= REQUIREMENT_A_TCHF;
+ if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR3))
+ requirement |= REQUIREMENT_A_TCHH;
+ break;
+ default:
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n");
+ /* FIXME: should allow handover of non-speech lchans */
+ return 0;
+ }
+
+ if (!requirement) {
+ LOGPHOLCHAN(lchan, LOGL_ERROR, "lchan doesn't fit its own requirements??\n");
+ return 0;
+ }
+
+ /* Requirement B and C */
+
+ /* We don't know how many timeslots are free in the remote BSS. We can only indicate that it
+ * would work out and hope for the best. */
+ if (requirement & REQUIREMENT_A_TCHF)
+ requirement |= REQUIREMENT_B_TCHF | REQUIREMENT_C_TCHF;
+ if (requirement & REQUIREMENT_A_TCHH)
+ requirement |= REQUIREMENT_B_TCHH | REQUIREMENT_C_TCHH;
+
+ /* return mask of fulfilled requirements */
+ return requirement;
+}
+
+/* Trigger handover or assignment depending on the target BTS */
+static int trigger_local_ho_or_as(struct ho_candidate *c, uint8_t requirements)
+{
+ struct gsm_lchan *lchan = c->lchan;
+ struct gsm_bts *new_bts = c->bts;
+ struct handover_out_req req;
+ struct gsm_bts *current_bts = lchan->ts->trx->bts;
+ int afs_bias = 0;
+ bool full_rate = false;
+
+ /* afs_bias becomes > 0, if AFS is used and is improved */
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ afs_bias = ho_get_hodec2_afs_bias_rxlev(new_bts->ho);
+
+ /* 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;
+ 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_INFO,
+ "Triggering handover to %s, due to %s\n",
+ full_rate ? "TCH/F" : "TCH/H",
+ ho_reason_name(global_ho_reason));
+
+ req = (struct handover_out_req){
+ .from_hodec_id = HODEC2,
+ .old_lchan = lchan,
+ .target_nik = *bts_ident_key(new_bts),
+ .new_lchan_type = full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H,
+ };
+ handover_request(&req);
+ return 0;
+}
+
+static int trigger_remote_bss_ho(struct ho_candidate *c, uint8_t requirements)
+{
+ struct handover_out_req req;
+
+ LOGPHOLCHANTOREMOTE(c->lchan, c->cil, LOGL_INFO,
+ "Triggering inter-BSC handover, due to %s\n",
+ ho_reason_name(global_ho_reason));
+
+ req = (struct handover_out_req){
+ .from_hodec_id = HODEC2,
+ .old_lchan = c->lchan,
+ .target_nik = c->nik,
+ };
+ handover_request(&req);
+ return 0;
+}
+
+static int trigger_ho(struct ho_candidate *c, uint8_t requirements)
+{
+ if (c->bts)
+ return trigger_local_ho_or_as(c, requirements);
+ else
+ return trigger_remote_bss_ho(c, requirements);
+}
+
+/* verbosely log about a handover candidate */
+static inline void debug_candidate(struct ho_candidate *candidate,
+ int8_t rxlev, int tchf_count, int tchh_count)
+{
+ struct gsm_lchan *lchan = candidate->lchan;
+
+#define HO_CANDIDATE_FMT(tchx, TCHX) "TCH/" #TCHX "={free %d (want %d), [%s%s%s]%s}"
+#define HO_CANDIDATE_ARGS(tchx, TCHX) \
+ tch##tchx##_count, ho_get_hodec2_tch##tchx##_min_slots(candidate->bts->ho), \
+ candidate->requirements & REQUIREMENT_A_TCH##TCHX ? "A" : \
+ (candidate->requirements & REQUIREMENT_TCH##TCHX##_MASK) == 0? "-" : "", \
+ candidate->requirements & REQUIREMENT_B_TCH##TCHX ? "B" : "", \
+ candidate->requirements & REQUIREMENT_B_TCH##TCHX ? "C" : "", \
+ (candidate->requirements & REQUIREMENT_TCH##TCHX##_MASK) == 0 ? " not a candidate" : \
+ ((candidate->requirements & REQUIREMENT_TCH##TCHX##_MASK) == REQUIREMENT_A_TCH##TCHX ? \
+ " more congestion" : \
+ (candidate->requirements & REQUIREMENT_B_TCH##TCHX ? \
+ " good" : \
+ /* now has to be candidate->requirements & REQUIREMENT_C_TCHX != 0: */ \
+ " less-or-equal congestion"))
+
+ if (!candidate->bts && !candidate->cil)
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "Empty candidate\n");
+ if (candidate->bts && candidate->cil)
+ LOGPHOLCHAN(lchan, LOGL_ERROR, "Invalid candidate: both local- and remote-BSS target\n");
+
+ if (candidate->cil)
+ LOGPHOLCHANTOREMOTE(lchan, candidate->cil, LOGL_DEBUG,
+ "RX level %d -> %d\n",
+ rxlev2dbm(rxlev), rxlev2dbm(candidate->avg));
+
+ if (candidate->bts == lchan->ts->trx->bts)
+ LOGPHOLCHANTOBTS(lchan, candidate->bts, LOGL_DEBUG,
+ "RX level %d; "
+ HO_CANDIDATE_FMT(f, F) "; " HO_CANDIDATE_FMT(h, H) "\n",
+ rxlev2dbm(candidate->avg),
+ HO_CANDIDATE_ARGS(f, F), HO_CANDIDATE_ARGS(h, H));
+ else if (candidate->bts)
+ LOGPHOLCHANTOBTS(lchan, candidate->bts, LOGL_DEBUG,
+ "RX level %d -> %d; "
+ HO_CANDIDATE_FMT(f, F) "; " HO_CANDIDATE_FMT(h, H) "\n",
+ rxlev2dbm(rxlev), rxlev2dbm(candidate->avg),
+ HO_CANDIDATE_ARGS(f, F), HO_CANDIDATE_ARGS(h, H));
+}
+
+/* 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 = (struct ho_candidate){
+ .lchan = lchan,
+ .bts = bts,
+ .requirements = check_requirements(lchan, bts, tchf_count, tchh_count),
+ .avg = av_rxlev,
+ };
+
+ debug_candidate(&c, 0, tchf_count, tchh_count);
+ clist[*candidates] = c;
+ (*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 = 0;
+ int tchh_count = 0;
+ struct gsm_bts *neighbor_bts;
+ const struct gsm0808_cell_id_list2 *neighbor_cil;
+ struct neighbor_ident_key ni = {
+ .from_bts = bts->nr,
+ .arfcn = nmp->arfcn,
+ .bsic = nmp->bsic,
+ };
+ int avg;
+ struct ho_candidate c;
+ int min_rxlev;
+ struct handover_cfg *neigh_cfg;
+
+ /* 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 BSIC %u measurement report is old"
+ " (nmp->last_seen_nr=%u lchan->meas_rep_last_seen_nr=%u)\n",
+ nmp->arfcn, nmp->bsic, nmp->last_seen_nr, lchan->meas_rep_last_seen_nr);
+ return;
+ }
+
+ neighbor_bts = bts_by_neighbor_ident(bts->network, &ni);
+
+ neighbor_cil = neighbor_ident_get(bts->network->neighbor_bss_cells, &ni);
+
+ if (neighbor_bts && neighbor_cil) {
+ LOGPHOBTS(bts, LOGL_ERROR, "Configuration error: %s exists as both local"
+ " neighbor (bts %u) and remote-BSS neighbor (%s). Will consider only"
+ " the local-BSS neighbor.\n",
+ neighbor_ident_key_name(&ni),
+ neighbor_bts->nr, gsm0808_cell_id_list_name(neighbor_cil));
+ neighbor_cil = NULL;
+ }
+
+ if (!neighbor_bts && !neighbor_cil) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "no neighbor ARFCN %u BSIC %u configured for this cell\n",
+ nmp->arfcn, nmp->bsic);
+ 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;
+ }
+
+ /* For cells in a remote BSS, we cannot query the target cell's handover config, and hence
+ * instead assume the local BTS' config to apply. */
+ neigh_cfg = (neighbor_bts ? : bts)->ho;
+
+ /* calculate average rxlev for this cell over the window */
+ avg = neigh_meas_avg(nmp, ho_get_hodec2_rxlev_neigh_avg_win(bts->ho));
+
+ c = (struct ho_candidate){
+ .lchan = lchan,
+ .avg = avg,
+ .nik = ni,
+ .bts = neighbor_bts,
+ .cil = neighbor_cil,
+ };
+
+ /* 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)) {
+ LOGPHOCAND(&c, LOGL_DEBUG,
+ "Not a candidate, because RX level (%d) is lower"
+ " or equal than current RX level (%d) + hysteresis (%d)\n",
+ rxlev2dbm(avg), rxlev2dbm(av_rxlev), pwr_hyst);
+ return;
+ }
+ }
+
+ /* if the minimum level is not reached.
+ * In case of a remote-BSS, use the current BTS' configuration. */
+ min_rxlev = ho_get_hodec2_min_rxlev(neigh_cfg);
+ if (rxlev2dbm(avg) < min_rxlev) {
+ LOGPHOCAND(&c, LOGL_DEBUG,
+ "Not a candidate, because RX level (%d) is lower"
+ " than the minimum required RX level (%d)\n",
+ rxlev2dbm(avg), min_rxlev);
+ return;
+ }
+
+ if (neighbor_bts) {
+ tchf_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_F);
+ tchh_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_H);
+ c.requirements = check_requirements(lchan, neighbor_bts, tchf_count,
+ tchh_count);
+ } else
+ c.requirements = check_requirements_remote_bss(lchan, neighbor_cil);
+
+ debug_candidate(&c, av_rxlev, tchf_count, tchh_count);
+
+ clist[*candidates] = c;
+ (*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;
+ bool assignment;
+ bool handover;
+ int neighbors_count = 0;
+ unsigned int rxlev_avg_win = ho_get_hodec2_rxlev_avg_win(bts->ho);
+
+ OSMO_ASSERT(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);
+
+ 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);
+ }
+ }
+}
+
+/*
+ * 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) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "No viable neighbor cells found\n");
+ return 0;
+ }
+
+ /* select best candidate that fulfills requirement B: no congestion after HO.
+ * Exclude remote-BSS neighbors: to avoid oscillation between neighboring BSS,
+ * rather keep subscribers in the local BSS unless there is critical RXLEV/TA. */
+ best_better_db = 0;
+ for (i = 0; i < candidates; i++) {
+ int afs_bias;
+ if (!(clist[i].requirements & REQUIREMENT_B_MASK))
+ continue;
+
+ /* Only consider Local-BSS cells */
+ if (!clist[i].bts)
+ 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) {
+ LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate, RX level %d%s\n",
+ rxlev2dbm(best_cand->avg),
+ best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : "");
+ return trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_B_MASK);
+ }
+
+ /* select best candidate that fulfills requirement C: less or equal congestion after HO,
+ * again excluding remote-BSS neighbors. */
+ best_better_db = 0;
+ for (i = 0; i < candidates; i++) {
+ int afs_bias;
+ if (!(clist[i].requirements & REQUIREMENT_C_MASK))
+ continue;
+
+ /* Only consider Local-BSS cells */
+ if (!clist[i].bts)
+ 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) {
+ LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate, RX level %d%s\n",
+ rxlev2dbm(best_cand->avg),
+ best_applied_afs_bias? " (applied AHS -> AFS rxlev bias)" : "");
+ return trigger_ho(best_cand, 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) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "No better/less congested neighbor cell found\n");
+ return 0;
+ }
+
+ /* 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.
+ * We're also prepared to handover to remote BSS. */
+ 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?
+ * (never to remote-BSS neighbors, since we will not change the lchan type for those.) */
+ afs_bias = 0;
+ if (ahs && (clist[i].requirements & REQUIREMENT_A_TCHF)
+ && clist[i].bts)
+ afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
+ 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) {
+ LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate: RX level %d%s\n",
+ rxlev2dbm(best_cand->avg),
+ best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : "");
+ return trigger_ho(best_cand, 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. */
+ LOGPHOLCHAN(lchan, LOGL_INFO, "No alternative lchan 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->assignment.new_lchan) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Initial Assignment is still ongoing\n");
+ return;
+ }
+ if (lchan->conn->ho.fi) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Handover still ongoing\n");
+ return;
+ }
+
+ /* 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;
+ }
+
+ /* improve levels in case of AFS, if defined */
+ if (lchan->type == GSM_LCHAN_TCH_F
+ && lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ int av_rxlev_was = av_rxlev;
+ int av_rxqual_was = av_rxqual;
+ int rxlev_bias = ho_get_hodec2_afs_bias_rxlev(bts->ho);
+ int rxqual_bias = ho_get_hodec2_afs_bias_rxqual(bts->ho);
+ if (av_rxlev >= 0)
+ av_rxlev = av_rxlev + rxlev_bias;
+ if (av_rxqual >= 0)
+ av_rxqual = OSMO_MAX(0, av_rxqual - rxqual_bias);
+
+ LOGPHOLCHAN(lchan, LOGL_DEBUG,
+ "Avg RX level = %d dBm, %+d dBm AFS bias = %d dBm;"
+ " Avg RX quality = %d%s, %+d AFS bias = %d\n",
+ rxlev2dbm(av_rxlev_was), rxlev_bias, rxlev2dbm(av_rxlev),
+ OSMO_MAX(-1, av_rxqual_was), av_rxqual_was < 0 ? " (invalid)" : "",
+ -rxqual_bias, OSMO_MAX(-1, av_rxqual));
+ } else {
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "Avg RX level = %d dBm; Avg RX quality = %d%s\n",
+ rxlev2dbm(av_rxlev),
+ OSMO_MAX(-1, av_rxqual), av_rxqual < 0 ? " (invalid)" : "");
+ }
+
+ /* 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_NOTICE, "RX level is TOO LOW: %d < %d\n",
+ rxlev2dbm(av_rxlev), ho_get_hodec2_min_rxlev(bts->ho));
+ 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_NOTICE, "TA is TOO HIGH: %u > %d\n",
+ lchan->rqd_ta, ho_get_hodec2_max_distance(bts->ho));
+ /* start penalty timer to prevent coming back too
+ * early. it must be started before selecting a better cell,
+ * so there is no assignment selected, due to running
+ * penalty timer. */
+ bts_penalty_time_add(lchan->conn, bts, ho_get_hodec2_penalty_max_dist(bts->ho));
+ find_alternative_lchan(lchan, true);
+ 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) {
+ 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_is) {
+ case GSM_PCHAN_TCH_F:
+ lc = &ts->lchan[0];
+ /* omit if channel not active */
+ if (lc->type != GSM_LCHAN_TCH_F
+ || !lchan_state_is(lc, LCHAN_ST_ESTABLISHED))
+ break;
+ /* omit if there is an ongoing ho/as */
+ if (!lc->conn || lc->conn->assignment.new_lchan
+ || lc->conn->ho.fi)
+ 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
+ || !lchan_state_is(lc, LCHAN_ST_ESTABLISHED))
+ continue;
+ /* omit of there is an ongoing ho/as */
+ if (!lc->conn
+ || lc->conn->assignment.new_lchan
+ || lc->conn->ho.fi)
+ 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++) {
+ LOGPHOCAND(&clist[i], 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;
+
+ /* Do not resolve congestion towards remote BSS, which would cause oscillation if the
+ * remote BSS is also congested. */
+ /* TODO: attempt inter-BSC HO if no local cells qualify, and rely on the remote BSS to
+ * deny receiving the handover if it also considers itself congested. Maybe do that only
+ * when the cell is absolutely full, i.e. not only min-free-slots. (x) */
+ if (!clist[i].bts)
+ continue;
+
+ if (!(clist[i].requirements & REQUIREMENT_B_MASK))
+ continue;
+ /* omit assignment from AHS to AFS */
+ if (clist[i].lchan->ts->trx->bts == clist[i].bts
+ && clist[i].lchan->type == GSM_LCHAN_TCH_H
+ && (clist[i].requirements & REQUIREMENT_B_TCHF))
+ continue;
+ /* omit candidates that will not solve/reduce congestion */
+ if (clist[i].lchan->type == GSM_LCHAN_TCH_F
+ && tchf_congestion <= 0)
+ continue;
+ if (clist[i].lchan->type == GSM_LCHAN_TCH_H
+ && tchh_congestion <= 0)
+ continue;
+
+ avg = clist[i].avg;
+ /* improve AHS */
+ if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
+ && clist[i].lchan->type == GSM_LCHAN_TCH_H
+ && (clist[i].requirements & REQUIREMENT_B_TCHF)) {
+ avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
+ is_improved = 1;
+ } else
+ is_improved = 0;
+ LOGPHOCAND(&clist[i], LOGL_DEBUG, "candidate %d: avg=%d best_avg_db=%d\n",
+ i, avg, best_avg_db);
+ if (avg > best_avg_db) {
+ best_cand = &clist[i];
+ best_avg_db = avg;
+ }
+ }
+
+ /* perform handover, if there is a candidate */
+ if (best_cand) {
+ any_ho = 1;
+ LOGPHOCAND(best_cand, LOGL_DEBUG, "Best candidate: RX level %d%s\n",
+ rxlev2dbm(best_cand->avg),
+ is_improved ? " (applied AHS->AFS bias)" : "");
+ trigger_ho(best_cand, 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;
+ }
+
+#if 0
+next_b2:
+#endif
+ /* select worst candidate that fulfills requirement B,
+ * select candidates that change from AHS to AFS only */
+ if (tchh_congestion > 0) {
+ /* since this will only check half rate channels, it will
+ * only need to be checked, if tchh is congested */
+ worst_avg_db = 999;
+ for (i = 0; i < candidates; i++) {
+ /* delete subscriber that just have handovered */
+ if (clist[i].lchan == delete_lchan)
+ clist[i].lchan = NULL;
+ /* omit all subscribers that are handovered */
+ if (!clist[i].lchan)
+ continue;
+
+ /* Do not resolve congestion towards remote BSS, which would cause oscillation if
+ * the remote BSS is also congested. */
+ /* TODO: see (x) above */
+ if (!clist[i].bts)
+ continue;
+
+ if (!(clist[i].requirements & REQUIREMENT_B_MASK))
+ continue;
+ /* omit all but assignment from AHS to AFS */
+ if (clist[i].lchan->ts->trx->bts != clist[i].bts
+ || clist[i].lchan->type != GSM_LCHAN_TCH_H
+ || !(clist[i].requirements & REQUIREMENT_B_TCHF))
+ continue;
+
+ avg = clist[i].avg;
+ /* improve AHS */
+ if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
+ && clist[i].lchan->type == GSM_LCHAN_TCH_H) {
+ avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
+ is_improved = 1;
+ } else
+ is_improved = 0;
+ if (avg < worst_avg_db) {
+ worst_cand = &clist[i];
+ worst_avg_db = avg;
+ }
+ }
+ }
+
+ /* perform handover, if there is a candidate */
+ if (worst_cand) {
+ any_ho = 1;
+ LOGPHOCAND(worst_cand, LOGL_INFO, "Worst candidate: RX level %d from TCH/H -> TCH/F%s\n",
+ rxlev2dbm(worst_cand->avg),
+ is_improved ? " (applied AHS -> AFS rxlev bias)" : "");
+ trigger_ho(worst_cand, worst_cand->requirements & REQUIREMENT_B_MASK);
+#if 0
+ /* if there is still congestion, mark lchan as deleted
+ * and redo this process */
+ tchh_congestion--;
+ if (tchh_congestion > 0) {
+ delete_lchan = worst_cand->lchan;
+ best_cand = NULL;
+ goto next_b2;
+ }
+#else
+ /* must exit here, because triggering handover/assignment
+ * will cause change in requirements. more check for this
+ * bts is performed in the next iteration.
+ */
+#endif
+ goto exit;
+ }
+
+#if 0
+next_c1:
+#endif
+ /* select best candidate that fulfills requirement C,
+ * omit change from AHS to AFS */
+ best_avg_db = 0;
+ for (i = 0; i < candidates; i++) {
+ /* delete subscriber that just have handovered */
+ if (clist[i].lchan == delete_lchan)
+ clist[i].lchan = NULL;
+ /* omit all subscribers that are handovered */
+ if (!clist[i].lchan)
+ continue;
+
+ /* Do not resolve congestion towards remote BSS, which would cause oscillation if
+ * the remote BSS is also congested. */
+ /* TODO: see (x) above */
+ if (!clist[i].bts)
+ continue;
+
+ if (!(clist[i].requirements & REQUIREMENT_C_MASK))
+ continue;
+ /* omit assignment from AHS to AFS */
+ if (clist[i].lchan->ts->trx->bts == clist[i].bts
+ && clist[i].lchan->type == GSM_LCHAN_TCH_H
+ && (clist[i].requirements & REQUIREMENT_C_TCHF))
+ continue;
+ /* omit candidates that will not solve/reduce congestion */
+ if (clist[i].lchan->type == GSM_LCHAN_TCH_F
+ && tchf_congestion <= 0)
+ continue;
+ if (clist[i].lchan->type == GSM_LCHAN_TCH_H
+ && tchh_congestion <= 0)
+ continue;
+
+ avg = clist[i].avg;
+ /* improve AHS */
+ if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
+ && clist[i].lchan->type == GSM_LCHAN_TCH_H
+ && (clist[i].requirements & REQUIREMENT_C_TCHF)) {
+ avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
+ is_improved = 1;
+ } else
+ is_improved = 0;
+ if (avg > best_avg_db) {
+ best_cand = &clist[i];
+ best_avg_db = avg;
+ }
+ }
+
+ /* perform handover, if there is a candidate */
+ if (best_cand) {
+ any_ho = 1;
+ LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate: RX level %d%s\n",
+ rxlev2dbm(best_cand->avg),
+ is_improved ? " (applied AHS -> AFS rxlev bias)" : "");
+ trigger_ho(best_cand, 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;
+
+ /* Do not resolve congestion towards remote BSS, which would cause oscillation if
+ * the remote BSS is also congested. */
+ /* TODO: see (x) above */
+ if (!clist[i].bts)
+ continue;
+
+ if (!(clist[i].requirements & REQUIREMENT_C_MASK))
+ continue;
+ /* omit all but assignment from AHS to AFS */
+ if (clist[i].lchan->ts->trx->bts != clist[i].bts
+ || clist[i].lchan->type != GSM_LCHAN_TCH_H
+ || !(clist[i].requirements & REQUIREMENT_C_TCHF))
+ continue;
+
+ avg = clist[i].avg;
+ /* improve AHS */
+ if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
+ && clist[i].lchan->type == GSM_LCHAN_TCH_H) {
+ avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
+ is_improved = 1;
+ } else
+ is_improved = 0;
+ LOGP(DHODEC, LOGL_DEBUG, "candidate %d: avg=%d worst_avg_db=%d\n", i, avg,
+ worst_avg_db);
+ if (avg < worst_avg_db) {
+ worst_cand = &clist[i];
+ worst_avg_db = avg;
+ }
+ }
+ }
+
+ /* perform handover, if there is a candidate */
+ if (worst_cand) {
+ any_ho = 1;
+ LOGPHOCAND(worst_cand, LOGL_INFO, "Worst candidate: RX level %d from TCH/H -> TCH/F%s\n",
+ rxlev2dbm(worst_cand->avg),
+ is_improved ? " (applied AHS -> AFS rxlev bias)" : "");
+ trigger_ho(worst_cand, worst_cand->requirements & REQUIREMENT_C_MASK);
+#if 0
+ /* if there is still congestion, mark lchan as deleted
+ * and redo this process */
+ tchh_congestion--;
+ if (tchh_congestion > 0) {
+ delete_lchan = worst_cand->lchan;
+ worst_cand = NULL;
+ goto next_c2;
+ }
+#else
+ /* must exit here, because triggering handover/assignment
+ * will cause change in requirements. more check for this
+ * bts is performed in the next iteration.
+ */
+#endif
+ goto exit;
+ }
+ LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a worst candidate that fulfills requirement C,"
+ " selecting candidates that change from AHS to AFS only\n");
+
+
+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);
+}
+
+static void on_handover_end(struct gsm_subscriber_connection *conn, enum handover_result result)
+{
+ struct gsm_bts *old_bts = NULL;
+ struct gsm_bts *new_bts = NULL;
+ int penalty;
+ struct handover *ho = &conn->ho;
+
+ /* If all went fine, then there are no penalty timers to set. */
+ if (result == HO_RESULT_OK)
+ return;
+
+ if (conn->lchan)
+ old_bts = conn->lchan->ts->trx->bts;
+ if (ho->new_lchan)
+ new_bts = ho->new_lchan->ts->trx->bts;
+
+ /* Only interested in handovers within this BSS or going out into another BSS. Incoming handovers
+ * from another BSS are accounted for in the other BSS. */
+ if (!old_bts)
+ return;
+
+ if (conn->hodec2.failures < ho_get_hodec2_retries(old_bts->ho)) {
+ conn->hodec2.failures++;
+ LOG_HO(conn, LOGL_NOTICE, "Failed, allowing handover decision to try again"
+ " (%d/%d attempts)\n",
+ conn->hodec2.failures, ho_get_hodec2_retries(old_bts->ho));
+ return;
+ }
+
+ switch (ho->scope) {
+ case HO_INTRA_CELL:
+ penalty = ho_get_hodec2_penalty_failed_as(old_bts->ho);
+ break;
+ default:
+ /* TODO: separate penalty for inter-BSC HO? */
+ penalty = ho_get_hodec2_penalty_failed_ho(old_bts->ho);
+ break;
+ }
+
+ LOG_HO(conn, LOGL_NOTICE, "Failed, starting penalty timer (%d s)\n", penalty);
+ conn->hodec2.failures = 0;
+
+ if (new_bts)
+ bts_penalty_time_add(conn, new_bts, penalty);
+ else
+ nik_penalty_time_add(conn, &ho->target_cell, penalty);
+}
+
+static struct handover_decision_callbacks hodec2_callbacks = {
+ .hodec_id = 2,
+ .on_measurement_report = on_measurement_report,
+ .on_handover_end = on_handover_end,
+};
+
+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_fsm.c b/src/osmo-bsc/handover_fsm.c
new file mode 100644
index 000000000..f2836cf37
--- /dev/null
+++ b/src/osmo-bsc/handover_fsm.c
@@ -0,0 +1,1236 @@
+/* Handover FSM implementation for intra-BSC and inter-BSC Handover.
+ *
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/socket.h>
+
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/osmo_bsc_lcls.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/codec_pref.h>
+#include <osmocom/bsc/gsm_08_08.h>
+
+#define LOG_FMT_BTS "bts %u lac-ci %u-%u arfcn-bsic %d-%d"
+#define LOG_ARGS_BTS(bts) \
+ (bts) ? (bts)->nr : 0, \
+ (bts) ? (bts)->location_area_code : 0, \
+ (bts) ? (bts)->cell_identity : 0, \
+ (bts) ? (bts)->c0->arfcn : 0, \
+ (bts) ? (bts)->bsic : 0
+
+#define LOG_FMT_FROM_LCHAN "%u-%u-%u-%s-%u-%s"
+#define LOG_ARGS_FROM_LCHAN(lchan) \
+ lchan ? lchan->ts->trx->bts->nr : 0, \
+ lchan ? lchan->ts->trx->nr : 0, \
+ lchan ? lchan->ts->nr : 0, \
+ lchan ? gsm_lchant_name(lchan->type) : "?", \
+ lchan ? lchan->nr : 0, \
+ lchan ? gsm48_chan_mode_name(lchan->tch_mode) : "?"
+
+#define LOG_FMT_TO_LCHAN "%u-%u-%u-%s%s%s-%u"
+#define LOG_ARGS_TO_LCHAN(lchan) \
+ lchan ? lchan->ts->trx->bts->nr : 0, \
+ lchan ? lchan->ts->trx->nr : 0, \
+ lchan ? lchan->ts->nr : 0, \
+ lchan ? gsm_pchan_name(lchan->ts->pchan_on_init) : "?", \
+ (!lchan || lchan->ts->pchan_on_init == lchan->ts->pchan_is)? "" : ":", \
+ (!lchan || lchan->ts->pchan_on_init == lchan->ts->pchan_is)? "" \
+ : gsm_pchan_name(lchan->ts->pchan_is), \
+ lchan ? lchan->nr : 0
+
+#define LOG_FMT_HO_SCOPE "(subscr %s) %s"
+#define LOG_ARGS_HO_SCOPE(conn) \
+ bsc_subscr_name(conn->bsub), \
+ handover_scope_name(conn->ho.scope)
+
+/* Assume presence of local var 'conn' as struct gsm_subscriber_connection.
+ * This is a macro to preserve the source file and line number in logging. */
+#define ho_count(counter) do { \
+ LOG_HO(conn, LOGL_DEBUG, "incrementing rate counter: %s %s\n", \
+ bsc_ctr_description[counter].name, \
+ bsc_ctr_description[counter].description); \
+ rate_ctr_inc(&conn->network->bsc_ctrs->ctr[counter]); \
+ } while(0)
+
+static uint8_t g_next_ho_ref = 1;
+
+const char *handover_status(struct gsm_subscriber_connection *conn)
+{
+ static char buf[256];
+ struct handover *ho = &conn->ho;
+
+ if (!conn)
+ return "";
+
+ if (ho->scope & (HO_INTRA_CELL | HO_INTRA_BSC)) {
+ if (ho->new_lchan)
+ snprintf(buf, sizeof(buf),
+ "("LOG_FMT_FROM_LCHAN") --HO-> (" LOG_FMT_TO_LCHAN ") " LOG_FMT_HO_SCOPE,
+ LOG_ARGS_FROM_LCHAN(conn->lchan),
+ LOG_ARGS_TO_LCHAN(ho->new_lchan),
+ LOG_ARGS_HO_SCOPE(conn));
+ else if (ho->new_bts)
+ snprintf(buf, sizeof(buf),
+ "("LOG_FMT_FROM_LCHAN") --HO-> ("LOG_FMT_BTS",%s) " LOG_FMT_HO_SCOPE,
+ LOG_ARGS_FROM_LCHAN(conn->lchan),
+ LOG_ARGS_BTS(ho->new_bts),
+ gsm_lchant_name(ho->new_lchan_type),
+ LOG_ARGS_HO_SCOPE(conn));
+ else
+ snprintf(buf, sizeof(buf),
+ "("LOG_FMT_FROM_LCHAN") --HO->(?) " LOG_FMT_HO_SCOPE,
+ LOG_ARGS_FROM_LCHAN(conn->lchan),
+ LOG_ARGS_HO_SCOPE(conn));
+
+ } else if (ho->scope & HO_INTER_BSC_OUT)
+ snprintf(buf, sizeof(buf),
+ "("LOG_FMT_FROM_LCHAN") --HO-> (%s) " LOG_FMT_HO_SCOPE,
+ LOG_ARGS_FROM_LCHAN(conn->lchan),
+ neighbor_ident_key_name(&ho->target_cell),
+ LOG_ARGS_HO_SCOPE(conn));
+
+ else if (ho->scope & HO_INTER_BSC_IN) {
+ if (ho->new_lchan)
+ snprintf(buf, sizeof(buf),
+ "(remote:%s) --HO-> (local:%s|"LOG_FMT_TO_LCHAN") " LOG_FMT_HO_SCOPE,
+ ho->inter_bsc_in.cell_id_serving_name,
+ ho->inter_bsc_in.cell_id_target_name,
+ LOG_ARGS_TO_LCHAN(ho->new_lchan),
+ LOG_ARGS_HO_SCOPE(conn));
+ else if (ho->new_bts)
+ snprintf(buf, sizeof(buf),
+ "(remote:%s) --HO-> (local:%s|"LOG_FMT_BTS",%s) " LOG_FMT_HO_SCOPE,
+ ho->inter_bsc_in.cell_id_serving_name,
+ ho->inter_bsc_in.cell_id_target_name,
+ LOG_ARGS_BTS(ho->new_bts),
+ gsm_lchant_name(ho->new_lchan_type),
+ LOG_ARGS_HO_SCOPE(conn));
+ else
+ snprintf(buf, sizeof(buf),
+ "(remote:%s) --HO-> (local:%s,%s) " LOG_FMT_HO_SCOPE,
+ ho->inter_bsc_in.cell_id_serving_name,
+ ho->inter_bsc_in.cell_id_target_name,
+ gsm_lchant_name(ho->new_lchan_type),
+ LOG_ARGS_HO_SCOPE(conn));
+ } else
+ snprintf(buf, sizeof(buf), LOG_FMT_HO_SCOPE, LOG_ARGS_HO_SCOPE(conn));
+ return buf;
+}
+
+static struct osmo_fsm ho_fsm;
+
+struct gsm_subscriber_connection *ho_fi_conn(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &ho_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+static const struct state_timeout ho_fsm_timeouts[32] = {
+ [HO_ST_WAIT_LCHAN_ACTIVE] = { .T = 23042 },
+ [HO_ST_WAIT_RR_HO_DETECT] = { .T = 23042 },
+ [HO_ST_WAIT_RR_HO_COMPLETE] = { .T = 23042 },
+ [HO_ST_WAIT_LCHAN_ESTABLISHED] = { .T = 23042 },
+ [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = 23042 },
+ [HO_OUT_ST_WAIT_HO_COMMAND] = { .T = 7 },
+ [HO_OUT_ST_WAIT_CLEAR] = { .T = 8 },
+};
+
+/* Transition to a state, using the T timer defined in ho_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define ho_fsm_state_chg(state) \
+ fsm_inst_state_chg_T(fi, state, \
+ ho_fsm_timeouts, \
+ ((struct gsm_subscriber_connection*)(fi->priv))->network->T_defs, \
+ 5)
+
+/* Log failure and transition to HO_ST_FAILURE, which triggers the appropriate actions. */
+#define ho_fail(result, fmt, args...) do { \
+ LOG_HO(conn, LOGL_ERROR, "Handover failed in state %s, %s: " fmt "\n", \
+ osmo_fsm_inst_state_name(conn->fi), handover_result_name(result), ## args); \
+ handover_end(conn, result); \
+ } while(0)
+
+#define ho_success() do { \
+ LOG_HO(conn, LOGL_DEBUG, "Handover succeeded\n"); \
+ handover_end(conn, HO_RESULT_OK); \
+ } while(0)
+
+/* issue handover to a cell identified by ARFCN and BSIC */
+void handover_request(struct handover_out_req *req)
+{
+ struct gsm_subscriber_connection *conn;
+ OSMO_ASSERT(req->old_lchan);
+
+ conn = req->old_lchan->conn;
+ OSMO_ASSERT(conn && conn->fi);
+
+ /* To make sure we're allowed to start a handover, go through a gscon event dispatch. */
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_START, req);
+}
+
+/* Check that ho has old_lchan and/or new_lchan and conn pointers match.
+ * If old_lchan and/or new_lchan are NULL, omit those checks.
+ * On error, return false, log an error and call handover_end() with HO_RESULT_ERROR. */
+bool handover_is_sane(struct gsm_subscriber_connection *conn, struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan)
+{
+ if (!conn->ho.fi) {
+ LOG_HO(conn, LOGL_ERROR, "No handover ongoing\n");
+ return false;
+ }
+
+ if (old_lchan
+ && (conn != old_lchan->conn || conn->lchan != old_lchan))
+ goto insane;
+ if (new_lchan
+ && (conn != new_lchan->conn || conn->ho.new_lchan != new_lchan))
+ goto insane;
+ if (conn->lchan && conn->lchan == conn->ho.new_lchan)
+ goto insane;
+
+ return true;
+insane:
+ LOG_HO(conn, LOGL_ERROR, "Handover state is corrupted\n");
+ handover_end(conn, HO_RESULT_ERROR);
+ return false;
+}
+
+static void ho_fsm_update_id(struct osmo_fsm_inst *fi, const char *label)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ if (conn->fi->id)
+ osmo_fsm_inst_update_id_f(fi, "%s_%s", label, conn->fi->id);
+ else
+ osmo_fsm_inst_update_id_f(fi, "%s_conn%u", label, conn->sccp.conn_id);
+}
+
+static void handover_reset(struct gsm_subscriber_connection *conn)
+{
+ struct mgwep_ci *ci;
+ if (conn->ho.new_lchan)
+ /* New lchan was activated but never passed to a conn */
+ lchan_release(conn->ho.new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+
+ ci = conn->ho.created_ci_for_msc;
+ if (ci) {
+ gscon_forget_mgw_endpoint_ci(conn, ci);
+ /* If this is the last endpoint released, the mgw_endpoint_fsm will terminate and tell
+ * the gscon about it. */
+ mgw_endpoint_ci_dlcx(ci);
+ }
+
+ conn->ho = (struct handover){
+ .fi = conn->ho.fi,
+ };
+}
+
+void handover_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&ho_fsm) == 0);
+}
+
+void handover_fsm_alloc(struct gsm_subscriber_connection *conn)
+{
+ OSMO_ASSERT(conn->fi);
+ OSMO_ASSERT(!conn->ho.fi);
+
+ conn->ho.fi = osmo_fsm_inst_alloc_child(&ho_fsm, conn->fi, GSCON_EV_HANDOVER_END);
+ OSMO_ASSERT(conn->ho.fi);
+ conn->ho.fi->priv = conn;
+}
+
+static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn);
+static void handover_start_inter_bsc_out(struct gsm_subscriber_connection *conn,
+ const struct gsm0808_cell_id_list2 *target_cells);
+
+/* Invoked by gscon if a handover was accepted to start now. */
+void handover_start(struct handover_out_req *req)
+{
+
+ OSMO_ASSERT(req && req->old_lchan && req->old_lchan->conn);
+ struct gsm_subscriber_connection *conn = req->old_lchan->conn;
+ struct handover *ho = &conn->ho;
+ struct gsm_bts *bts;
+ const struct gsm0808_cell_id_list2 *cil;
+
+ if (conn->ho.fi) {
+ LOG_HO(conn, LOGL_ERROR, "Handover requested while another handover is ongoing; Ignore\n");
+ return;
+ }
+ handover_reset(conn);
+
+ handover_fsm_alloc(conn);
+
+ ho->from_hodec_id = req->from_hodec_id;
+ ho->new_lchan_type = req->new_lchan_type == GSM_LCHAN_NONE ?
+ req->old_lchan->type : req->new_lchan_type;
+ ho->target_cell = req->target_nik;
+
+ bts = bts_by_neighbor_ident(conn->network, &req->target_nik);
+ if (bts) {
+ ho->new_bts = bts;
+ handover_start_intra_bsc(conn);
+ return;
+ }
+
+ cil = neighbor_ident_get(conn->network->neighbor_bss_cells, &req->target_nik);
+ if (cil) {
+ handover_start_inter_bsc_out(conn, cil);
+ return;
+ }
+
+ LOG_HO(conn, LOGL_ERROR, "Cannot handover %s: neighbor unknown\n",
+ neighbor_ident_key_name(&req->target_nik));
+ handover_end(conn, HO_RESULT_FAIL_NO_CHANNEL);
+}
+
+/*! 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. */
+static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
+{
+ struct handover *ho = &conn->ho;
+ struct osmo_fsm_inst *fi = conn->ho.fi;
+ struct lchan_activate_info info;
+
+ OSMO_ASSERT(ho->new_bts);
+ OSMO_ASSERT(ho->new_lchan_type != GSM_LCHAN_NONE);
+ OSMO_ASSERT(!ho->new_lchan);
+
+ ho->scope = (ho->new_bts == conn->lchan->ts->trx->bts) ? HO_INTRA_CELL : HO_INTRA_BSC;
+ ho->ho_ref = g_next_ho_ref++;
+ ho->async = true;
+
+ ho->new_lchan = lchan_select_by_type(ho->new_bts, ho->new_lchan_type);
+
+ if (ho->scope & HO_INTRA_CELL)
+ ho_fsm_update_id(fi, "intraCell");
+ else
+ ho_fsm_update_id(fi, "intraBSC");
+
+ ho_count(BSC_CTR_HANDOVER_ATTEMPTED);
+
+ if (!ho->new_lchan) {
+ ho_fail(HO_RESULT_FAIL_NO_CHANNEL,
+ "No %s lchan available on BTS %u",
+ gsm_lchant_name(ho->new_lchan_type), ho->new_bts->nr);
+ return;
+ }
+ LOG_HO(conn, LOGL_DEBUG, "Selected lchan %s\n", gsm_lchan_name(ho->new_lchan));
+
+ ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE);
+
+ info = (struct lchan_activate_info){
+ .activ_for = FOR_HANDOVER,
+ .for_conn = conn,
+ .chan_mode = conn->lchan->tch_mode,
+ .requires_voice_stream = conn->lchan->mgw_endpoint_ci_bts ? true : false,
+ .msc_assigned_cic = conn->ho.inter_bsc_in.msc_assigned_cic,
+ .old_lchan = conn->lchan,
+ .wait_before_switching_rtp = true,
+ };
+
+ lchan_activate(ho->new_lchan, &info);
+}
+
+/* 3GPP TS 48.008 § 3.2.1.8 Handover Request */
+static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struct msgb *msg,
+ struct handover_in_req *req)
+{
+ struct tlv_parsed tp_arr[2];
+ struct tlv_parsed *tp = &tp_arr[0];
+ struct tlv_parsed *tp2 = &tp_arr[1];
+ struct tlv_p_entry *e;
+ int payload_length;
+ bool aoip = gscon_is_aoip(conn);
+ bool sccplite = gscon_is_sccplite(conn);
+
+ if ((aoip && sccplite) || !(aoip || sccplite)) {
+ LOG_HO(conn, LOGL_ERROR, "Received BSSMAP Handover Request, but conn is not"
+ " marked as exactly one of AoIP or SCCPlite\n");
+ return false;
+ }
+
+ payload_length = msg->tail - msg->l4h;
+ if (tlv_parse2(tp_arr, 2, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0) <= 0) {
+ LOG_HO(conn, LOGL_ERROR, "Failed to parse IEs\n");
+ return false;
+ }
+
+ if (!(e = TLVP_GET(tp, GSM0808_IE_CHANNEL_TYPE))) {
+ LOG_HO(conn, LOGL_ERROR, "Missing Channel Type IE\n");
+ return false;
+ }
+ if (gsm0808_dec_channel_type(&req->ct, e->val, e->len) <= 0) {
+ LOG_HO(conn, LOGL_ERROR, "Failed to parse Channel Type IE\n");
+ return false;
+ }
+
+ if (!(e = TLVP_GET(tp, GSM0808_IE_ENCRYPTION_INFORMATION))) {
+ LOG_HO(conn, LOGL_ERROR, "Missing Encryption Information IE\n");
+ return false;
+ }
+ if (gsm0808_dec_encrypt_info(&req->ei, e->val, e->len) <= 0) {
+ LOG_HO(conn, LOGL_ERROR, "Failed to parse Encryption Information IE\n");
+ return false;
+ }
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_TYPE_1))) {
+ if (e->len != sizeof(req->classmark.classmark1)) {
+ LOG_HO(conn, LOGL_ERROR, "Classmark Information 1 has wrong size\n");
+ return false;
+ }
+ req->classmark.classmark1 = *(struct gsm48_classmark1*)e->val;
+ req->classmark.classmark1_set = true;
+ } else if ((e = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_T2))) {
+ uint8_t len = OSMO_MIN(sizeof(req->classmark.classmark2),
+ e->len);
+ if (!len) {
+ LOG_HO(conn, LOGL_ERROR, "Classmark Information 2 has zero size\n");
+ return false;
+ }
+ memcpy(&req->classmark.classmark2, e->val, len);
+ req->classmark.classmark2_len = len;
+ } else
+ LOG_HO(conn, LOGL_INFO,
+ "Missing mandatory IE: 3GPP mandates either Classmark Information 1 or 2"
+ " in BSSMAP Handover Request, but neither are present. Will continue without.\n");
+
+ if (TLVP_PRESENT(tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
+ int rc;
+ unsigned int u;
+ struct sockaddr_storage msc_rtp_sa;
+
+ if (!aoip) {
+ LOG_HO(conn, LOGL_ERROR,
+ "BSSMAP Handover Request contains AoIP Transport Address,"
+ " but this is not an AoIP connection\n");
+ return false;
+ }
+ rc = gsm0808_dec_aoip_trasp_addr(&msc_rtp_sa,
+ TLVP_VAL(tp, GSM0808_IE_AOIP_TRASP_ADDR),
+ TLVP_LEN(tp, GSM0808_IE_AOIP_TRASP_ADDR));
+ if (rc < 0) {
+ LOG_HO(conn, LOGL_ERROR, "Unable to decode AoIP Transport Address.\n");
+ return false;
+ }
+
+ u = osmo_sockaddr_to_str_and_uint(req->msc_assigned_rtp_addr,
+ sizeof(req->msc_assigned_rtp_addr),
+ &req->msc_assigned_rtp_port,
+ (const struct sockaddr*)&msc_rtp_sa);
+ if (!u || u >= sizeof(req->msc_assigned_rtp_addr)) {
+ LOG_HO(conn, LOGL_ERROR, "MSC's RTP address is too long\n");
+ return false;
+ }
+ } else if (aoip) {
+ LOG_HO(conn, LOGL_ERROR,
+ "BSSMAP Handover Request lacks AoIP Transport Address on an AoIP connection\n");
+ return false;
+ }
+
+ /* The Cell Identifier (Serving) and Cell Identifier (Target) are both 3.2.2.17 and are
+ * identified by the same tag. So get one from tp and the other from tp2. */
+ if (!(e = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER))) {
+ LOG_HO(conn, LOGL_ERROR, "Missing IE: Cell Identifier (Serving)\n");
+ return false;
+ }
+ if (gsm0808_dec_cell_id(&req->cell_id_serving, e->val, e->len) < 0) {
+ LOG_HO(conn, LOGL_ERROR, "Invalid IE: Cell Identifier (Serving)\n");
+ return false;
+ }
+ /* LOG_HO() also calls gsm0808_cell_id_name(), so to be able to use gsm0808_cell_id_name() in
+ * logging without getting mixed up with those static buffers, store the result. */
+ OSMO_STRLCPY_ARRAY(req->cell_id_serving_name, gsm0808_cell_id_name(&req->cell_id_serving));
+
+ if (!(e = TLVP_GET(tp2, GSM0808_IE_CELL_IDENTIFIER))) {
+ LOG_HO(conn, LOGL_ERROR, "Missing IE: Cell Identifier (Target)\n");
+ return false;
+ }
+ if (gsm0808_dec_cell_id(&req->cell_id_target, e->val, e->len) < 0) {
+ LOG_HO(conn, LOGL_ERROR, "Invalid IE: Cell Identifier (Target)\n");
+ return false;
+ }
+ OSMO_STRLCPY_ARRAY(req->cell_id_target_name, gsm0808_cell_id_name(&req->cell_id_target));
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE))) {
+ /* CIC is permitted in both AoIP and SCCPlite */
+ req->msc_assigned_cic = osmo_load16be(e->val);
+ } else if (sccplite) {
+ /* no CIC but SCCPlite: illegal */
+ LOG_HO(conn, LOGL_ERROR, "SCCPlite MSC, but no CIC in incoming inter-BSC Handover\n");
+ return false;
+ }
+
+ /* A lot of IEs remain ignored... */
+
+ return true;
+}
+
+static bool chan_mode_is_tch(enum gsm48_chan_mode mode)
+{
+ switch (mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_AMR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
+ struct msgb *ho_request_msg)
+{
+ struct lchan_activate_info info;
+ struct handover *ho = &conn->ho;
+ struct bsc_msc_data *msc = conn->sccp.msc;
+ struct handover_in_req *req = &ho->inter_bsc_in;
+ int match_idx;
+ enum gsm48_chan_mode mode;
+ bool full_rate = false;
+ uint16_t s15_s0;
+ struct osmo_fsm_inst *fi;
+
+ handover_fsm_alloc(conn);
+
+ *ho = (struct handover){
+ .fi = ho->fi,
+ .from_hodec_id = HODEC_REMOTE,
+ .scope = HO_INTER_BSC_IN,
+ .ho_ref = g_next_ho_ref++,
+ .async = true,
+ };
+
+ fi = ho->fi;
+ ho_fsm_update_id(fi, "interBSCin");
+
+ if (!parse_ho_request(conn, ho_request_msg, req)) {
+ ho_fail(HO_RESULT_ERROR, "Invalid Handover Request message from MSC\n");
+ return;
+ }
+
+ ho_count(BSC_CTR_INTER_BSC_HO_IN_ATTEMPTED);
+
+ /* Figure out which cell to handover to. */
+ for (match_idx = 0; ; match_idx++) {
+ struct gsm_bts *bts;
+ struct gsm_lchan *lchan;
+
+ bts = gsm_bts_by_cell_id(conn->network, &req->cell_id_target,
+ match_idx);
+
+ /* Did we iterate all matches? */
+ if (!bts)
+ break;
+
+ LOG_HO(conn, LOGL_DEBUG, "BTS %u matches cell id %s\n",
+ bts->nr, req->cell_id_target_name);
+
+ /* Figure out channel type */
+ if (match_codec_pref(&mode, &full_rate, &s15_s0, &req->ct, &req->scl, msc, bts)) {
+ LOG_HO(conn, LOGL_DEBUG,
+ "BTS %u has no matching channel codec (%s, speech codec list len = %u)\n",
+ bts->nr, gsm0808_channel_type_name(&req->ct), req->scl.len);
+ continue;
+ }
+
+ LOG_HO(conn, LOGL_DEBUG, "BTS %u: Found matching audio type: %s %s (for %s)\n",
+ bts->nr, gsm48_chan_mode_name(mode), full_rate? "full-rate" : "half-rate",
+ gsm0808_channel_type_name(&req->ct));
+
+ lchan = lchan_select_by_chan_mode(bts, mode, full_rate);
+ if (!lchan) {
+ LOG_HO(conn, LOGL_DEBUG, "BTS %u has no matching free channels\n", bts->nr);
+ continue;
+ }
+
+ /* Found a match. */
+ ho->new_bts = bts;
+ ho->new_lchan = lchan;
+ break;
+ }
+
+ if (!ho->new_bts) {
+ ho_fail(HO_RESULT_ERROR, "No local cell matches the target %s",
+ req->cell_id_target_name);
+ return;
+ }
+
+ if (!ho->new_lchan) {
+ ho_fail(HO_RESULT_ERROR, "No free/matching lchan found for %s %s (speech codec list len = %u)",
+ req->cell_id_target_name, gsm0808_channel_type_name(&req->ct), req->scl.len);
+ return;
+ }
+
+ /* Just for completeness' sake, maybe some logging uses it? */
+ ho->new_lchan_type = ho->new_lchan->type;
+
+ ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE);
+
+ info = (struct lchan_activate_info){
+ .activ_for = FOR_HANDOVER,
+ .for_conn = conn,
+ .chan_mode = mode,
+ .s15_s0 = s15_s0,
+ .requires_voice_stream = chan_mode_is_tch(mode),
+ .msc_assigned_cic = req->msc_assigned_cic,
+ };
+
+ lchan_activate(ho->new_lchan, &info);
+}
+
+#define FUNC_RESULT_COUNTER(name) \
+static int result_counter_##name(enum handover_result result) \
+{ \
+ switch (result) { \
+ case HO_RESULT_OK: \
+ return BSC_CTR_##name##_COMPLETED; \
+ case HO_RESULT_FAIL_NO_CHANNEL: \
+ return BSC_CTR_##name##_NO_CHANNEL; \
+ case HO_RESULT_FAIL_RR_HO_FAIL: \
+ return BSC_CTR_##name##_FAILED; \
+ case HO_RESULT_FAIL_TIMEOUT: \
+ return BSC_CTR_##name##_TIMEOUT; \
+ case HO_RESULT_CONN_RELEASE: \
+ return BSC_CTR_##name##_STOPPED; \
+ default: \
+ case HO_RESULT_ERROR: \
+ return BSC_CTR_##name##_ERROR; \
+ } \
+}
+
+FUNC_RESULT_COUNTER(ASSIGNMENT)
+FUNC_RESULT_COUNTER(HANDOVER)
+FUNC_RESULT_COUNTER(INTER_BSC_HO_IN)
+
+static int result_counter_INTER_BSC_HO_OUT(enum handover_result result) {
+ switch (result) {
+ case HO_RESULT_OK:
+ return BSC_CTR_INTER_BSC_HO_OUT_COMPLETED;
+ case HO_RESULT_FAIL_TIMEOUT:
+ return BSC_CTR_INTER_BSC_HO_OUT_TIMEOUT;
+ case HO_RESULT_CONN_RELEASE:
+ return BSC_CTR_INTER_BSC_HO_OUT_STOPPED;
+ default:
+ case HO_RESULT_ERROR:
+ return BSC_CTR_INTER_BSC_HO_OUT_ERROR;
+ }
+}
+
+static int result_counter(enum handover_scope scope, enum handover_result result)
+{
+ switch (scope) {
+ case HO_INTRA_CELL:
+ return result_counter_ASSIGNMENT(result);
+ default:
+ LOGP(DHO, LOGL_ERROR, "invalid enum handover_scope value: %s\n",
+ handover_scope_name(scope));
+ /* use "normal" HO_INTRA_BSC counter... */
+ case HO_INTRA_BSC:
+ return result_counter_HANDOVER(result);
+ case HO_INTER_BSC_OUT:
+ return result_counter_INTER_BSC_HO_OUT(result);
+ case HO_INTER_BSC_IN:
+ return result_counter_INTER_BSC_HO_IN(result);
+ }
+}
+
+/* Notify the handover decision algorithm of failure and clear out any handover state. */
+void handover_end(struct gsm_subscriber_connection *conn, enum handover_result result)
+{
+ struct handover_decision_callbacks *hdc;
+ struct handover *ho = &conn->ho;
+
+ /* Sanity -- an error result ensures beyond doubt that we don't use the new lchan below
+ * when the handover isn't actually allowed to change this conn. */
+ if (result == HO_RESULT_OK && ho->new_lchan) {
+ if (!(ho->scope & (HO_INTRA_CELL | HO_INTRA_BSC | HO_INTER_BSC_IN))) {
+ LOG_HO(conn, LOGL_ERROR, "Got new lchan, but this is not an incoming inter-BSC HO\n");
+ result = HO_RESULT_ERROR;
+ }
+ if (ho->new_lchan->conn != conn) {
+ LOG_HO(conn, LOGL_ERROR, "Got new lchan, but it is for another conn\n");
+ result = HO_RESULT_ERROR;
+ }
+ }
+
+ if (ho->scope & HO_INTER_BSC_IN) {
+ if (result == HO_RESULT_OK) {
+ if (!ho->new_lchan) {
+ LOG_HO(conn, LOGL_ERROR, "Inter-BSC HO IN ends in success,"
+ " but there is no lchan\n");
+ result = HO_RESULT_ERROR;
+ } else
+ result = bsc_tx_bssmap_ho_complete(conn, ho->new_lchan);
+ }
+ /* Not 'else': above checks may still result in HO_RESULT_ERROR. */
+ if (result == HO_RESULT_ERROR) {
+ /* Return a BSSMAP Handover Failure, as described in 3GPP TS 48.008 3.1.5.2.2
+ * "Handover Resource Allocation Failure" */
+ bsc_tx_bssmap_ho_failure(conn);
+ }
+ } else if (ho->scope & HO_INTER_BSC_OUT) {
+ switch (result) {
+ case HO_RESULT_OK:
+ break;
+ case HO_RESULT_FAIL_RR_HO_FAIL:
+ /* Return a BSSMAP Handover Failure, as described in 3GPP TS 48.008 3.1.5.3.2
+ * "Handover Failure" */
+ bsc_tx_bssmap_ho_failure(conn);
+ break;
+ default:
+ case HO_RESULT_FAIL_TIMEOUT:
+ switch (ho->fi->state) {
+ case HO_OUT_ST_WAIT_HO_COMMAND:
+ /* MSC never replied with a Handover Command. Fail and ignore the
+ * handover, continue to use the lchan. */
+ break;
+ default:
+ case HO_OUT_ST_WAIT_CLEAR:
+ /* 3GPP TS 48.008 3.1.5.3.3 "Abnormal Conditions": if neither MS reports
+ * HO Failure nor the MSC sends a Clear Command, we should release the
+ * dedicated radio resources and send a Clear Request to the MSC. */
+ lchan_release(conn->lchan, true, true, GSM48_RR_CAUSE_ABNORMAL_TIMER);
+ /* Once the channel release is through, the BSSMAP Clear will follow. */
+ break;
+ }
+ break;
+ }
+ }
+
+ /* Rembered this only for error handling: should handover fail, handover_reset() will release the
+ * MGW endpoint right away. If successful, the conn continues to use the endpoint. */
+ if (result == HO_RESULT_OK)
+ conn->ho.created_ci_for_msc = NULL;
+
+ hdc = handover_decision_callbacks_get(ho->from_hodec_id);
+ if (hdc && hdc->on_handover_end)
+ hdc->on_handover_end(conn, result);
+
+ ho_count(result_counter(ho->scope, result));
+
+ LOG_HO(conn, LOGL_INFO, "Result: %s\n", handover_result_name(result));
+
+ if (ho->new_lchan && result == HO_RESULT_OK) {
+ gscon_change_primary_lchan(conn, conn->ho.new_lchan);
+ ho->new_lchan = NULL;
+ }
+
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_END, &result);
+
+ /* Detach the new_lchan last, so we can still see it in above logging */
+ if (ho->new_lchan) {
+ /* Release new lchan, it didn't work out */
+ lchan_release(ho->new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ ho->new_lchan = NULL;
+ }
+
+ if ((ho->scope & HO_INTER_BSC_IN) && result == HO_RESULT_OK) {
+ conn->user_plane.msc_assigned_cic = conn->ho.inter_bsc_in.msc_assigned_cic;
+ osmo_strlcpy(conn->user_plane.msc_assigned_rtp_addr,
+ conn->ho.inter_bsc_in.msc_assigned_rtp_addr,
+ sizeof(conn->user_plane.msc_assigned_rtp_addr));
+ conn->user_plane.msc_assigned_rtp_port = conn->ho.inter_bsc_in.msc_assigned_rtp_port;
+ }
+
+ handover_reset(conn);
+
+ /* We've dispatched the handover result above, let's disconnect to not fire the same event again.
+ * The parent term event is a safety measure for unplanned termination. */
+ osmo_fsm_inst_unlink_parent(conn->ho.fi, conn);
+ osmo_fsm_inst_term(conn->ho.fi, OSMO_FSM_TERM_REGULAR, 0);
+}
+
+static void ho_fsm_wait_lchan_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ switch (event) {
+
+ case HO_EV_LCHAN_ACTIVE:
+ ho_fsm_state_chg(HO_ST_WAIT_RR_HO_DETECT);
+ return;
+
+ case HO_EV_LCHAN_ERROR:
+ ho_fail(HO_RESULT_ERROR, "error while activating lchan %s",
+ gsm_lchan_name(conn->ho.new_lchan));
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ho_fsm_wait_rr_ho_detect_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ struct handover *ho = &conn->ho;
+
+ struct msgb *rr_ho_cmd = gsm48_make_ho_cmd(ho->new_lchan,
+ ho->new_lchan->ms_power,
+ ho->ho_ref);
+ if (!rr_ho_cmd) {
+ ho_fail(HO_RESULT_ERROR, "Unable to compose RR Handover Command");
+ return;
+ }
+
+
+ if (ho->scope & (HO_INTRA_CELL | HO_INTRA_BSC)) {
+ /* conn->lchan is the old lchan being handovered from */
+ rr_ho_cmd->lchan = conn->lchan;
+ rc = gsm48_sendmsg(rr_ho_cmd);
+ if (rc)
+ ho_fail(HO_RESULT_ERROR, "Unable to Tx RR Handover Command (rc=%d %s)",
+ rc, strerror(-rc));
+ return;
+ }
+
+ if (ho->scope & HO_INTER_BSC_IN) {
+ rc = bsc_tx_bssmap_ho_request_ack(conn, rr_ho_cmd);
+ if (rc)
+ ho_fail(HO_RESULT_ERROR, "Unable to Tx BSSMAP Handover Request Ack (rc=%d %s)",
+ rc, strerror(-rc));
+ return;
+ }
+
+ ho_fail(HO_RESULT_ERROR, "Invalid situation, no target for RR Handover Command");
+}
+
+static void ho_fsm_wait_rr_ho_detect(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ struct handover *ho = &conn->ho;
+ switch (event) {
+
+ case HO_EV_RR_HO_DETECT:
+ {
+ struct handover_rr_detect_data *d = data;
+ OSMO_ASSERT(d);
+ if (d->access_delay) {
+ LOG_HO(conn, LOGL_DEBUG, "RR Handover Detect (Access Delay=%u)\n",
+ *(d->access_delay));
+ } else
+ LOG_HO(conn, LOGL_DEBUG, "RR Handover Detect (no Access Delay IE)\n");
+ }
+
+ if (ho->scope & HO_INTER_BSC_IN) {
+ int rc = bsc_tx_bssmap_ho_detect(conn);
+ if (rc) {
+ ho_fail(HO_RESULT_ERROR,
+ "Unable to send BSSMAP Handover Detect");
+ return;
+ }
+ }
+
+ if (ho->new_lchan->fi_rtp)
+ osmo_fsm_inst_dispatch(ho->new_lchan->fi_rtp,
+ LCHAN_RTP_EV_READY_TO_SWITCH_RTP, 0);
+ ho_fsm_state_chg(HO_ST_WAIT_RR_HO_COMPLETE);
+ /* The lchan FSM will already start to redirect the RTP stream */
+ return;
+
+ case HO_EV_RR_HO_COMPLETE:
+ LOG_HO(conn, LOGL_ERROR,
+ "Received RR Handover Complete, but haven't even seen a Handover Detect yet;"
+ " Accepting handover anyway\n");
+ if (ho->new_lchan->fi_rtp)
+ osmo_fsm_inst_dispatch(ho->new_lchan->fi_rtp,
+ LCHAN_RTP_EV_READY_TO_SWITCH_RTP, 0);
+ ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ESTABLISHED);
+ return;
+
+ case HO_EV_RR_HO_FAIL:
+ ho_fail(HO_RESULT_FAIL_RR_HO_FAIL, "Received RR Handover Fail message");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ho_fsm_wait_rr_ho_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+
+ switch (event) {
+
+ case HO_EV_RR_HO_DETECT:
+ /* Numerous HO Detect RACH bursts may follow after the initial one, ignore. */
+ return;
+
+ case HO_EV_LCHAN_ESTABLISHED:
+ LOG_HO(conn, LOGL_DEBUG, "lchan established, still waiting for RR Handover Complete\n");
+ /* The lchan is already done with all of its RTP setup. We will notice the lchan state
+ * being LCHAN_ST_ESTABLISHED in ho_fsm_wait_lchan_established_onenter(). */
+ return;
+
+ case HO_EV_RR_HO_COMPLETE:
+ ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ESTABLISHED);
+ return;
+
+ case HO_EV_RR_HO_FAIL:
+ ho_fail(HO_RESULT_FAIL_RR_HO_FAIL, "Received RR Handover Fail message");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ho_fsm_post_lchan_established(struct osmo_fsm_inst *fi);
+
+static void ho_fsm_wait_lchan_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+
+ if (conn->ho.fi && lchan_state_is(conn->ho.new_lchan, LCHAN_ST_ESTABLISHED)) {
+ LOG_HO(conn, LOGL_DEBUG, "lchan already established earlier\n");
+ ho_fsm_post_lchan_established(fi);
+ }
+}
+
+static void ho_fsm_wait_lchan_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case HO_EV_LCHAN_ESTABLISHED:
+ ho_fsm_post_lchan_established(fi);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ho_fsm_post_lchan_established(struct osmo_fsm_inst *fi)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ struct handover *ho = &conn->ho;
+
+ if (ho->new_lchan->activate.requires_voice_stream
+ && (ho->scope & HO_INTER_BSC_IN))
+ ho_fsm_state_chg(HO_ST_WAIT_MGW_ENDPOINT_TO_MSC);
+ else
+ ho_success();
+}
+
+static void ho_fsm_wait_mgw_endpoint_to_msc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ struct handover *ho = &conn->ho;
+
+ if (!gscon_connect_mgw_to_msc(conn,
+ ho->new_lchan,
+ ho->inter_bsc_in.msc_assigned_rtp_addr,
+ ho->inter_bsc_in.msc_assigned_rtp_port,
+ fi,
+ HO_EV_MSC_MGW_OK,
+ HO_EV_MSC_MGW_FAIL,
+ NULL,
+ &ho->created_ci_for_msc)) {
+ ho_fail(HO_RESULT_ERROR,
+ "Unable to connect MGW endpoint to the MSC side");
+ }
+}
+
+static void ho_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ switch (event) {
+
+ case HO_EV_MSC_MGW_OK:
+ /* For AoIP, we created the MGW endpoint. Ensure it is really there, and log it. */
+ if (gscon_is_aoip(conn)) {
+ const struct mgcp_conn_peer *mgw_info;
+ mgw_info = mgwep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
+ if (!mgw_info) {
+ ho_fail(HO_RESULT_ERROR,
+ "Unable to retrieve RTP port info allocated by MGW for"
+ " the MSC side.");
+ return;
+ }
+ LOG_HO(conn, LOGL_DEBUG, "MGW's MSC side CI: %s:%u\n",
+ mgw_info->addr, mgw_info->port);
+ }
+ ho_success();
+ return;
+
+ case HO_EV_MSC_MGW_FAIL:
+ ho_fail(HO_RESULT_ERROR,
+ "Unable to connect MGW endpoint to the MSC side");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+/* Inter-BSC OUT */
+
+static void handover_start_inter_bsc_out(struct gsm_subscriber_connection *conn,
+ const struct gsm0808_cell_id_list2 *target_cells)
+{
+ int rc;
+ struct handover *ho = &conn->ho;
+ struct osmo_fsm_inst *fi = conn->ho.fi;
+
+ ho->scope = HO_INTER_BSC_OUT;
+ ho_fsm_update_id(fi, "interBSCout");
+ ho_count(BSC_CTR_INTER_BSC_HO_OUT_ATTEMPTED);
+
+ rc = bsc_tx_bssmap_ho_required(conn->lchan, target_cells);
+ if (rc) {
+ ho_fail(HO_RESULT_ERROR, "Unable to send BSSMAP Handover Required message");
+ return;
+ }
+
+ ho_fsm_state_chg(HO_OUT_ST_WAIT_HO_COMMAND);
+}
+
+static void ho_out_fsm_wait_ho_command(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ int rc;
+ struct ho_out_rx_bssmap_ho_command *rx;
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ switch (event) {
+
+ case HO_OUT_EV_BSSMAP_HO_COMMAND:
+ rx = data;
+ if (!rx) {
+ ho_fail(HO_RESULT_ERROR,
+ "Rx BSSMAP Handover Command: no L3 info passed with event");
+ return;
+ }
+
+ LOG_HO(conn, LOGL_DEBUG, "Rx BSSMAP Handover Command: forwarding Layer 3 Info: %s\n",
+ osmo_hexdump(rx->l3_info, rx->l3_info_len));
+
+ rc = rsl_forward_layer3_info(conn->lchan, rx->l3_info, rx->l3_info_len);
+ if (rc) {
+ ho_fail(HO_RESULT_ERROR,
+ "Rx BSSMAP Handover Command: Failed to forward Layer 3 Info (rc=%d %s)",
+ rc, strerror(-rc));
+ return;
+ }
+
+ ho_fsm_state_chg(HO_OUT_ST_WAIT_CLEAR);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ho_out_fsm_wait_clear(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ switch (event) {
+ case HO_EV_RR_HO_FAIL:
+ ho_fail(HO_RESULT_FAIL_RR_HO_FAIL, "Received RR Handover Failure message");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state ho_fsm_states[] = {
+ [HO_ST_NOT_STARTED] = {
+ .name = "NOT_STARTED",
+ .out_state_mask = 0
+ | S(HO_ST_WAIT_LCHAN_ACTIVE)
+ | S(HO_OUT_ST_WAIT_HO_COMMAND)
+ ,
+ },
+ [HO_ST_WAIT_LCHAN_ACTIVE] = {
+ .name = "WAIT_LCHAN_ACTIVE",
+ .action = ho_fsm_wait_lchan_active,
+ .in_event_mask = 0
+ | S(HO_EV_LCHAN_ACTIVE)
+ | S(HO_EV_LCHAN_ERROR)
+ ,
+ .out_state_mask = 0
+ | S(HO_ST_WAIT_LCHAN_ACTIVE)
+ | S(HO_ST_WAIT_RR_HO_DETECT)
+ ,
+ },
+ [HO_ST_WAIT_RR_HO_DETECT] = {
+ .name = "WAIT_RR_HO_DETECT",
+ .onenter = ho_fsm_wait_rr_ho_detect_onenter,
+ .action = ho_fsm_wait_rr_ho_detect,
+ .in_event_mask = 0
+ | S(HO_EV_RR_HO_DETECT)
+ | S(HO_EV_RR_HO_COMPLETE) /* actually as error */
+ | S(HO_EV_RR_HO_FAIL)
+ ,
+ .out_state_mask = 0
+ | S(HO_ST_WAIT_RR_HO_COMPLETE)
+ | S(HO_ST_WAIT_LCHAN_ESTABLISHED)
+ ,
+ },
+ [HO_ST_WAIT_RR_HO_COMPLETE] = {
+ .name = "WAIT_RR_HO_COMPLETE",
+ .action = ho_fsm_wait_rr_ho_complete,
+ .in_event_mask = 0
+ | S(HO_EV_RR_HO_DETECT) /* ignore extra HO RACH */
+ | S(HO_EV_LCHAN_ESTABLISHED)
+ | S(HO_EV_RR_HO_COMPLETE)
+ | S(HO_EV_RR_HO_FAIL)
+ ,
+ .out_state_mask = 0
+ | S(HO_ST_WAIT_LCHAN_ESTABLISHED)
+ ,
+ },
+ [HO_ST_WAIT_LCHAN_ESTABLISHED] = {
+ .name = "WAIT_LCHAN_ESTABLISHED",
+ .onenter = ho_fsm_wait_lchan_established_onenter,
+ .action = ho_fsm_wait_lchan_established,
+ .in_event_mask = 0
+ | S(HO_EV_LCHAN_ESTABLISHED)
+ ,
+ .out_state_mask = 0
+ | S(HO_ST_WAIT_MGW_ENDPOINT_TO_MSC)
+ ,
+ },
+ [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = {
+ .name = "WAIT_MGW_ENDPOINT_TO_MSC",
+ .onenter = ho_fsm_wait_mgw_endpoint_to_msc_onenter,
+ .action = ho_fsm_wait_mgw_endpoint_to_msc,
+ .in_event_mask = 0
+ | S(HO_EV_MSC_MGW_OK)
+ | S(HO_EV_MSC_MGW_FAIL)
+ ,
+ },
+
+ [HO_OUT_ST_WAIT_HO_COMMAND] = {
+ .name = "inter-BSC-OUT:WAIT_HO_COMMAND",
+ .action = ho_out_fsm_wait_ho_command,
+ .in_event_mask = 0
+ | S(HO_OUT_EV_BSSMAP_HO_COMMAND)
+ ,
+ .out_state_mask = 0
+ | S(HO_OUT_ST_WAIT_CLEAR)
+ ,
+ },
+ [HO_OUT_ST_WAIT_CLEAR] = {
+ .name = "inter-BSC-OUT:WAIT_CLEAR",
+ .in_event_mask = 0
+ | S(HO_EV_RR_HO_FAIL)
+ ,
+ .action = ho_out_fsm_wait_clear,
+ },
+};
+
+static const struct value_string ho_fsm_event_names[] = {
+ OSMO_VALUE_STRING(HO_EV_LCHAN_ACTIVE),
+ OSMO_VALUE_STRING(HO_EV_LCHAN_ESTABLISHED),
+ OSMO_VALUE_STRING(HO_EV_LCHAN_ERROR),
+ OSMO_VALUE_STRING(HO_EV_RR_HO_DETECT),
+ OSMO_VALUE_STRING(HO_EV_RR_HO_COMPLETE),
+ OSMO_VALUE_STRING(HO_EV_RR_HO_FAIL),
+ OSMO_VALUE_STRING(HO_EV_MSC_MGW_OK),
+ OSMO_VALUE_STRING(HO_EV_MSC_MGW_FAIL),
+ OSMO_VALUE_STRING(HO_EV_CONN_RELEASING),
+ OSMO_VALUE_STRING(HO_OUT_EV_BSSMAP_HO_COMMAND),
+ {}
+};
+
+void ho_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ switch (event) {
+
+ case HO_EV_CONN_RELEASING:
+ switch (fi->state) {
+ case HO_OUT_ST_WAIT_CLEAR:
+ ho_success();
+ return;
+ default:
+ ho_fail(HO_RESULT_CONN_RELEASE,
+ "Connection releasing in the middle of handover");
+ return;
+ }
+
+ case HO_EV_LCHAN_ERROR:
+ switch (fi->state) {
+ case HO_OUT_ST_WAIT_HO_COMMAND:
+ case HO_OUT_ST_WAIT_CLEAR:
+ LOG_HO(conn, LOGL_ERROR, "Event not permitted: %s\n",
+ osmo_fsm_event_name(fi->fsm, event));
+ return;
+
+ default:
+ ho_fail(HO_RESULT_ERROR, "Error while establishing lchan %s",
+ gsm_lchan_name(data));
+ return;
+ }
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+int ho_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ ho_fail(HO_RESULT_FAIL_TIMEOUT, "Timeout");
+ return 0;
+}
+
+void ho_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ conn->ho.fi = NULL;
+}
+
+static struct osmo_fsm ho_fsm = {
+ .name = "handover",
+ .states = ho_fsm_states,
+ .num_states = ARRAY_SIZE(ho_fsm_states),
+ .log_subsys = DRSL,
+ .event_names = ho_fsm_event_names,
+ .allstate_action = ho_fsm_allstate_action,
+ .allstate_event_mask = 0
+ | S(HO_EV_CONN_RELEASING)
+ | S(HO_EV_LCHAN_ERROR)
+ ,
+ .timer_cb = ho_fsm_timer_cb,
+ .cleanup = ho_fsm_cleanup,
+};
diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c
new file mode 100644
index 000000000..5725213ef
--- /dev/null
+++ b/src/osmo-bsc/handover_logic.c
@@ -0,0 +1,198 @@
+/* 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+
+const struct value_string handover_scope_names[] = {
+ { HO_NO_HANDOVER, "HO-none" },
+ { HO_INTRA_CELL, "AS" },
+ { HO_INTRA_BSC, "HO-intraBSC" },
+ { HO_INTER_BSC_OUT, "HO-interBSC-Out" },
+ { HO_INTER_BSC_IN, "HO-interBSC-In" },
+ { HO_SCOPE_ALL, "HO-any" },
+ {}
+};
+
+const struct value_string handover_result_names[] = {
+ { HO_RESULT_OK, "Complete" },
+ { HO_RESULT_FAIL_NO_CHANNEL, "Failure (no channel could be allocated)" },
+ { HO_RESULT_FAIL_RR_HO_FAIL, "Failure (MS sent RR Handover Failure)" },
+ { HO_RESULT_FAIL_TIMEOUT, "Failure (timeout)" },
+ { HO_RESULT_CONN_RELEASE, "Connection released" },
+ { HO_RESULT_ERROR, "Failure" },
+ {}
+};
+
+static LLIST_HEAD(handover_decision_callbacks);
+
+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;
+}
+
+static void 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;
+ hdc->on_measurement_report(mr);
+}
+
+/* Count ongoing handovers within the given BTS.
+ * ho_scopes is an OR'd combination of enum handover_scope values to include in the count. */
+int bts_handover_count(struct gsm_bts *bts, int ho_scopes)
+{
+ struct gsm_bts_trx *trx;
+ int count = 0;
+
+ 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];
+ struct gsm_lchan *lchan;
+
+ /* skip administratively deactivated timeslots */
+ if (!nm_is_running(&ts->mo.nm_state))
+ continue;
+
+ ts_for_each_lchan(lchan, ts) {
+ if (!lchan->conn)
+ continue;
+ if (!lchan->conn->ho.fi)
+ continue;
+ if (lchan->conn->ho.scope & ho_scopes)
+ count++;
+ }
+ }
+ }
+
+ return count;
+}
+
+struct gsm_bts *bts_by_neighbor_ident(const struct gsm_network *net,
+ const struct neighbor_ident_key *search_for)
+{
+ struct gsm_bts *found = NULL;
+ struct gsm_bts *bts;
+ struct gsm_bts *wildcard_match = NULL;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct neighbor_ident_key entry = {
+ .from_bts = NEIGHBOR_IDENT_KEY_ANY_BTS,
+ .arfcn = bts->c0->arfcn,
+ .bsic = bts->bsic,
+ };
+ if (neighbor_ident_key_match(&entry, search_for, true)) {
+ if (found) {
+ LOGP(DHO, LOGL_ERROR, "CONFIG ERROR: Multiple BTS match %s: %d and %d\n",
+ neighbor_ident_key_name(search_for),
+ found->nr, bts->nr);
+ return found;
+ }
+ found = bts;
+ }
+ if (neighbor_ident_key_match(&entry, search_for, false))
+ wildcard_match = bts;
+ }
+
+ if (found)
+ return found;
+
+ return wildcard_match;
+}
+
+struct neighbor_ident_key *bts_ident_key(const struct gsm_bts *bts)
+{
+ static struct neighbor_ident_key key;
+ key = (struct neighbor_ident_key){
+ .arfcn = bts->c0->arfcn,
+ .bsic = bts->bsic,
+ };
+ return &key;
+}
+
+static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct lchan_signal_data *lchan_data;
+ struct gsm_lchan *lchan;
+
+ lchan_data = signal_data;
+ switch (subsys) {
+ case SS_LCHAN:
+ OSMO_ASSERT(lchan_data);
+ lchan = lchan_data->lchan;
+ OSMO_ASSERT(lchan);
+
+ switch (signal) {
+ case S_LCHAN_MEAS_REP:
+ ho_meas_rep(lchan_data->mr);
+ break;
+ }
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_ho_logic(void)
+{
+ osmo_signal_register_handler(SS_LCHAN, ho_logic_sig_cb, NULL);
+}
diff --git a/src/osmo-bsc/handover_vty.c b/src/osmo-bsc/handover_vty.c
new file mode 100644
index 000000000..79795115e
--- /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 <jolly@eversberg.eu>
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Andreas Eversberg <jolly@eversberg.eu>
+ * Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/handover_decision_2.h>
+
+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\n"
+ "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/lchan_fsm.c b/src/osmo-bsc/lchan_fsm.c
new file mode 100644
index 000000000..5e99239c8
--- /dev/null
+++ b/src/osmo-bsc/lchan_fsm.c
@@ -0,0 +1,1409 @@
+/* osmo-bsc API to allocate an lchan, complete with dyn TS switchover.
+ *
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/core/byteswap.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bsc_rll.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/assignment_fsm.h>
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/codec_pref.h>
+
+
+static struct osmo_fsm lchan_fsm;
+
+struct gsm_lchan *lchan_fi_lchan(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &lchan_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+bool lchan_may_receive_data(struct gsm_lchan *lchan)
+{
+ if (!lchan || !lchan->fi)
+ return false;
+
+ switch (lchan->fi->state) {
+ case LCHAN_ST_WAIT_RLL_RTP_ESTABLISH:
+ case LCHAN_ST_ESTABLISHED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void lchan_set_last_error(struct gsm_lchan *lchan, const char *fmt, ...)
+{
+ va_list ap;
+ /* This dance allows using an existing error reason in above fmt */
+ char *last_error_was = lchan->last_error;
+ lchan->last_error = NULL;
+
+ if (fmt) {
+ va_start(ap, fmt);
+ lchan->last_error = talloc_vasprintf(lchan->ts->trx, fmt, ap);
+ va_end(ap);
+
+ LOG_LCHAN(lchan, LOGL_ERROR, "%s\n", lchan->last_error);
+ }
+
+ if (last_error_was)
+ talloc_free(last_error_was);
+}
+
+/* The idea here is that we must not require to change any lchan state in order to deny a request. */
+#define lchan_on_activation_failure(lchan, for_conn, activ_for) \
+ _lchan_on_activation_failure(lchan, for_conn, activ_for, \
+ __FILE__, __LINE__)
+static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_activate_mode activ_for,
+ struct gsm_subscriber_connection *for_conn,
+ const char *file, int line)
+{
+ if (lchan->activate.concluded)
+ return;
+ lchan->activate.concluded = true;
+
+ switch (activ_for) {
+
+ case FOR_MS_CHANNEL_REQUEST:
+ if (lchan->activate.immediate_assignment_sent) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation failed, after Immediate Assignment message was sent (%s)\n",
+ lchan->last_error ? : "unknown error");
+ /* Likely the MS never showed up. Just tear down the lchan. */
+ } else {
+ /* Failure before Immediate Assignment message, send a reject. */
+ LOG_LCHAN(lchan, LOGL_NOTICE, "Tx Immediate Assignment Reject (%s)\n",
+ lchan->last_error ? : "unknown error");
+ rsl_tx_imm_ass_rej(lchan->ts->trx->bts, lchan->rqd_ref);
+ }
+ break;
+
+ case FOR_ASSIGNMENT:
+ LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Assignment FSM of error (%s)\n",
+ lchan->last_error ? : "unknown error");
+ _osmo_fsm_inst_dispatch(for_conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, lchan,
+ file, line);
+ return;
+
+ case FOR_HANDOVER:
+ LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Handover FSM of error (%s)\n",
+ lchan->last_error ? : "unknown error");
+ if (!for_conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for Handover failed, but activation request has"
+ " no conn\n");
+ break;
+ }
+ if (!for_conn->ho.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for Handover failed, but conn has no ongoing"
+ " handover procedure\n");
+ break;
+ }
+ _osmo_fsm_inst_dispatch(for_conn->ho.fi, HO_EV_LCHAN_ERROR, lchan, file, line);
+ break;
+
+ case FOR_VTY:
+ LOG_LCHAN(lchan, LOGL_ERROR, "VTY user invoked lchan activation failed (%s)\n",
+ lchan->last_error ? : "unknown error");
+ break;
+
+ default:
+ LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation failed (%s)\n",
+ lchan->last_error ? : "unknown error");
+ break;
+ }
+}
+
+static void lchan_on_fully_established(struct gsm_lchan *lchan)
+{
+ if (lchan->activate.concluded)
+ return;
+ lchan->activate.concluded = true;
+
+ switch (lchan->activate.activ_for) {
+ case FOR_MS_CHANNEL_REQUEST:
+ /* No signalling to do here, MS is free to use the channel, and should go on to connect
+ * to the MSC and establish a subscriber connection. */
+ break;
+
+ case FOR_ASSIGNMENT:
+ if (!lchan->conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for assignment succeeded, but lchan has no conn:"
+ " cannot trigger appropriate actions. Release.\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ break;
+ }
+ if (!lchan->conn->assignment.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for assignment succeeded, but lchan has no"
+ " assignment ongoing: cannot trigger appropriate actions. Release.\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ break;
+ }
+ osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ESTABLISHED,
+ lchan);
+ /* The lchan->fi_rtp will be notified of LCHAN_RTP_EV_ESTABLISHED in
+ * gscon_change_primary_lchan() upon assignment_success(). On failure before then, we
+ * will try to roll back a modified RTP connection. */
+ break;
+
+ case FOR_HANDOVER:
+ if (!lchan->conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for handover succeeded, but lchan has no conn\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ break;
+ }
+ if (!lchan->conn->ho.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for handover succeeded, but lchan has no"
+ " handover ongoing\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ break;
+ }
+ osmo_fsm_inst_dispatch(lchan->conn->ho.fi, HO_EV_LCHAN_ESTABLISHED, lchan);
+ /* The lchan->fi_rtp will be notified of LCHAN_RTP_EV_ESTABLISHED in
+ * gscon_change_primary_lchan() upon handover_end(HO_RESULT_OK). On failure before then,
+ * we will try to roll back a modified RTP connection. */
+ break;
+
+ default:
+ LOG_LCHAN(lchan, LOGL_NOTICE, "lchan %s fully established\n",
+ lchan_activate_mode_name(lchan->activate.activ_for));
+ break;
+ }
+}
+
+struct state_timeout lchan_fsm_timeouts[32] = {
+ [LCHAN_ST_WAIT_TS_READY] = { .T=23001 },
+ [LCHAN_ST_WAIT_ACTIV_ACK] = { .T=23002 },
+ [LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] = { .T=3101 },
+ [LCHAN_ST_WAIT_RLL_RTP_RELEASED] = { .T=3109 },
+ [LCHAN_ST_WAIT_BEFORE_RF_RELEASE] = { .T=3111 },
+ [LCHAN_ST_WAIT_RF_RELEASE_ACK] = { .T=3111 },
+ [LCHAN_ST_WAIT_AFTER_ERROR] = { .T=993111 },
+};
+
+/* Transition to a state, using the T timer defined in lchan_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define lchan_fsm_state_chg(state) \
+ fsm_inst_state_chg_T(fi, state, \
+ lchan_fsm_timeouts, \
+ ((struct gsm_lchan*)(fi->priv))->ts->trx->bts->network->T_defs, \
+ 5)
+
+/* Set a failure message, trigger the common actions to take on failure, transition to a state to
+ * continue with (using state timeouts from lchan_fsm_timeouts[]). Assumes local variable fi exists. */
+#define lchan_fail_to(STATE_CHG, fmt, args...) do { \
+ struct gsm_lchan *_lchan = fi->priv; \
+ struct osmo_fsm *fsm = fi->fsm; \
+ uint32_t state_was = fi->state; \
+ /* Snapshot the target state, in case the macro argument evaluates differently later */ \
+ const uint32_t state_chg = STATE_CHG; \
+ LOG_LCHAN(_lchan, LOGL_DEBUG, "Handling failure, will then transition to state %s\n", \
+ osmo_fsm_state_name(fsm, state_chg)); \
+ lchan_set_last_error(_lchan, "lchan %s in state %s: " fmt, \
+ _lchan->activate.concluded ? "failure" : "allocation failed", \
+ osmo_fsm_state_name(fsm, state_was), ## args); \
+ lchan_on_activation_failure(_lchan, _lchan->activate.activ_for, _lchan->conn); \
+ if (fi->state != state_chg) \
+ lchan_fsm_state_chg(state_chg); \
+ else \
+ LOG_LCHAN(_lchan, LOGL_DEBUG, "After failure handling, already in state %s\n", \
+ osmo_fsm_state_name(fsm, state_chg)); \
+ } while(0)
+
+/* Which state to transition to when lchan_fail() is called in a given state. */
+uint32_t lchan_fsm_on_error[32] = {
+ [LCHAN_ST_UNUSED] = LCHAN_ST_UNUSED,
+ [LCHAN_ST_WAIT_TS_READY] = LCHAN_ST_UNUSED,
+ [LCHAN_ST_WAIT_ACTIV_ACK] = LCHAN_ST_BORKEN,
+ [LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] = LCHAN_ST_WAIT_RF_RELEASE_ACK,
+ [LCHAN_ST_ESTABLISHED] = LCHAN_ST_WAIT_RLL_RTP_RELEASED,
+ [LCHAN_ST_WAIT_RLL_RTP_RELEASED] = LCHAN_ST_WAIT_RF_RELEASE_ACK,
+ [LCHAN_ST_WAIT_BEFORE_RF_RELEASE] = LCHAN_ST_WAIT_RF_RELEASE_ACK,
+ [LCHAN_ST_WAIT_RF_RELEASE_ACK] = LCHAN_ST_BORKEN,
+ [LCHAN_ST_WAIT_AFTER_ERROR] = LCHAN_ST_UNUSED,
+ [LCHAN_ST_BORKEN] = LCHAN_ST_BORKEN,
+};
+
+#define lchan_fail(fmt, args...) lchan_fail_to(lchan_fsm_on_error[fi->state], fmt, ## args)
+
+void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info)
+{
+ int rc;
+
+ OSMO_ASSERT(lchan && info);
+
+ if (!lchan_state_is(lchan, LCHAN_ST_UNUSED))
+ goto abort;
+
+ /* ensure some basic sanity up first, before we enter the machine. */
+ OSMO_ASSERT(lchan->ts && lchan->ts->fi && lchan->fi);
+
+ switch (info->activ_for) {
+
+ case FOR_ASSIGNMENT:
+ if (!info->for_conn
+ || !info->for_conn->fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Activation requested, but no conn\n");
+ goto abort;
+ }
+ if (info->for_conn->assignment.new_lchan != lchan) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Activation for Assignment requested, but conn's state does"
+ " not reflect this lchan to be activated (instead: %s)\n",
+ info->for_conn->assignment.new_lchan?
+ gsm_lchan_name(info->for_conn->assignment.new_lchan)
+ : "NULL");
+ goto abort;
+ }
+ break;
+
+ case FOR_HANDOVER:
+ if (!info->for_conn
+ || !info->for_conn->fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Activation requested, but no conn\n");
+ goto abort;
+ }
+ if (!info->for_conn->ho.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Activation for Handover requested, but conn has no HO pending.\n");
+ goto abort;
+ }
+ if (info->for_conn->ho.new_lchan != lchan) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Activation for Handover requested, but conn's HO state does"
+ " not reflect this lchan to be activated (instead: %s)\n",
+ info->for_conn->ho.new_lchan?
+ gsm_lchan_name(info->for_conn->ho.new_lchan)
+ : "NULL");
+ goto abort;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* To make sure that the lchan is actually allowed to initiate an activation, feed through an FSM
+ * event. */
+ rc = osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_ACTIVATE, info);
+
+ if (rc) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Activation requested, but cannot dispatch LCHAN_EV_ACTIVATE event\n");
+ goto abort;
+ }
+ return;
+
+abort:
+ lchan_on_activation_failure(lchan, info->activ_for, info->for_conn);
+ /* Remain in state UNUSED */
+}
+
+static void lchan_fsm_update_id(struct gsm_lchan *lchan)
+{
+ osmo_fsm_inst_update_id_f(lchan->fi, "%u-%u-%u-%s-%u",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ gsm_pchan_id(lchan->ts->pchan_on_init), lchan->nr);
+ if (lchan->fi_rtp)
+ osmo_fsm_inst_update_id_f(lchan->fi_rtp, lchan->fi->id);
+}
+
+extern void lchan_rtp_fsm_init();
+
+void lchan_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&lchan_fsm) == 0);
+ lchan_rtp_fsm_init();
+}
+
+void lchan_fsm_alloc(struct gsm_lchan *lchan)
+{
+ OSMO_ASSERT(lchan->ts);
+ OSMO_ASSERT(lchan->ts->fi);
+ OSMO_ASSERT(!lchan->fi);
+
+ lchan->fi = osmo_fsm_inst_alloc_child(&lchan_fsm, lchan->ts->fi, TS_EV_LCHAN_UNUSED);
+ OSMO_ASSERT(lchan->fi);
+ lchan->fi->priv = lchan;
+ lchan_fsm_update_id(lchan);
+ LOGPFSML(lchan->fi, LOGL_DEBUG, "new lchan\n");
+}
+
+/* Clear volatile state of the lchan. Clear all except
+ * - the ts backpointer,
+ * - the nr,
+ * - name,
+ * - the FSM instance including its current state,
+ * - last_error string.
+ */
+static void lchan_reset(struct gsm_lchan *lchan)
+{
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Clearing lchan state\n");
+
+ if (lchan->conn)
+ gscon_forget_lchan(lchan->conn, lchan);
+
+ if (lchan->rqd_ref) {
+ talloc_free(lchan->rqd_ref);
+ lchan->rqd_ref = NULL;
+ }
+ if (lchan->fi_rtp)
+ osmo_fsm_inst_term(lchan->fi_rtp, OSMO_FSM_TERM_REQUEST, 0);
+ if (lchan->mgw_endpoint_ci_bts) {
+ mgw_endpoint_ci_dlcx(lchan->mgw_endpoint_ci_bts);
+ lchan->mgw_endpoint_ci_bts = NULL;
+ }
+
+ /* NUL all volatile state */
+ *lchan = (struct gsm_lchan){
+ .ts = lchan->ts,
+ .nr = lchan->nr,
+ .fi = lchan->fi,
+ .name = lchan->name,
+
+ .meas_rep_last_seen_nr = 255,
+
+ .last_error = lchan->last_error,
+ };
+}
+
+static void lchan_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ lchan_reset(lchan);
+ osmo_fsm_inst_dispatch(lchan->ts->fi, TS_EV_LCHAN_UNUSED, lchan);
+}
+
+/* Configure the multirate setting on this channel. */
+static int lchan_mr_config(struct gsm_lchan *lchan, const struct gsm48_multi_rate_conf *mr_conf)
+{
+ bool full_rate = (lchan->type == GSM_LCHAN_TCH_F);
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ struct bsc_msc_data *msc = lchan->conn->sccp.msc;
+ struct amr_multirate_conf *mr;
+ int rc;
+ int rc_rate;
+ struct gsm48_multi_rate_conf mr_conf_filtered;
+ const struct gsm48_multi_rate_conf *mr_conf_bts;
+
+ /* There are two different active sets, depending on the channel rate,
+ * make sure the appropate one is selected. */
+ if (full_rate)
+ mr = &bts->mr_full;
+ else
+ mr = &bts->mr_half;
+
+ /* The VTY allows to forbid certain codec rates. Unfortunately we can
+ * not articulate all of the prohibitions on through S0-S15 on the A
+ * interface. To ensure that the VTY settings are observed we create
+ * a manipulated copy of the mr_conf that ensures forbidden codec rates
+ * are not used in the multirate configuration IE. */
+ rc_rate = calc_amr_rate_intersection(&mr_conf_filtered, &msc->amr_conf, mr_conf);
+ if (rc_rate < 0) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "can not encode multirate configuration (invalid amr rate setting, MSC)\n");
+ return -EINVAL;
+ }
+
+ /* The two last codec rates which are defined for AMR do only work with
+ * full rate channels. We will pinch off those rates für half-rate
+ * channels to ensure they are not included accidently. */
+ if (!full_rate) {
+ if (mr_conf_filtered.m10_2 || mr_conf_filtered.m12_2)
+ LOG_LCHAN(lchan, LOGL_ERROR, "ignoring unsupported amr codec rates\n");
+ mr_conf_filtered.m10_2 = 0;
+ mr_conf_filtered.m12_2 = 0;
+ }
+
+ /* Ensure that the resulting filtered conf is coherent with the
+ * configuration that is set for the BTS and the specified rate */
+ mr_conf_bts = (struct gsm48_multi_rate_conf *)mr->gsm48_ie;
+ rc_rate = calc_amr_rate_intersection(&mr_conf_filtered, mr_conf_bts, &mr_conf_filtered);
+ if (rc_rate < 0) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "can not encode multirate configuration (invalid amr rate setting, BTS)\n");
+ return -EINVAL;
+ }
+
+ /* Proceed with the generation of the multirate configuration IE
+ * (MS and BTS) */
+ rc = gsm48_multirate_config(lchan->mr_ms_lv, &mr_conf_filtered, mr->ms_mode, mr->num_modes);
+ if (rc != 0) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "can not encode multirate configuration (MS)\n");
+ return -EINVAL;
+ }
+ rc = gsm48_multirate_config(lchan->mr_bts_lv, &mr_conf_filtered, mr->bts_mode, mr->num_modes);
+ if (rc != 0) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "can not encode multirate configuration (BTS)\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct lchan_activate_info *info = data;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ struct gsm48_multi_rate_conf mr_conf;
+
+ switch (event) {
+
+ case LCHAN_EV_ACTIVATE:
+ OSMO_ASSERT(info);
+ OSMO_ASSERT(!lchan->conn);
+ OSMO_ASSERT(!lchan->mgw_endpoint_ci_bts);
+ lchan_set_last_error(lchan, NULL);
+ lchan->release.requested = false;
+
+ lchan->conn = info->for_conn;
+ lchan->activate.activ_for = info->activ_for;
+ lchan->activate.requires_voice_stream = info->requires_voice_stream;
+ lchan->activate.wait_before_switching_rtp = info->wait_before_switching_rtp;
+ lchan->activate.msc_assigned_cic = info->msc_assigned_cic;
+ lchan->activate.concluded = false;
+ lchan->activate.re_use_mgw_endpoint_from_lchan = info->old_lchan;
+
+ if (info->old_lchan)
+ lchan->encr = info->old_lchan->encr;
+ else {
+ lchan->encr = (struct gsm_encr){
+ .alg_id = RSL_ENC_ALG_A5(0), /* no encryption */
+ };
+ }
+
+ /* If there is a previous lchan, and the new lchan is on the same cell as previous one,
+ * take over power and TA values. Otherwise, use max power and zero TA. */
+ if (info->old_lchan && info->old_lchan->ts->trx->bts == bts) {
+ lchan->ms_power = info->old_lchan->ms_power;
+ lchan->bs_power = info->old_lchan->bs_power;
+ lchan->rqd_ta = info->old_lchan->rqd_ta;
+ } else {
+ lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+ /* From lchan_reset():
+ * - bs_power is still zero, 0dB reduction, output power = Pn.
+ * - TA is still zero, to be determined by RACH. */
+ }
+
+ if (info->chan_mode == GSM48_CMODE_SPEECH_AMR) {
+ gsm48_mr_cfg_from_gsm0808_sc_cfg(&mr_conf, info->s15_s0);
+ if (lchan_mr_config(lchan, &mr_conf) < 0) {
+ lchan_fail("Can not generate multirate configuration IE\n");
+ return;
+ }
+ }
+
+ switch (info->chan_mode) {
+
+ case GSM48_CMODE_SIGN:
+ lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
+ lchan->tch_mode = GSM48_CMODE_SIGN;
+ break;
+
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_AMR:
+ lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+ lchan->tch_mode = info->chan_mode;
+ break;
+
+ default:
+ lchan_fail("Not implemented: cannot activate for chan mode %s",
+ gsm48_chan_mode_name(info->chan_mode));
+ return;
+ }
+
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_TS_READY);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ struct mgwep_ci *use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
+
+ if (lchan->release.requested) {
+ lchan_fail("Release requested while activating");
+ return;
+ }
+
+ LOG_LCHAN(lchan, LOGL_INFO,
+ "Activation requested: %s voice=%s MGW-ci=%s type=%s tch-mode=%s\n",
+ lchan_activate_mode_name(lchan->activate.activ_for),
+ lchan->activate.requires_voice_stream ? "yes" : "no",
+ lchan->activate.requires_voice_stream ?
+ (use_mgwep_ci ? mgwep_ci_name(use_mgwep_ci) : "new")
+ : "none",
+ gsm_lchant_name(lchan->type),
+ gsm48_chan_mode_name(lchan->tch_mode));
+
+ /* Ask for the timeslot to make ready for this lchan->type.
+ * We'll receive LCHAN_EV_TS_READY or LCHAN_EV_TS_ERROR in response. */
+ osmo_fsm_inst_dispatch(lchan->ts->fi, TS_EV_LCHAN_REQUESTED, lchan);
+
+ /* Prepare an MGW endpoint CI if appropriate. */
+ if (lchan->activate.requires_voice_stream)
+ lchan_rtp_fsm_start(lchan);
+}
+
+static void lchan_fsm_wait_ts_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_EV_TS_READY:
+ /* timeslot agrees that we may Chan Activ now. Sending it in onenter. */
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_ACTIV_ACK);
+ break;
+
+ case LCHAN_EV_RTP_RELEASED:
+ case LCHAN_EV_RTP_ERROR:
+ if (lchan->release.in_release_handler) {
+ /* Already in release, the RTP is not the initial cause of failure.
+ * Just ignore. */
+ return;
+ }
+
+ lchan_fail("Failed to setup RTP stream: %s in state %s\n",
+ osmo_fsm_event_name(fi->fsm, event),
+ osmo_fsm_inst_state_name(fi));
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_fsm_wait_activ_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ uint8_t act_type;
+ uint8_t ho_ref = 0;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ if (lchan->release.requested) {
+ lchan_fail_to(LCHAN_ST_UNUSED, "Release requested while activating");
+ return;
+ }
+
+ switch (lchan->activate.activ_for) {
+ case FOR_MS_CHANNEL_REQUEST:
+ act_type = RSL_ACT_INTRA_IMM_ASS;
+ break;
+ case FOR_HANDOVER:
+ act_type = lchan->conn->ho.async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC;
+ ho_ref = lchan->conn->ho.ho_ref;
+ break;
+ default:
+ case FOR_ASSIGNMENT:
+ act_type = RSL_ACT_INTRA_NORM_ASS;
+ break;
+ }
+
+ rc = rsl_tx_chan_activ(lchan, act_type, ho_ref);
+ if (rc)
+ lchan_fail_to(LCHAN_ST_UNUSED, "Tx Chan Activ failed: %s (%d)", strerror(-rc), rc);
+}
+
+static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi);
+
+static void lchan_fsm_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_EV_RSL_CHAN_ACTIV_ACK:
+ lchan->activate.activ_ack = true;
+ lchan_fsm_post_activ_ack(fi);
+ break;
+
+ case LCHAN_EV_RSL_CHAN_ACTIV_NACK:
+ lchan->release.in_release_handler = true;
+ if (data) {
+ uint32_t next_state;
+ lchan->release.rsl_error_cause = *(uint8_t*)data;
+ lchan->release.in_error = true;
+ if (lchan->release.rsl_error_cause != RSL_ERR_RCH_ALR_ACTV_ALLOC)
+ next_state = LCHAN_ST_BORKEN;
+ else
+ /* Taking this over from legacy code: send an RF Chan Release even though
+ * the Activ was NACKed. Is this really correct? */
+ next_state = LCHAN_ST_WAIT_RF_RELEASE_ACK;
+
+ lchan_fail_to(next_state, "Chan Activ NACK: %s (0x%x)",
+ rsl_err_name(lchan->release.rsl_error_cause), lchan->release.rsl_error_cause);
+ } else {
+ lchan->release.rsl_error_cause = RSL_ERR_IE_NONEXIST;
+ lchan->release.in_error = true;
+ lchan_fail_to(LCHAN_ST_BORKEN, "Chan Activ NACK without cause IE");
+ }
+ lchan->release.in_release_handler = false;
+ break;
+
+ case LCHAN_EV_RTP_RELEASED:
+ case LCHAN_EV_RTP_ERROR:
+ if (lchan->release.in_release_handler) {
+ /* Already in release, the RTP is not the initial cause of failure.
+ * Just ignore. */
+ return;
+ }
+
+ lchan_fail_to(LCHAN_ST_WAIT_RF_RELEASE_ACK,
+ "Failed to setup RTP stream: %s in state %s\n",
+ osmo_fsm_event_name(fi->fsm, event),
+ osmo_fsm_inst_state_name(fi));
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi)
+{
+ int rc;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ if (lchan->release.requested) {
+ lchan_fail_to(LCHAN_ST_WAIT_RF_RELEASE_ACK, "Release requested while activating");
+ return;
+ }
+
+ switch (lchan->activate.activ_for) {
+
+ case FOR_MS_CHANNEL_REQUEST:
+ rc = rsl_tx_imm_assignment(lchan);
+ if (rc) {
+ lchan_fail("Failed to Tx RR Immediate Assignment message (rc=%d %s)\n",
+ rc, strerror(-rc));
+ return;
+ }
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Tx RR Immediate Assignment\n");
+ lchan->activate.immediate_assignment_sent = true;
+ break;
+
+ case FOR_ASSIGNMENT:
+ if (!lchan->conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for assignment succeeded, but lchan has no conn:"
+ " cannot trigger appropriate actions. Release.\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ break;
+ }
+ if (!lchan->conn->assignment.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for assignment succeeded, but lchan has no"
+ " assignment ongoing: cannot trigger appropriate actions. Release.\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ break;
+ }
+ /* After the Chan Activ Ack, the MS expects to receive an RR Assignment Command.
+ * Let the assignment_fsm handle that. */
+ osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ACTIVE, lchan);
+ break;
+
+ case FOR_HANDOVER:
+ if (!lchan->conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for handover succeeded, but lchan has no conn:"
+ " cannot trigger appropriate actions. Release.\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ break;
+ }
+ if (!lchan->conn->ho.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for handover succeeded, but lchan has no"
+ " handover ongoing: cannot trigger appropriate actions. Release.\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ break;
+ }
+ /* After the Chan Activ Ack of the new lchan, send the MS an RR Handover Command on the
+ * old channel. The handover_fsm handles that. */
+ osmo_fsm_inst_dispatch(lchan->conn->ho.fi, HO_EV_LCHAN_ACTIVE, lchan);
+ break;
+
+ default:
+ LOG_LCHAN(lchan, LOGL_NOTICE, "lchan %s is now active\n",
+ lchan_activate_mode_name(lchan->activate.activ_for));
+ break;
+ }
+
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH);
+}
+
+static void lchan_fsm_wait_rll_rtp_establish_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ if (lchan->fi_rtp)
+ osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_LCHAN_READY, 0);
+}
+
+static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_EV_RLL_ESTABLISH_IND:
+ if (!lchan->activate.requires_voice_stream
+ || lchan_rtp_established(lchan))
+ lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
+ return;
+
+ case LCHAN_EV_RTP_READY:
+ if (lchan->sapis[0] != LCHAN_SAPI_UNUSED)
+ lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
+ return;
+
+ case LCHAN_EV_RTP_RELEASED:
+ case LCHAN_EV_RTP_ERROR:
+ if (lchan->release.in_release_handler) {
+ /* Already in release, the RTP is not the initial cause of failure.
+ * Just ignore. */
+ return;
+ }
+
+ lchan_fail("Failed to setup RTP stream: %s in state %s\n",
+ osmo_fsm_event_name(fi->fsm, event),
+ osmo_fsm_inst_state_name(fi));
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ if (lchan->release.requested) {
+ lchan_fail("Release requested while activating");
+ return;
+ }
+
+ lchan_on_fully_established(lchan);
+}
+
+#define for_each_sapi(sapi, start, lchan) \
+ for (sapi = start; sapi < ARRAY_SIZE(lchan->sapis); sapi++)
+
+static int next_active_sapi(struct gsm_lchan *lchan, int from_sapi)
+{
+ int sapi;
+ for_each_sapi(sapi, from_sapi, lchan) {
+ if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED)
+ continue;
+ return sapi;
+ }
+ return sapi;
+}
+
+#define for_each_active_sapi(sapi, start, lchan) \
+ for (sapi = next_active_sapi(lchan, start); \
+ sapi < ARRAY_SIZE(lchan->sapis); sapi=next_active_sapi(lchan, sapi+1))
+
+static int lchan_active_sapis(struct gsm_lchan *lchan, int start)
+{
+ int sapis = 0;
+ int sapi;
+ for_each_active_sapi(sapi, start, lchan) {
+ LOG_LCHAN(lchan, LOGL_DEBUG,
+ "Still active: SAPI[%d] (%d)\n", sapi, lchan->sapis[sapi]);
+ sapis ++;
+ }
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Still active SAPIs: %d\n", sapis);
+ return sapis;
+}
+
+static void handle_rll_rel_ind_or_conf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ uint8_t link_id;
+ uint8_t sapi;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ OSMO_ASSERT(data);
+ link_id = *(uint8_t*)data;
+ sapi = link_id & 7;
+
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Rx RLL Release %s: SAPI=%u link_id=0x%x\n",
+ event == LCHAN_EV_RLL_REL_CONF ? "CONF" : "IND", sapi, link_id);
+
+ /* TODO this reflects the code state before the lchan FSM. However, it would make more sense to
+ * me that a Release IND is indeed a cue for us to send a Release Request, and not count it as an
+ * equal to Release CONF. */
+
+ lchan->sapis[sapi] = LCHAN_SAPI_UNUSED;
+ rll_indication(lchan, link_id, BSC_RLLR_IND_REL_IND);
+
+ /* Releasing SAPI 0 means the conn becomes invalid; but not if the link_id contains a TCH flag.
+ * (TODO: is this the correct interpretation?) */
+ if (lchan->conn && sapi == 0 && !(link_id & 0xc0)) {
+ LOG_LCHAN(lchan, LOGL_DEBUG, "lchan is releasing\n");
+ gscon_lchan_releasing(lchan->conn, lchan);
+ }
+
+ /* The caller shall check whether all SAPIs are released and cause a state chg */
+}
+
+static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ switch (event) {
+ case LCHAN_EV_RLL_ESTABLISH_IND:
+ /* abis_rsl.c has noticed that a SAPI was established, no need to take action here. */
+ return;
+
+ case LCHAN_EV_RLL_REL_IND:
+ case LCHAN_EV_RLL_REL_CONF:
+ handle_rll_rel_ind_or_conf(fi, event, data);
+ if (!lchan_active_sapis(lchan, 0))
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_RELEASED);
+ return;
+
+ case LCHAN_EV_RTP_RELEASED:
+ case LCHAN_EV_RTP_ERROR:
+ if (lchan->release.in_release_handler) {
+ /* Already in release, the RTP is not the initial cause of failure.
+ * Just ignore. */
+ return;
+ }
+
+ lchan_fail("RTP stream closed unexpectedly: %s in state %s\n",
+ osmo_fsm_event_name(fi->fsm, event),
+ osmo_fsm_inst_state_name(fi));
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static bool should_sacch_deact(struct gsm_lchan *lchan)
+{
+ switch (lchan->ts->pchan_is) {
+ 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:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void lchan_do_release(struct gsm_lchan *lchan)
+{
+ if (lchan->release.do_rr_release && lchan->sapis[0] != LCHAN_SAPI_UNUSED)
+ gsm48_send_rr_release(lchan);
+
+ if (lchan->fi_rtp)
+ osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_RELEASE, 0);
+
+ if (should_sacch_deact(lchan))
+ rsl_deact_sacch(lchan);
+}
+
+static void lchan_fsm_wait_rll_rtp_released_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int sapis;
+ int sapi;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ for (sapi=0; sapi < ARRAY_SIZE(lchan->sapis); sapi++)
+ if (lchan->sapis[sapi])
+ LOG_LCHAN(lchan, LOGL_DEBUG, "SAPI[%d] = %d\n", sapi, lchan->sapis[sapi]);
+
+ lchan_do_release(lchan);
+
+ sapis = 0;
+ for_each_active_sapi(sapi, 1, lchan) {
+ uint8_t link_id = sapi;
+
+ if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)
+ link_id |= 0x40;
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Tx: Release SAPI %u link_id 0x%x\n", sapi, link_id);
+ rsl_release_request(lchan, link_id, RSL_REL_LOCAL_END);
+ sapis ++;
+ }
+
+ /* 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) {
+
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Nokia InSite BTS: not waiting for RELease CONFirm\n");
+
+ for_each_active_sapi(sapi, 1, lchan)
+ lchan->sapis[sapi] = LCHAN_SAPI_UNUSED;
+ sapis = 0;
+ }
+
+ if (!sapis && !lchan->fi_rtp)
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_BEFORE_RF_RELEASE);
+}
+
+static void lchan_fsm_wait_rll_rtp_released(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_EV_RLL_REL_IND:
+ case LCHAN_EV_RLL_REL_CONF:
+ /* When we're telling the MS to release, we're fine to carry on with RF Channel Release
+ * when SAPI 0 release is not confirmed yet.
+ * TODO: that's how the code was before lchan FSM, is this correct/useful? */
+ handle_rll_rel_ind_or_conf(fi, event, data);
+ break;
+
+ case LCHAN_EV_RTP_RELEASED:
+ case LCHAN_EV_RTP_ERROR:
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+
+ if (!lchan_active_sapis(lchan, 1) && !lchan->fi_rtp)
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_BEFORE_RF_RELEASE);
+}
+
+static void lchan_fsm_wait_rf_release_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ /* For planned releases, a conn has already forgotten about the lchan. And later on, in
+ * lchan_reset(), we make sure it does. But in case of releases from error handling, the
+ * conn might as well notice now already that its lchan is becoming unusable. */
+ if (lchan->conn) {
+ gscon_forget_lchan(lchan->conn, lchan);
+ lchan_forget_conn(lchan);
+ }
+
+ rc = rsl_tx_rf_chan_release(lchan);
+ if (rc)
+ LOG_LCHAN(lchan, LOGL_ERROR, "Failed to Tx RSL RF Channel Release: rc=%d %s\n",
+ rc, strerror(-rc));
+}
+
+static void lchan_fsm_wait_rf_release_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_EV_RSL_RF_CHAN_REL_ACK:
+ if (lchan->release.in_error)
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_AFTER_ERROR);
+ else
+ lchan_fsm_state_chg(LCHAN_ST_UNUSED);
+ break;
+
+ case LCHAN_EV_RTP_RELEASED:
+ /* ignore late lchan_rtp_fsm release events */
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_fsm_borken_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ lchan_reset(lchan);
+}
+
+static void lchan_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_EV_RSL_CHAN_ACTIV_ACK:
+ /* A late Chan Activ ACK? Release. */
+ lchan->release.in_error = true;
+ lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING;
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RF_RELEASE_ACK);
+ return;
+
+ case LCHAN_EV_RSL_CHAN_ACTIV_NACK:
+ /* A late Chan Activ NACK? Ok then, unused. */
+ lchan_fsm_state_chg(LCHAN_ST_UNUSED);
+ return;
+
+ case LCHAN_EV_RSL_RF_CHAN_REL_ACK:
+ /* A late Release ACK? */
+ lchan->release.in_error = true;
+ lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING;
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_AFTER_ERROR);
+ /* TODO: we used to do this only for sysmobts:
+ int do_free = is_sysmobts_v2(ts->trx->bts);
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s CHAN REL ACK for broken channel. %s.\n",
+ gsm_lchan_name(lchan),
+ do_free ? "Releasing it" : "Keeping it broken");
+ if (do_free)
+ do_lchan_free(lchan);
+ * Clarify the reason. If a BTS sends a RF Chan Rel ACK, we can consider it released,
+ * independently from the BTS model, right?? */
+ return;
+
+ case LCHAN_EV_RTP_RELEASED:
+ case LCHAN_EV_RTP_ERROR:
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state lchan_fsm_states[] = {
+ [LCHAN_ST_UNUSED] = {
+ .name = "UNUSED",
+ .onenter = lchan_fsm_unused_onenter,
+ .action = lchan_fsm_unused,
+ .in_event_mask = 0
+ | S(LCHAN_EV_ACTIVATE)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_WAIT_TS_READY)
+ | S(LCHAN_ST_CBCH)
+ ,
+ },
+ [LCHAN_ST_CBCH] = {
+ .name = "CBCH",
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ ,
+ },
+ [LCHAN_ST_WAIT_TS_READY] = {
+ .name = "WAIT_TS_READY",
+ .onenter = lchan_fsm_wait_ts_ready_onenter,
+ .action = lchan_fsm_wait_ts_ready,
+ .in_event_mask = 0
+ | S(LCHAN_EV_TS_READY)
+ | S(LCHAN_EV_RTP_ERROR)
+ | S(LCHAN_EV_RTP_RELEASED)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ | S(LCHAN_ST_WAIT_ACTIV_ACK)
+ ,
+ },
+ [LCHAN_ST_WAIT_ACTIV_ACK] = {
+ .name = "WAIT_ACTIV_ACK",
+ .onenter = lchan_fsm_wait_activ_ack_onenter,
+ .action = lchan_fsm_wait_activ_ack,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RSL_CHAN_ACTIV_ACK)
+ | S(LCHAN_EV_RSL_CHAN_ACTIV_NACK)
+ | S(LCHAN_EV_RTP_ERROR)
+ | S(LCHAN_EV_RTP_RELEASED)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ | S(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH)
+ | S(LCHAN_ST_BORKEN)
+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+ ,
+ },
+ [LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] = {
+ .name = "WAIT_RLL_RTP_ESTABLISH",
+ .onenter = lchan_fsm_wait_rll_rtp_establish_onenter,
+ .action = lchan_fsm_wait_rll_rtp_establish,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RLL_ESTABLISH_IND)
+ | S(LCHAN_EV_RTP_READY)
+ | S(LCHAN_EV_RTP_ERROR)
+ | S(LCHAN_EV_RTP_RELEASED)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ | S(LCHAN_ST_ESTABLISHED)
+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+ | S(LCHAN_ST_WAIT_RLL_RTP_RELEASED)
+ ,
+ },
+ [LCHAN_ST_ESTABLISHED] = {
+ .name = "ESTABLISHED",
+ .onenter = lchan_fsm_established_onenter,
+ .action = lchan_fsm_established,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RLL_REL_IND)
+ | S(LCHAN_EV_RLL_REL_CONF)
+ | S(LCHAN_EV_RLL_ESTABLISH_IND) /* ignored */
+ | S(LCHAN_EV_RTP_ERROR)
+ | S(LCHAN_EV_RTP_RELEASED)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ | S(LCHAN_ST_WAIT_RLL_RTP_RELEASED)
+ | S(LCHAN_ST_WAIT_BEFORE_RF_RELEASE)
+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+ ,
+ },
+ [LCHAN_ST_WAIT_RLL_RTP_RELEASED] = {
+ .name = "WAIT_RLL_RTP_RELEASED",
+ .onenter = lchan_fsm_wait_rll_rtp_released_onenter,
+ .action = lchan_fsm_wait_rll_rtp_released,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RLL_REL_IND)
+ | S(LCHAN_EV_RLL_REL_CONF)
+ | S(LCHAN_EV_RTP_ERROR)
+ | S(LCHAN_EV_RTP_RELEASED)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ | S(LCHAN_ST_WAIT_BEFORE_RF_RELEASE)
+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+ ,
+ },
+ [LCHAN_ST_WAIT_BEFORE_RF_RELEASE] = {
+ .name = "WAIT_BEFORE_RF_RELEASE",
+ .in_event_mask = 0
+ | S(LCHAN_EV_RLL_REL_IND) /* allow late REL_IND of SAPI[0] */
+ | S(LCHAN_EV_RTP_RELEASED) /* ignore late lchan_rtp_fsm release events */
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+ ,
+ },
+ [LCHAN_ST_WAIT_RF_RELEASE_ACK] = {
+ .name = "WAIT_RF_RELEASE_ACK",
+ .onenter = lchan_fsm_wait_rf_release_ack_onenter,
+ .action = lchan_fsm_wait_rf_release_ack,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RSL_RF_CHAN_REL_ACK)
+ | S(LCHAN_EV_RTP_RELEASED) /* ignore late lchan_rtp_fsm release events */
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ | S(LCHAN_ST_WAIT_AFTER_ERROR)
+ | S(LCHAN_ST_BORKEN)
+ ,
+ },
+ [LCHAN_ST_WAIT_AFTER_ERROR] = {
+ .name = "WAIT_AFTER_ERROR",
+ .in_event_mask = 0
+ | S(LCHAN_EV_RTP_RELEASED) /* ignore late lchan_rtp_fsm release events */
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ ,
+ },
+ [LCHAN_ST_BORKEN] = {
+ .name = "BORKEN",
+ .onenter = lchan_fsm_borken_onenter,
+ .action = lchan_fsm_borken,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RSL_CHAN_ACTIV_ACK)
+ | S(LCHAN_EV_RSL_CHAN_ACTIV_NACK)
+ | S(LCHAN_EV_RSL_RF_CHAN_REL_ACK)
+ | S(LCHAN_EV_RTP_ERROR)
+ | S(LCHAN_EV_RTP_RELEASED)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_UNUSED)
+ | S(LCHAN_ST_WAIT_AFTER_ERROR)
+ ,
+ },
+};
+
+static const struct value_string lchan_fsm_event_names[] = {
+ OSMO_VALUE_STRING(LCHAN_EV_ACTIVATE),
+ OSMO_VALUE_STRING(LCHAN_EV_TS_READY),
+ OSMO_VALUE_STRING(LCHAN_EV_TS_ERROR),
+ OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_ACTIV_ACK),
+ OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_ACTIV_NACK),
+ OSMO_VALUE_STRING(LCHAN_EV_RLL_ESTABLISH_IND),
+ OSMO_VALUE_STRING(LCHAN_EV_RTP_READY),
+ OSMO_VALUE_STRING(LCHAN_EV_RTP_ERROR),
+ OSMO_VALUE_STRING(LCHAN_EV_RTP_RELEASED),
+ OSMO_VALUE_STRING(LCHAN_EV_RLL_REL_IND),
+ OSMO_VALUE_STRING(LCHAN_EV_RLL_REL_CONF),
+ OSMO_VALUE_STRING(LCHAN_EV_RSL_RF_CHAN_REL_ACK),
+ OSMO_VALUE_STRING(LCHAN_EV_RLL_ERR_IND),
+ OSMO_VALUE_STRING(LCHAN_EV_CHAN_MODE_MODIF_ACK),
+ OSMO_VALUE_STRING(LCHAN_EV_CHAN_MODE_MODIF_ERROR),
+ {}
+};
+
+void lchan_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCHAN_EV_TS_ERROR:
+ lchan_fail_to(LCHAN_ST_UNUSED, "LCHAN_EV_TS_ERROR");
+ return;
+
+ default:
+ return;
+ }
+}
+
+int lchan_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (fi->state) {
+
+ case LCHAN_ST_WAIT_BEFORE_RF_RELEASE:
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RF_RELEASE_ACK);
+ return 0;
+
+ case LCHAN_ST_WAIT_AFTER_ERROR:
+ lchan_fsm_state_chg(LCHAN_ST_UNUSED);
+ return 0;
+
+ default:
+ lchan->release.in_error = true;
+ lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING;
+ lchan_fail("Timeout");
+ return 0;
+ }
+}
+
+void lchan_release(struct gsm_lchan *lchan, bool do_rr_release,
+ bool err, enum gsm48_rr_cause cause_rr)
+{
+ if (!lchan || !lchan->fi)
+ return;
+
+ if (lchan->release.in_release_handler)
+ return;
+ lchan->release.in_release_handler = true;
+
+ struct osmo_fsm_inst *fi = lchan->fi;
+
+ lchan->release.in_error = err;
+ lchan->release.rsl_error_cause = cause_rr;
+ lchan->release.do_rr_release = do_rr_release;
+
+ /* States waiting for events will notice the desire to release when done waiting, so it is enough
+ * to mark for release. */
+ lchan->release.requested = true;
+
+ /* If we took the RTP over from another lchan, put it back. */
+ if (lchan->fi_rtp && lchan->release.in_error)
+ osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_ROLLBACK, 0);
+
+ /* But when in error, don't wait for the next state to pick up release_requested. */
+ if (lchan->release.in_error) {
+ switch (lchan->fi->state) {
+ default:
+ /* Normally we signal release in lchan_fsm_wait_rll_rtp_released_onenter(). When
+ * skipping that, do it now. */
+ lchan_do_release(lchan);
+ /* fall thru */
+ case LCHAN_ST_WAIT_RLL_RTP_RELEASED:
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RF_RELEASE_ACK);
+ goto exit_release_handler;
+ case LCHAN_ST_WAIT_TS_READY:
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_RELEASED);
+ goto exit_release_handler;
+ case LCHAN_ST_WAIT_RF_RELEASE_ACK:
+ case LCHAN_ST_BORKEN:
+ goto exit_release_handler;
+ }
+ }
+
+ /* The only non-broken state that would stay stuck without noticing the release_requested flag
+ * is: */
+ if (fi->state == LCHAN_ST_ESTABLISHED)
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_RELEASED);
+
+exit_release_handler:
+ lchan->release.in_release_handler = false;
+}
+
+void lchan_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ lchan_reset(lchan);
+ if (lchan->last_error) {
+ talloc_free(lchan->last_error);
+ lchan->last_error = NULL;
+ }
+ lchan->fi = NULL;
+}
+
+/* The conn is deallocating, just forget all about it */
+void lchan_forget_conn(struct gsm_lchan *lchan)
+{
+ struct gsm_subscriber_connection *conn;
+ if (!lchan)
+ return;
+
+ conn = lchan->conn;
+ if (conn) {
+ /* Log for both lchan FSM and conn FSM to ease reading the log in case of problems */
+ if (lchan->fi)
+ LOGPFSML(lchan->fi, LOGL_DEBUG, "lchan detaches from conn %s\n",
+ conn->fi? osmo_fsm_inst_name(conn->fi) : "(conn without FSM)");
+ if (conn->fi)
+ LOGPFSML(conn->fi, LOGL_DEBUG, "lchan %s detaches from conn\n",
+ lchan->fi? osmo_fsm_inst_name(lchan->fi) : gsm_lchan_name(lchan));
+ }
+
+ lchan_forget_mgw_endpoint(lchan);
+ lchan->conn = NULL;
+}
+
+static struct osmo_fsm lchan_fsm = {
+ .name = "lchan",
+ .states = lchan_fsm_states,
+ .num_states = ARRAY_SIZE(lchan_fsm_states),
+ .log_subsys = DCHAN,
+ .event_names = lchan_fsm_event_names,
+ .allstate_action = lchan_fsm_allstate_action,
+ .allstate_event_mask = 0
+ | S(LCHAN_EV_TS_ERROR)
+ ,
+ .timer_cb = lchan_fsm_timer_cb,
+ .cleanup = lchan_fsm_cleanup,
+};
diff --git a/src/osmo-bsc/lchan_rtp_fsm.c b/src/osmo-bsc/lchan_rtp_fsm.c
new file mode 100644
index 000000000..aaba563ca
--- /dev/null
+++ b/src/osmo-bsc/lchan_rtp_fsm.c
@@ -0,0 +1,761 @@
+/* osmo-bsc API to switch the RTP stream for an lchan.
+ *
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+
+static struct osmo_fsm lchan_rtp_fsm;
+
+struct gsm_lchan *lchan_rtp_fi_lchan(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &lchan_rtp_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+struct state_timeout lchan_rtp_fsm_timeouts[32] = {
+ [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = { .T=23004 },
+ [LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK] = { .T=23005 },
+ [LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = { .T=23006 },
+ [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = { .T=23004 },
+};
+
+/* Transition to a state, using the T timer defined in lchan_rtp_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define lchan_rtp_fsm_state_chg(state) \
+ fsm_inst_state_chg_T(fi, state, \
+ lchan_rtp_fsm_timeouts, \
+ ((struct gsm_lchan*)(fi->priv))->ts->trx->bts->network->T_defs, \
+ 5)
+
+/* Set a failure message, trigger the common actions to take on failure, transition to a state to
+ * continue with (using state timeouts from lchan_rtp_fsm_timeouts[]). Assumes local variable fi exists. */
+#define lchan_rtp_fail(fmt, args...) do { \
+ struct gsm_lchan *_lchan = fi->priv; \
+ uint32_t state_was = fi->state; \
+ lchan_set_last_error(_lchan, "lchan-rtp failure in state %s: " fmt, \
+ osmo_fsm_state_name(fi->fsm, state_was), ## args); \
+ osmo_fsm_inst_dispatch(_lchan->fi, LCHAN_EV_RTP_ERROR, 0); \
+ } while(0)
+
+/* Called from lchan_fsm_init(), does not need to be visible in lchan_rtp_fsm.h */
+void lchan_rtp_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&lchan_rtp_fsm) == 0);
+}
+
+static void lchan_rtp_fsm_update_id(struct gsm_lchan *lchan)
+{
+ OSMO_ASSERT(lchan->fi);
+ OSMO_ASSERT(lchan->fi_rtp);
+ osmo_fsm_inst_update_id_f(lchan->fi_rtp, lchan->fi->id);
+}
+
+bool lchan_rtp_established(struct gsm_lchan *lchan)
+{
+ if (!lchan->fi_rtp)
+ return false;
+ switch (lchan->fi_rtp->state) {
+ case LCHAN_RTP_ST_READY:
+ case LCHAN_RTP_ST_ESTABLISHED:
+ case LCHAN_RTP_ST_ROLLBACK:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void lchan_rtp_fsm_start(struct gsm_lchan *lchan)
+{
+ struct osmo_fsm_inst *fi;
+
+ OSMO_ASSERT(lchan->ts);
+ OSMO_ASSERT(lchan->ts->fi);
+ OSMO_ASSERT(lchan->fi);
+ OSMO_ASSERT(!lchan->fi_rtp);
+
+ fi = osmo_fsm_inst_alloc_child(&lchan_rtp_fsm, lchan->fi, LCHAN_EV_RTP_RELEASED);
+ OSMO_ASSERT(fi);
+ fi->priv = lchan;
+ lchan->fi_rtp = fi;
+ lchan_rtp_fsm_update_id(lchan);
+
+ /* Use old lchan only if there is an MGW endpoint present. Otherwise, on ROLLBACK, we might put
+ * an endpoint "back" to an lchan that never had one to begin with. */
+ if (lchan->activate.re_use_mgw_endpoint_from_lchan
+ && !lchan->activate.re_use_mgw_endpoint_from_lchan->mgw_endpoint_ci_bts)
+ lchan->activate.re_use_mgw_endpoint_from_lchan = NULL;
+
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE);
+}
+
+/* While activating an lchan, for example for Handover, we may want to re-use another lchan's MGW
+ * endpoint CI. If Handover fails half way, the old lchan must keep its MGW endpoint CI, and we must not
+ * clean it up. Hence keep another lchan's mgw_endpoint_ci_bts out of lchan until all is done. */
+struct mgwep_ci *lchan_use_mgw_endpoint_ci_bts(struct gsm_lchan *lchan)
+{
+ if (lchan->mgw_endpoint_ci_bts)
+ return lchan->mgw_endpoint_ci_bts;
+ if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED))
+ return NULL;
+ if (lchan->activate.re_use_mgw_endpoint_from_lchan)
+ return lchan->activate.re_use_mgw_endpoint_from_lchan->mgw_endpoint_ci_bts;
+ return NULL;
+}
+
+static void lchan_rtp_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_inst *fi,
+ uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ struct mgw_endpoint *mgwep;
+ struct mgwep_ci *use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
+ struct mgcp_conn_peer crcx_info = {};
+
+ if (use_mgwep_ci) {
+ LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "MGW endpoint already available: %s\n",
+ mgwep_ci_name(use_mgwep_ci));
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY);
+ return;
+ }
+
+ mgwep = gscon_ensure_mgw_endpoint(lchan->conn, lchan->activate.msc_assigned_cic);
+ if (!mgwep) {
+ lchan_rtp_fail("Internal error: cannot obtain MGW endpoint handle for conn");
+ return;
+ }
+
+ lchan->mgw_endpoint_ci_bts = mgw_endpoint_ci_add(mgwep, "to-BTS");
+
+ if (lchan->conn) {
+ crcx_info.call_id = lchan->conn->sccp.conn_id;
+ if (lchan->conn->sccp.msc)
+ crcx_info.x_osmo_ign = lchan->conn->sccp.msc->x_osmo_ign;
+ }
+ crcx_info.ptime = 20;
+ mgcp_pick_codec(&crcx_info, lchan, true);
+
+ mgw_endpoint_ci_request(lchan->mgw_endpoint_ci_bts, MGCP_VERB_CRCX, &crcx_info,
+ fi, LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE, LCHAN_RTP_EV_MGW_ENDPOINT_ERROR,
+ 0);
+}
+
+static void lchan_rtp_fsm_wait_mgw_endpoint_available(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE:
+ LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "MGW endpoint: %s\n",
+ mgwep_ci_name(lchan_use_mgw_endpoint_ci_bts(lchan)));
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY);
+ return;
+
+ case LCHAN_RTP_EV_LCHAN_READY:
+ /* will notice lchan->activate.activ_ack == true in
+ * lchan_rtp_fsm_wait_lchan_ready_onenter() */
+ return;
+
+ case LCHAN_RTP_EV_MGW_ENDPOINT_ERROR:
+ lchan_rtp_fail("Failure to create MGW endpoint");
+ return;
+
+ case LCHAN_RTP_EV_ROLLBACK:
+ case LCHAN_RTP_EV_RELEASE:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_rtp_fsm_post_lchan_ready(struct osmo_fsm_inst *fi);
+
+static void lchan_rtp_fsm_wait_lchan_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+ if (lchan->activate.activ_ack) {
+ LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Activ Ack received earlier, no need to wait\n");
+ lchan_rtp_fsm_post_lchan_ready(fi);
+ }
+}
+
+static void lchan_rtp_fsm_wait_lchan_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCHAN_RTP_EV_LCHAN_READY:
+ lchan_rtp_fsm_post_lchan_ready(fi);
+ return;
+
+ case LCHAN_RTP_EV_ROLLBACK:
+ case LCHAN_RTP_EV_RELEASE:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_rtp_fsm_switch_rtp(struct osmo_fsm_inst *fi)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+ if (lchan->activate.wait_before_switching_rtp) {
+ LOG_LCHAN_RTP(lchan, LOGL_DEBUG,
+ "Waiting for an event by caller before switching RTP\n");
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP);
+ } else
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED);
+}
+
+static void lchan_rtp_fsm_post_lchan_ready(struct osmo_fsm_inst *fi)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+ if (is_ipaccess_bts(lchan->ts->trx->bts))
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK);
+ else
+ lchan_rtp_fsm_switch_rtp(fi);
+}
+
+static void lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ int val;
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+ if (lchan->release.requested) {
+ lchan_rtp_fail("Release requested while activating");
+ return;
+ }
+
+ val = ipacc_speech_mode(lchan->tch_mode, lchan->type);
+ if (val < 0) {
+ lchan_rtp_fail("Cannot determine Abis/IP speech mode for tch_mode=%s type=%s\n",
+ get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
+ gsm_lchant_name(lchan->type));
+ return;
+ }
+ lchan->abis_ip.speech_mode = val;
+
+ val = ipacc_payload_type(lchan->tch_mode, lchan->type);
+ if (val < 0) {
+ lchan_rtp_fail("Cannot determine Abis/IP payload type for tch_mode=%s type=%s\n",
+ get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
+ gsm_lchant_name(lchan->type));
+ return;
+ }
+ lchan->abis_ip.rtp_payload = val;
+
+ /* recv-only */
+ ipacc_speech_mode_set_direction(&lchan->abis_ip.speech_mode, false);
+
+ rc = rsl_tx_ipacc_crcx(lchan);
+ if (rc)
+ lchan_rtp_fail("Failure to transmit IPACC CRCX to BTS (rc=%d, %s)",
+ rc, strerror(-rc));
+}
+
+static void lchan_rtp_fsm_wait_ipacc_crcx_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_RTP_EV_IPACC_CRCX_ACK:
+ /* the CRCX ACK parsing has already noted the RTP port information at
+ * lchan->abis_ip.bound_*, see ipac_parse_rtp(). We'll use that in
+ * lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(). */
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK);
+ return;
+
+ case LCHAN_RTP_EV_IPACC_CRCX_NACK:
+ lchan_rtp_fail("Received NACK on IPACC CRCX");
+ return;
+
+ case LCHAN_RTP_EV_READY_TO_SWITCH_RTP:
+ lchan->activate.wait_before_switching_rtp = false;
+ return;
+
+ case LCHAN_RTP_EV_RELEASE:
+ case LCHAN_RTP_EV_ROLLBACK:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ const struct mgcp_conn_peer *mgw_rtp;
+
+ if (lchan->release.requested) {
+ lchan_rtp_fail("Release requested while activating");
+ return;
+ }
+
+ mgw_rtp = mgwep_ci_get_rtp_info(lchan_use_mgw_endpoint_ci_bts(lchan));
+
+ if (!mgw_rtp) {
+ lchan_rtp_fail("Cannot send IPACC MDCX to BTS:"
+ " there is no RTP IP+port set that the BTS should send RTP to.");
+ return;
+ }
+
+ /* Other RTP settings were already setup in lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter() */
+ lchan->abis_ip.connect_ip = ntohl(inet_addr(mgw_rtp->addr));
+ lchan->abis_ip.connect_port = mgw_rtp->port;
+
+ /* send-recv */
+ ipacc_speech_mode_set_direction(&lchan->abis_ip.speech_mode, true);
+
+ rc = rsl_tx_ipacc_mdcx(lchan);
+ if (rc)
+ lchan_rtp_fail("Failure to transmit IPACC MDCX to BTS (rc=%d, %s)",
+ rc, strerror(-rc));
+
+}
+
+static void lchan_rtp_fsm_wait_ipacc_mdcx_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_RTP_EV_IPACC_MDCX_ACK:
+ lchan_rtp_fsm_switch_rtp(fi);
+ return;
+
+ case LCHAN_RTP_EV_IPACC_MDCX_NACK:
+ lchan_rtp_fail("Received NACK on IPACC MDCX");
+ return;
+
+ case LCHAN_RTP_EV_READY_TO_SWITCH_RTP:
+ lchan->activate.wait_before_switching_rtp = false;
+ return;
+
+ case LCHAN_RTP_EV_RELEASE:
+ case LCHAN_RTP_EV_ROLLBACK:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_rtp_fsm_wait_ready_to_switch_rtp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCHAN_RTP_EV_READY_TO_SWITCH_RTP:
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED);
+ return;
+
+ case LCHAN_RTP_EV_RELEASE:
+ case LCHAN_RTP_EV_ROLLBACK:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void connect_mgw_endpoint_to_lchan(struct osmo_fsm_inst *fi,
+ struct mgwep_ci *ci,
+ struct gsm_lchan *to_lchan)
+{
+ int rc;
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ struct mgcp_conn_peer mdcx_info;
+ struct in_addr addr;
+ const char *addr_str;
+
+ mdcx_info = (struct mgcp_conn_peer){
+ .port = to_lchan->abis_ip.bound_port,
+ .ptime = 20,
+ };
+ mgcp_pick_codec(&mdcx_info, to_lchan, true);
+
+ addr.s_addr = ntohl(to_lchan->abis_ip.bound_ip);
+ addr_str = inet_ntoa(addr);
+ rc = osmo_strlcpy(mdcx_info.addr, addr_str, sizeof(mdcx_info.addr));
+ if (rc <= 0 || rc >= sizeof(mdcx_info.addr)) {
+ lchan_rtp_fail("Cannot compose BTS side RTP IP address to send to MGW: '%s'",
+ addr_str);
+ return;
+ }
+
+ if (!ci) {
+ lchan_rtp_fail("No MGW endpoint ci configured");
+ return;
+ }
+
+ LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Sending BTS side RTP port info %s:%u to MGW %s\n",
+ mdcx_info.addr, mdcx_info.port, mgwep_ci_name(ci));
+ mgw_endpoint_ci_request(ci, MGCP_VERB_MDCX, &mdcx_info,
+ fi, LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED,
+ LCHAN_RTP_EV_MGW_ENDPOINT_ERROR, 0);
+}
+
+static void lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(struct osmo_fsm_inst *fi,
+ uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ struct gsm_lchan *old_lchan = lchan->activate.re_use_mgw_endpoint_from_lchan;
+
+ if (lchan->release.requested) {
+ lchan_rtp_fail("Release requested while activating");
+ return;
+ }
+
+ /* At this point, we are taking over an old lchan's MGW endpoint (if any). */
+ if (!lchan->mgw_endpoint_ci_bts && old_lchan) {
+ /* The old lchan shall forget the enpoint now. We might put it back upon ROLLBACK */
+ lchan->mgw_endpoint_ci_bts = old_lchan->mgw_endpoint_ci_bts;
+ old_lchan->mgw_endpoint_ci_bts = NULL;
+ }
+
+ if (!lchan->mgw_endpoint_ci_bts) {
+ lchan_rtp_fail("No MGW endpoint ci configured");
+ return;
+ }
+
+ connect_mgw_endpoint_to_lchan(fi, lchan->mgw_endpoint_ci_bts, lchan);
+}
+
+static void lchan_rtp_fsm_wait_mgw_endpoint_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED:
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY);
+ return;
+
+ case LCHAN_RTP_EV_MGW_ENDPOINT_ERROR:
+ lchan_rtp_fail("Error while redirecting the MGW to the lchan's RTP port");
+ return;
+
+ case LCHAN_RTP_EV_ROLLBACK:
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_ROLLBACK);
+ return;
+
+ case LCHAN_RTP_EV_RELEASE:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_rtp_fsm_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RTP_READY, 0);
+}
+
+static void lchan_rtp_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCHAN_RTP_EV_ESTABLISHED:
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_ESTABLISHED);
+ return;
+
+ case LCHAN_RTP_EV_RELEASE:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+ return;
+
+ case LCHAN_RTP_EV_ROLLBACK:
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_ROLLBACK);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_rtp_fsm_rollback_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ struct gsm_lchan *old_lchan = lchan->activate.re_use_mgw_endpoint_from_lchan;
+
+ if (!lchan->mgw_endpoint_ci_bts || !old_lchan) {
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+ return;
+ }
+ connect_mgw_endpoint_to_lchan(fi, lchan->mgw_endpoint_ci_bts, old_lchan);
+}
+
+static void lchan_rtp_fsm_rollback(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ struct gsm_lchan *old_lchan = lchan->activate.re_use_mgw_endpoint_from_lchan;
+
+ switch (event) {
+
+ case LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED:
+ old_lchan->mgw_endpoint_ci_bts = lchan->mgw_endpoint_ci_bts;
+ lchan->mgw_endpoint_ci_bts = NULL;
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
+ return;
+
+ case LCHAN_RTP_EV_MGW_ENDPOINT_ERROR:
+ LOG_LCHAN_RTP(lchan, LOGL_ERROR,
+ "Error while connecting the MGW back to the old lchan's RTP port:"
+ " %s %s\n",
+ mgwep_ci_name(lchan->mgw_endpoint_ci_bts),
+ gsm_lchan_name(old_lchan));
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, 0);
+ return;
+
+ case LCHAN_RTP_EV_RELEASE:
+ case LCHAN_RTP_EV_ROLLBACK:
+ /* Already rolling back, ignore. */
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_rtp_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+ /* Make sure that we will not hand back the MGW endpoint to any old lchan from here on. */
+ lchan->activate.re_use_mgw_endpoint_from_lchan = NULL;
+}
+
+static void lchan_rtp_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCHAN_RTP_EV_RELEASE:
+ case LCHAN_RTP_EV_ROLLBACK:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state lchan_rtp_fsm_states[] = {
+ [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = {
+ .name = "WAIT_MGW_ENDPOINT_AVAILABLE",
+ .onenter = lchan_rtp_fsm_wait_mgw_endpoint_available_onenter,
+ .action = lchan_rtp_fsm_wait_mgw_endpoint_available,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE)
+ | S(LCHAN_RTP_EV_MGW_ENDPOINT_ERROR)
+ | S(LCHAN_RTP_EV_LCHAN_READY)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE) /* for init */
+ | S(LCHAN_RTP_ST_WAIT_LCHAN_READY)
+ ,
+ },
+ [LCHAN_RTP_ST_WAIT_LCHAN_READY] = {
+ .name = "WAIT_LCHAN_READY",
+ .onenter = lchan_rtp_fsm_wait_lchan_ready_onenter,
+ .action = lchan_rtp_fsm_wait_lchan_ready,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_LCHAN_READY)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK)
+ | S(LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP)
+ | S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
+ ,
+ },
+ [LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK] = {
+ .name = "WAIT_IPACC_CRCX_ACK",
+ .onenter = lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter,
+ .action = lchan_rtp_fsm_wait_ipacc_crcx_ack,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP)
+ | S(LCHAN_RTP_EV_IPACC_CRCX_ACK)
+ | S(LCHAN_RTP_EV_IPACC_CRCX_NACK)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK)
+ ,
+ },
+ [LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = {
+ .name = "WAIT_IPACC_MDCX_ACK",
+ .onenter = lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter,
+ .action = lchan_rtp_fsm_wait_ipacc_mdcx_ack,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP)
+ | S(LCHAN_RTP_EV_IPACC_MDCX_ACK)
+ | S(LCHAN_RTP_EV_IPACC_MDCX_NACK)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP)
+ | S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
+ ,
+ },
+ [LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP] = {
+ .name = "WAIT_READY_TO_SWITCH_RTP",
+ .action = lchan_rtp_fsm_wait_ready_to_switch_rtp,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
+ ,
+ },
+ [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = {
+ .name = "WAIT_MGW_ENDPOINT_CONFIGURED",
+ .onenter = lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter,
+ .action = lchan_rtp_fsm_wait_mgw_endpoint_configured,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED)
+ | S(LCHAN_RTP_EV_MGW_ENDPOINT_ERROR)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_RTP_ST_READY)
+ ,
+ },
+ [LCHAN_RTP_ST_READY] = {
+ .name = "READY",
+ .onenter = lchan_rtp_fsm_ready_onenter,
+ .action = lchan_rtp_fsm_ready,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_ESTABLISHED)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_RTP_ST_ESTABLISHED)
+ | S(LCHAN_RTP_ST_ROLLBACK)
+ ,
+ },
+ [LCHAN_RTP_ST_ESTABLISHED] = {
+ .name = "ESTABLISHED",
+ .onenter = lchan_rtp_fsm_established_onenter,
+ .action = lchan_rtp_fsm_established,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ },
+ [LCHAN_RTP_ST_ROLLBACK] = {
+ .name = "ROLLBACK",
+ .onenter = lchan_rtp_fsm_rollback_onenter,
+ .action = lchan_rtp_fsm_rollback,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED)
+ | S(LCHAN_RTP_EV_MGW_ENDPOINT_ERROR)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ },
+};
+
+static const struct value_string lchan_rtp_fsm_event_names[] = {
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_LCHAN_READY),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_READY_TO_SWITCH_RTP),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_MGW_ENDPOINT_ERROR),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_IPACC_CRCX_ACK),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_IPACC_CRCX_NACK),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_IPACC_MDCX_ACK),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_IPACC_MDCX_NACK),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_READY_TO_SWITCH),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_ROLLBACK),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_ESTABLISHED),
+ OSMO_VALUE_STRING(LCHAN_RTP_EV_RELEASE),
+ {}
+};
+
+int lchan_rtp_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ lchan->release.in_error = true;
+ lchan->release.rsl_error_cause = RSL_ERR_EQUIPMENT_FAIL;
+ lchan_rtp_fail("Timeout");
+ return 0;
+}
+
+void lchan_rtp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ if (lchan->mgw_endpoint_ci_bts) {
+ mgw_endpoint_ci_dlcx(lchan->mgw_endpoint_ci_bts);
+ lchan->mgw_endpoint_ci_bts = NULL;
+ }
+ lchan->fi_rtp = NULL;
+ if (lchan->fi)
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RTP_RELEASED, 0);
+}
+
+/* The mgw_endpoint was invalidated, just and simply forget the pointer without cleanup. */
+void lchan_forget_mgw_endpoint(struct gsm_lchan *lchan)
+{
+ if (!lchan)
+ return;
+ lchan->mgw_endpoint_ci_bts = NULL;
+}
+
+static struct osmo_fsm lchan_rtp_fsm = {
+ .name = "lchan_rtp",
+ .states = lchan_rtp_fsm_states,
+ .num_states = ARRAY_SIZE(lchan_rtp_fsm_states),
+ .log_subsys = DCHAN,
+ .event_names = lchan_rtp_fsm_event_names,
+ .timer_cb = lchan_rtp_fsm_timer_cb,
+ .cleanup = lchan_rtp_fsm_cleanup,
+};
diff --git a/src/osmo-bsc/lchan_select.c b/src/osmo-bsc/lchan_select.c
new file mode 100644
index 000000000..742c7de82
--- /dev/null
+++ b/src/osmo-bsc/lchan_select.c
@@ -0,0 +1,260 @@
+/* Select a suitable lchan from a given cell.
+ *
+ * (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/debug.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+
+#include <osmocom/bsc/lchan_select.h>
+
+static struct gsm_lchan *
+_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan,
+ enum gsm_phys_chan_config as_pchan)
+{
+ struct gsm_lchan *lchan;
+ struct gsm_bts_trx_ts *ts;
+ int j, start, stop, dir;
+
+#define LOGPLCHANALLOC(fmt, args...) \
+ LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s%s%s: " fmt, \
+ gsm_pchan_name(pchan), \
+ pchan == as_pchan ? "" : " as ", \
+ pchan == as_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_on_init != pchan) {
+ LOGPLCHANALLOC("%s is != %s\n", gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(pchan));
+ continue;
+ }
+ /* Next, is this timeslot in or can it be switched to the pchan we want to use it for? */
+ if (!ts_usable_as_pchan(ts, as_pchan)) {
+ LOGPLCHANALLOC("%s is not usable as %s\n", gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(as_pchan));
+ continue;
+ }
+
+ /* TS is (going to be) in desired pchan mode. Go ahead and check for an available lchan. */
+ ts_as_pchan_for_each_lchan(lchan, ts, as_pchan) {
+ if (lchan->fi->state == LCHAN_ST_UNUSED) {
+ LOGPLCHANALLOC("%s ss=%d is available%s\n",
+ gsm_ts_and_pchan_name(ts), lchan->nr,
+ ts->pchan_is != as_pchan ? " after dyn PCHAN change" : "");
+ return lchan;
+ }
+ LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n",
+ gsm_ts_and_pchan_name(ts), lchan->nr,
+ gsm_lchant_name(lchan->type),
+ osmo_fsm_inst_state_name(lchan->fi));
+ }
+ }
+
+ 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);
+}
+
+struct gsm_lchan *lchan_select_by_chan_mode(struct gsm_bts *bts,
+ enum gsm48_chan_mode chan_mode, bool full_rate)
+{
+ enum gsm_chan_t type;
+
+ switch (chan_mode) {
+ case GSM48_CMODE_SIGN:
+ type = GSM_LCHAN_SDCCH;
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_AMR:
+ type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H;
+ break;
+ default:
+ return NULL;
+ }
+
+ return lchan_select_by_type(bts, type);
+}
+
+/* Return a matching lchan from a specific BTS that is currently available. The next logical step is
+ * lchan_activate() on it, which would possibly cause dynamic timeslot pchan switching, taken care of by
+ * the lchan and timeslot FSMs. */
+struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts, enum gsm_chan_t type)
+{
+ struct gsm_lchan *lchan = NULL;
+ enum gsm_phys_chan_config first, first_cbch, second, second_cbch;
+
+ LOGP(DRLL, LOGL_DEBUG, "(bts=%d) lchan_select_by_type(%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);
+ 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;
+ LOG_LCHAN(lchan, LOGL_INFO, "Selected\n");
+ } else
+ LOGP(DRLL, LOGL_ERROR, "(bts=%d) Failed to select %s channel\n",
+ bts->nr, gsm_lchant_name(type));
+
+ return lchan;
+}
+
diff --git a/src/osmo-bsc/meas_feed.c b/src/osmo-bsc/meas_feed.c
new file mode 100644
index 000000000..8450f69ca
--- /dev/null
+++ b/src/osmo-bsc/meas_feed.c
@@ -0,0 +1,185 @@
+/* UDP-Feed of measurement reports */
+
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+
+#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/meas_feed.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/debug.h>
+
+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_is;
+ 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/meas_rep.h>
+
+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/mgw_endpoint_fsm.c b/src/osmo-bsc/mgw_endpoint_fsm.c
new file mode 100644
index 000000000..fc49886c3
--- /dev/null
+++ b/src/osmo-bsc/mgw_endpoint_fsm.c
@@ -0,0 +1,777 @@
+/* osmo-bsc API to manage all sides of an MGW endpoint
+ *
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/netif/rtp.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_timers.h>
+
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/gsm_data.h>
+
+#define LOG_CI(ci, level, fmt, args...) do { \
+ if (!ci || !ci->mgwep) \
+ LOGP(DLGLOBAL, level, "(unknown MGW endpoint) " fmt, ## args); \
+ else \
+ LOG_MGWEP(ci->mgwep, level, "CI[%d] %s%s%s: " fmt, \
+ (int)(ci - ci->mgwep->ci), \
+ ci->label ? : "-", \
+ ci->mgcp_ci_str[0] ? " CI=" : "", \
+ ci->mgcp_ci_str[0] ? ci->mgcp_ci_str : "", \
+ ## args); \
+ } while(0)
+
+#define LOG_CI_VERB(ci, level, fmt, args...) do { \
+ if (ci->verb_info.addr) \
+ LOG_CI(ci, level, "%s %s:%u: " fmt, \
+ mgcp_verb_name(ci->verb), ci->verb_info.addr, ci->verb_info.port, \
+ ## args); \
+ else \
+ LOG_CI(ci, level, "%s: " fmt, \
+ mgcp_verb_name(ci->verb), \
+ ## args); \
+ } while(0)
+
+#define FIRST_CI_EVENT (_MGWEP_EV_LAST + (_MGWEP_EV_LAST & 1)) /* rounded up to even nr */
+#define USABLE_CI ((32 - FIRST_CI_EVENT)/2)
+#define EV_TO_CI_IDX(event) ((event - FIRST_CI_EVENT) / 2)
+
+#define CI_EV_SUCCESS(ci) (FIRST_CI_EVENT + (((ci) - ci->mgwep->ci) * 2))
+#define CI_EV_FAILURE(ci) (CI_EV_SUCCESS(ci) + 1)
+
+static struct osmo_fsm mgwep_fsm;
+
+struct mgwep_ci {
+ struct mgw_endpoint *mgwep;
+
+ bool occupied;
+ char label[64];
+ struct osmo_fsm_inst *mgcp_client_fi;
+
+ bool pending;
+ bool sent;
+ enum mgcp_verb verb;
+ struct mgcp_conn_peer verb_info;
+ struct osmo_fsm_inst *notify;
+ uint32_t notify_success;
+ uint32_t notify_failure;
+ void *notify_data;
+
+ bool got_port_info;
+ struct mgcp_conn_peer rtp_info;
+ char mgcp_ci_str[MGCP_CONN_ID_LENGTH];
+};
+
+struct mgw_endpoint {
+ struct mgcp_client *mgcp_client;
+ struct osmo_fsm_inst *fi;
+ char endpoint[MGCP_ENDPOINT_MAXLEN];
+
+ struct mgwep_ci ci[USABLE_CI];
+};
+
+static const struct value_string mgcp_verb_names[] = {
+ { MGCP_VERB_CRCX, "CRCX" },
+ { MGCP_VERB_MDCX, "MDCX" },
+ { MGCP_VERB_DLCX, "DLCX" },
+ { MGCP_VERB_AUEP, "AUEP" },
+ { MGCP_VERB_RSIP, "RSIP" },
+ {}
+};
+
+static inline const char *mgcp_verb_name(enum mgcp_verb val)
+{ return get_value_string(mgcp_verb_names, val); }
+
+static struct mgwep_ci *mgwep_check_ci(struct mgwep_ci *ci)
+{
+ if (!ci)
+ return NULL;
+ if (!ci->mgwep)
+ return NULL;
+ if (ci < ci->mgwep->ci || ci >= &ci->mgwep->ci[USABLE_CI])
+ return NULL;
+ return ci;
+}
+
+static struct mgwep_ci *mgwep_ci_for_event(struct mgw_endpoint *mgwep, uint32_t event)
+{
+ int idx;
+ if (event < FIRST_CI_EVENT)
+ return NULL;
+ idx = EV_TO_CI_IDX(event);
+ if (idx >= sizeof(mgwep->ci))
+ return NULL;
+ return mgwep_check_ci(&mgwep->ci[idx]);
+}
+
+const char *mgw_endpoint_name(const struct mgw_endpoint *mgwep)
+{
+ if (!mgwep)
+ return "NULL";
+ if (mgwep->endpoint[0])
+ return mgwep->endpoint;
+ return osmo_fsm_inst_name(mgwep->fi);
+}
+
+const char *mgcp_conn_peer_name(const struct mgcp_conn_peer *info)
+{
+ /* I'd be fine with a smaller buffer and accept truncation, but gcc possibly refuses to build if
+ * this buffer is too small. */
+ static char buf[1024];
+
+ if (!info)
+ return "NULL";
+
+ if (info->endpoint[0]
+ && info->addr[0])
+ snprintf(buf, sizeof(buf), "%s:%s:%u",
+ info->endpoint, info->addr, info->port);
+ else if (info->endpoint[0])
+ snprintf(buf, sizeof(buf), "%s", info->endpoint);
+ else if (info->addr[0])
+ snprintf(buf, sizeof(buf), "%s:%u", info->addr, info->port);
+ else
+ return "empty";
+ return buf;
+}
+
+const char *mgwep_ci_name(const struct mgwep_ci *ci)
+{
+ const struct mgcp_conn_peer *rtp_info;
+
+ if (!ci)
+ return "NULL";
+
+ rtp_info = mgwep_ci_get_rtp_info(ci);
+
+ if (rtp_info)
+ return mgcp_conn_peer_name(rtp_info);
+ return mgw_endpoint_name(ci->mgwep);
+}
+
+static struct value_string mgwep_fsm_event_names[33] = {};
+
+static char mgwep_fsm_event_name_bufs[32][32] = {};
+
+static void fill_event_names()
+{
+ int i;
+ for (i = 0; i < (ARRAY_SIZE(mgwep_fsm_event_names) - 1); i++) {
+ if (i < _MGWEP_EV_LAST)
+ continue;
+ if (i < FIRST_CI_EVENT || EV_TO_CI_IDX(i) > USABLE_CI) {
+ mgwep_fsm_event_names[i] = (struct value_string){i, "Unused"};
+ continue;
+ }
+ snprintf(mgwep_fsm_event_name_bufs[i], sizeof(mgwep_fsm_event_name_bufs[i]),
+ "MGW Response for CI #%d", EV_TO_CI_IDX(i));
+ mgwep_fsm_event_names[i] = (struct value_string){i, mgwep_fsm_event_name_bufs[i]};
+ }
+}
+
+static struct T_def *g_T_defs = NULL;
+
+void mgw_endpoint_fsm_init(struct T_def *T_defs)
+{
+ g_T_defs = T_defs;
+ OSMO_ASSERT(osmo_fsm_register(&mgwep_fsm) == 0);
+ fill_event_names();
+}
+
+struct mgw_endpoint *mgwep_fi_mgwep(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &mgwep_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+struct mgw_endpoint *mgw_endpoint_alloc(struct osmo_fsm_inst *parent, uint32_t parent_term_event,
+ struct mgcp_client *mgcp_client,
+ const char *fsm_id,
+ const char *endpoint_str_fmt, ...)
+{
+ va_list ap;
+ struct osmo_fsm_inst *fi;
+ struct mgw_endpoint *mgwep;
+ int rc;
+
+ if (!mgcp_client)
+ return NULL;
+
+ /* use mgcp_client as talloc ctx, so that the conn, lchan, ts can deallocate while MGCP DLCX are
+ * still going on. */
+ fi = osmo_fsm_inst_alloc_child(&mgwep_fsm, parent, parent_term_event);
+ OSMO_ASSERT(fi);
+
+ osmo_fsm_inst_update_id(fi, fsm_id);
+
+ mgwep = talloc_zero(fi, struct mgw_endpoint);
+ OSMO_ASSERT(mgwep);
+
+ mgwep->mgcp_client = mgcp_client;
+ mgwep->fi = fi;
+ mgwep->fi->priv = mgwep;
+
+ va_start(ap, endpoint_str_fmt);
+ rc = vsnprintf(mgwep->endpoint, sizeof(mgwep->endpoint), endpoint_str_fmt, ap);
+ va_end(ap);
+
+ if (rc <= 0 || rc >= sizeof(mgwep->endpoint)) {
+ LOG_MGWEP(mgwep, LOGL_ERROR, "Endpoint name too long or too short: %s\n",
+ mgwep->endpoint);
+ osmo_fsm_inst_term(mgwep->fi, OSMO_FSM_TERM_ERROR, 0);
+ return NULL;
+ }
+
+ return mgwep;
+}
+
+struct mgwep_ci *mgw_endpoint_ci_add(struct mgw_endpoint *mgwep,
+ const char *label_fmt, ...)
+{
+ va_list ap;
+ int i;
+ struct mgwep_ci *ci;
+
+ for (i = 0; i < USABLE_CI; i++) {
+ ci = &mgwep->ci[i];
+
+ if (ci->occupied || ci->mgcp_client_fi)
+ continue;
+
+ *ci = (struct mgwep_ci){
+ .mgwep = mgwep,
+ .occupied = true,
+ };
+ if (label_fmt) {
+ va_start(ap, label_fmt);
+ vsnprintf(ci->label, sizeof(ci->label), label_fmt, ap);
+ va_end(ap);
+ }
+ return ci;
+ }
+
+ LOG_MGWEP(mgwep, LOGL_ERROR,
+ "Cannot allocate another endpoint, all "
+ OSMO_STRINGIFY_VAL(USABLE_CI) " are in use\n");
+
+ return NULL;
+}
+
+static void mgwep_fsm_check_state_chg_after_response(struct osmo_fsm_inst *fi);
+
+static void on_failure(struct mgwep_ci *ci)
+{
+ if (!ci->occupied)
+ return;
+
+ if (ci->notify)
+ osmo_fsm_inst_dispatch(ci->notify, ci->notify_failure, ci->notify_data);
+
+ *ci = (struct mgwep_ci){
+ .mgwep = ci->mgwep,
+ };
+
+
+ mgwep_fsm_check_state_chg_after_response(ci->mgwep->fi);
+}
+
+static void on_success(struct mgwep_ci *ci, void *data)
+{
+ struct mgcp_conn_peer *rtp_info;
+
+ if (!ci->occupied)
+ return;
+
+ ci->pending = false;
+
+ switch (ci->verb) {
+ case MGCP_VERB_CRCX:
+ /* If we sent a wildcarded endpoint name on CRCX, we need to store the resulting endpoint
+ * name here. Also, we receive the MGW's RTP port information. */
+ rtp_info = data;
+ OSMO_ASSERT(rtp_info);
+ ci->got_port_info = true;
+ ci->rtp_info = *rtp_info;
+ osmo_strlcpy(ci->mgcp_ci_str, mgcp_conn_get_ci(ci->mgcp_client_fi),
+ sizeof(ci->mgcp_ci_str));
+ if (rtp_info->endpoint[0]) {
+ int rc;
+ rc = osmo_strlcpy(ci->mgwep->endpoint, rtp_info->endpoint,
+ sizeof(ci->mgwep->endpoint));
+ if (rc <= 0 || rc >= sizeof(ci->mgwep->endpoint)) {
+ LOG_CI(ci, LOGL_ERROR, "Unable to copy endpoint name '%s'\n",
+ rtp_info->endpoint);
+ mgw_endpoint_ci_dlcx(ci);
+ on_failure(ci);
+ return;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ LOG_CI(ci, LOGL_DEBUG, "received successful response to %s RTP=%s%s\n",
+ mgcp_verb_name(ci->verb),
+ mgcp_conn_peer_name(ci->got_port_info? &ci->rtp_info : NULL),
+ ci->notify ? "" : " (not sending a notification)");
+
+ if (ci->notify)
+ osmo_fsm_inst_dispatch(ci->notify, ci->notify_success, ci->notify_data);
+
+ mgwep_fsm_check_state_chg_after_response(ci->mgwep->fi);
+}
+
+const struct mgcp_conn_peer *mgwep_ci_get_rtp_info(const struct mgwep_ci *ci)
+{
+ ci = mgwep_check_ci((struct mgwep_ci*)ci);
+ if (!ci)
+ return NULL;
+ if (!ci->got_port_info)
+ return NULL;
+ return &ci->rtp_info;
+}
+
+bool mgwep_ci_get_crcx_info_to_sockaddr(const struct mgwep_ci *ci, struct sockaddr_storage *dest)
+{
+ const struct mgcp_conn_peer *rtp_info;
+ struct sockaddr_in *sin;
+
+ rtp_info = mgwep_ci_get_rtp_info(ci);
+ if (!rtp_info)
+ return false;
+
+ sin = (struct sockaddr_in *)dest;
+
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = inet_addr(rtp_info->addr);
+ sin->sin_port = osmo_ntohs(rtp_info->port);
+ return true;
+}
+
+
+static const struct state_timeout mgwep_fsm_timeouts[32] = {
+ [MGWEP_ST_WAIT_MGW_RESPONSE] = { .T=23042 },
+};
+
+/* Transition to a state, using the T timer defined in assignment_fsm_timeouts.
+ * The actual timeout value is in turn obtained from g_T_defs.
+ * Assumes local variable fi exists. */
+#define mgwep_fsm_state_chg(state) \
+ fsm_inst_state_chg_T(fi, state, mgwep_fsm_timeouts, g_T_defs, 5)
+
+void mgw_endpoint_ci_request(struct mgwep_ci *ci,
+ enum mgcp_verb verb, const struct mgcp_conn_peer *verb_info,
+ struct osmo_fsm_inst *notify,
+ uint32_t event_success, uint32_t event_failure,
+ void *notify_data)
+{
+ struct mgw_endpoint *mgwep;
+ struct osmo_fsm_inst *fi;
+ struct mgwep_ci cleared_ci;
+ ci = mgwep_check_ci(ci);
+
+ if (!ci) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Invalid MGW endpoint request: no ci\n");
+ goto dispatch_error;
+ }
+ if (!verb_info && verb != MGCP_VERB_DLCX) {
+ LOG_CI(ci, LOGL_ERROR, "Invalid MGW endpoint request: missing verb details for %s\n",
+ mgcp_verb_name(verb));
+ goto dispatch_error;
+ }
+ if ((verb < 0) || (verb > MGCP_VERB_RSIP)) {
+ LOG_CI(ci, LOGL_ERROR, "Invalid MGW endpoint request: unknown verb: %s\n",
+ mgcp_verb_name(verb));
+ goto dispatch_error;
+ }
+
+ mgwep = ci->mgwep;
+ fi = mgwep->fi;
+
+ /* Clear volatile state by explicitly keeping those that should remain. Because we can't assign
+ * the char[] directly, dance through cleared_ci and copy back. */
+ cleared_ci = (struct mgwep_ci){
+ .mgwep = mgwep,
+ .mgcp_client_fi = ci->mgcp_client_fi,
+ .got_port_info = ci->got_port_info,
+ .rtp_info = ci->rtp_info,
+
+ .occupied = true,
+ /* .pending = true follows below */
+ .verb = verb,
+ .notify = notify,
+ .notify_success = event_success,
+ .notify_failure = event_failure,
+ .notify_data = notify_data,
+ };
+ osmo_strlcpy(cleared_ci.label, ci->label, sizeof(cleared_ci.label));
+ osmo_strlcpy(cleared_ci.mgcp_ci_str, ci->mgcp_ci_str, sizeof(cleared_ci.mgcp_ci_str));
+ *ci = cleared_ci;
+
+ LOG_CI_VERB(ci, LOGL_DEBUG, "notify=%s\n", osmo_fsm_inst_name(ci->notify));
+
+ if (verb_info)
+ ci->verb_info = *verb_info;
+
+ if (mgwep->endpoint[0]) {
+ if (ci->verb_info.endpoint[0] && strcmp(ci->verb_info.endpoint, mgwep->endpoint))
+ LOG_CI(ci, LOGL_ERROR,
+ "Warning: Requested %s on endpoint %s, but this CI is on endpoint %s."
+ " Using the proper endpoint instead.\n",
+ mgcp_verb_name(verb), ci->verb_info.endpoint, mgwep->endpoint);
+ osmo_strlcpy(ci->verb_info.endpoint, mgwep->endpoint, sizeof(ci->verb_info.endpoint));
+ }
+
+ switch (ci->verb) {
+ case MGCP_VERB_CRCX:
+ if (ci->mgcp_client_fi) {
+ LOG_CI(ci, LOGL_ERROR, "CRCX can be called only once per MGW endpoint CI\n");
+ on_failure(ci);
+ return;
+ }
+ break;
+
+ case MGCP_VERB_MDCX:
+ case MGCP_VERB_DLCX:
+ if (!ci->mgcp_client_fi) {
+ LOG_CI_VERB(ci, LOGL_ERROR, "The first verb on an unused MGW endpoint CI must be CRCX, not %s\n",
+ mgcp_verb_name(ci->verb));
+ on_failure(ci);
+ return;
+ }
+ break;
+
+ default:
+ LOG_CI(ci, LOGL_ERROR, "This verb is not supported: %s\n", mgcp_verb_name(ci->verb));
+ on_failure(ci);
+ return;
+ }
+
+ ci->pending = true;
+
+ LOG_CI_VERB(ci, LOGL_DEBUG, "Scheduling\n");
+
+ if (mgwep->fi->state != MGWEP_ST_WAIT_MGW_RESPONSE)
+ mgwep_fsm_state_chg(MGWEP_ST_WAIT_MGW_RESPONSE);
+
+ return;
+dispatch_error:
+ if (notify)
+ osmo_fsm_inst_dispatch(notify, event_failure, notify_data);
+}
+
+static int send_verb(struct mgwep_ci *ci)
+{
+ int rc;
+ struct mgw_endpoint *mgwep = ci->mgwep;
+
+ if (!ci->occupied || !ci->pending || ci->sent)
+ return 0;
+
+ switch (ci->verb) {
+
+ case MGCP_VERB_CRCX:
+ OSMO_ASSERT(!ci->mgcp_client_fi);
+ LOG_CI_VERB(ci, LOGL_DEBUG, "Sending\n");
+ ci->mgcp_client_fi = mgcp_conn_create(mgwep->mgcp_client, mgwep->fi,
+ CI_EV_FAILURE(ci), CI_EV_SUCCESS(ci),
+ &ci->verb_info);
+ ci->sent = true;
+ if (!ci->mgcp_client_fi){
+ LOG_CI_VERB(ci, LOGL_ERROR, "Cannot send\n");
+ on_failure(ci);
+ }
+ osmo_fsm_inst_update_id(ci->mgcp_client_fi, ci->label);
+ break;
+
+ case MGCP_VERB_MDCX:
+ OSMO_ASSERT(ci->mgcp_client_fi);
+ LOG_CI_VERB(ci, LOGL_DEBUG, "Sending\n");
+ rc = mgcp_conn_modify(ci->mgcp_client_fi, CI_EV_SUCCESS(ci), &ci->verb_info);
+ ci->sent = true;
+ if (rc) {
+ LOG_CI_VERB(ci, LOGL_ERROR, "Cannot send (rc=%d %s)\n", rc, strerror(-rc));
+ on_failure(ci);
+ }
+ break;
+
+ case MGCP_VERB_DLCX:
+ LOG_CI(ci, LOGL_DEBUG, "Sending MGCP: %s %s\n",
+ mgcp_verb_name(ci->verb), ci->mgcp_ci_str);
+ /* The way this is designed, we actually need to forget all about the ci right away. */
+ mgcp_conn_delete(ci->mgcp_client_fi);
+ if (ci->notify)
+ osmo_fsm_inst_dispatch(ci->notify, ci->notify_success, ci->notify_data);
+ *ci = (struct mgwep_ci){
+ .mgwep = mgwep,
+ };
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+
+ return 1;
+}
+
+void mgw_endpoint_clear(struct mgw_endpoint *mgwep)
+{
+ if (!mgwep)
+ return;
+ osmo_fsm_inst_term(mgwep->fi, OSMO_FSM_TERM_REGULAR, 0);
+}
+
+static void mgwep_count(struct mgw_endpoint *mgwep, int *occupied, int *pending_not_sent,
+ int *waiting_for_response)
+{
+ int i;
+
+ if (occupied)
+ *occupied = 0;
+
+ if (pending_not_sent)
+ *pending_not_sent = 0;
+
+ if (waiting_for_response)
+ *waiting_for_response = 0;
+
+ for (i = 0; i < ARRAY_SIZE(mgwep->ci); i++) {
+ struct mgwep_ci *ci = &mgwep->ci[i];
+ if (ci->occupied) {
+ if (occupied)
+ (*occupied)++;
+ } else
+ continue;
+
+ if (ci->pending)
+ LOG_CI_VERB(ci, LOGL_DEBUG, "%s\n",
+ ci->sent ? "waiting for response" : "waiting to be sent");
+ else
+ LOG_CI_VERB(ci, LOGL_DEBUG, "%s\n", mgcp_conn_peer_name(mgwep_ci_get_rtp_info(ci)));
+
+ if (ci->pending && ci->sent)
+ if (waiting_for_response)
+ (*waiting_for_response)++;
+ if (ci->pending && !ci->sent)
+ if (pending_not_sent)
+ (*pending_not_sent)++;
+ }
+}
+
+static void mgwep_fsm_check_state_chg_after_response(struct osmo_fsm_inst *fi)
+{
+ int waiting_for_response;
+ int occupied;
+ struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
+
+ mgwep_count(mgwep, &occupied, NULL, &waiting_for_response);
+ LOG_MGWEP(mgwep, LOGL_DEBUG, "CI in use: %d, waiting for response: %d\n", occupied, waiting_for_response);
+
+ if (!occupied) {
+ /* All CI have been released. The endpoint no longer exists. Notify the parent FSM, by
+ * terminating. */
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
+ return;
+ }
+
+ if (!waiting_for_response) {
+ if (fi->state != MGWEP_ST_IN_USE)
+ mgwep_fsm_state_chg(MGWEP_ST_IN_USE);
+ return;
+ }
+
+}
+
+static void mgwep_fsm_wait_mgw_response_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int count = 0;
+ int i;
+ struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
+
+ for (i = 0; i < ARRAY_SIZE(mgwep->ci); i++) {
+ count += send_verb(&mgwep->ci[i]);
+ }
+
+ LOG_MGWEP(mgwep, LOGL_DEBUG, "Sent messages: %d\n", count);
+ mgwep_fsm_check_state_chg_after_response(fi);
+
+}
+
+static void mgwep_fsm_handle_ci_events(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgwep_ci *ci;
+ struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
+ ci = mgwep_ci_for_event(mgwep, event);
+ if (ci) {
+ if (event == CI_EV_SUCCESS(ci))
+ on_success(ci, data);
+ else
+ on_failure(ci);
+ }
+}
+
+static void mgwep_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int pending_not_sent;
+ struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
+
+ mgwep_count(mgwep, NULL, &pending_not_sent, NULL);
+ if (pending_not_sent)
+ mgwep_fsm_state_chg(MGWEP_ST_WAIT_MGW_RESPONSE);
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state mgwep_fsm_states[] = {
+ [MGWEP_ST_UNUSED] = {
+ .name = "UNUSED",
+ .in_event_mask = 0,
+ .out_state_mask = 0
+ | S(MGWEP_ST_WAIT_MGW_RESPONSE)
+ ,
+ },
+ [MGWEP_ST_WAIT_MGW_RESPONSE] = {
+ .name = "WAIT_MGW_RESPONSE",
+ .onenter = mgwep_fsm_wait_mgw_response_onenter,
+ .action = mgwep_fsm_handle_ci_events,
+ .in_event_mask = 0xffffffff,
+ .out_state_mask = 0
+ | S(MGWEP_ST_IN_USE)
+ ,
+ },
+ [MGWEP_ST_IN_USE] = {
+ .name = "IN_USE",
+ .onenter = mgwep_fsm_in_use_onenter,
+ .action = mgwep_fsm_handle_ci_events,
+ .in_event_mask = 0xffffffff, /* mgcp_client_fsm may send parent term anytime */
+ .out_state_mask = 0
+ | S(MGWEP_ST_WAIT_MGW_RESPONSE)
+ ,
+ },
+};
+
+static int mgwep_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ int i;
+ struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
+
+ switch (fi->T) {
+ default:
+ for (i = 0; i < ARRAY_SIZE(mgwep->ci); i++) {
+ struct mgwep_ci *ci = &mgwep->ci[i];
+ if (!ci->occupied)
+ continue;
+ if (!(ci->pending && ci->sent))
+ continue;
+ on_failure(ci);
+ }
+ return 0;
+ }
+
+ return 0;
+}
+
+static struct osmo_fsm mgwep_fsm = {
+ .name = "mgw-endpoint",
+ .states = mgwep_fsm_states,
+ .num_states = ARRAY_SIZE(mgwep_fsm_states),
+ .log_subsys = DRSL,
+ .event_names = mgwep_fsm_event_names,
+ .timer_cb = mgwep_fsm_timer_cb,
+ /* The FSM termination will automatically trigger any mgcp_client_fsm instances to DLCX. */
+};
+
+/* Depending on the channel mode and rate, return the codec type that is signalled towards the MGW. */
+enum mgcp_codecs chan_mode_to_mgcp_codec(enum gsm48_chan_mode chan_mode, bool full_rate)
+{
+ switch (chan_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (full_rate)
+ return CODEC_GSM_8000_1;
+ return CODEC_GSMHR_8000_1;
+
+ case GSM48_CMODE_SPEECH_EFR:
+ return CODEC_GSMEFR_8000_1;
+
+ case GSM48_CMODE_SPEECH_AMR:
+ return CODEC_AMR_8000_1;
+
+ default:
+ return -1;
+ }
+}
+
+int chan_mode_to_mgcp_bss_pt(enum mgcp_codecs codec)
+{
+ switch (codec) {
+ case CODEC_GSMHR_8000_1:
+ return RTP_PT_GSM_HALF;
+
+ case CODEC_GSMEFR_8000_1:
+ return RTP_PT_GSM_EFR;
+
+ case CODEC_AMR_8000_1:
+ return RTP_PT_AMR;
+
+ default:
+ /* Not an error, we just leave it to libosmo-mgcp-client to
+ * decide over the PT. */
+ return -1;
+ }
+}
+
+void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *lchan, bool bss_side)
+{
+ enum mgcp_codecs codec = chan_mode_to_mgcp_codec(lchan->tch_mode,
+ lchan->type == GSM_LCHAN_TCH_H? false : true);
+ int custom_pt;
+
+ if (codec < 0) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Unable to determine MGCP codec type for %s in chan-mode %s\n",
+ gsm_lchant_name(lchan->type), gsm48_chan_mode_name(lchan->tch_mode));
+ verb_info->codecs_len = 0;
+ return;
+ }
+
+ verb_info->codecs[0] = codec;
+ verb_info->codecs_len = 1;
+
+ /* Setup custom payload types (only for BSS side and when required) */
+ custom_pt = chan_mode_to_mgcp_bss_pt(codec);
+ if (bss_side && custom_pt > 0) {
+ verb_info->ptmap[0].codec = codec;
+ verb_info->ptmap[0].pt = custom_pt;
+ verb_info->ptmap_len = 1;
+ }
+}
diff --git a/src/osmo-bsc/neighbor_ident.c b/src/osmo-bsc/neighbor_ident.c
new file mode 100644
index 000000000..4a0cd47ad
--- /dev/null
+++ b/src/osmo-bsc/neighbor_ident.c
@@ -0,0 +1,255 @@
+/* Manage identity of neighboring BSS cells for inter-BSC handover.
+ *
+ * Measurement reports tell us about neighbor ARFCN and BSIC. If that ARFCN and BSIC is not managed by
+ * this local BSS, we need to tell the MSC a cell identity, like CGI, LAC+CI, etc. -- hence we need a
+ * mapping from ARFCN+BSIC to Cell Identifier List, which needs to be configured by the user.
+ */
+/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/bsc/neighbor_ident.h>
+
+struct neighbor_ident_list {
+ struct llist_head list;
+};
+
+struct neighbor_ident {
+ struct llist_head entry;
+
+ struct neighbor_ident_key key;
+ struct gsm0808_cell_id_list2 val;
+};
+
+#define APPEND_THING(func, args...) do { \
+ int remain = buflen - (pos - buf); \
+ int l = func(pos, remain, ##args); \
+ if (l < 0 || l > remain) \
+ pos = buf + buflen; \
+ else \
+ pos += l; \
+ } while(0)
+#define APPEND_STR(fmt, args...) APPEND_THING(snprintf, fmt, ##args)
+
+const char *_neighbor_ident_key_name(char *buf, size_t buflen, const struct neighbor_ident_key *ni_key)
+{
+ char *pos = buf;
+
+ APPEND_STR("BTS ");
+ if (ni_key->from_bts == NEIGHBOR_IDENT_KEY_ANY_BTS)
+ APPEND_STR("*");
+ else if (ni_key->from_bts >= 0 && ni_key->from_bts <= 255)
+ APPEND_STR("%d", ni_key->from_bts);
+ else
+ APPEND_STR("invalid(%d)", ni_key->from_bts);
+
+ APPEND_STR(" to ");
+ if (ni_key->bsic == BSIC_ANY)
+ APPEND_STR("ARFCN %u (any BSIC)", ni_key->arfcn);
+ else
+ APPEND_STR("ARFCN %u BSIC %u", ni_key->arfcn, ni_key->bsic & 0x3f);
+ return buf;
+}
+
+const char *neighbor_ident_key_name(const struct neighbor_ident_key *ni_key)
+{
+ static char buf[64];
+ return _neighbor_ident_key_name(buf, sizeof(buf), ni_key);
+}
+
+struct neighbor_ident_list *neighbor_ident_init(void *talloc_ctx)
+{
+ struct neighbor_ident_list *nil = talloc_zero(talloc_ctx, struct neighbor_ident_list);
+ OSMO_ASSERT(nil);
+ INIT_LLIST_HEAD(&nil->list);
+ return nil;
+}
+
+void neighbor_ident_free(struct neighbor_ident_list *nil)
+{
+ if (!nil)
+ return;
+ talloc_free(nil);
+}
+
+/* Return true when the entry matches the search_for requirements.
+ * If exact_match is false, a BSIC_ANY entry acts as wildcard to match any search_for on that ARFCN,
+ * and a BSIC_ANY in search_for likewise returns any one entry that matches the ARFCN;
+ * also a from_bts == NEIGHBOR_IDENT_KEY_ANY_BTS in either entry or search_for will match.
+ * If exact_match is true, only identical bsic values and identical from_bts values return a match.
+ * Note, typically wildcard BSICs are only in entry, e.g. the user configured list, and search_for
+ * contains a specific BSIC, e.g. as received from a Measurement Report. */
+bool neighbor_ident_key_match(const struct neighbor_ident_key *entry,
+ const struct neighbor_ident_key *search_for,
+ bool exact_match)
+{
+ if (exact_match
+ && entry->from_bts != search_for->from_bts)
+ return false;
+
+ if (search_for->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS
+ && entry->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS
+ && entry->from_bts != search_for->from_bts)
+ return false;
+
+ if (entry->arfcn != search_for->arfcn)
+ return false;
+
+ if (exact_match && entry->bsic != search_for->bsic)
+ return false;
+
+ if (entry->bsic == BSIC_ANY || search_for->bsic == BSIC_ANY)
+ return true;
+
+ return entry->bsic == search_for->bsic;
+}
+
+static struct neighbor_ident *_neighbor_ident_get(const struct neighbor_ident_list *nil,
+ const struct neighbor_ident_key *key,
+ bool exact_match)
+{
+ struct neighbor_ident *ni;
+ struct neighbor_ident *wildcard_match = NULL;
+
+ /* Do both exact-bsic and wildcard matching in the same iteration:
+ * Any exact match returns immediately, while for a wildcard match we still go through all
+ * remaining items in case an exact match exists. */
+ llist_for_each_entry(ni, &nil->list, entry) {
+ if (neighbor_ident_key_match(&ni->key, key, true))
+ return ni;
+ if (!exact_match) {
+ if (neighbor_ident_key_match(&ni->key, key, false))
+ wildcard_match = ni;
+ }
+ }
+ return wildcard_match;
+}
+
+static void _neighbor_ident_free(struct neighbor_ident *ni)
+{
+ llist_del(&ni->entry);
+ talloc_free(ni);
+}
+
+bool neighbor_ident_key_valid(const struct neighbor_ident_key *key)
+{
+ if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS
+ && (key->from_bts < 0 || key->from_bts > 255))
+ return false;
+
+ if (key->bsic != BSIC_ANY && key->bsic > 0x3f)
+ return false;
+ return true;
+}
+
+/*! Add Cell Identifiers to an ARFCN+BSIC entry.
+ * Exactly one kind of identifier is allowed per ARFCN+BSIC entry, and any number of entries of that kind
+ * may be added up to the capacity of gsm0808_cell_id_list2, by one or more calls to this function. To
+ * replace an existing entry, first call neighbor_ident_del(nil, key).
+ * \returns number of entries in the resulting identifier list, or negative on error:
+ * see gsm0808_cell_id_list_add() for the meaning of returned error codes;
+ * return -ENOMEM when the list is not initialized, -ERANGE when the BSIC value is too large. */
+int neighbor_ident_add(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key,
+ const struct gsm0808_cell_id_list2 *val)
+{
+ struct neighbor_ident *ni;
+ int rc;
+
+ if (!nil)
+ return -ENOMEM;
+
+ if (!neighbor_ident_key_valid(key))
+ return -ERANGE;
+
+ ni = _neighbor_ident_get(nil, key, true);
+ if (!ni) {
+ ni = talloc_zero(nil, struct neighbor_ident);
+ OSMO_ASSERT(ni);
+ *ni = (struct neighbor_ident){
+ .key = *key,
+ .val = *val,
+ };
+ llist_add_tail(&ni->entry, &nil->list);
+ return ni->val.id_list_len;
+ }
+
+ rc = gsm0808_cell_id_list_add(&ni->val, val);
+
+ if (rc < 0)
+ return rc;
+
+ return ni->val.id_list_len;
+}
+
+/*! Find cell identity for given BTS, ARFCN and BSIC, as previously added by neighbor_ident_add().
+ */
+const struct gsm0808_cell_id_list2 *neighbor_ident_get(const struct neighbor_ident_list *nil,
+ const struct neighbor_ident_key *key)
+{
+ struct neighbor_ident *ni;
+ if (!nil)
+ return NULL;
+ ni = _neighbor_ident_get(nil, key, false);
+ if (!ni)
+ return NULL;
+ return &ni->val;
+}
+
+bool neighbor_ident_del(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key)
+{
+ struct neighbor_ident *ni;
+ if (!nil)
+ return false;
+ ni = _neighbor_ident_get(nil, key, true);
+ if (!ni)
+ return false;
+ _neighbor_ident_free(ni);
+ return true;
+}
+
+void neighbor_ident_clear(struct neighbor_ident_list *nil)
+{
+ struct neighbor_ident *ni;
+ while ((ni = llist_first_entry_or_null(&nil->list, struct neighbor_ident, entry)))
+ _neighbor_ident_free(ni);
+}
+
+/*! Iterate all neighbor_ident_list entries and call iter_cb for each.
+ * If iter_cb returns false, the iteration is stopped. */
+void neighbor_ident_iter(const struct neighbor_ident_list *nil,
+ bool (* iter_cb )(const struct neighbor_ident_key *key,
+ const struct gsm0808_cell_id_list2 *val,
+ void *cb_data),
+ void *cb_data)
+{
+ struct neighbor_ident *ni, *ni_next;
+ if (!nil)
+ return;
+ llist_for_each_entry_safe(ni, ni_next, &nil->list, entry) {
+ if (!iter_cb(&ni->key, &ni->val, cb_data))
+ return;
+ }
+}
diff --git a/src/osmo-bsc/neighbor_ident_vty.c b/src/osmo-bsc/neighbor_ident_vty.c
new file mode 100644
index 000000000..203b15057
--- /dev/null
+++ b/src/osmo-bsc/neighbor_ident_vty.c
@@ -0,0 +1,580 @@
+/* Quagga VTY implementation to manage identity of neighboring BSS cells for inter-BSC handover. */
+/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/gsm_data.h>
+
+static struct gsm_network *g_net = NULL;
+static struct neighbor_ident_list *g_neighbor_cells = NULL;
+
+/* Parse VTY parameters matching NEIGHBOR_IDENT_VTY_KEY_PARAMS. Pass a pointer so that argv[0] is the
+ * ARFCN value followed by the BSIC keyword and value. vty *must* reference a BTS_NODE. */
+bool neighbor_ident_vty_parse_key_params(struct vty *vty, const char **argv,
+ struct neighbor_ident_key *key)
+{
+ struct gsm_bts *bts = vty->index;
+
+ OSMO_ASSERT(vty->node == BTS_NODE);
+ OSMO_ASSERT(bts);
+
+ return neighbor_ident_bts_parse_key_params(vty, bts, argv, key);
+}
+
+/* same as neighbor_ident_vty_parse_key_params() but pass an explicit bts, so it works on any node. */
+bool neighbor_ident_bts_parse_key_params(struct vty *vty, struct gsm_bts *bts, const char **argv,
+ struct neighbor_ident_key *key)
+{
+ const char *arfcn_str = argv[0];
+ const char *bsic_str = argv[1];
+
+ OSMO_ASSERT(bts);
+
+ *key = (struct neighbor_ident_key){
+ .from_bts = bts->nr,
+ .arfcn = atoi(arfcn_str),
+ };
+
+ if (!strcmp(bsic_str, "any"))
+ key->bsic = BSIC_ANY;
+ else
+ key->bsic = atoi(bsic_str);
+ return true;
+}
+
+#define NEIGHBOR_ADD_CMD "neighbor "
+#define NEIGHBOR_DEL_CMD "no neighbor "
+#define NEIGHBOR_DOC "Manage local and remote-BSS neighbor cells\n"
+#define NEIGHBOR_ADD_DOC NEIGHBOR_DOC "Add "
+#define NEIGHBOR_DEL_DOC NO_STR "Remove local or remote-BSS neighbor cell\n"
+
+#define LAC_PARAMS "lac <0-65535>"
+#define LAC_DOC "Neighbor cell by LAC\n" "LAC\n"
+
+#define LAC_CI_PARAMS "lac-ci <0-65535> <0-65535>"
+#define LAC_CI_DOC "Neighbor cell by LAC and CI\n" "LAC\n" "CI\n"
+
+#define CGI_PARAMS "cgi <0-999> <0-999> <0-65535> <0-65535>"
+#define CGI_DOC "Neighbor cell by cgi\n" "MCC\n" "MNC\n" "LAC\n" "CI\n"
+
+#define LOCAL_BTS_PARAMS "bts <0-255>"
+#define LOCAL_BTS_DOC "Neighbor cell by local BTS number\n" "BTS number\n"
+
+static struct gsm_bts *neighbor_ident_vty_parse_bts_nr(struct vty *vty, const char **argv)
+{
+ const char *bts_nr_str = argv[0];
+ struct gsm_bts *bts = gsm_bts_num(g_net, atoi(bts_nr_str));
+ if (!bts)
+ vty_out(vty, "%% No such BTS: nr = %s%s\n", bts_nr_str, VTY_NEWLINE);
+ return bts;
+}
+
+static struct gsm_bts *bts_by_cell_id(struct vty *vty, struct gsm0808_cell_id *cell_id)
+{
+ struct gsm_bts *bts = gsm_bts_by_cell_id(g_net, cell_id, 0);
+ if (!bts)
+ vty_out(vty, "%% No such BTS: %s%s\n", gsm0808_cell_id_name(cell_id), VTY_NEWLINE);
+ return bts;
+}
+
+static struct gsm0808_cell_id *neighbor_ident_vty_parse_lac(struct vty *vty, const char **argv)
+{
+ static struct gsm0808_cell_id cell_id;
+ cell_id = (struct gsm0808_cell_id){
+ .id_discr = CELL_IDENT_LAC,
+ .id.lac = atoi(argv[0]),
+ };
+ return &cell_id;
+}
+
+static struct gsm0808_cell_id *neighbor_ident_vty_parse_lac_ci(struct vty *vty, const char **argv)
+{
+ static struct gsm0808_cell_id cell_id;
+ cell_id = (struct gsm0808_cell_id){
+ .id_discr = CELL_IDENT_LAC_AND_CI,
+ .id.lac_and_ci = {
+ .lac = atoi(argv[0]),
+ .ci = atoi(argv[1]),
+ },
+ };
+ return &cell_id;
+}
+
+static struct gsm0808_cell_id *neighbor_ident_vty_parse_cgi(struct vty *vty, const char **argv)
+{
+ static struct gsm0808_cell_id cell_id;
+ cell_id = (struct gsm0808_cell_id){
+ .id_discr = CELL_IDENT_WHOLE_GLOBAL,
+ };
+ struct osmo_cell_global_id *cgi = &cell_id.id.global;
+ const char *mcc = argv[0];
+ const char *mnc = argv[1];
+ const char *lac = argv[2];
+ const char *ci = argv[3];
+
+ if (osmo_mcc_from_str(mcc, &cgi->lai.plmn.mcc)) {
+ vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE);
+ return NULL;
+ }
+
+ if (osmo_mnc_from_str(mnc, &cgi->lai.plmn.mnc, &cgi->lai.plmn.mnc_3_digits)) {
+ vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE);
+ return NULL;
+ }
+
+ cgi->lai.lac = atoi(lac);
+ cgi->cell_identity = atoi(ci);
+ return &cell_id;
+}
+
+static int add_local_bts(struct vty *vty, struct gsm_bts *neigh)
+{
+ int rc;
+ struct gsm_bts *bts = vty->index;
+ if (vty->node != BTS_NODE) {
+ vty_out(vty, "%% Error: cannot add local BTS neighbor, not on BTS node%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!bts) {
+ vty_out(vty, "%% Error: cannot add local BTS neighbor, no BTS on this node%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!neigh) {
+ vty_out(vty, "%% Error: cannot add local BTS neighbor to BTS %u, no such neighbor BTS%s"
+ "%% (To add remote-BSS neighbors, pass full ARFCN and BSIC as well)%s",
+ bts->nr, VTY_NEWLINE, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ rc = gsm_bts_local_neighbor_add(bts, neigh);
+ if (rc < 0) {
+ vty_out(vty, "%% Error: cannot add local BTS %u as neighbor to BTS %u: %s%s",
+ neigh->nr, bts->nr, strerror(-rc), VTY_NEWLINE);
+ return CMD_WARNING;
+ } else
+ vty_out(vty, "%% BTS %u %s local neighbor BTS %u with LAC %u CI %u and ARFCN %u BSIC %u%s",
+ bts->nr, rc? "now has" : "already had",
+ neigh->nr, neigh->location_area_code, neigh->cell_identity,
+ neigh->c0->arfcn, neigh->bsic, VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+static int del_local_bts(struct vty *vty, struct gsm_bts *neigh)
+{
+ int rc;
+ struct gsm_bts *bts = vty->index;
+ if (vty->node != BTS_NODE) {
+ vty_out(vty, "%% Error: cannot remove local BTS neighbor, not on BTS node%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!bts) {
+ vty_out(vty, "%% Error: cannot remove local BTS neighbor, no BTS on this node%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!neigh) {
+ vty_out(vty, "%% Error: cannot remove local BTS neighbor from BTS %u, no such neighbor BTS%s",
+ bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ rc = gsm_bts_local_neighbor_del(bts, neigh);
+ if (rc < 0) {
+ vty_out(vty, "%% Error: cannot remove local BTS %u neighbor from BTS %u: %s%s",
+ neigh->nr, bts->nr, strerror(-rc), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (rc == 0)
+ vty_out(vty, "%% BTS %u is no neighbor of BTS %u%s",
+ neigh->nr, bts->nr, VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_neighbor_add_bts_nr, cfg_neighbor_add_bts_nr_cmd,
+ NEIGHBOR_ADD_CMD LOCAL_BTS_PARAMS,
+ NEIGHBOR_ADD_DOC LOCAL_BTS_DOC)
+{
+ return add_local_bts(vty, neighbor_ident_vty_parse_bts_nr(vty, argv));
+}
+
+DEFUN(cfg_neighbor_add_lac, cfg_neighbor_add_lac_cmd,
+ NEIGHBOR_ADD_CMD LAC_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_DOC)
+{
+ return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac(vty, argv)));
+}
+
+DEFUN(cfg_neighbor_add_lac_ci, cfg_neighbor_add_lac_ci_cmd,
+ NEIGHBOR_ADD_CMD LAC_CI_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_CI_DOC)
+{
+ return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac_ci(vty, argv)));
+}
+
+DEFUN(cfg_neighbor_add_cgi, cfg_neighbor_add_cgi_cmd,
+ NEIGHBOR_ADD_CMD CGI_PARAMS,
+ NEIGHBOR_ADD_DOC CGI_DOC)
+{
+ return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_cgi(vty, argv)));
+}
+
+bool neighbor_ident_key_matches_bts(const struct neighbor_ident_key *key, struct gsm_bts *bts)
+{
+ if (!bts || !key)
+ return false;
+ return key->arfcn == bts->c0->arfcn
+ && (key->bsic == BSIC_ANY || key->bsic == bts->bsic);
+}
+
+static int add_remote_or_local_bts(struct vty *vty, const struct gsm0808_cell_id *cell_id,
+ const struct neighbor_ident_key *key)
+{
+ int rc;
+ struct gsm_bts *local_neigh;
+ const struct gsm0808_cell_id_list2 *exists;
+ struct gsm0808_cell_id_list2 cil;
+ struct gsm_bts *bts = vty->index;
+
+ if (vty->node != BTS_NODE) {
+ vty_out(vty, "%% Error: cannot add BTS neighbor, not on BTS node%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!bts) {
+ vty_out(vty, "%% Error: cannot add BTS neighbor, no BTS on this node%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Is there a local BTS that matches the cell_id? */
+ local_neigh = gsm_bts_by_cell_id(g_net, cell_id, 0);
+ if (local_neigh) {
+ /* But do the advertised ARFCN and BSIC match as intended?
+ * The user may omit ARFCN and BSIC for local cells, but if they are provided,
+ * they need to match. */
+ if (!neighbor_ident_key_matches_bts(key, local_neigh)) {
+ vty_out(vty, "%% Error: bts %u: neighbor cell id %s indicates local BTS %u,"
+ " but it does not match ARFCN+BSIC %s%s",
+ bts->nr, gsm0808_cell_id_name(cell_id), local_neigh->nr,
+ neighbor_ident_key_name(key), VTY_NEWLINE);
+ /* TODO: error out fatally for non-interactive VTY? */
+ return CMD_WARNING;
+ }
+ return add_local_bts(vty, local_neigh);
+ }
+
+ /* Allow only one cell ID per remote-BSS neighbor, see OS#3656 */
+ exists = neighbor_ident_get(g_neighbor_cells, key);
+ if (exists) {
+ vty_out(vty, "%% Error: only one Cell Identifier entry is allowed per remote neighbor."
+ " Already have: %s -> %s%s", neighbor_ident_key_name(key),
+ gsm0808_cell_id_list_name(exists), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* The cell_id is not known in this BSS, so it must be a remote cell. */
+ gsm0808_cell_id_to_list(&cil, cell_id);
+ rc = neighbor_ident_add(g_neighbor_cells, key, &cil);
+
+ if (rc < 0) {
+ const char *reason;
+ switch (rc) {
+ case -EINVAL:
+ reason = ": mismatching type between current and newly added cell identifier";
+ break;
+ case -ENOSPC:
+ reason = ": list is full";
+ break;
+ default:
+ reason = "";
+ break;
+ }
+
+ vty_out(vty, "%% Error adding neighbor-BSS Cell Identifier %s%s%s",
+ gsm0808_cell_id_name(cell_id), reason, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% %s now has %d remote BSS Cell Identifier List %s%s",
+ neighbor_ident_key_name(key), rc, rc == 1? "entry" : "entries", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+static int del_by_key(struct vty *vty, const struct neighbor_ident_key *key)
+{
+ int removed = 0;
+ int rc;
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_ref *neigh, *safe;
+
+ if (vty->node != BTS_NODE) {
+ vty_out(vty, "%% Error: cannot remove BTS neighbor, not on BTS node%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!bts) {
+ vty_out(vty, "%% Error: cannot remove BTS neighbor, no BTS on this node%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Is there a local BTS that matches the key? */
+ llist_for_each_entry_safe(neigh, safe, &bts->local_neighbors, entry) {
+ struct gsm_bts *neigh_bts = neigh->bts;
+ if (!neighbor_ident_key_matches_bts(key, neigh->bts))
+ continue;
+ rc = gsm_bts_local_neighbor_del(bts, neigh->bts);
+ if (rc > 0) {
+ vty_out(vty, "%% Removed local neighbor bts %u to bts %u%s",
+ bts->nr, neigh_bts->nr, VTY_NEWLINE);
+ removed += rc;
+ }
+ }
+
+ if (neighbor_ident_del(g_neighbor_cells, key)) {
+ vty_out(vty, "%% Removed remote BSS neighbor %s%s",
+ neighbor_ident_key_name(key), VTY_NEWLINE);
+ removed ++;
+ }
+
+ if (!removed) {
+ vty_out(vty, "%% Cannot remove, no such neighbor: %s%s",
+ neighbor_ident_key_name(key), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_neighbor_add_lac_arfcn_bsic, cfg_neighbor_add_lac_arfcn_bsic_cmd,
+ NEIGHBOR_ADD_CMD LAC_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_DOC NEIGHBOR_IDENT_VTY_KEY_DOC)
+{
+ struct neighbor_ident_key nik;
+ struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_lac(vty, argv);
+ if (!cell_id)
+ return CMD_WARNING;
+ if (!neighbor_ident_vty_parse_key_params(vty, argv + 1, &nik))
+ return CMD_WARNING;
+ return add_remote_or_local_bts(vty, cell_id, &nik);
+}
+
+DEFUN(cfg_neighbor_add_lac_ci_arfcn_bsic, cfg_neighbor_add_lac_ci_arfcn_bsic_cmd,
+ NEIGHBOR_ADD_CMD LAC_CI_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_CI_DOC NEIGHBOR_IDENT_VTY_KEY_DOC)
+{
+ struct neighbor_ident_key nik;
+ struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_lac_ci(vty, argv);
+ if (!cell_id)
+ return CMD_WARNING;
+ if (!neighbor_ident_vty_parse_key_params(vty, argv + 2, &nik))
+ return CMD_WARNING;
+ return add_remote_or_local_bts(vty, cell_id, &nik);
+}
+
+DEFUN(cfg_neighbor_add_cgi_arfcn_bsic, cfg_neighbor_add_cgi_arfcn_bsic_cmd,
+ NEIGHBOR_ADD_CMD CGI_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
+ NEIGHBOR_ADD_DOC CGI_DOC NEIGHBOR_IDENT_VTY_KEY_DOC)
+{
+ struct neighbor_ident_key nik;
+ struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_cgi(vty, argv);
+ if (!cell_id)
+ return CMD_WARNING;
+ if (!neighbor_ident_vty_parse_key_params(vty, argv + 4, &nik))
+ return CMD_WARNING;
+ return add_remote_or_local_bts(vty, cell_id, &nik);
+}
+
+DEFUN(cfg_neighbor_del_bts_nr, cfg_neighbor_del_bts_nr_cmd,
+ NEIGHBOR_DEL_CMD LOCAL_BTS_PARAMS,
+ NEIGHBOR_DEL_DOC LOCAL_BTS_DOC)
+{
+ return del_local_bts(vty, neighbor_ident_vty_parse_bts_nr(vty, argv));
+}
+
+DEFUN(cfg_neighbor_del_arfcn_bsic, cfg_neighbor_del_arfcn_bsic_cmd,
+ NEIGHBOR_DEL_CMD NEIGHBOR_IDENT_VTY_KEY_PARAMS,
+ NEIGHBOR_DEL_DOC NEIGHBOR_IDENT_VTY_KEY_DOC)
+{
+ struct neighbor_ident_key key;
+
+ if (!neighbor_ident_vty_parse_key_params(vty, argv, &key))
+ return CMD_WARNING;
+
+ return del_by_key(vty, &key);
+}
+
+struct write_neighbor_ident_entry_data {
+ struct vty *vty;
+ const char *indent;
+ struct gsm_bts *bts;
+};
+
+static bool write_neighbor_ident_list(const struct neighbor_ident_key *key,
+ const struct gsm0808_cell_id_list2 *val,
+ void *cb_data)
+{
+ struct write_neighbor_ident_entry_data *d = cb_data;
+ struct vty *vty = d->vty;
+ int i;
+
+ if (d->bts) {
+ if (d->bts->nr != key->from_bts)
+ return true;
+ } else if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS)
+ return true;
+
+#define NEIGH_BSS_WRITE(fmt, args...) do { \
+ vty_out(vty, "%sneighbor " fmt " arfcn %u ", d->indent, ## args, key->arfcn); \
+ if (key->bsic == BSIC_ANY) \
+ vty_out(vty, "bsic any"); \
+ else \
+ vty_out(vty, "bsic %u", key->bsic & 0x3f); \
+ vty_out(vty, "%s", VTY_NEWLINE); \
+ } while(0)
+
+ switch (val->id_discr) {
+ case CELL_IDENT_LAC:
+ for (i = 0; i < val->id_list_len; i++) {
+ NEIGH_BSS_WRITE("lac %u", val->id_list[i].lac);
+ }
+ break;
+ case CELL_IDENT_LAC_AND_CI:
+ for (i = 0; i < val->id_list_len; i++) {
+ NEIGH_BSS_WRITE("lac-ci %u %u",
+ val->id_list[i].lac_and_ci.lac,
+ val->id_list[i].lac_and_ci.ci);
+ }
+ break;
+ case CELL_IDENT_WHOLE_GLOBAL:
+ for (i = 0; i < val->id_list_len; i++) {
+ const struct osmo_cell_global_id *cgi = &val->id_list[i].global;
+ NEIGH_BSS_WRITE("cgi %s %s %u %u",
+ osmo_mcc_name(cgi->lai.plmn.mcc),
+ osmo_mnc_name(cgi->lai.plmn.mnc, cgi->lai.plmn.mnc_3_digits),
+ cgi->lai.lac, cgi->cell_identity);
+ }
+ break;
+ default:
+ vty_out(vty, "%% Unsupported Cell Identity%s", VTY_NEWLINE);
+ }
+#undef NEIGH_BSS_WRITE
+
+ return true;
+}
+
+void neighbor_ident_vty_write_remote_bss(struct vty *vty, const char *indent, struct gsm_bts *bts)
+{
+ struct write_neighbor_ident_entry_data d = {
+ .vty = vty,
+ .indent = indent,
+ .bts = bts,
+ };
+
+ neighbor_ident_iter(g_neighbor_cells, write_neighbor_ident_list, &d);
+}
+
+void neighbor_ident_vty_write_local_neighbors(struct vty *vty, const char *indent, struct gsm_bts *bts)
+{
+ struct gsm_bts_ref *neigh;
+
+ llist_for_each_entry(neigh, &bts->local_neighbors, entry) {
+ vty_out(vty, "%sneighbor bts %u%s", indent, neigh->bts->nr, VTY_NEWLINE);
+ }
+}
+
+void neighbor_ident_vty_write(struct vty *vty, const char *indent, struct gsm_bts *bts)
+{
+ neighbor_ident_vty_write_local_neighbors(vty, indent, bts);
+ neighbor_ident_vty_write_remote_bss(vty, indent, bts);
+}
+
+DEFUN(show_bts_neighbor, show_bts_neighbor_cmd,
+ "show bts <0-255> neighbor " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
+ SHOW_STR "Display information about a BTS\n" "BTS number\n"
+ "Query which cell would be the target for this neighbor ARFCN+BSIC\n"
+ NEIGHBOR_IDENT_VTY_KEY_DOC)
+{
+ int found = 0;
+ struct neighbor_ident_key key;
+ struct gsm_bts_ref *neigh;
+ const struct gsm0808_cell_id_list2 *res;
+ struct gsm_bts *bts = gsm_bts_num(g_net, atoi(argv[0]));
+ struct write_neighbor_ident_entry_data d = {
+ .vty = vty,
+ .indent = "% ",
+ .bts = bts,
+ };
+
+ if (!bts) {
+ vty_out(vty, "%% Error: cannot find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!neighbor_ident_bts_parse_key_params(vty, bts, &argv[1], &key))
+ return CMD_WARNING;
+
+ /* Is there a local BTS that matches the key? */
+ llist_for_each_entry(neigh, &bts->local_neighbors, entry) {
+ if (!neighbor_ident_key_matches_bts(&key, neigh->bts))
+ continue;
+ vty_out(vty, "%% %s resolves to local BTS %u lac-ci %u %u%s",
+ neighbor_ident_key_name(&key), neigh->bts->nr, neigh->bts->location_area_code,
+ neigh->bts->cell_identity, VTY_NEWLINE);
+ found++;
+ }
+
+ res = neighbor_ident_get(g_neighbor_cells, &key);
+ if (res) {
+ write_neighbor_ident_list(&key, res, &d);
+ found++;
+ }
+
+ if (!found)
+ vty_out(vty, "%% No entry for %s%s", neighbor_ident_key_name(&key), VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+void neighbor_ident_vty_init(struct gsm_network *net, struct neighbor_ident_list *nil)
+{
+ g_net = net;
+ g_neighbor_cells = nil;
+ install_element(BTS_NODE, &cfg_neighbor_add_bts_nr_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_add_lac_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_add_lac_ci_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_add_cgi_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_add_lac_arfcn_bsic_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_add_lac_ci_arfcn_bsic_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_add_cgi_arfcn_bsic_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_del_bts_nr_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_del_arfcn_bsic_cmd);
+ install_element_ve(&show_bts_neighbor_cmd);
+}
diff --git a/src/osmo-bsc/net_init.c b/src/osmo-bsc/net_init.c
new file mode 100644
index 000000000..1199bdc82
--- /dev/null
+++ b/src/osmo-bsc/net_init.c
@@ -0,0 +1,85 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/gsm_timers.h>
+
+static struct T_def gsm_network_T_defs[] = {
+ { .T=7, .default_val=10, .desc="inter-BSC Handover MO, HO Required to HO Command" },
+ { .T=8, .default_val=10, .desc="inter-BSC Handover MO, HO Command to final Clear" },
+ { .T=10, .default_val=6, .desc="RR Assignment" },
+ { .T=101, .default_val=10, .desc="inter-BSC Handover MT, HO Request to HO Accept" },
+ { .T=3101, .default_val=3, .desc="RR Immediate Assignment" },
+ { .T=3103, .default_val=5, .desc="Handover" },
+ { .T=3105, .default_val=100, .unit=T_MS, .desc="Physical Information" },
+ { .T=3107, .default_val=5, .desc="(unused)" },
+ { .T=3109, .default_val=5, .desc="RSL SACCH deactivation" },
+ { .T=3111, .default_val=2, .desc="Wait time before RSL RF Channel Release" },
+ { .T=993111, .default_val=4, .desc="Wait time after lchan was released in error (should be T3111 + 2s)" },
+ { .T=3113, .default_val=10, .desc="Paging"},
+ { .T=3115, .default_val=10, .desc="(unused)" },
+ { .T=3117, .default_val=10, .desc="(unused)" },
+ { .T=3119, .default_val=10, .desc="(unused)" },
+ { .T=3122, .default_val=GSM_T3122_DEFAULT, .desc="Wait time after RR Immediate Assignment Reject" },
+ { .T=3141, .default_val=10, .desc="(unused)" },
+ { .T=3212, .default_val=5, .unit=T_CUSTOM,
+ .desc="Periodic Location Update timer, sent to MS (1 = 6 minutes)" },
+ { .T=993210, .default_val=20, .desc="After L3 Complete, wait for MSC to confirm" },
+ { .T=999, .default_val=60, .desc="After Clear Request, wait for MSC to Clear Command (sanity)" },
+ { .T=992427, .default_val=4, .desc="MGCP timeout (2427 is the default MGCP port number)" },
+ {}
+};
+
+/* 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);
+
+ 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->T_defs = gsm_network_T_defs;
+ T_defs_reset(net->T_defs);
+
+ return net;
+}
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
new file mode 100644
index 000000000..f03fb6ea3
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -0,0 +1,1136 @@
+/* GSM 08.08 BSSMAP handling */
+/* (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
+ * (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/osmo_bsc_grace.h>
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/codec_pref.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/handover_fsm.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/osmo_bsc_lcls.h>
+#include <osmocom/bsc/a_reset.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/socket.h>
+
+#define IP_V4_ADDR_LEN 4
+
+/*
+ * helpers for the assignment command
+ */
+
+
+static int bssmap_handle_reset_ack(struct bsc_msc_data *msc,
+ struct msgb *msg, unsigned int length)
+{
+ LOGP(DMSC, LOGL_NOTICE, "RESET ACK from MSC: %s\n",
+ osmo_sccp_addr_name(osmo_ss7_instance_find(msc->a.cs7_instance),
+ &msc->a.msc_addr));
+
+ /* Inform the FSM that controls the RESET/RESET-ACK procedure
+ * that we have successfully received the reset-ack message */
+ a_reset_ack_confirm(msc);
+
+ return 0;
+}
+
+/* Handle MSC sided reset */
+static int bssmap_handle_reset(struct bsc_msc_data *msc,
+ struct msgb *msg, unsigned int length)
+{
+ LOGP(DMSC, LOGL_NOTICE, "RESET from MSC: %s\n",
+ osmo_sccp_addr_name(osmo_ss7_instance_find(msc->a.cs7_instance),
+ &msc->a.msc_addr));
+
+ /* Instruct the bsc to close all open sigtran connections and to
+ * close all active channels on the BTS side as well */
+ osmo_bsc_sigtran_reset(msc);
+
+ /* Drop all ongoing paging requests that this MSC has created on any BTS */
+ paging_flush_network(msc->network, msc);
+
+ /* Inform the MSC that we have received the reset request and
+ * that we acted accordingly */
+ osmo_bsc_sigtran_tx_reset_ack(msc);
+
+ return 0;
+}
+
+/* Page a subscriber based on TMSI and LAC via the specified BTS.
+ * The msc parameter is the MSC which issued the corresponding paging request.
+ * Log an error if paging failed. */
+static void
+page_subscriber(struct bsc_msc_data *msc, struct gsm_bts *bts,
+ uint32_t tmsi, uint32_t lac, const char *mi_string, uint8_t chan_needed)
+{
+ struct bsc_subscr *subscr;
+ int ret;
+
+ LOGP(DMSC, LOGL_INFO, "Paging request from MSC BTS: %d IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n",
+ bts->nr, mi_string, tmsi, tmsi, lac);
+
+ subscr = bsc_subscr_find_or_create_by_imsi(msc->network->bsc_subscribers,
+ mi_string);
+ if (!subscr) {
+ LOGP(DMSC, LOGL_ERROR, "Paging request failed: Could not allocate subscriber for %s\n", mi_string);
+ return;
+ }
+
+ subscr->lac = lac;
+ subscr->tmsi = tmsi;
+
+ ret = bsc_grace_paging_request(msc->network->bsc_data->rf_ctrl->policy, subscr, chan_needed, msc, bts);
+ if (ret == 0)
+ LOGP(DMSC, LOGL_ERROR, "Paging request failed: BTS: %d IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n",
+ bts->nr, mi_string, tmsi, tmsi, lac);
+
+ /* the paging code has grabbed its own references */
+ bsc_subscr_put(subscr);
+}
+
+static void
+page_all_bts(struct bsc_msc_data *msc, uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+{
+ struct gsm_bts *bts;
+ llist_for_each_entry(bts, &msc->network->bts_list, list)
+ page_subscriber(msc, bts, tmsi, GSM_LAC_RESERVED_ALL_BTS, mi_string, chan_needed);
+}
+
+static void
+page_cgi(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
+ uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+{
+ int i;
+ for (i = 0; i < cil->id_list_len; i++) {
+ struct osmo_cell_global_id *id = &cil->id_list[i].global;
+ if (!osmo_plmn_cmp(&id->lai.plmn, &msc->network->plmn)) {
+ int paged = 0;
+ struct gsm_bts *bts;
+ llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ if (bts->location_area_code != id->lai.lac)
+ continue;
+ if (bts->cell_identity != id->cell_identity)
+ continue;
+ page_subscriber(msc, bts, tmsi, id->lai.lac, mi_string, chan_needed);
+ paged = 1;
+ }
+ if (!paged) {
+ LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with LAC %d and CI %d not found\n",
+ mi_string, id->lai.lac, id->cell_identity);
+ }
+ } else {
+ LOGP(DMSC, LOGL_DEBUG, "Paging IMSI %s: MCC-MNC in Cell Identifier List "
+ "(%s) do not match our network (%s)\n",
+ mi_string, osmo_plmn_name(&id->lai.plmn),
+ osmo_plmn_name2(&msc->network->plmn));
+ }
+ }
+}
+
+static void
+page_lac_and_ci(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
+ uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+{
+ int i;
+
+ for (i = 0; i < cil->id_list_len; i++) {
+ struct osmo_lac_and_ci_id *id = &cil->id_list[i].lac_and_ci;
+ int paged = 0;
+ struct gsm_bts *bts;
+ llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ if (bts->location_area_code != id->lac)
+ continue;
+ if (bts->cell_identity != id->ci)
+ continue;
+ page_subscriber(msc, bts, tmsi, id->lac, mi_string, chan_needed);
+ paged = 1;
+ }
+ if (!paged) {
+ LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with LAC %d and CI %d not found\n",
+ mi_string, id->lac, id->ci);
+ }
+ }
+}
+
+static void
+page_ci(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
+ uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+{
+ int i;
+
+ for (i = 0; i < cil->id_list_len; i++) {
+ uint16_t ci = cil->id_list[i].ci;
+ int paged = 0;
+ struct gsm_bts *bts;
+ llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ if (bts->cell_identity != ci)
+ continue;
+ page_subscriber(msc, bts, tmsi, GSM_LAC_RESERVED_ALL_BTS, mi_string, chan_needed);
+ paged = 1;
+ }
+ if (!paged) {
+ LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with CI %d not found\n",
+ mi_string, ci);
+ }
+ }
+}
+
+static void
+page_lai_and_lac(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
+ uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+{
+ int i;
+
+ for (i = 0; i < cil->id_list_len; i++) {
+ struct osmo_location_area_id *id = &cil->id_list[i].lai_and_lac;
+ if (!osmo_plmn_cmp(&id->plmn, &msc->network->plmn)) {
+ int paged = 0;
+ struct gsm_bts *bts;
+ llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ if (bts->location_area_code != id->lac)
+ continue;
+ page_subscriber(msc, bts, tmsi, id->lac, mi_string, chan_needed);
+ paged = 1;
+ }
+ if (!paged) {
+ LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with LAC %d not found\n",
+ mi_string, id->lac);
+ }
+ } else {
+ LOGP(DMSC, LOGL_DEBUG, "Paging IMSI %s: MCC-MNC in Cell Identifier List "
+ "(%s) do not match our network (%s)\n",
+ mi_string, osmo_plmn_name(&id->plmn),
+ osmo_plmn_name2(&msc->network->plmn));
+ }
+ }
+}
+
+static void
+page_lac(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
+ uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+{
+ int i;
+
+ for (i = 0; i < cil->id_list_len; i++) {
+ uint16_t lac = cil->id_list[i].lac;
+ int paged = 0;
+ struct gsm_bts *bts;
+ llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ if (bts->location_area_code != lac)
+ continue;
+ page_subscriber(msc, bts, tmsi, lac, mi_string, chan_needed);
+ paged = 1;
+ }
+ if (!paged) {
+ LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with LAC %d not found\n",
+ mi_string, lac);
+ }
+ }
+}
+
+/* GSM 08.08 § 3.2.1.19 */
+static int bssmap_handle_paging(struct bsc_msc_data *msc,
+ struct msgb *msg, unsigned int payload_length)
+{
+ struct tlv_parsed tp;
+ char mi_string[GSM48_MI_SIZE];
+ uint32_t tmsi = GSM_RESERVED_TMSI;
+ uint8_t data_length;
+ int remain;
+ const uint8_t *data;
+ uint8_t chan_needed = RSL_CHANNEED_ANY;
+ struct gsm0808_cell_id_list2 cil;
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+ remain = payload_length - 1;
+
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) {
+ LOGP(DMSC, LOGL_ERROR, "Mandatory IMSI not present.\n");
+ return -1;
+ } else if ((TLVP_VAL(&tp, GSM0808_IE_IMSI)[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI) {
+ LOGP(DMSC, LOGL_ERROR, "Wrong content in the IMSI\n");
+ return -1;
+ }
+ remain -= TLVP_LEN(&tp, GSM0808_IE_IMSI);
+
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) {
+ LOGP(DMSC, LOGL_ERROR, "Mandatory CELL IDENTIFIER LIST not present.\n");
+ return -1;
+ }
+
+ if (TLVP_PRESENT(&tp, GSM0808_IE_TMSI) &&
+ TLVP_LEN(&tp, GSM0808_IE_TMSI) == 4) {
+ tmsi = ntohl(tlvp_val32_unal(&tp, GSM0808_IE_TMSI));
+ remain -= TLVP_LEN(&tp, GSM0808_IE_TMSI);
+ }
+
+ if (remain <= 0) {
+ LOGP(DMSC, LOGL_ERROR, "Payload too short.\n");
+ return -1;
+ }
+
+ /*
+ * parse the IMSI
+ */
+ gsm48_mi_to_string(mi_string, sizeof(mi_string),
+ TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI));
+
+ /*
+ * There are various cell identifier list types defined at 3GPP TS § 08.08, we don't support all
+ * of them yet. To not disrupt paging operation just because we're lacking some implementation,
+ * interpret any unknown cell identifier type as "page the entire BSS".
+ */
+ data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+ data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+ if (gsm0808_dec_cell_id_list2(&cil, data, data_length) < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Paging IMSI %s: Could not parse Cell Identifier List\n",
+ mi_string);
+ return -1;
+ }
+ remain = 0;
+
+ if (TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_NEEDED) && TLVP_LEN(&tp, GSM0808_IE_CHANNEL_NEEDED) == 1)
+ chan_needed = TLVP_VAL(&tp, GSM0808_IE_CHANNEL_NEEDED)[0] & 0x03;
+
+ if (TLVP_PRESENT(&tp, GSM0808_IE_EMLPP_PRIORITY)) {
+ LOGP(DMSC, LOGL_ERROR, "eMLPP is not handled\n");
+ }
+
+ rate_ctr_inc(&msc->network->bsc_ctrs->ctr[BSC_CTR_PAGING_ATTEMPTED]);
+
+ switch (cil.id_discr) {
+ case CELL_IDENT_NO_CELL:
+ page_all_bts(msc, tmsi, mi_string, chan_needed);
+ break;
+
+ case CELL_IDENT_WHOLE_GLOBAL:
+ page_cgi(msc, &cil, tmsi, mi_string, chan_needed);
+ break;
+
+ case CELL_IDENT_LAC_AND_CI:
+ page_lac_and_ci(msc, &cil, tmsi, mi_string, chan_needed);
+ break;
+
+ case CELL_IDENT_CI:
+ page_ci(msc, &cil, tmsi, mi_string, chan_needed);
+ break;
+
+ case CELL_IDENT_LAI_AND_LAC:
+ page_lai_and_lac(msc, &cil, tmsi, mi_string, chan_needed);
+ break;
+
+ case CELL_IDENT_LAC:
+ page_lac(msc, &cil, tmsi, mi_string, chan_needed);
+ break;
+
+ case CELL_IDENT_BSS:
+ if (data_length != 1) {
+ LOGP(DMSC, LOGL_ERROR, "Paging IMSI %s: Cell Identifier List for BSS (0x%x)"
+ " has invalid length: %u, paging entire BSS anyway (%s)\n",
+ mi_string, CELL_IDENT_BSS, data_length, osmo_hexdump(data, data_length));
+ }
+ page_all_bts(msc, tmsi, mi_string, chan_needed);
+ break;
+
+ default:
+ LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: unimplemented Cell Identifier List (0x%x),"
+ " paging entire BSS instead (%s)\n",
+ mi_string, cil.id_discr, osmo_hexdump(data, data_length));
+ page_all_bts(msc, tmsi, mi_string, chan_needed);
+ break;
+ }
+
+ return 0;
+}
+
+/* select the best cipher permitted by the intersection of both masks */
+static int select_best_cipher(uint8_t msc_mask, uint8_t bsc_mask)
+{
+ uint8_t intersection = msc_mask & bsc_mask;
+ int i;
+
+ for (i = 7; i >= 0; i--) {
+ if (intersection & (1 << i))
+ return i;
+ }
+ return -1;
+}
+
+/*! We received a GSM 08.08 CIPHER MODE from the MSC */
+static int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
+ const uint8_t *key, int len, int include_imeisv)
+{
+ if (cipher > 0 && key == NULL) {
+ LOGP(DRSL, LOGL_ERROR, "%s: Need to have an encryption key.\n",
+ bsc_subscr_name(conn->bsub));
+ return -1;
+ }
+
+ if (len > MAX_A5_KEY_LEN) {
+ LOGP(DRSL, LOGL_ERROR, "%s: The key is too long: %d\n",
+ bsc_subscr_name(conn->bsub), len);
+ return -1;
+ }
+
+ LOGP(DRSL, LOGL_DEBUG, "(subscr %s) Cipher Mode: cipher=%d key=%s include_imeisv=%d\n",
+ bsc_subscr_name(conn->bsub), cipher, osmo_hexdump_nospc(key, len), include_imeisv);
+
+ conn->lchan->encr.alg_id = RSL_ENC_ALG_A5(cipher);
+ if (key) {
+ conn->lchan->encr.key_len = len;
+ memcpy(conn->lchan->encr.key, key, len);
+ }
+
+ return gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv);
+}
+
+/*
+ * GSM 08.08 § 3.1.14 cipher mode handling. We will have to pick
+ * the cipher to be used for this. In case we are already using
+ * a cipher we will have to send cipher mode reject to the MSC,
+ * otherwise we will have to pick something that we and the MS
+ * is supporting. Currently we are doing it in a rather static
+ * way by picking one encryption or no encryption.
+ */
+static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int payload_length)
+{
+ uint16_t len;
+ struct gsm_network *network = NULL;
+ const uint8_t *data;
+ struct tlv_parsed tp;
+ struct msgb *resp;
+ int reject_cause = -1;
+ int include_imeisv = 1;
+ const uint8_t *enc_key;
+ uint16_t enc_key_len;
+ uint8_t enc_bits_msc;
+ int chosen_cipher;
+
+ if (!conn) {
+ LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n");
+ return -1;
+ }
+
+ if (conn->ciphering_handled) {
+ LOGP(DMSC, LOGL_ERROR, "Already seen ciphering command. Protocol Error.\n");
+ reject_cause = GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC;
+ goto reject;
+ }
+
+ conn->ciphering_handled = 1;
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_ENCRYPTION_INFORMATION)) {
+ LOGP(DMSC, LOGL_ERROR, "IE Encryption Information missing.\n");
+ reject_cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+
+ /*
+ * check if our global setting is allowed
+ * - Currently we check for A5/0 and A5/1
+ * - Copy the key if that is necessary
+ * - Otherwise reject
+ */
+ len = TLVP_LEN(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+ if (len < 1) {
+ LOGP(DMSC, LOGL_ERROR, "IE Encryption Information is too short.\n");
+ reject_cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+
+ network = conn_get_bts(conn)->network;
+ data = TLVP_VAL(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+ enc_bits_msc = data[0];
+ enc_key = &data[1];
+ enc_key_len = len - 1;
+
+ if (TLVP_PRESENT(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE))
+ include_imeisv = TLVP_VAL(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)[0] & 0x1;
+
+ /* Identical to the GSM0808_IE_ENCRYPTION_INFORMATION above:
+ * a5_encryption == 0 --> 0x01
+ * a5_encryption == 1 --> 0x02
+ * a5_encryption == 2 --> 0x04 ... */
+ enc_bits_msc = data[0];
+
+ /* The bit-mask of permitted ciphers from the MSC (sent in ASSIGNMENT COMMAND) is intersected
+ * with the vty-configured mask a the BSC. Finally, the best (highest) possible cipher is
+ * chosen. */
+ chosen_cipher = select_best_cipher(enc_bits_msc, network->a5_encryption_mask);
+ if (chosen_cipher < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Reject: no overlapping A5 ciphers between BSC (0x%02x) "
+ "and MSC (0x%02x)\n", network->a5_encryption_mask, enc_bits_msc);
+ reject_cause = GSM0808_CAUSE_CIPHERING_ALGORITHM_NOT_SUPPORTED;
+ goto reject;
+ }
+
+ /* To complete the confusion, gsm0808_cipher_mode again expects the encryption as a number
+ * from 0 to 7. */
+ if (gsm0808_cipher_mode(conn, chosen_cipher, enc_key, enc_key_len,
+ include_imeisv)) {
+ reject_cause = GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC;
+ goto reject;
+ }
+ return 0;
+
+reject:
+ resp = gsm0808_create_cipher_reject(reject_cause);
+ if (!resp) {
+ LOGP(DMSC, LOGL_ERROR, "Sending the cipher reject failed.\n");
+ return -1;
+ }
+
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+ return -1;
+}
+
+/* handle LCLS specific IES in BSSMAP ASS REQ */
+static void bssmap_handle_ass_req_lcls(struct gsm_subscriber_connection *conn,
+ const struct tlv_parsed *tp)
+{
+ const uint8_t *config, *control, *gcr, gcr_len = TLVP_LEN(tp, GSM0808_IE_GLOBAL_CALL_REF);
+
+ if (gcr_len > sizeof(conn->lcls.global_call_ref))
+ LOGPFSML(conn->fi, LOGL_ERROR, "Global Call Ref IE of %u bytes is too long\n",
+ gcr_len);
+ else {
+ gcr = TLVP_VAL_MINLEN(tp, GSM0808_IE_GLOBAL_CALL_REF, 13);
+ if (gcr) {
+ LOGPFSM(conn->fi, "Setting GCR to %s\n", osmo_hexdump_nospc(gcr, gcr_len));
+ memcpy(&conn->lcls.global_call_ref, gcr, gcr_len);
+ conn->lcls.global_call_ref_len = gcr_len;
+ } else
+ LOGPFSML(conn->fi, LOGL_ERROR, "Global Call Ref IE of %u bytes is too short\n",
+ gcr_len);
+ }
+
+ config = TLVP_VAL_MINLEN(tp, GSM0808_IE_LCLS_CONFIG, 1);
+ control = TLVP_VAL_MINLEN(tp, GSM0808_IE_LCLS_CONN_STATUS_CTRL, 1);
+
+ if (config || control) {
+ LOGPFSM(conn->fi, "BSSMAP ASS REQ contains LCLS (%s / %s)\n",
+ config ? gsm0808_lcls_config_name(*config) : "NULL",
+ control ? gsm0808_lcls_control_name(*control) : "NULL");
+ }
+
+ /* Update the LCLS state with Config + CSC (if any) */
+ lcls_update_config(conn, config, control);
+
+ /* Do not attempt to perform correlation yet, as during processing of the ASS REQ
+ * we don't have the MGCP/MGW connections yet, and hence couldn't enable LS. */
+}
+
+/* TS 48.008 3.2.1.91 */
+static int bssmap_handle_lcls_connect_ctrl(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct msgb *resp;
+ struct tlv_parsed tp;
+ const uint8_t *config, *control;
+ int rc;
+
+ OSMO_ASSERT(conn);
+
+ rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+ if (rc < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Error parsing TLVs of LCLS CONNT CTRL: %s\n",
+ msgb_hexdump(msg));
+ return rc;
+ }
+ config = TLVP_VAL_MINLEN(&tp, GSM0808_IE_LCLS_CONFIG, 1);
+ control = TLVP_VAL_MINLEN(&tp, GSM0808_IE_LCLS_CONN_STATUS_CTRL, 1);
+
+ LOGPFSM(conn->fi, "Rx LCLS CONNECT CTRL (%s / %s)\n",
+ config ? gsm0808_lcls_config_name(*config) : "NULL",
+ control ? gsm0808_lcls_control_name(*control) : "NULL");
+
+ if (conn->lcls.global_call_ref_len == 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Ignoring LCLS as no GCR was set before\n");
+ return 0;
+ }
+ /* Update the LCLS state with Config + CSC (if any) */
+ lcls_update_config(conn, TLVP_VAL_MINLEN(&tp, GSM0808_IE_LCLS_CONFIG, 1),
+ TLVP_VAL_MINLEN(&tp, GSM0808_IE_LCLS_CONN_STATUS_CTRL, 1));
+ lcls_apply_config(conn);
+
+ LOGPFSM(conn->fi, "Tx LCLS CONNECT CTRL ACK (%s)\n",
+ gsm0808_lcls_status_name(lcls_get_status(conn)));
+ resp = gsm0808_create_lcls_conn_ctrl_ack(lcls_get_status(conn));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+
+ return 0;
+}
+
+/*
+ * Handle the assignment request message.
+ *
+ * See §3.2.1.1 for the message type
+ */
+static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct msgb *resp;
+ struct bsc_msc_data *msc;
+ struct tlv_parsed tp;
+ uint16_t cic = 0;
+ enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN;
+ bool full_rate = false;
+ uint16_t s15_s0 = 0;
+ bool aoip = false;
+ struct sockaddr_storage rtp_addr;
+ struct gsm0808_channel_type ct;
+ uint8_t cause;
+ int rc;
+ struct assignment_request req = {};
+
+ if (!conn) {
+ LOGP(DMSC, LOGL_ERROR,
+ "No lchan/msc_data in Assignment Request\n");
+ return -1;
+ }
+
+ msc = conn->sccp.msc;
+ aoip = gscon_is_aoip(conn);
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+
+ /* Check for channel type element, if its missing, immediately reject */
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)) {
+ LOGP(DMSC, LOGL_ERROR, "Mandatory channel type not present.\n");
+ cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+
+ /* Decode Channel Type element */
+ rc = gsm0808_dec_channel_type(&ct, TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE),
+ TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "unable to decode channel type.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+
+ bssmap_handle_ass_req_lcls(conn, &tp);
+
+ /* Currently we only support a limited subset of all
+ * possible channel types, such as multi-slot or CSD */
+ switch (ct.ch_indctr) {
+ case GSM0808_CHAN_DATA:
+ LOGP(DMSC, LOGL_ERROR, "Unsupported channel type, currently only speech is supported!\n");
+ cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP;
+ goto reject;
+ case GSM0808_CHAN_SPEECH:
+ if (TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+ /* CIC is permitted in both AoIP and SCCPlite */
+ cic = osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+ } else {
+ if (!aoip) {
+ /* no CIC but SCCPlite: illegal */
+ LOGP(DMSC, LOGL_ERROR, "SCCPlite MSC, but no CIC in ASSIGN REQ?\n");
+ cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+ }
+ if (TLVP_PRESENT(&tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
+ if (!aoip) {
+ /* SCCPlite and AoIP transport address: illegal */
+ LOGP(DMSC, LOGL_ERROR, "AoIP Transport address over IPA ?!?\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ /* Decode AoIP transport address element */
+ rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr,
+ TLVP_VAL(&tp, GSM0808_IE_AOIP_TRASP_ADDR),
+ TLVP_LEN(&tp, GSM0808_IE_AOIP_TRASP_ADDR));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ } else if (aoip) {
+ /* no AoIP transport level address but AoIP transport: illegal */
+ LOGP(DMSC, LOGL_ERROR, "AoIP transport address missing in ASSIGN REQ, "
+ "audio would not work; rejecting\n");
+ cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+
+ /* Decode speech codec list. First set len = 0. */
+ conn->codec_list = (struct gsm0808_speech_codec_list){};
+ /* Check for speech codec list element */
+ if (TLVP_PRESENT(&tp, GSM0808_IE_SPEECH_CODEC_LIST)) {
+ /* Decode Speech Codec list */
+ rc = gsm0808_dec_speech_codec_list(&conn->codec_list,
+ TLVP_VAL(&tp, GSM0808_IE_SPEECH_CODEC_LIST),
+ TLVP_LEN(&tp, GSM0808_IE_SPEECH_CODEC_LIST));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ }
+
+ if (aoip && !conn->codec_list.len) {
+ LOGP(DMSC, LOGL_ERROR, "%s: AoIP speech mode Assignment Request:"
+ " Missing or empty Speech Codec List IE\n", bsc_subscr_name(conn->bsub));
+ cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+
+ /* Match codec information from the assignment command against the
+ * local preferences of the BSC and BTS */
+ rc = match_codec_pref(&chan_mode, &full_rate, &s15_s0, &ct, &conn->codec_list,
+ msc, conn_get_bts(conn));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "No supported audio type found for channel_type ="
+ " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
+ ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len));
+ /* TODO: actually output codec names, e.g. implement
+ * gsm0808_permitted_speech_names[] and iterate perm_spch. */
+ cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
+ goto reject;
+ }
+
+ DEBUGP(DMSC, "Found matching audio type: %s %s for channel_type ="
+ " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
+ full_rate? "full rate" : "half rate",
+ get_value_string(gsm48_chan_mode_names, chan_mode),
+ ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len));
+
+ req = (struct assignment_request){
+ .aoip = aoip,
+ .msc_assigned_cic = cic,
+ .chan_mode = chan_mode,
+ .full_rate = full_rate,
+ .s15_s0 = s15_s0
+ };
+ if (aoip) {
+ unsigned int rc = osmo_sockaddr_to_str_and_uint(req.msc_rtp_addr,
+ sizeof(req.msc_rtp_addr),
+ &req.msc_rtp_port,
+ (const struct sockaddr*)&rtp_addr);
+ if (!rc || rc >= sizeof(req.msc_rtp_addr)) {
+ LOGP(DMSC, LOGL_ERROR, "Assignment request: RTP address is too long\n");
+ cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
+ goto reject;
+ }
+ }
+ break;
+ case GSM0808_CHAN_SIGN:
+ req = (struct assignment_request){
+ .aoip = aoip,
+ .chan_mode = chan_mode,
+ };
+ break;
+ default:
+ cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
+ goto reject;
+ }
+
+ return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req);
+
+reject:
+ resp = gsm0808_create_assignment_failure(cause, NULL);
+ OSMO_ASSERT(resp);
+
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+ return -1;
+}
+
+/* Handle Handover Command message, part of inter-BSC handover:
+ * This BSS sent a Handover Required message.
+ * The MSC contacts the remote BSS and receives from it an RR Handover Command; this BSSMAP Handover
+ * Command passes the RR Handover Command over to us and it's our job to forward to the MS.
+ *
+ * See 3GPP TS 48.008 §3.2.1.11
+ */
+static int bssmap_handle_handover_cmd(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+
+ if (!conn->ho.fi) {
+ LOGPFSML(conn->fi, LOGL_ERROR,
+ "Received Handover Command, but no handover was requested\n");
+ /* Should we actually allow the MSC to make us handover without us having requested it
+ * first? Doesn't make any practical sense AFAICT. */
+ return -EINVAL;
+ }
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+
+ /* Check for channel type element, if its missing, immediately reject */
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_INFORMATION)) {
+ LOGPFSML(conn->fi, LOGL_ERROR,
+ "Received Handover Command,"
+ " but mandatory IE not present: Layer 3 Information\n");
+ goto reject;
+ }
+
+ /* Due to constness, need to declare this after tlv_parse(). */
+ struct ho_out_rx_bssmap_ho_command rx = {
+ .l3_info = TLVP_VAL(&tp, GSM0808_IE_LAYER_3_INFORMATION),
+ .l3_info_len = TLVP_LEN(&tp, GSM0808_IE_LAYER_3_INFORMATION),
+ };
+
+ osmo_fsm_inst_dispatch(conn->ho.fi, HO_OUT_EV_BSSMAP_HO_COMMAND, &rx);
+ return 0;
+reject:
+ /* No "Handover Command Reject" message or similar is specified, so we cannot reply in case of
+ * failure. Or is there?? */
+ handover_end(conn, HO_RESULT_ERROR);
+ return -EINVAL;
+}
+
+static int bssmap_rcvmsg_udt(struct bsc_msc_data *msc,
+ struct msgb *msg, unsigned int length)
+{
+ int ret = 0;
+
+ if (length < 1) {
+ LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
+ return -1;
+ }
+
+ LOGP(DMSC, LOGL_INFO, "Rx MSC UDT BSSMAP %s\n",
+ gsm0808_bssmap_name(msg->l4h[0]));
+
+ switch (msg->l4h[0]) {
+ case BSS_MAP_MSG_RESET_ACKNOWLEDGE:
+ ret = bssmap_handle_reset_ack(msc, msg, length);
+ break;
+ case BSS_MAP_MSG_RESET:
+ ret = bssmap_handle_reset(msc, msg, length);
+ break;
+ case BSS_MAP_MSG_PAGING:
+ ret = bssmap_handle_paging(msc, msg, length);
+ break;
+ default:
+ LOGP(DMSC, LOGL_NOTICE, "Received unimplemented BSSMAP UDT %s\n",
+ gsm0808_bssmap_name(msg->l4h[0]));
+ break;
+ }
+
+ return ret;
+}
+
+static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ int ret = 0;
+
+ if (length < 1) {
+ LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
+ return -1;
+ }
+
+ LOGP(DMSC, LOGL_INFO, "Rx MSC DT1 BSSMAP %s\n",
+ gsm0808_bssmap_name(msg->l4h[0]));
+
+ switch (msg->l4h[0]) {
+ case BSS_MAP_MSG_CLEAR_CMD:
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, msg);
+ break;
+ case BSS_MAP_MSG_CIPHER_MODE_CMD:
+ ret = bssmap_handle_cipher_mode(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_ASSIGMENT_RQST:
+ ret = bssmap_handle_assignm_req(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_LCLS_CONNECT_CTRL:
+ ret = bssmap_handle_lcls_connect_ctrl(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_HANDOVER_CMD:
+ ret = bssmap_handle_handover_cmd(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_CLASSMARK_RQST:
+ ret = gsm48_send_rr_classmark_enquiry(conn->lchan);
+ break;
+ default:
+ LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n",
+ gsm0808_bssmap_name(msg->l4h[0]));
+ break;
+ }
+
+ return ret;
+}
+
+int bsc_send_welcome_ussd(struct gsm_subscriber_connection *conn)
+{
+ bsc_send_ussd_notify(conn, 1, conn->sccp.msc->ussd_welcome_txt);
+ bsc_send_ussd_release_complete(conn);
+
+ return 0;
+}
+
+static int dtap_rcvmsg(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct dtap_header *header;
+ struct msgb *gsm48;
+ uint8_t *data;
+ int rc, dtap_rc;
+
+ LOGP(DMSC, LOGL_DEBUG, "Rx MSC DTAP: %s\n",
+ osmo_hexdump(msg->l3h, length));
+
+ if (!conn) {
+ LOGP(DMSC, LOGL_ERROR, "No subscriber connection available\n");
+ return -1;
+ }
+
+ header = (struct dtap_header *) msg->l3h;
+ if (sizeof(*header) >= length) {
+ LOGP(DMSC, LOGL_ERROR, "The DTAP header does not fit. Wanted: %zu got: %u\n", sizeof(*header), length);
+ LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length));
+ return -1;
+ }
+
+ if (header->length > length - sizeof(*header)) {
+ LOGP(DMSC, LOGL_ERROR, "The DTAP l4 information does not fit: header: %u length: %u\n", header->length, length);
+ LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length));
+ return -1;
+ }
+
+ LOGP(DMSC, LOGL_INFO, "Rx MSC DTAP, SAPI: %u CHAN: %u\n", header->link_id & 0x07, header->link_id & 0xC0);
+
+ /* forward the data */
+ gsm48 = gsm48_msgb_alloc_name("GSM 04.08 DTAP RCV");
+ if (!gsm48) {
+ LOGP(DMSC, LOGL_ERROR, "Allocation of the message failed.\n");
+ return -1;
+ }
+
+ gsm48->l3h = gsm48->data;
+ data = msgb_put(gsm48, length - sizeof(*header));
+ memcpy(data, msg->l3h + sizeof(*header), length - sizeof(*header));
+
+ /* pass it to the filter for extra actions */
+ rc = bsc_scan_msc_msg(conn, gsm48);
+ /* Store link_id in msgb->cb */
+ OBSC_LINKID_CB(msg) = header->link_id;
+ dtap_rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, gsm48);
+ if (rc == BSS_SEND_USSD)
+ bsc_send_welcome_ussd(conn);
+ return dtap_rc;
+}
+
+int bsc_handle_udt(struct bsc_msc_data *msc,
+ struct msgb *msgb, unsigned int length)
+{
+ struct bssmap_header *bs;
+
+ LOGP(DMSC, LOGL_DEBUG, "Rx MSC UDT: %s\n",
+ osmo_hexdump(msgb->l3h, length));
+
+ if (length < sizeof(*bs)) {
+ LOGP(DMSC, LOGL_ERROR, "The header is too short.\n");
+ return -1;
+ }
+
+ bs = (struct bssmap_header *) msgb->l3h;
+ if (bs->length < length - sizeof(*bs))
+ return -1;
+
+ switch (bs->type) {
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ msgb->l4h = &msgb->l3h[sizeof(*bs)];
+ bssmap_rcvmsg_udt(msc, msgb, length - sizeof(*bs));
+ break;
+ default:
+ LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n",
+ gsm0808_bssmap_name(bs->type));
+ }
+
+ return 0;
+}
+
+int bsc_handle_dt(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int len)
+{
+ if (len < sizeof(struct bssmap_header)) {
+ LOGP(DMSC, LOGL_ERROR, "The header is too short.\n");
+ }
+
+ switch (msg->l3h[0]) {
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ msg->l4h = &msg->l3h[sizeof(struct bssmap_header)];
+ bssmap_rcvmsg_dt1(conn, msg, len - sizeof(struct bssmap_header));
+ break;
+ case BSSAP_MSG_DTAP:
+ dtap_rcvmsg(conn, msg, len);
+ break;
+ default:
+ LOGP(DMSC, LOGL_NOTICE, "Unimplemented BSSAP msg type: %s\n",
+ gsm0808_bssap_name(msg->l3h[0]));
+ }
+
+ return -1;
+}
+
+int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell_id_list2 *target_cells)
+{
+ int rc;
+ struct msgb *msg;
+ struct gsm0808_handover_required params = {
+ .cause = GSM0808_CAUSE_BETTER_CELL,
+ .cil = *target_cells,
+ .current_channel_type_1_present = true,
+ .current_channel_type_1 = gsm0808_current_channel_type_1(lchan->type),
+ };
+
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ params.speech_version_used_present = true;
+ params.speech_version_used = gsm0808_permitted_speech(lchan->type,
+ lchan->tch_mode);
+ if (!params.speech_version_used) {
+ LOG_HO(lchan->conn, LOGL_ERROR, "Cannot encode Speech Version (Used)"
+ " for BSSMAP Handover Required message\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ msg = gsm0808_create_handover_required(&params);
+ if (!msg) {
+ LOG_HO(lchan->conn, LOGL_ERROR, "Cannot compose BSSMAP Handover Required message\n");
+ return -EINVAL;
+ }
+
+ rc = gscon_sigtran_send(lchan->conn, msg);
+ if (rc) {
+ LOG_HO(lchan->conn, LOGL_ERROR, "Cannot send BSSMAP Handover Required message\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+/* Inter-BSC MT HO, new BSS has allocated a channel and sends the RR Handover Command via MSC to the old
+ * BSS, encapsulated in a BSSMAP Handover Request Acknowledge. */
+int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn, struct msgb *rr_ho_command)
+{
+ struct msgb *msg;
+ struct gsm_lchan *new_lchan = conn->ho.new_lchan;
+
+ LOG_HO(conn, LOGL_DEBUG, "Sending BSSMAP Handover Request Acknowledge\n");
+ msg = gsm0808_create_handover_request_ack(rr_ho_command->data, rr_ho_command->len,
+ gsm0808_chosen_channel(new_lchan->type,
+ new_lchan->tch_mode),
+ new_lchan->encr.alg_id,
+ gsm0808_permitted_speech(new_lchan->type,
+ new_lchan->tch_mode));
+ msgb_free(rr_ho_command);
+ if (!msg)
+ return -ENOMEM;
+ return osmo_bsc_sigtran_send(conn, msg);
+}
+
+int bsc_tx_bssmap_ho_detect(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *msg;
+ msg = gsm0808_create_handover_detect();
+ if (!msg)
+ return -ENOMEM;
+
+ return osmo_bsc_sigtran_send(conn, msg);
+}
+
+enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection *conn,
+ struct gsm_lchan *lchan)
+{
+ int rc;
+ struct msgb *msg;
+ struct handover *ho = &conn->ho;
+ enum gsm0808_lcls_status lcls_status = lcls_get_status(conn);
+
+ struct gsm0808_handover_complete params = {
+ .chosen_encr_alg_present = true,
+ .chosen_encr_alg = lchan->encr.alg_id,
+
+ .chosen_channel_present = true,
+ .chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode),
+
+ .lcls_bss_status_present = (lcls_status != 0xff),
+ .lcls_bss_status = lcls_status,
+ };
+
+ /* speech_codec_chosen */
+ if (ho->new_lchan->activate.requires_voice_stream && gscon_is_aoip(conn)) {
+ int perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode);
+ params.speech_codec_chosen_present = true;
+ rc = gsm0808_speech_codec_from_chan_type(&params.speech_codec_chosen, perm_spch);
+ if (rc) {
+ LOG_HO(conn, LOGL_ERROR, "Unable to compose Speech Codec (Chosen)\n");
+ return HO_RESULT_ERROR;
+ }
+ }
+
+ msg = gsm0808_create_handover_complete(&params);
+ if (!msg) {
+ LOG_HO(conn, LOGL_ERROR, "Unable to compose BSSMAP Handover Complete message\n");
+ return HO_RESULT_ERROR;
+ }
+
+ rc = osmo_bsc_sigtran_send(conn, msg);
+ if (rc) {
+ LOG_HO(conn, LOGL_ERROR, "Cannot send BSSMAP Handover Complete message\n");
+ return HO_RESULT_ERROR;
+ }
+
+ return HO_RESULT_OK;
+}
+
+void bsc_tx_bssmap_ho_failure(struct gsm_subscriber_connection *conn)
+{
+ int rc;
+ struct msgb *msg;
+ struct gsm0808_handover_failure params = {};
+
+ msg = gsm0808_create_handover_failure(&params);
+ if (!msg) {
+ LOG_HO(conn, LOGL_ERROR, "Unable to compose BSSMAP Handover Failure message\n");
+ return;
+ }
+
+ rc = osmo_bsc_sigtran_send(conn, msg);
+ if (rc)
+ LOG_HO(conn, LOGL_ERROR, "Cannot send BSSMAP Handover Failure message (rc=%d %s)\n",
+ rc, strerror(-rc));
+}
diff --git a/src/osmo-bsc/osmo_bsc_ctrl.c b/src/osmo-bsc/osmo_bsc_ctrl.c
new file mode 100644
index 000000000..80699f877
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_ctrl.c
@@ -0,0 +1,778 @@
+/* (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2011 by Holger Hans Peter Freyther
+ * (C) 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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/bsc/ctrl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/gsm_04_80.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/signal.h>
+
+#include <osmocom/ctrl/control_if.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/ipa.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+/* Obtain SS7 application server currently handling given MSC (DPC) */
+static struct osmo_ss7_as *msc_get_ss7_as(struct bsc_msc_data *msc)
+{
+ struct osmo_ss7_route *rt;
+ struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(msc->a.sccp);
+ rt = osmo_ss7_route_lookup(ss7, msc->a.msc_addr.pc);
+ if (!rt)
+ return NULL;
+ return rt->dest.as;
+}
+
+
+/* Encode a CTRL command and send it to the given ASP
+ * \param[in] asp ASP through which we shall send the encoded message
+ * \param[in] cmd decoded CTRL command to be encoded and sent. Ownership is *NOT*
+ * transferred, to permit caller to send the same CMD to several ASPs.
+ * Caller must hence free 'cmd' itself.
+ * \returns 0 on success; negative on error */
+static int sccplite_asp_ctrl_cmd_send(struct osmo_ss7_asp *asp, struct ctrl_cmd *cmd)
+{
+ /* this is basically like libosmoctrl:ctrl_cmd_send(), not for a dedicated
+ * CTRL connection but for the CTRL piggy-back on the IPA/SCCPlite link */
+ struct msgb *msg;
+
+ /* don't attempt to send CTRL on a non-SCCPlite ASP */
+ if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA)
+ return 0;
+
+ msg = ctrl_cmd_make(cmd);
+ if (!msg)
+ return -1;
+
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+
+ return osmo_ss7_asp_send(asp, msg);
+}
+
+/* Ownership of 'cmd' is *NOT* transferred, to permit caller to send the same CMD to several ASPs.
+ * Caller must hence free 'cmd' itself. */
+static int sccplite_msc_ctrl_cmd_send(struct bsc_msc_data *msc, struct ctrl_cmd *cmd)
+{
+ struct osmo_ss7_as *as;
+ struct osmo_ss7_asp *asp;
+ unsigned int i;
+
+ as = msc_get_ss7_as(msc);
+ if (!as)
+ return -1;
+
+ /* don't attempt to send CTRL on a non-SCCPlite AS */
+ if (as->cfg.proto != OSMO_SS7_ASP_PROT_IPA)
+ return 0;
+
+ /* FIXME: unify with xua_as_transmit_msg() and perform proper ASP lookup */
+ for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) {
+ asp = as->cfg.asps[i];
+ if (!asp)
+ continue;
+ /* FIXME: deal with multiple ASPs per AS */
+ return sccplite_asp_ctrl_cmd_send(asp, cmd);
+ }
+ return -1;
+}
+
+/* receive + process a CTRL command from the piggy-back on the IPA/SCCPlite link */
+int bsc_sccplite_rx_ctrl(struct osmo_ss7_asp *asp, struct msgb *msg)
+{
+ struct ctrl_cmd *cmd;
+ bool parse_failed;
+ int rc;
+
+ /* caller has already ensured ipaccess_head + ipaccess_head_ext */
+ OSMO_ASSERT(msg->l2h);
+
+ /* prase raw (ASCII) CTRL command into ctrl_cmd */
+ cmd = ctrl_cmd_parse3(asp, msg, &parse_failed);
+ OSMO_ASSERT(cmd);
+ msgb_free(msg);
+ if (cmd->type == CTRL_TYPE_ERROR && parse_failed)
+ goto send_reply;
+
+ /* handle the CTRL command */
+ ctrl_cmd_handle(bsc_gsmnet->ctrl, cmd, bsc_gsmnet);
+
+send_reply:
+ rc = sccplite_asp_ctrl_cmd_send(asp, cmd);
+ talloc_free(cmd);
+ return rc;
+}
+
+
+void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_data *msc_data)
+{
+ struct ctrl_cmd *trap;
+ struct ctrl_handle *ctrl;
+
+ ctrl = msc_data->network->ctrl;
+
+ trap = ctrl_cmd_trap(cmd);
+ if (!trap) {
+
+ LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n");
+ return;
+ }
+
+ ctrl_cmd_send_to_all(ctrl, trap);
+ sccplite_msc_ctrl_cmd_send(msc_data, trap);
+
+ talloc_free(trap);
+}
+
+CTRL_CMD_DEFINE_RO(msc_connection_status, "connection_status");
+static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+ struct bsc_msc_data *msc = (struct bsc_msc_data *)cmd->node;
+ struct osmo_ss7_as *as;
+ const char *as_state_name;
+
+ if (msc == NULL) {
+ cmd->reply = "msc not found";
+ return CTRL_CMD_ERROR;
+ }
+ as = msc_get_ss7_as(msc);
+ if (!as) {
+ cmd->reply = "AS not found for MSC";
+ return CTRL_CMD_ERROR;
+ }
+
+ as_state_name = osmo_fsm_inst_state_name(as->fi);
+ if (!strcmp(as_state_name, "AS_ACTIVE"))
+ cmd->reply = "connected";
+ else
+ cmd->reply = "disconnected";
+ return CTRL_CMD_REPLY;
+}
+
+/* Backwards compat. */
+CTRL_CMD_DEFINE_RO(msc0_connection_status, "msc_connection_status");
+static int msc_connection_status = 0; /* XXX unused */
+
+static int get_msc0_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+ struct bsc_msc_data *msc = osmo_msc_data_find(bsc_gsmnet, 0);
+ void *old_node = cmd->node;
+ int rc;
+
+ cmd->node = msc;
+ rc = get_msc_connection_status(cmd, data);
+ cmd->node = old_node;
+
+ return rc;
+}
+
+static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+ struct ctrl_cmd *cmd;
+ struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
+
+ if (signal == S_MSC_LOST && msc_connection_status == 1) {
+ LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n");
+ msc_connection_status = 0;
+ } else if (signal == S_MSC_CONNECTED && msc_connection_status == 0) {
+ LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n");
+ msc_connection_status = 1;
+ } else {
+ return 0;
+ }
+
+ cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+ return 0;
+ }
+
+ cmd->id = "0";
+ cmd->variable = "msc_connection_status";
+
+ get_msc0_connection_status(cmd, NULL);
+
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+
+ talloc_free(cmd);
+
+ return 0;
+}
+
+CTRL_CMD_DEFINE_RO(bts_connection_status, "bts_connection_status");
+static int bts_connection_status = 0;
+
+static int get_bts_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+ if (bts_connection_status)
+ cmd->reply = "connected";
+ else
+ cmd->reply = "disconnected";
+ return CTRL_CMD_REPLY;
+}
+
+static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+ struct ctrl_cmd *cmd;
+ struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
+ struct gsm_bts *bts;
+ int bts_current_status;
+
+ if (signal != S_L_INP_TEI_DN && signal != S_L_INP_TEI_UP) {
+ return 0;
+ }
+
+ bts_current_status = 0;
+ /* Check if OML on at least one BTS is up */
+ llist_for_each_entry(bts, &gsmnet->bts_list, list) {
+ if (bts->oml_link) {
+ bts_current_status = 1;
+ break;
+ }
+ }
+ if (bts_connection_status == 0 && bts_current_status == 1) {
+ LOGP(DCTRL, LOGL_DEBUG, "BTS connection (re)established, sending TRAP.\n");
+ } else if (bts_connection_status == 1 && bts_current_status == 0) {
+ LOGP(DCTRL, LOGL_DEBUG, "No more BTS connected, sending TRAP.\n");
+ } else {
+ return 0;
+ }
+
+ cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+ return 0;
+ }
+
+ bts_connection_status = bts_current_status;
+
+ cmd->id = "0";
+ cmd->variable = "bts_connection_status";
+
+ get_bts_connection_status(cmd, NULL);
+
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+
+ talloc_free(cmd);
+
+ return 0;
+}
+
+static int get_bts_loc(struct ctrl_cmd *cmd, void *data);
+
+static void generate_location_state_trap(struct gsm_bts *bts, struct bsc_msc_data *msc)
+{
+ struct ctrl_cmd *cmd;
+ const char *oper, *admin, *policy;
+
+ cmd = ctrl_cmd_create(msc, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n");
+ return;
+ }
+
+ cmd->id = "0";
+ cmd->variable = talloc_asprintf(cmd, "bts.%i.location-state", bts->nr);
+
+ /* Prepare the location reply */
+ cmd->node = bts;
+ get_bts_loc(cmd, NULL);
+
+ oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
+ admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
+ policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
+
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ ",%s,%s,%s,%s,%s",
+ oper, admin, policy,
+ osmo_mcc_name(bts->network->plmn.mcc),
+ osmo_mnc_name(bts->network->plmn.mnc,
+ bts->network->plmn.mnc_3_digits));
+
+ osmo_bsc_send_trap(cmd, msc);
+ talloc_free(cmd);
+}
+
+void bsc_gen_location_state_trap(struct gsm_bts *bts)
+{
+ struct bsc_msc_data *msc;
+
+ llist_for_each_entry(msc, &bts->network->bsc_data->mscs, entry)
+ generate_location_state_trap(bts, msc);
+}
+
+static int location_equal(struct bts_location *a, struct bts_location *b)
+{
+ return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) &&
+ (a->lon == b->lon) && (a->height == b->height));
+}
+
+static void cleanup_locations(struct llist_head *locations)
+{
+ struct bts_location *myloc, *tmp;
+ int invalpos = 0, i = 0;
+
+ LOGP(DCTRL, LOGL_DEBUG, "Checking position list.\n");
+ llist_for_each_entry_safe(myloc, tmp, locations, list) {
+ i++;
+ if (i > 3) {
+ LOGP(DCTRL, LOGL_DEBUG, "Deleting old position.\n");
+ llist_del(&myloc->list);
+ talloc_free(myloc);
+ } else if (myloc->valid == BTS_LOC_FIX_INVALID) {
+ /* Only capture the newest of subsequent invalid positions */
+ invalpos++;
+ if (invalpos > 1) {
+ LOGP(DCTRL, LOGL_DEBUG, "Deleting subsequent invalid position.\n");
+ invalpos--;
+ i--;
+ llist_del(&myloc->list);
+ talloc_free(myloc);
+ }
+ } else {
+ invalpos = 0;
+ }
+ }
+ LOGP(DCTRL, LOGL_DEBUG, "Found %i positions.\n", i);
+}
+
+CTRL_CMD_DEFINE(bts_loc, "location");
+static int get_bts_loc(struct ctrl_cmd *cmd, void *data)
+{
+ struct bts_location *curloc;
+ struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (llist_empty(&bts->loc_list)) {
+ cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0");
+ return CTRL_CMD_REPLY;
+ } else {
+ curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "%lu,%s,%f,%f,%f", curloc->tstamp,
+ get_value_string(bts_loc_fix_names, curloc->valid), curloc->lat, curloc->lon, curloc->height);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_loc(struct ctrl_cmd *cmd, void *data)
+{
+ char *saveptr, *lat, *lon, *height, *tstamp, *valid, *tmp;
+ struct bts_location *curloc, *lastloc;
+ int ret;
+ struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
+
+ curloc = talloc_zero(tall_bsc_ctx, struct bts_location);
+ if (!curloc) {
+ talloc_free(tmp);
+ goto oom;
+ }
+ INIT_LLIST_HEAD(&curloc->list);
+
+
+ tstamp = strtok_r(tmp, ",", &saveptr);
+ valid = strtok_r(NULL, ",", &saveptr);
+ lat = strtok_r(NULL, ",", &saveptr);
+ lon = strtok_r(NULL, ",", &saveptr);
+ height = strtok_r(NULL, "\0", &saveptr);
+
+ curloc->tstamp = atol(tstamp);
+ curloc->valid = get_string_value(bts_loc_fix_names, valid);
+ curloc->lat = atof(lat);
+ curloc->lon = atof(lon);
+ curloc->height = atof(height);
+ talloc_free(tmp);
+
+ lastloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+
+ /* Add location to the end of the list */
+ llist_add(&curloc->list, &bts->loc_list);
+
+ ret = get_bts_loc(cmd, data);
+
+ if (!location_equal(curloc, lastloc))
+ bsc_gen_location_state_trap(bts);
+
+ cleanup_locations(&bts->loc_list);
+
+ return ret;
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+
+static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ char *saveptr, *latstr, *lonstr, *heightstr, *tstampstr, *validstr, *tmp;
+ time_t tstamp;
+ int valid;
+ double lat, lon, height __attribute__((unused));
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ tstampstr = strtok_r(tmp, ",", &saveptr);
+ validstr = strtok_r(NULL, ",", &saveptr);
+ latstr = strtok_r(NULL, ",", &saveptr);
+ lonstr = strtok_r(NULL, ",", &saveptr);
+ heightstr = strtok_r(NULL, "\0", &saveptr);
+
+ if ((tstampstr == NULL) || (validstr == NULL) || (latstr == NULL) ||
+ (lonstr == NULL) || (heightstr == NULL))
+ goto err;
+
+ tstamp = atol(tstampstr);
+ valid = get_string_value(bts_loc_fix_names, validstr);
+ lat = atof(latstr);
+ lon = atof(lonstr);
+ height = atof(heightstr);
+ talloc_free(tmp);
+ tmp = NULL;
+
+ if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) ||
+ (lon < -180) || (lon > 180) || (valid < 0)) {
+ goto err;
+ }
+
+ return 0;
+
+err:
+ talloc_free(tmp);
+ cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>");
+ return 1;
+}
+
+CTRL_CMD_DEFINE(net_timezone, "timezone");
+static int get_net_timezone(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = (struct gsm_network*)cmd->node;
+
+ struct gsm_tz *tz = &net->tz;
+ if (tz->override)
+ cmd->reply = talloc_asprintf(cmd, "%d,%d,%d",
+ tz->hr, tz->mn, tz->dst);
+ else
+ cmd->reply = talloc_asprintf(cmd, "off");
+
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_net_timezone(struct ctrl_cmd *cmd, void *data)
+{
+ char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0;
+ int override = 0;
+ struct gsm_network *net = (struct gsm_network*)cmd->node;
+ struct gsm_tz *tz = &net->tz;
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
+
+ hourstr = strtok_r(tmp, ",", &saveptr);
+ minstr = strtok_r(NULL, ",", &saveptr);
+ dststr = strtok_r(NULL, ",", &saveptr);
+
+ if (hourstr != NULL) {
+ override = strcasecmp(hourstr, "off") != 0;
+ if (override) {
+ tz->hr = atol(hourstr);
+ tz->mn = minstr ? atol(minstr) : 0;
+ tz->dst = dststr ? atol(dststr) : 0;
+ }
+ }
+
+ tz->override = override;
+
+
+ talloc_free(tmp);
+ tmp = NULL;
+
+ return get_net_timezone(cmd, data);
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+
+static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ char *saveptr, *hourstr, *minstr, *dststr, *tmp;
+ int override, tz_hours, tz_mins, tz_dst;
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ hourstr = strtok_r(tmp, ",", &saveptr);
+ minstr = strtok_r(NULL, ",", &saveptr);
+ dststr = strtok_r(NULL, ",", &saveptr);
+
+ if (hourstr == NULL)
+ goto err;
+
+ override = strcasecmp(hourstr, "off") != 0;
+
+ if (!override) {
+ talloc_free(tmp);
+ return 0;
+ }
+
+ if (minstr == NULL || dststr == NULL)
+ goto err;
+
+ tz_hours = atol(hourstr);
+ tz_mins = atol(minstr);
+ tz_dst = atol(dststr);
+
+ talloc_free(tmp);
+ tmp = NULL;
+
+ if ((tz_hours < -19) || (tz_hours > 19) ||
+ (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) ||
+ (tz_dst < 0) || (tz_dst > 2))
+ goto err;
+
+ return 0;
+
+err:
+ talloc_free(tmp);
+ cmd->reply = talloc_strdup(cmd, "The format is <hours>,<mins>,<dst> or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2");
+ return 1;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_notification, "notification");
+static int set_net_notification(struct ctrl_cmd *cmd, void *data)
+{
+ struct ctrl_cmd *trap;
+ struct gsm_network *net;
+
+ net = cmd->node;
+
+ trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!trap) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
+ goto handled;
+ }
+
+ trap->id = "0";
+ trap->variable = "notification";
+ trap->reply = talloc_strdup(trap, cmd->value);
+
+ /*
+ * This should only be sent to local systems. In the future
+ * we might even ask for systems to register to receive
+ * the notifications.
+ */
+ ctrl_cmd_send_to_all(net->ctrl, trap);
+ talloc_free(trap);
+
+handled:
+ return CTRL_CMD_HANDLED;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_inform_msc, "inform-msc-v1");
+static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net;
+ struct bsc_msc_data *msc;
+
+ net = cmd->node;
+ llist_for_each_entry(msc, &net->bsc_data->mscs, entry) {
+ struct ctrl_cmd *trap;
+
+ trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!trap) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
+ continue;
+ }
+
+ trap->id = "0";
+ trap->variable = "inform-msc-v1";
+ trap->reply = talloc_strdup(trap, cmd->value);
+ sccplite_msc_ctrl_cmd_send(msc, trap);
+ talloc_free(trap);
+ }
+
+
+ return CTRL_CMD_HANDLED;
+}
+
+CTRL_CMD_DEFINE_WO(net_ussd_notify, "ussd-notify-v1");
+static int set_net_ussd_notify(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_subscriber_connection *conn;
+ struct gsm_network *net;
+ char *saveptr = NULL;
+ char *cic_str, *alert_str, *text_str;
+ int cic, alert;
+
+ /* Verify has done the test for us */
+ cic_str = strtok_r(cmd->value, ",", &saveptr);
+ alert_str = strtok_r(NULL, ",", &saveptr);
+ text_str = strtok_r(NULL, ",", &saveptr);
+
+ if (!cic_str || !alert_str || !text_str) {
+ cmd->reply = "Programming issue. How did this pass verify?";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "No connection found";
+
+ cic = atoi(cic_str);
+ alert = atoi(alert_str);
+
+ net = cmd->node;
+ llist_for_each_entry(conn, &net->subscr_conns, entry) {
+ if (conn->user_plane.msc_assigned_cic != cic)
+ continue;
+
+ /*
+ * This is a hack. My E71 does not like to immediately
+ * receive a release complete on a TCH. So schedule a
+ * release complete to clear any previous attempt. The
+ * right thing would be to track invokeId and only send
+ * the release complete when we get a returnResultLast
+ * for this invoke id.
+ */
+ bsc_send_ussd_release_complete(conn);
+ bsc_send_ussd_notify(conn, alert, text_str);
+ cmd->reply = "Found a connection";
+ break;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int verify_net_ussd_notify(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ char *saveptr = NULL;
+ char *inp, *cic, *alert, *text;
+
+ OSMO_ASSERT(cmd);
+ inp = talloc_strdup(cmd, value);
+
+ cic = strtok_r(inp, ",", &saveptr);
+ alert = strtok_r(NULL, ",", &saveptr);
+ text = strtok_r(NULL, ",", &saveptr);
+
+ talloc_free(inp);
+ if (!cic || !alert || !text)
+ return 1;
+ return 0;
+}
+
+static int msc_signal_handler(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct msc_signal_data *msc;
+ struct gsm_network *net;
+ struct gsm_bts *bts;
+
+ if (subsys != SS_MSC)
+ return 0;
+ if (signal != S_MSC_AUTHENTICATED)
+ return 0;
+
+ msc = signal_data;
+
+ net = msc->data->network;
+ llist_for_each_entry(bts, &net->bts_list, list)
+ generate_location_state_trap(bts, msc->data);
+
+ return 0;
+}
+
+int bsc_ctrl_cmds_install(struct gsm_network *net)
+{
+ int rc;
+
+ rc = bsc_base_ctrl_cmds_install();
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_loc);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_MSC, &cmd_msc_connection_status);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc0_connection_status);
+ if (rc)
+ goto end;
+ rc = osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net);
+ if (rc)
+ goto end;
+ rc = osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_ussd_notify);
+ if (rc)
+ goto end;
+ rc = osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net);
+
+end:
+ return rc;
+}
diff --git a/src/osmo-bsc/osmo_bsc_filter.c b/src/osmo-bsc/osmo_bsc_filter.c
new file mode 100644
index 000000000..332ba6b83
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_filter.c
@@ -0,0 +1,163 @@
+/* (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+
+#include <stdlib.h>
+
+static int send_welcome_ussd(struct gsm_subscriber_connection *conn)
+{
+ if (!conn->sccp.msc->ussd_welcome_txt) {
+ LOGP(DMSC, LOGL_DEBUG, "No USSD Welcome text defined.\n");
+ return 0;
+ }
+
+ return BSS_SEND_USSD;
+}
+
+static int bsc_patch_mm_info(struct gsm_subscriber_connection *conn,
+ uint8_t *data, unsigned int length)
+{
+ struct tlv_parsed tp;
+ int parse_res;
+ struct gsm_bts *bts = conn_get_bts(conn);
+ int tzunits;
+ uint8_t tzbsd = 0;
+ uint8_t dst = 0;
+
+ parse_res = tlv_parse(&tp, &gsm48_mm_att_tlvdef, data, length, 0, 0);
+ if (parse_res <= 0 && parse_res != -3)
+ /* FIXME: -3 means unknown IE error, so this accepts messages
+ * with unknown IEs. But parsing has aborted with the unknown
+ * IE and the message is broken or parsed incompletely. */
+ return 0;
+
+ /* Is TZ patching enabled? */
+ struct gsm_tz *tz = &bts->network->tz;
+ if (!tz->override)
+ return 0;
+
+ /* Convert tz.hr and tz.mn to units */
+ if (tz->hr < 0) {
+ tzunits = -tz->hr*4;
+ tzbsd |= 0x08;
+ } else
+ tzunits = tz->hr*4;
+
+ tzunits = tzunits + (tz->mn/15);
+
+ tzbsd |= (tzunits % 10)*0x10 + (tzunits / 10);
+
+ /* Convert DST value */
+ if (tz->dst >= 0 && tz->dst <= 2)
+ dst = tz->dst;
+
+ if (TLVP_PRESENT(&tp, GSM48_IE_UTC)) {
+ LOGP(DMSC, LOGL_DEBUG,
+ "Changing 'Local time zone' from 0x%02x to 0x%02x.\n",
+ TLVP_VAL(&tp, GSM48_IE_UTC)[6], tzbsd);
+ ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_UTC)))[0] = tzbsd;
+ }
+ if (TLVP_PRESENT(&tp, GSM48_IE_NET_TIME_TZ)) {
+ LOGP(DMSC, LOGL_DEBUG,
+ "Changing 'Universal time and local time zone' TZ from "
+ "0x%02x to 0x%02x.\n",
+ TLVP_VAL(&tp, GSM48_IE_NET_TIME_TZ)[6], tzbsd);
+ ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_NET_TIME_TZ)))[6] = tzbsd;
+ }
+#ifdef GSM48_IE_NET_DST
+ if (TLVP_PRESENT(&tp, GSM48_IE_NET_DST)) {
+ LOGP(DMSC, LOGL_DEBUG,
+ "Changing 'Network daylight saving time' from "
+ "0x%02x to 0x%02x.\n",
+ TLVP_VAL(&tp, GSM48_IE_NET_DST)[0], dst);
+ ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_NET_DST)))[0] = dst;
+ }
+#endif
+
+ return 0;
+}
+
+static int has_core_identity(struct bsc_msc_data *msc)
+{
+ if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID)
+ return 1;
+ if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
+ return 1;
+ if (msc->core_lac != -1)
+ return 1;
+ if (msc->core_ci != -1)
+ return 1;
+ return 0;
+}
+
+/**
+ * Messages coming back from the MSC.
+ */
+int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct bsc_msc_data *msc;
+ struct gsm48_loc_area_id *lai;
+ struct gsm48_hdr *gh;
+ uint8_t pdisc;
+ uint8_t mtype;
+ int length = msgb_l3len(msg);
+
+ if (length < sizeof(*gh)) {
+ LOGP(DMSC, LOGL_ERROR, "GSM48 header does not fit.\n");
+ return -1;
+ }
+
+ gh = (struct gsm48_hdr *) msgb_l3(msg);
+ length -= (const char *)&gh->data[0] - (const char *)gh;
+
+ pdisc = gsm48_hdr_pdisc(gh);
+ if (pdisc != GSM48_PDISC_MM)
+ return 0;
+
+ mtype = gsm48_hdr_msg_type(gh);
+ msc = conn->sccp.msc;
+
+ if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) {
+ if (has_core_identity(msc)) {
+ if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) {
+ /* overwrite LAI in the message */
+ lai = (struct gsm48_loc_area_id *) &gh->data[0];
+ gsm48_generate_lai2(lai, bts_lai(conn_get_bts(conn)));
+ }
+ }
+
+ if (conn->new_subscriber)
+ return send_welcome_ussd(conn);
+ return 0;
+ } else if (mtype == GSM48_MT_MM_INFO) {
+ bsc_patch_mm_info(conn, &gh->data[0], length);
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_grace.c b/src/osmo-bsc/osmo_bsc_grace.c
new file mode 100644
index 000000000..2cc3d1a4e
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_grace.c
@@ -0,0 +1,149 @@
+/*
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/osmo_bsc_grace.h>
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/lchan_fsm.h>
+
+int bsc_grace_allow_new_connection(struct gsm_network *network, struct gsm_bts *bts)
+{
+ if (bts->excl_from_rf_lock)
+ return 1;
+ return network->bsc_data->rf_ctrl->policy == S_RF_ON;
+}
+
+
+/* Return value is like paging_request_bts():
+ * returns 1 on success (one BTS was paged); 0 in case of error (e.g. TRX down) */
+static int locked_paging_bts(struct gsm_bts *bts,
+ struct bsc_subscr *subscr,
+ int chan_needed,
+ struct bsc_msc_data *msc)
+{
+ /* Return error if the BTS is not excluded from the lock. */
+ if (!bts->excl_from_rf_lock)
+ return 0;
+
+ /* in case of no lac patching is in place, check the BTS */
+ if (msc->core_lac == -1 && subscr->lac != bts->location_area_code)
+ return 0;
+
+ return paging_request_bts(bts, subscr, chan_needed, msc);
+}
+
+/**
+ * Page a subscriber in an MSC.
+ * \param[in] rf_policy if not S_RF_ON, page only BTSs which are not excluded from the RF lock
+ * \param[in] subscr subscriber we want to page
+ * \param[in] chan_needed value of the GSM0808_IE_CHANNEL_NEEDED IE
+ * \param[in] msc MSC which has issued this paging
+ * \param[in] bts The BTS to issue the paging on
+ * \returns 1 if paging was issued to the BTS, 0 if not
+ */
+int bsc_grace_paging_request(enum signal_rf rf_policy,
+ struct bsc_subscr *subscr,
+ int chan_needed,
+ struct bsc_msc_data *msc,
+ struct gsm_bts *bts)
+{
+ if (rf_policy == S_RF_ON)
+ return paging_request_bts(bts, subscr, chan_needed, msc);
+ return locked_paging_bts(bts, subscr, chan_needed, msc);
+}
+
+static int handle_sub(struct gsm_lchan *lchan, const char *text)
+{
+ struct gsm_subscriber_connection *conn;
+
+ /* only send it to TCH */
+ if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F)
+ return -1;
+
+ /* only send on the primary channel */
+ conn = lchan->conn;
+ if (!conn)
+ return -1;
+
+ if (conn->lchan != lchan)
+ return -1;
+
+ /* only when active */
+ if (lchan->fi->state != LCHAN_ST_ESTABLISHED)
+ return -1;
+
+ bsc_send_ussd_notify(conn, 0, text);
+ bsc_send_ussd_release_complete(conn);
+
+ return 0;
+}
+
+/*
+ * The place to handle the grace mode. Right now we will send
+ * USSD messages to the subscriber, in the future we might start
+ * a timer to have different modes for the grace period.
+ */
+static int handle_grace(struct gsm_network *network)
+{
+ int ts_nr, lchan_nr;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+
+ if (!network->bsc_data->mid_call_txt)
+ return 0;
+
+ llist_for_each_entry(bts, &network->bts_list, list) {
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ++ts_nr) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; ++lchan_nr) {
+ handle_sub(&ts->lchan[lchan_nr],
+ network->bsc_data->mid_call_txt);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int handle_rf_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct rf_signal_data *sig;
+
+ if (subsys != SS_RF)
+ return -1;
+
+ sig = signal_data;
+
+ if (signal == S_RF_GRACE)
+ handle_grace(sig->net);
+
+ return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_grace(void)
+{
+ osmo_signal_register_handler(SS_RF, handle_rf_signal, NULL);
+}
diff --git a/src/osmo-bsc/osmo_bsc_lcls.c b/src/osmo-bsc/osmo_bsc_lcls.c
new file mode 100644
index 000000000..cdd655798
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_lcls.c
@@ -0,0 +1,900 @@
+/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/osmo_bsc_lcls.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+
+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);
+}
+
+/* Redirect BTS's RTP traffic via RSL MDCX command:
+ * when enable == true, redirect to remote BTS's IP:port
+ * when enable == false, redirect back to the MGW's IP:port
+ */
+static inline void lcls_rsl(const struct gsm_subscriber_connection *conn, bool enable)
+{
+ const struct gsm_lchan *lchan = conn->lchan;
+ /* RSL_IE_IPAC_REMOTE_IP */
+ uint32_t ip = enable ? conn->lcls.other->lchan->abis_ip.bound_ip : lchan->abis_ip.connect_ip;
+ /* RSL_IE_IPAC_REMOTE_PORT */
+ uint16_t port = enable ? conn->lcls.other->lchan->abis_ip.bound_port : lchan->abis_ip.connect_port;
+
+ if (!conn->lcls.other) {
+ LOGPFSM(conn->lcls.fi, "%s LCLS: other conn is not available!\n", enable ? "enable" : "disable");
+ return;
+ }
+
+ abis_rsl_sendmsg(rsl_make_ipacc_mdcx(lchan, ip, port));
+}
+
+static inline bool lcls_check_toggle_allowed(const struct gsm_subscriber_connection *conn, bool enable)
+{
+ if (conn->lcls.other &&
+ conn->sccp.msc->lcls_mode != conn->lcls.other->sccp.msc->lcls_mode) {
+ LOGPFSM(conn->lcls.fi, "FIXME: LCLS connection mode mismatch: %s != %s\n",
+ get_value_string(bsc_lcls_mode_names, conn->sccp.msc->lcls_mode),
+ get_value_string(bsc_lcls_mode_names, conn->lcls.other->sccp.msc->lcls_mode));
+ return false;
+ }
+
+ if (conn->sccp.msc->lcls_mode == BSC_LCLS_MODE_MGW_LOOP && !conn->user_plane.mgw_endpoint_ci_msc) {
+ /* the MGCP FSM has died, e.g. due to some MGCP/SDP parsing error */
+ LOGPFSML(conn->lcls.fi, LOGL_NOTICE, "Cannot %s LCLS without MSC-side MGCP FSM\n",
+ enable ? "enable" : "disable");
+ return false;
+ }
+
+ return true;
+}
+
+/* Close the loop for LCLS using MGCP */
+static inline void lcls_mdcx(const struct gsm_subscriber_connection *conn, struct mgcp_conn_peer *mdcx_info)
+{
+ mgcp_pick_codec(mdcx_info, conn->lchan, false);
+
+ mgw_endpoint_ci_request(conn->user_plane.mgw_endpoint_ci_msc, MGCP_VERB_MDCX, mdcx_info,
+ NULL, 0, 0, NULL);
+}
+
+static void lcls_break_local_switching(struct gsm_subscriber_connection *conn)
+{
+ struct mgcp_conn_peer mdcx_info;
+
+ LOGPFSM(conn->lcls.fi, "=== HERE IS WHERE WE DISABLE LCLS(%s)\n",
+ bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
+
+ if (!lcls_check_toggle_allowed(conn, false))
+ return;
+
+ switch(conn->sccp.msc->lcls_mode) {
+ case BSC_LCLS_MODE_MGW_LOOP:
+ mdcx_info.port = conn->user_plane.msc_assigned_rtp_port;
+ osmo_strlcpy(mdcx_info.addr, conn->user_plane.msc_assigned_rtp_addr, sizeof(mdcx_info.addr));
+ lcls_mdcx(conn, &mdcx_info);
+ break;
+ case BSC_LCLS_MODE_BTS_LOOP:
+ lcls_rsl(conn, false);
+ break;
+ case BSC_LCLS_MODE_DISABLED:
+ LOGPFSM(conn->lcls.fi, "FIXME: attempt to break LCLS loop while LCLS is disabled?!\n");
+ break;
+ default:
+ LOGPFSM(conn->lcls.fi, "FIXME: unknown LCLS mode %s\n",
+ bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
+ }
+}
+
+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;
+ }
+
+ if (conn->lchan->type != conn->lcls.other->lchan->type
+ && conn->sccp.msc->lcls_codec_mismatch_allow == false) {
+ LOGPFSM(conn->lcls.fi,
+ "Not enabling LS due to channel type mismatch: %s:%s != %s:%s\n",
+ gsm_lchan_name(conn->lchan),
+ gsm_chan_t_name(conn->lchan->type),
+ gsm_lchan_name(conn->lcls.other->lchan),
+ gsm_chan_t_name(conn->lcls.other->lchan->type));
+ return false;
+ }
+
+ if (conn->lchan->tch_mode != conn->lcls.other->lchan->tch_mode
+ && conn->sccp.msc->lcls_codec_mismatch_allow == false) {
+ LOGPFSM(conn->lcls.fi,
+ "Not enabling LS due to TCH-mode mismatch: %s:%s != %s:%s\n",
+ gsm_lchan_name(conn->lchan),
+ gsm48_chan_mode_name(conn->lchan->tch_mode),
+ gsm_lchan_name(conn->lcls.other->lchan),
+ gsm48_chan_mode_name(conn->lcls.other->lchan->
+ tch_mode));
+ 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;
+ const struct mgcp_conn_peer *other_mgw_info;
+ struct mgcp_conn_peer mdcx_info;
+
+ OSMO_ASSERT(conn_other);
+
+ if (!lcls_check_toggle_allowed(conn, true))
+ return;
+
+ LOGPFSM(fi, "=== HERE IS WHERE WE ENABLE LCLS(%s)\n",
+ bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
+
+ if (!conn_other->user_plane.mgw_endpoint_ci_msc) {
+ LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without MSC-side MGCP FSM. FIXME\n");
+ return;
+ }
+
+ other_mgw_info = mgwep_ci_get_rtp_info(conn_other->user_plane.mgw_endpoint_ci_msc);
+ if (!other_mgw_info) {
+ LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without RTP port info of MSC-side"
+ " -- missing CRCX?\n");
+ return;
+ }
+
+ switch(conn->sccp.msc->lcls_mode) {
+ case BSC_LCLS_MODE_MGW_LOOP:
+ mdcx_info = *other_mgw_info;
+ /* Make sure the request doesn't want to use the other side's endpoint string. */
+ mdcx_info.endpoint[0] = 0;
+ lcls_mdcx(conn, &mdcx_info);
+ break;
+ case BSC_LCLS_MODE_BTS_LOOP:
+ lcls_rsl(conn, true);
+ break;
+ case BSC_LCLS_MODE_DISABLED:
+ LOGPFSM(conn->lcls.fi, "FIXME: attempt to close LCLS loop while LCLS is disabled?!\n");
+ break;
+ default:
+ LOGPFSM(conn->lcls.fi, "FIXME: unknown LCLS mode %s\n",
+ bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
+ }
+}
+
+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,
+};
+
+/* 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 */
+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);
+ }
+}
+
+
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
new file mode 100644
index 000000000..67fccd337
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -0,0 +1,933 @@
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/lienses/>.
+ *
+ */
+
+#include <osmocom/bsc/bss.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/ctrl.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/handover_decision.h>
+#include <osmocom/bsc/handover_decision_2.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/assignment_fsm.h>
+#include <osmocom/bsc/handover_fsm.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_vty.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/abis/abis.h>
+#include <osmocom/bsc/abis_om2000.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/e1_config.h>
+#include <osmocom/bsc/codec_pref.h>
+
+#include <osmocom/mgcp_client/mgcp_client.h>
+
+#include <osmocom/sigtran/xua_msg.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+
+#include "../../bscconfig.h"
+
+struct gsm_network *bsc_gsmnet = 0;
+static const char *config_file = "osmo-bsc.cfg";
+static const char *rf_ctrl = NULL;
+static int daemonize = 0;
+static LLIST_HEAD(access_lists);
+
+struct llist_head *bsc_access_lists(void)
+{
+ return &access_lists;
+}
+
+static void print_usage()
+{
+ printf("Usage: osmo-bsc\n");
+}
+
+static void print_help()
+{
+ printf(" Some useful help...\n");
+ printf(" -h --help This text.\n");
+ printf(" -D --daemonize Fork the process into a background daemon.\n");
+ printf(" -d --debug option --debug=DRLL:DMM:DRR:DRSL:DNM enable debugging.\n");
+ printf(" -s --disable-color Disable coloring log in stderr.\n");
+ printf(" -T --timestamp Print a timestamp in the debug output.\n");
+ printf(" -V --version Print the version of OsmoBSC.\n");
+ printf(" -c --config-file filename The config file to use.\n");
+ printf(" -l --local IP The local address of the MGCP.\n");
+ printf(" -e --log-level number Set a global loglevel.\n");
+ printf(" -r --rf-ctl NAME A unix domain socket to listen for cmds.\n");
+ printf(" -t --testmode A special mode to provoke failures at the MSC.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"debug", 1, 0, 'd'},
+ {"daemonize", 0, 0, 'D'},
+ {"config-file", 1, 0, 'c'},
+ {"disable-color", 0, 0, 's'},
+ {"timestamp", 0, 0, 'T'},
+ {"version", 0, 0, 'V' },
+ {"local", 1, 0, 'l'},
+ {"log-level", 1, 0, 'e'},
+ {"rf-ctl", 1, 0, 'r'},
+ {"testmode", 0, 0, 't'},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "hd:DsTVc:e:r:t",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_usage();
+ print_help();
+ exit(0);
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'T':
+ log_set_print_timestamp(osmo_stderr_target, 1);
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ case 'e':
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'r':
+ rf_ctrl = optarg;
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+}
+
+/* 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_deferred(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);
+ OSMO_ASSERT(ts->fi);
+ osmo_fsm_inst_dispatch(ts->fi, TS_EV_RSL_READY, NULL);
+ }
+}
+
+static void all_ts_dispatch_event(struct gsm_bts_trx *trx, uint32_t event)
+{
+ int ts_i;
+ for (ts_i = 0; ts_i < ARRAY_SIZE(trx->ts); ts_i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_i];
+ if (ts->fi)
+ osmo_fsm_inst_dispatch(ts->fi, event, 0);
+ }
+}
+
+/* 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;
+ /* 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]);
+ all_ts_dispatch_event(trx, TS_EV_OML_DOWN);
+ } else if (isd->link_type == E1INP_SIGN_RSL) {
+ rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]);
+ acc_ramp_abort(&trx->bts->acc_ramp);
+ all_ts_dispatch_event(trx, TS_EV_RSL_DOWN);
+ }
+
+ 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 */
+
+ /* Set ccch config by looking at ts config */
+ for (n=0, i=0; i<8; i++)
+ n += bts->c0->ts[i].pchan_is == 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) {
+ case GSMNET_NODE:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ break;
+ case BTS_NODE:
+ vty->node = GSMNET_NODE;
+ {
+ /* set vty->index correctly ! */
+ struct gsm_bts *bts = vty->index;
+ vty->index = bts->network;
+ vty->index_sub = NULL;
+ }
+ break;
+ case TRX_NODE:
+ vty->node = BTS_NODE;
+ {
+ /* set vty->index correctly ! */
+ struct gsm_bts_trx *trx = vty->index;
+ vty->index = trx->bts;
+ vty->index_sub = &trx->bts->description;
+ }
+ break;
+ case TS_NODE:
+ vty->node = TRX_NODE;
+ {
+ /* set vty->index correctly ! */
+ struct gsm_bts_trx_ts *ts = vty->index;
+ vty->index = ts->trx;
+ vty->index_sub = &ts->trx->description;
+ }
+ break;
+ case OML_NODE:
+ case OM2K_NODE:
+ vty->node = ENABLE_NODE;
+ /* NOTE: this only works because it's not part of the config
+ * tree, where outer commands are searched via vty_go_parent()
+ * and only (!) executed when a matching one is found.
+ */
+ talloc_free(vty->index);
+ vty->index = NULL;
+ break;
+ case OM2K_CON_GROUP_NODE:
+ vty->node = BTS_NODE;
+ {
+ struct con_group *cg = vty->index;
+ struct gsm_bts *bts = cg->bts;
+ vty->index = bts;
+ vty->index_sub = &bts->description;
+ }
+ break;
+ case BSC_NODE:
+ case MSC_NODE:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ break;
+ default:
+ osmo_ss7_vty_go_parent(vty);
+ }
+
+ return vty->node;
+}
+
+static int bsc_vty_is_config_node(struct vty *vty, int node)
+{
+ /* Check if libosmo-sccp declares the node in
+ * question as config node */
+ if (osmo_ss7_is_config_node(vty, node))
+ return 1;
+
+ switch (node) {
+ /* add items that are not config */
+ case OML_NODE:
+ case OM2K_NODE:
+ case CONFIG_NODE:
+ return 0;
+
+ default:
+ return 1;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoBSC",
+ .copyright =
+ "Copyright (C) 2008-2018 Harald Welte, Holger Freyther\r\n"
+ "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n"
+ "Dieter Spaar, Andreas Eversberg, Sylvain Munaut, Neels Hofmeyr\r\n\r\n"
+ "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = bsc_vty_go_parent,
+ .is_config_node = bsc_vty_is_config_node,
+};
+
+extern int bsc_shutdown_net(struct gsm_network *net);
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ case SIGTERM:
+ bsc_shutdown_net(bsc_gsmnet);
+ osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL);
+ sleep(3);
+ exit(0);
+ break;
+ case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report
+ * and then return to the caller, who will abort the process */
+ case SIGUSR1:
+ talloc_report(tall_vty_ctx, stderr);
+ talloc_report_full(tall_bsc_ctx, stderr);
+ break;
+ case SIGUSR2:
+ if (!bsc_gsmnet->bsc_data)
+ return;
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct log_info_cat osmo_bsc_categories[] = {
+ [DRLL] = {
+ .name = "DRLL",
+ .description = "A-bis Radio Link Layer (RLL)",
+ .color = "\033[1;31m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMM] = {
+ .name = "DMM",
+ .description = "Layer3 Mobility Management (MM)",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DRR] = {
+ .name = "DRR",
+ .description = "Layer3 Radio Resource (RR)",
+ .color = "\033[1;34m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DRSL] = {
+ .name = "DRSL",
+ .description = "A-bis Radio Signalling Link (RSL)",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DCHAN] = {
+ .name = "DCHAN",
+ .description = "lchan FSM",
+ .color = "\033[1;32m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DTS] = {
+ .name = "DTS",
+ .description = "timeslot FSM",
+ .color = "\033[1;31m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DAS] = {
+ .name = "DAS",
+ .description = "assignment FSM",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DNM] = {
+ .name = "DNM",
+ .description = "A-bis Network Management / O&M (NM/OML)",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DPAG] = {
+ .name = "DPAG",
+ .description = "Paging Subsystem",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMEAS] = {
+ .name = "DMEAS",
+ .description = "Radio Measurement Processing",
+ .enabled = 0, .loglevel = LOGL_NOTICE,
+ },
+ [DMSC] = {
+ .name = "DMSC",
+ .description = "Mobile Switching Center",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DHO] = {
+ .name = "DHO",
+ .description = "Hand-Over Process",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DHODEC] = {
+ .name = "DHODEC",
+ .description = "Hand-Over Decision",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DREF] = {
+ .name = "DREF",
+ .description = "Reference Counting",
+ .enabled = 0, .loglevel = LOGL_NOTICE,
+ },
+ [DNAT] = {
+ .name = "DNAT",
+ .description = "GSM 08.08 NAT/Multiplexer",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DCTRL] = {
+ .name = "DCTRL",
+ .description = "Control interface",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DFILTER] = {
+ .name = "DFILTER",
+ .description = "BSC/NAT IMSI based filtering",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DPCU] = {
+ .name = "DPCU",
+ .description = "PCU Interface",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DLCLS] = {
+ .name = "DLCLS",
+ .description = "Local Call, Local Switch",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+
+};
+
+static int filter_fn(const struct log_context *ctx, struct log_target *tar)
+{
+ const struct bsc_subscr *bsub = ctx->ctx[LOG_CTX_BSC_SUBSCR];
+
+ if ((tar->filter_map & (1 << LOG_FLT_BSC_SUBSCR)) != 0
+ && bsub && bsub == tar->filter_data[LOG_FLT_BSC_SUBSCR])
+ return 1;
+
+ return 0;
+}
+
+const struct log_info log_info = {
+ .filter_fn = filter_fn,
+ .cat = osmo_bsc_categories,
+ .num_cat = ARRAY_SIZE(osmo_bsc_categories),
+};
+
+extern void *tall_paging_ctx;
+extern void *tall_fle_ctx;
+extern void *tall_tqe_ctx;
+extern void *tall_ctr_ctx;
+
+int main(int argc, char **argv)
+{
+ struct bsc_msc_data *msc;
+ struct osmo_bsc_data *data;
+ int rc;
+
+ tall_bsc_ctx = talloc_named_const(NULL, 1, "osmo-bsc");
+ msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+ osmo_signal_talloc_ctx_init(tall_bsc_ctx);
+ osmo_xua_msg_tall_ctx_init(tall_bsc_ctx);
+ vty_info.tall_ctx = tall_bsc_ctx;
+
+ tall_paging_ctx = talloc_named_const(tall_bsc_ctx, 0, "paging_request");
+ tall_fle_ctx = talloc_named_const(tall_bsc_ctx, 0, "bs11_file_list_entry");
+ tall_tqe_ctx = talloc_named_const(tall_bsc_ctx, 0, "subch_txq_entry");
+ tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter");
+
+ osmo_init_logging2(tall_bsc_ctx, &log_info);
+ osmo_stats_init(tall_bsc_ctx);
+ rate_ctr_init(tall_bsc_ctx);
+
+ /* Allocate global gsm_network struct */
+ rc = bsc_network_alloc();
+ if (rc) {
+ fprintf(stderr, "Allocation failed. exiting.\n");
+ exit(1);
+ }
+
+ bsc_gsmnet->mgw.conf = talloc_zero(bsc_gsmnet, struct mgcp_client_conf);
+ mgcp_client_conf_init(bsc_gsmnet->mgw.conf);
+
+ bts_init();
+ libosmo_abis_init(tall_bsc_ctx);
+
+ /* enable filters */
+
+ /* This needs to precede handle_options() */
+ vty_init(&vty_info);
+ bsc_vty_init(bsc_gsmnet);
+ bsc_msg_acc_lst_vty_init(tall_bsc_ctx, &access_lists, BSC_NODE);
+ ctrl_vty_init(tall_bsc_ctx);
+ logging_vty_add_deprecated_subsys(tall_bsc_ctx, "cc");
+ logging_vty_add_deprecated_subsys(tall_bsc_ctx, "mgcp");
+
+ /* Initalize SS7 */
+ osmo_ss7_init();
+ osmo_ss7_vty_init_asp(tall_bsc_ctx);
+ osmo_sccp_vty_init();
+
+ /* parse options */
+ handle_options(argc, argv);
+
+ /* seed the PRNG */
+ srand(time(NULL));
+
+ ts_fsm_init();
+ lchan_fsm_init();
+ bsc_subscr_conn_fsm_init();
+ assignment_fsm_init();
+ mgw_endpoint_fsm_init(bsc_gsmnet->T_defs);
+ handover_fsm_init();
+
+ /* Read the config */
+ rc = bsc_network_configure(config_file);
+ if (rc < 0) {
+ fprintf(stderr, "Bootstrapping the network failed. exiting.\n");
+ exit(1);
+ }
+
+ /* start control interface after reading config for
+ * ctrl_vty_get_bind_addr() */
+ bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet,
+ ctrl_vty_get_bind_addr(),
+ OSMO_CTRL_PORT_NITB_BSC);
+ if (!bsc_gsmnet->ctrl) {
+ fprintf(stderr, "Failed to init the control interface. Exiting.\n");
+ exit(1);
+ }
+
+ rc = bsc_ctrl_cmds_install(bsc_gsmnet);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to install control commands. Exiting.\n");
+ exit(1);
+ }
+
+ data = bsc_gsmnet->bsc_data;
+ if (rf_ctrl)
+ osmo_talloc_replace_string(data, &data->rf_ctrl_name, rf_ctrl);
+
+ data->rf_ctrl = osmo_bsc_rf_create(data->rf_ctrl_name, bsc_gsmnet);
+ if (!data->rf_ctrl) {
+ fprintf(stderr, "Failed to create the RF service.\n");
+ exit(1);
+ }
+
+ rc = check_codec_pref(&bsc_gsmnet->bsc_data->mscs);
+ if (rc < 0)
+ LOGP(DMSC, LOGL_ERROR, "Configuration contains mutually exclusive codec settings -- check configuration!\n");
+
+ llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) {
+ if (osmo_bsc_msc_init(msc) != 0) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to start up. Exiting.\n");
+ exit(1);
+ }
+ }
+
+ bsc_gsmnet->mgw.client = mgcp_client_init(bsc_gsmnet, bsc_gsmnet->mgw.conf);
+
+ if (mgcp_client_connect(bsc_gsmnet->mgw.client)) {
+ LOGP(DNM, LOGL_ERROR, "MGW connect failed at (%s:%u)\n",
+ bsc_gsmnet->mgw.conf->remote_addr,
+ bsc_gsmnet->mgw.conf->remote_port);
+ exit(1);
+ }
+
+ if (osmo_bsc_sigtran_init(&bsc_gsmnet->bsc_data->mscs) != 0) {
+ LOGP(DNM, LOGL_ERROR, "Failed to initalize sigtran backhaul.\n");
+ exit(1);
+ }
+
+ handover_decision_1_init();
+ hodec2_init(bsc_gsmnet);
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGTERM, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ osmo_init_ignore_signals();
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ osmo_select_main(0);
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c
new file mode 100644
index 000000000..71931e615
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_msc.c
@@ -0,0 +1,121 @@
+/*
+ * Handle the connection to the MSC. This include ping/timeout/reconnect
+ * (C) 2008-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2015 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/crypt/auth.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/abis/ipa.h>
+
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+
+int osmo_bsc_msc_init(struct bsc_msc_data *data)
+{
+ /* FIXME: This is a leftover from the old architecture that used
+ * sccp-lite with osmocom specific authentication. Since we now
+ * changed to AoIP the connected status and the authentication
+ * status is managed differently. However osmo_bsc_filter.c still
+ * needs the flags to be set to one. See also: OS#3112 */
+ data->is_authenticated = 1;
+
+ return 0;
+}
+
+struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *net, int nr)
+{
+ struct bsc_msc_data *msc_data;
+
+ llist_for_each_entry(msc_data, &net->bsc_data->mscs, entry)
+ if (msc_data->nr == nr)
+ return msc_data;
+ return NULL;
+}
+
+struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr)
+{
+ struct bsc_msc_data *msc_data;
+ unsigned int i;
+
+ /* check if there is already one */
+ msc_data = osmo_msc_data_find(net, nr);
+ if (msc_data)
+ return msc_data;
+
+ msc_data = talloc_zero(net, struct bsc_msc_data);
+ if (!msc_data)
+ return NULL;
+
+ llist_add_tail(&msc_data->entry, &net->bsc_data->mscs);
+
+ /* Init back pointer */
+ msc_data->network = net;
+
+ msc_data->core_plmn = (struct osmo_plmn_id){
+ .mcc = GSM_MCC_MNC_INVALID,
+ .mnc = GSM_MCC_MNC_INVALID,
+ };
+ msc_data->core_ci = -1;
+ msc_data->core_lac = -1;
+ msc_data->rtp_base = 4000;
+
+ msc_data->nr = nr;
+ msc_data->allow_emerg = 1;
+ msc_data->a.asp_proto = OSMO_SS7_ASP_PROT_M3UA;
+
+ /* Defaults for the audio setup */
+ msc_data->amr_conf.m5_90 = 1;
+
+ /* Allow the full set of possible codecs by default */
+ msc_data->audio_length = 5;
+ msc_data->audio_support =
+ talloc_zero_array(msc_data, struct gsm_audio_support *,
+ msc_data->audio_length);
+ for (i = 0; i < msc_data->audio_length; i++) {
+ msc_data->audio_support[i] =
+ talloc_zero(msc_data->audio_support,
+ struct gsm_audio_support);
+ }
+ msc_data->audio_support[0]->ver = 1;
+ msc_data->audio_support[0]->hr = 0;
+ msc_data->audio_support[1]->ver = 1;
+ msc_data->audio_support[1]->hr = 1;
+ msc_data->audio_support[2]->ver = 2;
+ msc_data->audio_support[2]->hr = 0;
+ msc_data->audio_support[3]->ver = 3;
+ msc_data->audio_support[3]->hr = 0;
+ msc_data->audio_support[4]->ver = 3;
+ msc_data->audio_support[4]->hr = 1;
+
+ return msc_data;
+}
+
diff --git a/src/osmo-bsc/osmo_bsc_sigtran.c b/src/osmo-bsc/osmo_bsc_sigtran.c
new file mode 100644
index 000000000..4b2c4aeac
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_sigtran.c
@@ -0,0 +1,603 @@
+/* (C) 2017 by sysmocom s.f.m.c. GmbH, Author: Philipp Maier
+ * (C) 2017-2018 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/sigtran/osmo_ss7.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/osmo_bsc_grace.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/a_reset.h>
+#include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/mgcp_client/mgcp_common.h>
+
+/* A pointer to a list with all involved MSCs
+ * (a copy of the pointer location submitted with osmo_bsc_sigtran_init() */
+static struct llist_head *msc_list;
+
+#define RESET_INTERVAL 1 /* sek */
+#define SCCP_MSG_MAXSIZE 1024
+#define CS7_POINTCODE_DEFAULT_OFFSET 2
+
+/* The SCCP stack will not assign connection IDs to us automatically, we
+ * will do this ourselves using a counter variable, that counts one up
+ * for every new connection */
+static uint32_t conn_id_counter;
+
+/* Helper function to Check if the given connection id is already assigned */
+static struct gsm_subscriber_connection *get_bsc_conn_by_conn_id(int conn_id)
+{
+ conn_id &= 0xFFFFFF;
+ struct gsm_subscriber_connection *conn;
+
+ llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
+ if (conn->sccp.conn_id == conn_id)
+ return conn;
+ }
+
+ return NULL;
+}
+
+/* Pick a free connection id */
+static int pick_free_conn_id(const struct bsc_msc_data *msc)
+{
+ int conn_id = conn_id_counter;
+ int i;
+
+ for (i = 0; i < 0xFFFFFF; i++) {
+ conn_id++;
+ conn_id &= 0xFFFFFF;
+ if (get_bsc_conn_by_conn_id(conn_id) == false) {
+ conn_id_counter = conn_id;
+ return conn_id;
+ }
+ }
+
+ return -1;
+}
+
+/* Send reset to MSC */
+static void osmo_bsc_sigtran_tx_reset(const struct bsc_msc_data *msc)
+{
+ struct osmo_ss7_instance *ss7;
+ struct msgb *msg;
+
+ ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DMSC, LOGL_NOTICE, "Sending RESET to MSC: %s\n", osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
+ msg = gsm0808_create_reset();
+ osmo_sccp_tx_unitdata_msg(msc->a.sccp_user, &msc->a.bsc_addr,
+ &msc->a.msc_addr, msg);
+}
+
+/* Send reset-ack to MSC */
+void osmo_bsc_sigtran_tx_reset_ack(const struct bsc_msc_data *msc)
+{
+ struct osmo_ss7_instance *ss7;
+ struct msgb *msg;
+ OSMO_ASSERT(msc);
+
+ ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DMSC, LOGL_NOTICE, "Sending RESET ACK to MSC: %s\n", osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
+ msg = gsm0808_create_reset_ack();
+ osmo_sccp_tx_unitdata_msg(msc->a.sccp_user, &msc->a.bsc_addr,
+ &msc->a.msc_addr, msg);
+}
+
+/* Find an MSC by its sigtran point code */
+static struct bsc_msc_data *get_msc_by_addr(const struct osmo_sccp_addr *msc_addr)
+{
+ struct osmo_ss7_instance *ss7;
+ struct bsc_msc_data *msc;
+ llist_for_each_entry(msc, msc_list, entry) {
+ if (memcmp(msc_addr, &msc->a.msc_addr, sizeof(*msc_addr)) == 0)
+ return msc;
+ }
+
+ ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DMSC, LOGL_ERROR, "Unable to find MSC data under address: %s\n", osmo_sccp_addr_name(ss7, msc_addr));
+ return NULL;
+}
+
+/* Send data to MSC, use the connection id which MSC it is */
+static int handle_data_from_msc(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ msg->l3h = msgb_l2(msg);
+ return bsc_handle_dt(conn, msg, msgb_l2len(msg));
+}
+
+/* Sent unitdata to MSC, use the point code to determine which MSC it is */
+static int handle_unitdata_from_msc(const struct osmo_sccp_addr *msc_addr, struct msgb *msg,
+ const struct osmo_sccp_user *scu)
+{
+ struct osmo_ss7_instance *ss7;
+ struct bsc_msc_data *msc = get_msc_by_addr(msc_addr);
+ int rc = -EINVAL;
+
+ if (msc) {
+ msg->l3h = msgb_l2(msg);
+ rc = bsc_handle_udt(msc, msg, msgb_l2len(msg));
+ } else {
+ ss7 = osmo_sccp_get_ss7(osmo_sccp_get_sccp(scu));
+ OSMO_ASSERT(ss7);
+ LOGP(DMSC, LOGL_NOTICE, "incoming unitdata data from unknown remote address: %s\n",
+ osmo_sccp_addr_name(ss7, msc_addr));
+ }
+ return rc;
+}
+
+static int handle_n_connect_from_msc(struct osmo_sccp_user *scu, struct osmo_scu_prim *scu_prim)
+{
+ struct bsc_msc_data *msc = get_msc_by_addr(&scu_prim->u.connect.calling_addr);
+ struct gsm_subscriber_connection *conn;
+ int rc = 0;
+
+ conn = get_bsc_conn_by_conn_id(scu_prim->u.connect.conn_id);
+ if (conn) {
+ LOGP(DMSC, LOGL_NOTICE,
+ "(calling_addr=%s conn_id=%u) N-CONNECT.ind with already used conn_id, ignoring\n",
+ osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr),
+ scu_prim->u.connect.conn_id);
+ /* The situation is illogical. A conn was already established with this conn id, if we
+ * would like to reply with a disconn onto this conn id, we would close the existing
+ * conn. So just ignore this impossible N-CONNECT completely (including the BSSMAP PDU). */
+ return -EINVAL;
+ }
+
+ if (!msc) {
+ LOGP(DMSC, LOGL_NOTICE, "(calling_addr=%s conn_id=%u) N-CONNECT.ind from unknown MSC\n",
+ osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr),
+ scu_prim->u.connect.conn_id);
+ rc = -ENOENT;
+ goto refuse;
+ }
+
+ conn = bsc_subscr_con_allocate(bsc_gsmnet);
+ if (!conn)
+ return -ENOMEM;
+ conn->sccp.msc = msc;
+ conn->sccp.conn_id = scu_prim->u.connect.conn_id;
+
+ /* Take actions asked for by the enclosed PDU */
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_IND, scu_prim);
+
+ return 0;
+refuse:
+ osmo_sccp_tx_disconn(scu, scu_prim->u.connect.conn_id, &scu_prim->u.connect.called_addr, 0);
+ return rc;
+}
+
+/* Callback function, called by the SSCP stack when data arrives */
+static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
+{
+ struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *)oph;
+ struct osmo_sccp_user *scu = _scu;
+ struct gsm_subscriber_connection *conn;
+ int rc = 0;
+
+ switch (OSMO_PRIM_HDR(&scu_prim->oph)) {
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
+ /* Handle inbound UNITDATA */
+ DEBUGP(DMSC, "N-UNITDATA.ind(%s)\n", osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+ rc = handle_unitdata_from_msc(&scu_prim->u.unitdata.calling_addr, oph->msg, scu);
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
+ /* Handle inbound connections */
+ DEBUGP(DMSC, "N-CONNECT.ind(X->%u)\n", scu_prim->u.connect.conn_id);
+ rc = handle_n_connect_from_msc(scu, scu_prim);
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
+ /* Handle outbound connection confirmation */
+ DEBUGP(DMSC, "N-CONNECT.cnf(%u, %s)\n", scu_prim->u.connect.conn_id,
+ osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+ conn = get_bsc_conn_by_conn_id(scu_prim->u.connect.conn_id);
+ if (conn) {
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_CFM, scu_prim);
+ conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
+ if (msgb_l2len(oph->msg) > 0)
+ handle_data_from_msc(conn, oph->msg);
+ } else {
+ LOGP(DMSC, LOGL_ERROR, "N-CONNECT.cfm(%u, %s) for unknown conn?!?\n",
+ scu_prim->u.connect.conn_id, osmo_hexdump(msgb_l2(oph->msg),
+ msgb_l2len(oph->msg)));
+ }
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
+ /* Handle incoming connection oriented data */
+ DEBUGP(DMSC, "N-DATA.ind(%u, %s)\n", scu_prim->u.data.conn_id,
+ osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+
+ /* Incoming data is a sign of a vital connection */
+ conn = get_bsc_conn_by_conn_id(scu_prim->u.data.conn_id);
+ if (conn) {
+ a_reset_conn_success(conn->sccp.msc);
+ handle_data_from_msc(conn, oph->msg);
+ }
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
+ DEBUGP(DMSC, "N-DISCONNECT.ind(%u, %s, cause=%i)\n", scu_prim->u.disconnect.conn_id,
+ osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)),
+ scu_prim->u.disconnect.cause);
+ /* indication of disconnect */
+ conn = get_bsc_conn_by_conn_id(scu_prim->u.disconnect.conn_id);
+ if (conn) {
+ conn->sccp.state = SUBSCR_SCCP_ST_NONE;
+ if (msgb_l2len(oph->msg) > 0)
+ handle_data_from_msc(conn, oph->msg);
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_DISC_IND, scu_prim);
+ }
+ break;
+
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Unhandled SIGTRAN operation %s on primitive %u\n",
+ get_value_string(osmo_prim_op_names, oph->operation), oph->primitive);
+ break;
+ }
+
+ msgb_free(oph->msg);
+ return rc;
+}
+
+/* Allocate resources to make a new connection oriented sigtran connection
+ * (not the connection ittself!) */
+enum bsc_con osmo_bsc_sigtran_new_conn(struct gsm_subscriber_connection *conn, struct bsc_msc_data *msc)
+{
+ struct osmo_ss7_instance *ss7;
+ struct gsm_bts *bts = conn_get_bts(conn);
+
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(msc);
+
+ ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DMSC, LOGL_NOTICE, "Initializing resources for new SIGTRAN connection to MSC: %s...\n",
+ osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
+
+ if (a_reset_conn_ready(msc) == false) {
+ LOGP(DMSC, LOGL_ERROR, "MSC is not connected. Dropping.\n");
+ return BSC_CON_REJECT_NO_LINK;
+ }
+
+ if (!bsc_grace_allow_new_connection(bts->network, bts)) {
+ LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n");
+ return BSC_CON_REJECT_RF_GRACE;
+ }
+
+ conn->sccp.msc = msc;
+
+ return BSC_CON_SUCCESS;
+}
+
+/* Open a new connection oriented sigtran connection */
+int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct osmo_ss7_instance *ss7;
+ struct bsc_msc_data *msc;
+ int conn_id;
+ int rc;
+
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(conn->sccp.msc);
+ OSMO_ASSERT(conn->sccp.conn_id == -1);
+
+ msc = conn->sccp.msc;
+
+ if (a_reset_conn_ready(msc) == false) {
+ LOGP(DMSC, LOGL_ERROR, "MSC is not connected. Dropping.\n");
+ return -EINVAL;
+ }
+
+ conn->sccp.conn_id = conn_id = pick_free_conn_id(msc);
+ if (conn->sccp.conn_id < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to allocate SCCP Connection ID\n");
+ return -1;
+ }
+ LOGP(DMSC, LOGL_DEBUG, "Allocated new connection id: %d\n", conn->sccp.conn_id);
+ ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DMSC, LOGL_NOTICE, "Opening new SIGTRAN connection (id=%i) to MSC: %s\n", conn_id,
+ osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
+
+ rc = osmo_sccp_tx_conn_req_msg(msc->a.sccp_user, conn_id, &msc->a.bsc_addr,
+ &msc->a.msc_addr, msg);
+ if (rc >= 0)
+ conn->sccp.state = SUBSCR_SCCP_ST_WAIT_CONN_CONF;
+
+ return rc;
+}
+
+/* Send data to MSC, the function will take ownership of *msg */
+int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct osmo_ss7_instance *ss7;
+ int conn_id;
+ int rc;
+ struct bsc_msc_data *msc;
+
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(msg);
+
+ if (!conn->sccp.msc) {
+ LOGP(DMSC, LOGL_ERROR, "MSC is not connected. Dropping.\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ msc = conn->sccp.msc;
+
+ /* Log the type of the message we are sending. This is just
+ * informative, do not stop if detecting the type fails */
+ if (msg->len >= 3) {
+ switch (msg->data[0]) {
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ LOGP(DMSC, LOGL_INFO, "Tx MSC: BSSMAP: %s\n",
+ gsm0808_bssmap_name(msg->data[2]));
+ break;
+ case BSSAP_MSG_DTAP:
+ LOGP(DMSC, LOGL_INFO, "Tx MSC: DTAP\n");
+ break;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Tx MSC: unknown message type: 0x%x\n",
+ msg->data[0]);
+ }
+ } else
+ LOGP(DMSC, LOGL_ERROR, "Tx MSC: message too short: %u\n", msg->len);
+
+ if (a_reset_conn_ready(msc) == false) {
+ LOGP(DMSC, LOGL_ERROR, "MSC is not connected. Dropping.\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ conn_id = conn->sccp.conn_id;
+
+ ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DMSC, LOGL_DEBUG, "Sending connection (id=%i) oriented data to MSC: %s (%s)\n",
+ conn_id, osmo_sccp_addr_name(ss7, &msc->a.msc_addr), osmo_hexdump(msg->data, msg->len));
+
+ rc = osmo_sccp_tx_data_msg(msc->a.sccp_user, conn_id, msg);
+
+ return rc;
+}
+
+/* Send an USSD notification in case we loose the connection to the MSC */
+static void bsc_notify_msc_lost(struct gsm_subscriber_connection *conn)
+{
+ /* Check if sccp conn is still present */
+ if (!conn)
+ return;
+
+ /* check for config string */
+ if (!conn->sccp.msc->ussd_msc_lost_txt)
+ return;
+ if (conn->sccp.msc->ussd_msc_lost_txt[0] == '\0')
+ return;
+
+ /* send USSD notification */
+ bsc_send_ussd_notify(conn, 1, conn->sccp.msc->ussd_msc_lost_txt);
+ bsc_send_ussd_release_complete(conn);
+}
+
+/* Close all open sigtran connections and channels */
+void osmo_bsc_sigtran_reset(const struct bsc_msc_data *msc)
+{
+ struct gsm_subscriber_connection *conn, *conn_temp;
+ OSMO_ASSERT(msc);
+
+ /* Close all open connections */
+ llist_for_each_entry_safe(conn, conn_temp, &bsc_gsmnet->subscr_conns, entry) {
+
+ /* We only may close connections which actually belong to this
+ * MSC. All other open connections are left untouched */
+ if (conn->sccp.msc == msc) {
+ /* Notify active connection users via USSD that the MSC is down */
+ bsc_notify_msc_lost(conn);
+
+ /* Take down all occopied RF channels */
+ /* Disconnect all Sigtran connections */
+ /* Delete subscriber connection */
+ osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REQUEST, NULL);
+ }
+ }
+}
+
+/* Callback function: Close all open connections */
+static void osmo_bsc_sigtran_reset_cb(const void *priv)
+{
+ struct bsc_msc_data *msc = (struct bsc_msc_data*) priv;
+
+ /* Shut down all ongoing traffic */
+ osmo_bsc_sigtran_reset(msc);
+
+ /* Send reset to MSC */
+ osmo_bsc_sigtran_tx_reset(msc);
+}
+
+/* Default point-code to be used as local address (BSC) */
+#define BSC_DEFAULT_PC "0.23.3"
+
+/* Default point-code to be used as remote address (MSC) */
+#define MSC_DEFAULT_PC "0.23.1"
+
+static int asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *msg);
+
+/* Initalize osmo sigtran backhaul */
+int osmo_bsc_sigtran_init(struct llist_head *mscs)
+{
+ bool free_attempt_used = false;
+ bool fail_on_next_invalid_cfg = false;
+
+ struct bsc_msc_data *msc;
+ char msc_name[32];
+ uint32_t default_pc;
+
+ osmo_ss7_register_rx_unknown_cb(&asp_rx_unknown);
+
+ OSMO_ASSERT(mscs);
+ msc_list = mscs;
+
+ llist_for_each_entry(msc, msc_list, entry) {
+ snprintf(msc_name, sizeof(msc_name), "msc-%u", msc->nr);
+ LOGP(DMSC, LOGL_NOTICE, "Initializing SCCP connection to MSC %s\n", msc_name);
+
+ /* Check if the VTY could determine a valid CS7 instance,
+ * use safe default in case none is set */
+ if (msc->a.cs7_instance_valid == false) {
+ msc->a.cs7_instance = 0;
+ if (fail_on_next_invalid_cfg)
+ goto fail_auto_cofiguration;
+ free_attempt_used = true;
+ }
+ LOGP(DMSC, LOGL_NOTICE, "CS7 Instance identifier, A-Interface: %u\n", msc->a.cs7_instance);
+
+ /* Pre-Check if there is an ss7 instance present */
+ if (osmo_ss7_instance_find(msc->a.cs7_instance) == NULL) {
+ if (fail_on_next_invalid_cfg)
+ goto fail_auto_cofiguration;
+ free_attempt_used = true;
+ }
+
+ /* SS7 Protocol stack */
+ default_pc = osmo_ss7_pointcode_parse(NULL, BSC_DEFAULT_PC);
+ msc->a.sccp =
+ osmo_sccp_simple_client_on_ss7_id(msc, msc->a.cs7_instance, msc_name, default_pc,
+ msc->a.asp_proto, 0, NULL, 0, NULL);
+ if (!msc->a.sccp)
+ return -EINVAL;
+
+ /* In SCCPlite, the MSC side of the MGW endpoint is configured by the MSC. Since we have
+ * no way to figure out which CallID ('C:') the MSC will issue in its CRCX command, set
+ * an X-Osmo-IGN flag telling osmo-mgw to ignore CallID mismatches for this endpoint.
+ * If an explicit VTY command has already indicated whether or not to send X-Osmo-IGN, do
+ * not overwrite that setting. */
+ if (msc->a.asp_proto == OSMO_SS7_ASP_PROT_IPA
+ && !msc->x_osmo_ign_configured)
+ msc->x_osmo_ign |= MGCP_X_OSMO_IGN_CALLID;
+
+ /* If unset, use default local SCCP address */
+ if (!msc->a.bsc_addr.presence)
+ osmo_sccp_local_addr_by_instance(&msc->a.bsc_addr, msc->a.sccp,
+ OSMO_SCCP_SSN_BSSAP);
+
+ if (!osmo_sccp_check_addr(&msc->a.bsc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
+ LOGP(DMSC, LOGL_ERROR,
+ "(%s) A-interface: invalid local (BSC) SCCP address: %s\n",
+ msc_name, osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.bsc_addr));
+ return -EINVAL;
+ }
+
+ /* If unset, use default SCCP address for the MSC */
+ if (!msc->a.msc_addr.presence)
+ osmo_sccp_make_addr_pc_ssn(&msc->a.msc_addr,
+ osmo_ss7_pointcode_parse(NULL, MSC_DEFAULT_PC),
+ OSMO_SCCP_SSN_BSSAP);
+
+ if (!osmo_sccp_check_addr(&msc->a.msc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
+ LOGP(DMSC, LOGL_ERROR,
+ "(%s) A-interface: invalid remote (MSC) SCCP address: %s\n",
+ msc_name, osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.msc_addr));
+ return -EINVAL;
+ }
+
+ LOGP(DMSC, LOGL_NOTICE, "(%s) A-interface: local (BSC) SCCP address: %s\n",
+ msc_name, osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.bsc_addr));
+ LOGP(DMSC, LOGL_NOTICE, "(%s) A-interface: remote (MSC) SCCP address: %s\n",
+ msc_name, osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.msc_addr));
+
+ /* Bind SCCP user */
+ msc->a.sccp_user = osmo_sccp_user_bind(msc->a.sccp, msc_name, sccp_sap_up, msc->a.bsc_addr.ssn);
+ if (!msc->a.sccp_user)
+ return -EINVAL;
+
+ /* Start MSC-Reset procedure */
+ a_reset_alloc(msc, msc_name, osmo_bsc_sigtran_reset_cb);
+
+ /* If we have detected that the SS7 configuration of the MSC we have just initalized
+ * was incomplete or completely missing, we can not tolerate another incomplete
+ * configuration. The reson for this is that we do only specify exactly one default
+ * pointcode pair. We also specify localhost as default IP-Address. If we have wanted
+ * to support multiple MSCs with automatic configuration we would be forced to invent
+ * a complex ruleset how to allocate the pointcodes and respective IP-Addresses.
+ * Furthermore, the situation where a single BSC is connected to multiple MSCs
+ * is a very rare situation anyway. In this case we expect the user to experienced
+ * enough to create a valid SS7/CS7 VTY configuration that does not lack any
+ * components */
+ if (free_attempt_used)
+ fail_on_next_invalid_cfg = true;
+ }
+
+ return 0;
+
+fail_auto_cofiguration:
+ LOGP(DMSC, LOGL_ERROR,
+ "A-interface: More than one invalid/inclomplete configuration detected, unable to revover - check config file!\n");
+ return -EINVAL;
+}
+
+/* this function receives all messages received on an ASP for a PPID / StreamID that
+ * libosmo-sigtran doesn't know about, such as piggy-backed CTRL and/or MGCP */
+static int asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *msg)
+{
+ struct ipaccess_head *iph;
+ struct ipaccess_head_ext *iph_ext;
+
+ if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ switch (ppid_mux) {
+ case IPAC_PROTO_OSMO:
+ if (msg->len < sizeof(*iph) + sizeof(*iph_ext)) {
+ LOGP(DMSC, LOGL_ERROR, "The message is too short.\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ iph = (struct ipaccess_head *) msg->data;
+ iph_ext = (struct ipaccess_head_ext *) iph->data;
+ msg->l2h = iph_ext->data;
+ switch (iph_ext->proto) {
+ case IPAC_PROTO_EXT_CTRL:
+ return bsc_sccplite_rx_ctrl(asp, msg);
+ }
+ break;
+ default:
+ break;
+ }
+ msgb_free(msg);
+ return 0; /* OSMO_SS7_UNKNOWN? */
+}
diff --git a/src/osmo-bsc/osmo_bsc_vty.c b/src/osmo-bsc/osmo_bsc_vty.c
new file mode 100644
index 000000000..a32f58087
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_vty.c
@@ -0,0 +1,1021 @@
+/* Osmo BSC VTY Configuration */
+/* (C) 2009-2015 by Holger Hans Peter Freyther
+ * (C) 2009-2014 by On-Waves
+ * (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bsc_msg_filter.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+
+
+#include <time.h>
+
+
+#define IPA_STR "IP.ACCESS specific\n"
+
+static struct osmo_bsc_data *osmo_bsc_data(struct vty *vty)
+{
+ return bsc_gsmnet->bsc_data;
+}
+
+static struct bsc_msc_data *bsc_msc_data(struct vty *vty)
+{
+ return vty->index;
+}
+
+static struct cmd_node bsc_node = {
+ BSC_NODE,
+ "%s(config-bsc)# ",
+ 1,
+};
+
+static struct cmd_node msc_node = {
+ MSC_NODE,
+ "%s(config-msc)# ",
+ 1,
+};
+
+DEFUN(cfg_net_msc, cfg_net_msc_cmd,
+ "msc [<0-1000>]", "Configure MSC details\n" "MSC connection to configure\n")
+{
+ int index = argc == 1 ? atoi(argv[0]) : 0;
+ struct bsc_msc_data *msc;
+
+ msc = osmo_msc_data_alloc(bsc_gsmnet, index);
+ if (!msc) {
+ vty_out(vty, "%%Failed to allocate MSC data.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty->index = msc;
+ vty->node = MSC_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc, cfg_net_bsc_cmd,
+ "bsc", "Configure BSC\n")
+{
+ vty->node = BSC_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_msc_amr_options(struct vty *vty, struct bsc_msc_data *msc)
+{
+#define WRITE_AMR(vty, msc, name, var) \
+ vty_out(vty, " amr-config %s %s%s", \
+ name, msc->amr_conf.var ? "allowed" : "forbidden", \
+ VTY_NEWLINE);
+
+ WRITE_AMR(vty, msc, "12_2k", m12_2);
+ WRITE_AMR(vty, msc, "10_2k", m10_2);
+ WRITE_AMR(vty, msc, "7_95k", m7_95);
+ WRITE_AMR(vty, msc, "7_40k", m7_40);
+ WRITE_AMR(vty, msc, "6_70k", m6_70);
+ WRITE_AMR(vty, msc, "5_90k", m5_90);
+ WRITE_AMR(vty, msc, "5_15k", m5_15);
+ WRITE_AMR(vty, msc, "4_75k", m4_75);
+#undef WRITE_AMR
+}
+
+static void write_msc(struct vty *vty, struct bsc_msc_data *msc)
+{
+ vty_out(vty, "msc %d%s", msc->nr, VTY_NEWLINE);
+ if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID)
+ vty_out(vty, " core-mobile-network-code %s%s",
+ osmo_mnc_name(msc->core_plmn.mnc, msc->core_plmn.mnc_3_digits), VTY_NEWLINE);
+ if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
+ vty_out(vty, " core-mobile-country-code %s%s",
+ osmo_mcc_name(msc->core_plmn.mcc), VTY_NEWLINE);
+ if (msc->core_lac != -1)
+ vty_out(vty, " core-location-area-code %d%s",
+ msc->core_lac, VTY_NEWLINE);
+ if (msc->core_ci != -1)
+ vty_out(vty, " core-cell-identity %d%s",
+ msc->core_ci, VTY_NEWLINE);
+ vty_out(vty, " ip.access rtp-base %d%s", msc->rtp_base, VTY_NEWLINE);
+
+ if (msc->ussd_welcome_txt)
+ vty_out(vty, " bsc-welcome-text %s%s", msc->ussd_welcome_txt, VTY_NEWLINE);
+ else
+ vty_out(vty, " no bsc-welcome-text%s", VTY_NEWLINE);
+
+ if (msc->ussd_msc_lost_txt && msc->ussd_msc_lost_txt[0])
+ vty_out(vty, " bsc-msc-lost-text %s%s", msc->ussd_msc_lost_txt, VTY_NEWLINE);
+ else
+ vty_out(vty, " no bsc-msc-lost-text%s", VTY_NEWLINE);
+
+ if (msc->ussd_grace_txt && msc->ussd_grace_txt[0])
+ vty_out(vty, " bsc-grace-text %s%s", msc->ussd_grace_txt, VTY_NEWLINE);
+ else
+ vty_out(vty, " no bsc-grace-text%s", VTY_NEWLINE);
+
+ if (msc->audio_length != 0) {
+ int i;
+
+ vty_out(vty, " codec-list ");
+ for (i = 0; i < msc->audio_length; ++i) {
+ if (i != 0)
+ vty_out(vty, " ");
+
+ if (msc->audio_support[i]->hr)
+ vty_out(vty, "hr%.1u", msc->audio_support[i]->ver);
+ else
+ vty_out(vty, "fr%.1u", msc->audio_support[i]->ver);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ }
+
+ vty_out(vty, " type %s%s", msc->type == MSC_CON_TYPE_NORMAL ?
+ "normal" : "local", VTY_NEWLINE);
+ vty_out(vty, " allow-emergency %s%s", msc->allow_emerg ?
+ "allow" : "deny", VTY_NEWLINE);
+
+ if (msc->local_pref)
+ vty_out(vty, " local-prefix %s%s", msc->local_pref, VTY_NEWLINE);
+
+ if (msc->acc_lst_name)
+ vty_out(vty, " access-list-name %s%s", msc->acc_lst_name, VTY_NEWLINE);
+
+ /* write amr options */
+ write_msc_amr_options(vty, msc);
+
+ /* write sccp connection configuration */
+ if (msc->a.bsc_addr_name) {
+ vty_out(vty, " bsc-addr %s%s",
+ msc->a.bsc_addr_name, VTY_NEWLINE);
+ }
+ if (msc->a.msc_addr_name) {
+ vty_out(vty, " msc-addr %s%s",
+ msc->a.msc_addr_name, VTY_NEWLINE);
+ }
+ vty_out(vty, " asp-protocol %s%s", osmo_ss7_asp_protocol_name(msc->a.asp_proto), VTY_NEWLINE);
+ vty_out(vty, " lcls-mode %s%s", get_value_string(bsc_lcls_mode_names, msc->lcls_mode),
+ VTY_NEWLINE);
+
+ if (msc->lcls_codec_mismatch_allow)
+ vty_out(vty, " lcls-codec-mismatch allowed%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " lcls-codec-mismatch forbidden%s", VTY_NEWLINE);
+
+ /* write MGW configuration */
+ mgcp_client_config_write(vty, " ");
+
+ if (msc->x_osmo_ign_configured) {
+ if (!msc->x_osmo_ign)
+ vty_out(vty, " no mgw x-osmo-ign%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " mgw x-osmo-ign call-id%s", VTY_NEWLINE);
+ }
+}
+
+static int config_write_msc(struct vty *vty)
+{
+ struct bsc_msc_data *msc;
+ struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+ llist_for_each_entry(msc, &bsc->mscs, entry)
+ write_msc(vty, msc);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_bsc(struct vty *vty)
+{
+ struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+ vty_out(vty, "bsc%s", VTY_NEWLINE);
+ if (bsc->mid_call_txt)
+ vty_out(vty, " mid-call-text %s%s", bsc->mid_call_txt, VTY_NEWLINE);
+ vty_out(vty, " mid-call-timeout %d%s", bsc->mid_call_timeout, VTY_NEWLINE);
+ if (bsc->rf_ctrl_name)
+ vty_out(vty, " bsc-rf-socket %s%s",
+ bsc->rf_ctrl_name, VTY_NEWLINE);
+
+ if (bsc->auto_off_timeout != -1)
+ vty_out(vty, " bsc-auto-rf-off %d%s",
+ bsc->auto_off_timeout, VTY_NEWLINE);
+
+ if (bsc->ussd_no_msc_txt && bsc->ussd_no_msc_txt[0])
+ vty_out(vty, " missing-msc-text %s%s", bsc->ussd_no_msc_txt, VTY_NEWLINE);
+ else
+ vty_out(vty, " no missing-msc-text%s", VTY_NEWLINE);
+ if (bsc->acc_lst_name)
+ vty_out(vty, " access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE);
+
+ bsc_msg_acc_lst_write(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_ncc,
+ cfg_net_bsc_ncc_cmd,
+ "core-mobile-network-code <1-999>",
+ "Use this network code for the core network\n" "MNC value\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(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;
+ }
+ data->core_plmn.mnc = mnc;
+ data->core_plmn.mnc_3_digits = mnc_3_digits;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mcc,
+ cfg_net_bsc_mcc_cmd,
+ "core-mobile-country-code <1-999>",
+ "Use this country code for the core network\n" "MCC value\n")
+{
+ uint16_t mcc;
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ if (osmo_mcc_from_str(argv[0], &mcc)) {
+ vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ data->core_plmn.mcc = mcc;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_lac,
+ cfg_net_bsc_lac_cmd,
+ "core-location-area-code <0-65535>",
+ "Use this location area code for the core network\n" "LAC value\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->core_lac = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_ci,
+ cfg_net_bsc_ci_cmd,
+ "core-cell-identity <0-65535>",
+ "Use this cell identity for the core network\n" "CI value\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->core_ci = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_rtp_base,
+ cfg_net_bsc_rtp_base_cmd,
+ "ip.access rtp-base <1-65000>",
+ IPA_STR
+ "Set the rtp-base port for the RTP stream\n"
+ "Port number\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->rtp_base = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_codec_list,
+ cfg_net_bsc_codec_list_cmd,
+ "codec-list .LIST",
+ "Set the allowed audio codecs\n"
+ "List of audio codecs, e.g. fr3 fr1 hr3\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ int i;
+
+ /* free the old list... if it exists */
+ if (data->audio_support) {
+ talloc_free(data->audio_support);
+ data->audio_support = NULL;
+ data->audio_length = 0;
+ }
+
+ /* create a new array */
+ data->audio_support =
+ talloc_zero_array(osmo_bsc_data(vty), struct gsm_audio_support *, argc);
+ data->audio_length = argc;
+
+ for (i = 0; i < argc; ++i) {
+ /* check for hrX or frX */
+ if (strlen(argv[i]) != 3
+ || argv[i][1] != 'r'
+ || (argv[i][0] != 'h' && argv[i][0] != 'f')
+ || argv[i][2] < 0x30
+ || argv[i][2] > 0x39)
+ goto error;
+
+ data->audio_support[i] = talloc_zero(data->audio_support,
+ struct gsm_audio_support);
+ data->audio_support[i]->ver = atoi(argv[i] + 2);
+
+ if (strncmp("hr", argv[i], 2) == 0)
+ data->audio_support[i]->hr = 1;
+ else if (strncmp("fr", argv[i], 2) == 0)
+ data->audio_support[i]->hr = 0;
+ }
+
+ return CMD_SUCCESS;
+
+error:
+ vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s",
+ argv[i], VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+}
+
+DEFUN(cfg_net_msc_welcome_ussd,
+ cfg_net_msc_welcome_ussd_cmd,
+ "bsc-welcome-text .TEXT",
+ "Set the USSD notification to be sent\n" "Text to be sent\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ char *str = argv_concat(argv, argc, 0);
+ if (!str)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_welcome_txt, str);
+ talloc_free(str);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_welcome_ussd,
+ cfg_net_msc_no_welcome_ussd_cmd,
+ "no bsc-welcome-text",
+ NO_STR "Clear the USSD notification to be sent\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ talloc_free(data->ussd_welcome_txt);
+ data->ussd_welcome_txt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_lost_ussd,
+ cfg_net_msc_lost_ussd_cmd,
+ "bsc-msc-lost-text .TEXT",
+ "Set the USSD notification to be sent on MSC connection loss\n" "Text to be sent\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ char *str = argv_concat(argv, argc, 0);
+ if (!str)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_msc_lost_txt, str);
+ talloc_free(str);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_lost_ussd,
+ cfg_net_msc_no_lost_ussd_cmd,
+ "no bsc-msc-lost-text",
+ NO_STR "Clear the USSD notification to be sent on MSC connection loss\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ talloc_free(data->ussd_msc_lost_txt);
+ data->ussd_msc_lost_txt = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_grace_ussd,
+ cfg_net_msc_grace_ussd_cmd,
+ "bsc-grace-text .TEXT",
+ "Set the USSD notification to be sent when the MSC has entered the grace period\n" "Text to be sent\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ char *str = argv_concat(argv, argc, 0);
+ if (!str)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_grace_txt, str);
+ talloc_free(str);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_grace_ussd,
+ cfg_net_msc_no_grace_ussd_cmd,
+ "no bsc-grace-text",
+ NO_STR "Clear the USSD notification to be sent when the MSC has entered the grace period\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ talloc_free(data->ussd_grace_txt);
+ data->ussd_grace_txt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_missing_msc_ussd,
+ cfg_net_bsc_missing_msc_ussd_cmd,
+ "missing-msc-text .TEXT",
+ "Set the USSD notification to be send when a MSC has not been found.\n" "Text to be sent\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ char *txt = argv_concat(argv, argc, 0);
+ if (!txt)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(data, &data->ussd_no_msc_txt, txt);
+ talloc_free(txt);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_no_missing_msc_text,
+ cfg_net_bsc_no_missing_msc_text_cmd,
+ "no missing-msc-text",
+ NO_STR "Clear the USSD notification to be send when a MSC has not been found.\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+
+ talloc_free(data->ussd_no_msc_txt);
+ data->ussd_no_msc_txt = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_net_msc_type,
+ cfg_net_msc_type_cmd,
+ "type (normal|local)",
+ "Select the MSC type\n"
+ "Plain GSM MSC\n" "Special MSC for local call routing\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ if (strcmp(argv[0], "normal") == 0)
+ data->type = MSC_CON_TYPE_NORMAL;
+ else if (strcmp(argv[0], "local") == 0)
+ data->type = MSC_CON_TYPE_LOCAL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_emerg,
+ cfg_net_msc_emerg_cmd,
+ "allow-emergency (allow|deny)",
+ "Allow CM ServiceRequests with type emergency\n"
+ "Allow\n" "Deny\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->allow_emerg = strcmp("allow", argv[0]) == 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_local_prefix,
+ cfg_net_msc_local_prefix_cmd,
+ "local-prefix REGEXP",
+ "Prefix for local numbers\n" "REGEXP used\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+ if (gsm_parse_reg(msc, &msc->local_pref_reg, &msc->local_pref, argc, argv) != 0) {
+ vty_out(vty, "%%Failed to parse the regexp: '%s'%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define AMR_CONF_STR "AMR Multirate Configuration\n"
+#define AMR_COMMAND(name) \
+ DEFUN(cfg_net_msc_amr_##name, \
+ cfg_net_msc_amr_##name##_cmd, \
+ "amr-config " #name "k (allowed|forbidden)", \
+ AMR_CONF_STR "Bitrate\n" "Allowed\n" "Forbidden\n") \
+{ \
+ struct bsc_msc_data *msc = bsc_msc_data(vty); \
+ \
+ msc->amr_conf.m##name = strcmp(argv[0], "allowed") == 0; \
+ return CMD_SUCCESS; \
+}
+
+AMR_COMMAND(12_2)
+AMR_COMMAND(10_2)
+AMR_COMMAND(7_95)
+AMR_COMMAND(7_40)
+AMR_COMMAND(6_70)
+AMR_COMMAND(5_90)
+AMR_COMMAND(5_15)
+AMR_COMMAND(4_75)
+
+DEFUN(cfg_msc_acc_lst_name,
+ cfg_msc_acc_lst_name_cmd,
+ "access-list-name NAME",
+ "Set the name of the access list to use.\n"
+ "The name of the to be used access list.")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+ osmo_talloc_replace_string(msc, &msc->acc_lst_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_no_acc_lst_name,
+ cfg_msc_no_acc_lst_name_cmd,
+ "no access-list-name",
+ NO_STR "Remove the access list from the NAT.\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+ if (msc->acc_lst_name) {
+ talloc_free(msc->acc_lst_name);
+ msc->acc_lst_name = NULL;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* Make sure only standard SSN numbers are used. If no ssn number is
+ * configured, silently apply the default SSN */
+static void enforce_standard_ssn(struct vty *vty, struct osmo_sccp_addr *addr)
+{
+ if (addr->presence & OSMO_SCCP_ADDR_T_SSN) {
+ if (addr->ssn != OSMO_SCCP_SSN_BSSAP)
+ vty_out(vty,
+ "setting an SSN (%u) different from the standard (%u) is not allowd, will use standard SSN for address: %s%s",
+ addr->ssn, OSMO_SCCP_SSN_BSSAP, osmo_sccp_addr_dump(addr), VTY_NEWLINE);
+ }
+
+ addr->presence |= OSMO_SCCP_ADDR_T_SSN;
+ addr->ssn = OSMO_SCCP_SSN_BSSAP;
+}
+
+DEFUN(cfg_msc_cs7_bsc_addr,
+ cfg_msc_cs7_bsc_addr_cmd,
+ "bsc-addr NAME",
+ "Calling Address (local address of this BSC)\n" "SCCP address name\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ const char *bsc_addr_name = argv[0];
+ struct osmo_ss7_instance *ss7;
+
+ ss7 = osmo_sccp_addr_by_name(&msc->a.bsc_addr, bsc_addr_name);
+ if (!ss7) {
+ vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", bsc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+
+ /* Prevent mixing addresses from different CS7/SS7 instances */
+ if (msc->a.cs7_instance_valid) {
+ if (msc->a.cs7_instance != ss7->cfg.id) {
+ vty_out(vty,
+ "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
+ bsc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ }
+
+ msc->a.cs7_instance = ss7->cfg.id;
+ msc->a.cs7_instance_valid = true;
+ enforce_standard_ssn(vty, &msc->a.bsc_addr);
+ msc->a.bsc_addr_name = talloc_strdup(msc, bsc_addr_name);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_cs7_msc_addr,
+ cfg_msc_cs7_msc_addr_cmd,
+ "msc-addr NAME",
+ "Called Address (remote address of the MSC)\n" "SCCP address name\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ const char *msc_addr_name = argv[0];
+ struct osmo_ss7_instance *ss7;
+
+ ss7 = osmo_sccp_addr_by_name(&msc->a.msc_addr, msc_addr_name);
+ if (!ss7) {
+ vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", msc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+
+ /* Prevent mixing addresses from different CS7/SS7 instances */
+ if (msc->a.cs7_instance_valid) {
+ if (msc->a.cs7_instance != ss7->cfg.id) {
+ vty_out(vty,
+ "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
+ msc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ }
+
+ msc->a.cs7_instance = ss7->cfg.id;
+ msc->a.cs7_instance_valid = true;
+ enforce_standard_ssn(vty, &msc->a.msc_addr);
+ msc->a.msc_addr_name = talloc_strdup(msc, msc_addr_name);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_cs7_asp_proto,
+ cfg_msc_cs7_asp_proto_cmd,
+ "asp-protocol (m3ua|sua|ipa)",
+ "A interface protocol to use for this MSC)\n"
+ "MTP3 User Adaptation\n"
+ "SCCP User Adaptation\n"
+ "IPA Multiplex (SCCP Lite)\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+ msc->a.asp_proto = get_string_value(osmo_ss7_asp_protocol_vals, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_lcls_mode,
+ cfg_net_msc_lcls_mode_cmd,
+ "lcls-mode (disabled|mgw-loop|bts-loop)",
+ "Configure 3GPP LCLS (Local Call, Local Switch)\n"
+ "Disable LCLS for all calls of this MSC\n"
+ "Enable LCLS with looping traffic in MGW\n"
+ "Enable LCLS with looping traffic between BTS\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->lcls_mode = get_string_value(bsc_lcls_mode_names, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_lcls_mismtch,
+ cfg_net_msc_lcls_mismtch_cmd,
+ "lcls-codec-mismatch (allowed|forbidden)",
+ "Allow 3GPP LCLS (Local Call, Local Switch) when call legs use different codec/rate\n"
+ "Allow LCLS only only for calls that use the same codec/rate on both legs\n"
+ "Do not Allow LCLS for calls that use a different codec/rate on both legs\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ if (strcmp(argv[0], "allowed") == 0)
+ data->lcls_codec_mismatch_allow = true;
+ else
+ data->lcls_codec_mismatch_allow = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_mgw_x_osmo_ign,
+ cfg_msc_mgw_x_osmo_ign_cmd,
+ "mgw x-osmo-ign call-id",
+ MGCP_CLIENT_MGW_STR
+ "Set a (non-standard) X-Osmo-IGN header in all CRCX messages for RTP streams"
+ " associated with this MSC, useful for A/SCCPlite MSCs, since osmo-bsc cannot know"
+ " the MSC's chosen CallID. This is enabled by default for A/SCCPlite connections,"
+ " disabled by default for all others.\n"
+ "Send 'X-Osmo-IGN: C' to ignore CallID mismatches. See OsmoMGW.\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ msc->x_osmo_ign |= MGCP_X_OSMO_IGN_CALLID;
+ msc->x_osmo_ign_configured = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_no_mgw_x_osmo_ign,
+ cfg_msc_no_mgw_x_osmo_ign_cmd,
+ "no mgw x-osmo-ign",
+ NO_STR
+ MGCP_CLIENT_MGW_STR
+ "Do not send X-Osmo-IGN MGCP header to this MSC\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ msc->x_osmo_ign = 0;
+ msc->x_osmo_ign_configured = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mid_call_text,
+ cfg_net_bsc_mid_call_text_cmd,
+ "mid-call-text .TEXT",
+ "Set the USSD notification sent to running calls when switching from Grace to Off.\n"
+ "Text to be sent\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ char *txt = argv_concat(argv, argc, 0);
+ if (!txt)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(data, &data->mid_call_txt, txt);
+ talloc_free(txt);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mid_call_timeout,
+ cfg_net_bsc_mid_call_timeout_cmd,
+ "mid-call-timeout NR",
+ "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ data->mid_call_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rf_socket,
+ cfg_net_rf_socket_cmd,
+ "bsc-rf-socket PATH",
+ "Set the filename for the RF control interface.\n" "RF Control path\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+
+ osmo_talloc_replace_string(data, &data->rf_ctrl_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rf_off_time,
+ cfg_net_rf_off_time_cmd,
+ "bsc-auto-rf-off <1-65000>",
+ "Disable RF on MSC Connection\n" "Timeout\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ data->auto_off_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_no_rf_off_time,
+ cfg_net_no_rf_off_time_cmd,
+ "no bsc-auto-rf-off",
+ NO_STR "Disable RF on MSC Connection\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ data->auto_off_timeout = -1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_acc_lst_name,
+ cfg_bsc_acc_lst_name_cmd,
+ "access-list-name NAME",
+ "Set the name of the access list to use.\n"
+ "The name of the to be used access list.")
+{
+ struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+ osmo_talloc_replace_string(bsc, &bsc->acc_lst_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_acc_lst_name,
+ cfg_bsc_no_acc_lst_name_cmd,
+ "no access-list-name",
+ NO_STR "Remove the access list from the BSC\n")
+{
+ struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+ if (bsc->acc_lst_name) {
+ talloc_free(bsc->acc_lst_name);
+ bsc->acc_lst_name = NULL;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_statistics,
+ show_statistics_cmd,
+ "show statistics",
+ SHOW_STR "Statistics about the BSC\n")
+{
+ openbsc_vty_print_statistics(vty, bsc_gsmnet);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_mscs,
+ show_mscs_cmd,
+ "show mscs",
+ SHOW_STR "MSC Connections and State\n")
+{
+ struct bsc_msc_data *msc;
+ llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) {
+ vty_out(vty, "%d %s %s ",
+ msc->a.cs7_instance,
+ osmo_ss7_asp_protocol_name(msc->a.asp_proto),
+ osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.bsc_addr));
+ vty_out(vty, "%s%s",
+ osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.msc_addr),
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_pos,
+ show_pos_cmd,
+ "show position",
+ SHOW_STR "Position information of the BTS\n")
+{
+ struct gsm_bts *bts;
+ struct bts_location *curloc;
+ struct tm time;
+ char timestr[50];
+
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ if (llist_empty(&bts->loc_list)) {
+ vty_out(vty, "BTS Nr: %d position invalid%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+ if (gmtime_r(&curloc->tstamp, &time) == NULL) {
+ vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ if (asctime_r(&time, timestr) == NULL) {
+ vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ /* Last character in asctime is \n */
+ timestr[strlen(timestr)-1] = 0;
+
+ vty_out(vty, "BTS Nr: %d position: %s time: %s%s", bts->nr,
+ get_value_string(bts_loc_fix_names, curloc->valid), timestr,
+ VTY_NEWLINE);
+ vty_out(vty, " lat: %f lon: %f height: %f%s", curloc->lat, curloc->lon,
+ curloc->height, VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(gen_position_trap,
+ gen_position_trap_cmd,
+ "generate-location-state-trap <0-255>",
+ "Generate location state report\n"
+ "BTS to report\n")
+{
+ int bts_nr;
+ struct gsm_bts *bts;
+ struct gsm_network *net = bsc_gsmnet;
+
+ 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);
+ bsc_gen_location_state_trap(bts);
+ return CMD_SUCCESS;
+}
+
+DEFUN(logging_fltr_imsi,
+ logging_fltr_imsi_cmd,
+ "logging filter imsi IMSI",
+ LOGGING_STR FILTER_STR
+ "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
+{
+ struct bsc_subscr *bsc_subscr;
+ struct log_target *tgt = osmo_log_vty2tgt(vty);
+ const char *imsi = argv[0];
+
+ bsc_subscr = bsc_subscr_find_by_imsi(bsc_gsmnet->bsc_subscribers, imsi);
+
+ if (!bsc_subscr) {
+ vty_out(vty, "%%no subscriber with IMSI(%s)%s",
+ imsi, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ log_set_filter_bsc_subscr(tgt, bsc_subscr);
+ return CMD_SUCCESS;
+}
+
+static void dump_one_sub(struct vty *vty, struct bsc_subscr *bsub)
+{
+ vty_out(vty, " %15s %08x %5u %d%s", bsub->imsi, bsub->tmsi, bsub->lac, bsub->use_count,
+ VTY_NEWLINE);
+}
+
+DEFUN(show_subscr_all,
+ show_subscr_all_cmd,
+ "show subscriber all",
+ SHOW_STR "Display information about subscribers\n" "All Subscribers\n")
+{
+ struct bsc_subscr *bsc_subscr;
+
+ vty_out(vty, " IMSI TMSI LAC Use%s", VTY_NEWLINE);
+ /* " 001010123456789 ffffffff 65534 1" */
+
+ llist_for_each_entry(bsc_subscr, bsc_gsmnet->bsc_subscribers, entry)
+ dump_one_sub(vty, bsc_subscr);
+
+ return CMD_SUCCESS;
+}
+
+#define LEGACY_STR "This command has no effect, it is kept to support legacy config files\n"
+
+DEFUN_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_ping_time_cmd,
+ "timeout-ping ARG", LEGACY_STR "-\n")
+{
+ vty_out(vty, "%% timeout-ping / timeout-pong config is deprecated and has no effect%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+}
+
+ALIAS_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_no_ping_time_cmd,
+ "no timeout-ping [ARG]", NO_STR LEGACY_STR "-\n");
+
+ALIAS_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_pong_time_cmd,
+ "timeout-pong ARG", LEGACY_STR "-\n");
+
+DEFUN_DEPRECATED(cfg_net_msc_dest, cfg_net_msc_dest_cmd,
+ "dest A.B.C.D <1-65000> <0-255>", LEGACY_STR "-\n" "-\n" "-\n")
+{
+ vty_out(vty, "%% dest config is deprecated and has no effect%s", VTY_NEWLINE);
+ return CMD_WARNING;
+}
+
+ALIAS_DEPRECATED(cfg_net_msc_dest, cfg_net_msc_no_dest_cmd,
+ "no dest A.B.C.D <1-65000> <0-255>", NO_STR LEGACY_STR "-\n" "-\n" "-\n");
+
+int bsc_vty_init_extra(void)
+{
+ struct gsm_network *net = bsc_gsmnet;
+
+ install_element(CONFIG_NODE, &cfg_net_msc_cmd);
+ install_element(CONFIG_NODE, &cfg_net_bsc_cmd);
+
+ install_node(&bsc_node, config_write_bsc);
+ install_element(BSC_NODE, &cfg_net_bsc_mid_call_text_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_mid_call_timeout_cmd);
+ install_element(BSC_NODE, &cfg_net_rf_socket_cmd);
+ install_element(BSC_NODE, &cfg_net_rf_off_time_cmd);
+ install_element(BSC_NODE, &cfg_net_no_rf_off_time_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_missing_msc_ussd_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_no_missing_msc_text_cmd);
+ install_element(BSC_NODE, &cfg_bsc_acc_lst_name_cmd);
+ install_element(BSC_NODE, &cfg_bsc_no_acc_lst_name_cmd);
+
+ install_node(&msc_node, config_write_msc);
+ install_element(MSC_NODE, &cfg_net_bsc_ncc_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_mcc_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_lac_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_ci_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_rtp_base_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_dest_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_dest_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_welcome_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_lost_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_lost_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_grace_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_grace_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_type_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_emerg_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_local_prefix_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_12_2_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_10_2_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_7_95_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_7_40_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_6_70_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_5_90_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_5_15_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_4_75_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_lcls_mode_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_lcls_mismtch_cmd);
+ install_element(MSC_NODE, &cfg_msc_acc_lst_name_cmd);
+ install_element(MSC_NODE, &cfg_msc_no_acc_lst_name_cmd);
+ install_element(MSC_NODE, &cfg_msc_cs7_bsc_addr_cmd);
+ install_element(MSC_NODE, &cfg_msc_cs7_msc_addr_cmd);
+ install_element(MSC_NODE, &cfg_msc_cs7_asp_proto_cmd);
+
+ /* Deprecated: ping time config, kept to support legacy config files. */
+ install_element(MSC_NODE, &cfg_net_msc_no_ping_time_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_pong_time_cmd);
+
+ install_element_ve(&show_statistics_cmd);
+ install_element_ve(&show_mscs_cmd);
+ install_element_ve(&show_pos_cmd);
+ install_element_ve(&logging_fltr_imsi_cmd);
+ install_element_ve(&show_subscr_all_cmd);
+
+ install_element(ENABLE_NODE, &gen_position_trap_cmd);
+
+ install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
+
+ mgcp_client_vty_init(net, MSC_NODE, net->mgw.conf);
+ install_element(MSC_NODE, &cfg_msc_mgw_x_osmo_ign_cmd);
+ install_element(MSC_NODE, &cfg_msc_no_mgw_x_osmo_ign_cmd);
+
+ return 0;
+}
diff --git a/src/osmo-bsc/paging.c b/src/osmo-bsc/paging.c
new file mode 100644
index 000000000..afe32453e
--- /dev/null
+++ b/src/osmo-bsc/paging.c
@@ -0,0 +1,478 @@
+/* Paging helper and manager.... */
+/* (C) 2009,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0502.h>
+
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+
+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));
+ rsl_paging_cmd(bts, page_group, mi_len, mi, request->chan_type, false);
+ 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, T_def_get(bts->network->T_defs, 3113, T_S, -1), 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);
+ conn->bsub = bsc_subscr_get(bsub);
+ gscon_update_id(conn);
+
+ /* 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..b71621dae
--- /dev/null
+++ b/src/osmo-bsc/pcu_sock.c
@@ -0,0 +1,716 @@
+/* pcu_sock.c: Connect from PCU via unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2012 by Andreas Eversberg <jolly@eversberg.eu>
+ * (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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/l1sap.h>
+#include <osmocom/gsm/gsm0502.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/pcu_if.h>
+#include <osmocom/bsc/pcuif_proto.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+
+static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg);
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx);
+int pcu_direct = 1;
+
+static 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;
+}
+
+/* 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->pchan_is == GSM_PCHAN_PDCH) {
+ 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_is == 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;
+ }
+
+ LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket: %s\n", path);
+
+ 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..02cf2468a
--- /dev/null
+++ b/src/osmo-bsc/penalty_timers.c
@@ -0,0 +1,129 @@
+/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <talloc.h>
+#include <time.h>
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+
+#include <osmocom/bsc/penalty_timers.h>
+#include <osmocom/bsc/gsm_data.h>
+
+struct penalty_timers {
+ struct llist_head timers;
+};
+
+struct penalty_timer {
+ struct llist_head entry;
+ const void *for_object;
+ unsigned int timeout;
+};
+
+static unsigned int time_now(void)
+{
+ time_t now;
+ time(&now);
+ /* FIXME: use monotonic clock */
+ return (unsigned int)now;
+}
+
+struct penalty_timers *penalty_timers_init(void *ctx)
+{
+ struct penalty_timers *pt = talloc_zero(ctx, struct penalty_timers);
+ if (!pt)
+ return NULL;
+ INIT_LLIST_HEAD(&pt->timers);
+ return pt;
+}
+
+void penalty_timers_add(struct penalty_timers *pt, const 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, const 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, const 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 <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/core/bitvec.h>
+#include <osmocom/gsm/bitvec_gsm.h>
+#include <osmocom/bsc/rest_octets.h>
+#include <osmocom/bsc/arfcn_range_encode.h>
+#include <osmocom/bsc/system_information.h>
+
+/* 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)
+
+<SI6 rest octets> ::=
+{L | H <PCH and NCH info>}
+{L | H <VBS/VGCS options : bit(2)>}
+{ < DTM_support : bit == L > I < DTM_support : bit == H >
+< RAC : bit (8) >
+< MAX_LAPDm : bit (3) > }
+< Band indicator >
+{ L | H < GPRS_MS_TXPWR_MAX_CCH : bit (5) > }
+<implicit spare >;
+*/
+int rest_octets_si6(uint8_t *data, bool is1800_net)
+{
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = data;
+ bv.data_len = 1;
+
+ /* no PCH/NCH info */
+ bitvec_set_bit(&bv, L);
+ /* no VBS/VGCS options */
+ bitvec_set_bit(&bv, L);
+ /* no DTM_support */
+ bitvec_set_bit(&bv, L);
+ /* band indicator */
+ if (is1800_net)
+ bitvec_set_bit(&bv, L);
+ else
+ bitvec_set_bit(&bv, H);
+ /* no GPRS_MS_TXPWR_MAX_CCH */
+ bitvec_set_bit(&bv, L);
+
+ bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
+ return bv.data_len;
+}
+
+/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a:
+ < GPRS Mobile Allocation IE > ::=
+ < HSN : bit (6) >
+ { 0 | 1 < RFL number list : < RFL number list struct > > }
+ { 0 < MA_LENGTH : bit (6) >
+ < MA_BITMAP: bit (val(MA_LENGTH) + 1) >
+ | 1 { 0 | 1 <ARFCN index list : < ARFCN index list struct > > } } ;
+
+ < RFL number list struct > :: =
+ < RFL_NUMBER : bit (4) >
+ { 0 | 1 < RFL number list struct > } ;
+ < ARFCN index list struct > ::=
+ < ARFCN_INDEX : bit(6) >
+ { 0 | 1 < ARFCN index list struct > } ;
+ */
+static int append_gprs_mobile_alloc(struct bitvec *bv)
+{
+ /* Hopping Sequence Number */
+ bitvec_set_uint(bv, 0, 6);
+
+ if (0) {
+ /* We want to use a RFL number list */
+ bitvec_set_bit(bv, 1);
+ /* FIXME: RFL number list */
+ } else
+ bitvec_set_bit(bv, 0);
+
+ if (0) {
+ /* We want to use a MA_BITMAP */
+ bitvec_set_bit(bv, 0);
+ /* FIXME: MA_LENGTH, MA_BITMAP, ... */
+ } else {
+ bitvec_set_bit(bv, 1);
+ if (0) {
+ /* We want to provide an ARFCN index list */
+ bitvec_set_bit(bv, 1);
+ /* FIXME */
+ } else
+ bitvec_set_bit(bv, 0);
+ }
+ return 0;
+}
+
+static int encode_t3192(unsigned int t3192)
+{
+ /* See also 3GPP TS 44.060
+ Table 12.24.2: GPRS Cell Options information element details */
+ if (t3192 == 0)
+ return 3;
+ else if (t3192 <= 80)
+ return 4;
+ else if (t3192 <= 120)
+ return 5;
+ else if (t3192 <= 160)
+ return 6;
+ else if (t3192 <= 200)
+ return 7;
+ else if (t3192 <= 500)
+ return 0;
+ else if (t3192 <= 1000)
+ return 1;
+ else if (t3192 <= 1500)
+ return 2;
+ else
+ return -EINVAL;
+}
+
+static int encode_drx_timer(unsigned int drx)
+{
+ if (drx == 0)
+ return 0;
+ else if (drx == 1)
+ return 1;
+ else if (drx == 2)
+ return 2;
+ else if (drx <= 4)
+ return 3;
+ else if (drx <= 8)
+ return 4;
+ else if (drx <= 16)
+ return 5;
+ else if (drx <= 32)
+ return 6;
+ else if (drx <= 64)
+ return 7;
+ else
+ return -EINVAL;
+}
+
+/* GPRS Cell Options as per TS 04.60 Chapter 12.24
+ < GPRS Cell Options IE > ::=
+ < NMO : bit(2) >
+ < T3168 : bit(3) >
+ < T3192 : bit(3) >
+ < DRX_TIMER_MAX: bit(3) >
+ < ACCESS_BURST_TYPE: bit >
+ < CONTROL_ACK_TYPE : bit >
+ < BS_CV_MAX: bit(4) >
+ { 0 | 1 < PAN_DEC : bit(3) >
+ < PAN_INC : bit(3) >
+ < PAN_MAX : bit(3) >
+ { 0 | 1 < Extension Length : bit(6) >
+ < bit (val(Extension Length) + 1
+ & { < Extension Information > ! { bit ** = <no string> } } ;
+ < Extension Information > ::=
+ { 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit >
+ < BEP_PERIOD : bit(4) > }
+ < PFC_FEATURE_MODE : bit >
+ < DTM_SUPPORT : bit >
+ <BSS_PAGING_COORDINATION: bit >
+ <spare bit > ** ;
+ */
+static int append_gprs_cell_opt(struct bitvec *bv,
+ const struct gprs_cell_options *gco)
+{
+ int t3192, drx_timer_max;
+
+ t3192 = encode_t3192(gco->t3192);
+ if (t3192 < 0)
+ return t3192;
+
+ drx_timer_max = encode_drx_timer(gco->drx_timer_max);
+ if (drx_timer_max < 0)
+ return drx_timer_max;
+
+ bitvec_set_uint(bv, gco->nmo, 2);
+
+ /* See also 3GPP TS 44.060
+ Table 12.24.2: GPRS Cell Options information element details */
+ bitvec_set_uint(bv, gco->t3168 / 500 - 1, 3);
+
+ bitvec_set_uint(bv, t3192, 3);
+ bitvec_set_uint(bv, drx_timer_max, 3);
+ /* ACCESS_BURST_TYPE: Hard-code 8bit */
+ bitvec_set_bit(bv, 0);
+ /* CONTROL_ACK_TYPE: */
+ bitvec_set_bit(bv, gco->ctrl_ack_type_use_block);
+ bitvec_set_uint(bv, gco->bs_cv_max, 4);
+
+ if (0) {
+ /* hard-code no PAN_{DEC,INC,MAX} */
+ bitvec_set_bit(bv, 0);
+ } else {
+ /* copied from ip.access BSC protocol trace */
+ bitvec_set_bit(bv, 1);
+ bitvec_set_uint(bv, 1, 3); /* DEC */
+ bitvec_set_uint(bv, 1, 3); /* INC */
+ bitvec_set_uint(bv, 15, 3); /* MAX */
+ }
+
+ if (!gco->ext_info_present) {
+ /* no extension information */
+ bitvec_set_bit(bv, 0);
+ } else {
+ /* extension information */
+ bitvec_set_bit(bv, 1);
+ if (!gco->ext_info.egprs_supported) {
+ /* 6bit length of extension */
+ bitvec_set_uint(bv, (1 + 3)-1, 6);
+ /* EGPRS supported in the cell */
+ bitvec_set_bit(bv, 0);
+ } else {
+ /* 6bit length of extension */
+ bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
+ /* EGPRS supported in the cell */
+ bitvec_set_bit(bv, 1);
+
+ /* 1bit EGPRS PACKET CHANNEL REQUEST */
+ 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..4709f7fc0
--- /dev/null
+++ b/src/osmo-bsc/system_information.c
@@ -0,0 +1,1264 @@
+/* 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 <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+
+#include <osmocom/core/bitvec.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <osmocom/gsm/gsm48_ie.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/rest_octets.h>
+#include <osmocom/bsc/arfcn_range_encode.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/acc_ramp.h>
+#include <osmocom/bsc/neighbor_ident.h>
+
+struct gsm0808_cell_id_list2;
+
+/*
+ * 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;
+
+ if (gsm_arfcn2band_rc(arfcn, &band) < 0) {
+ LOGP(DRR, LOGL_ERROR, "Invalid arfcn %d detected!\n", arfcn);
+ return 0;
+ }
+
+ /* 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_set ? bts->force_combined_si : bts->model->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\n", 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);
+}
+
+struct generate_bcch_chan_list__ni_iter_data {
+ struct gsm_bts *bts;
+ struct bitvec *bv;
+};
+
+static bool generate_bcch_chan_list__ni_iter_cb(const struct neighbor_ident_key *key,
+ const struct gsm0808_cell_id_list2 *val,
+ void *cb_data)
+{
+ struct generate_bcch_chan_list__ni_iter_data *data = cb_data;
+
+ if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS
+ && key->from_bts != data->bts->nr)
+ return true;
+
+ bitvec_set_bit_pos(data->bv, key->arfcn, 1);
+ return true;
+}
+
+/*! 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;
+
+ /* first we generate a bitvec of the BCCH ARFCN's in our BSC */
+ 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);
+
+ if (llist_empty(&bts->local_neighbors)) {
+ /* There are no explicit neighbors, assume all BTS are. */
+ llist_for_each_entry(cur_bts, &bts->network->bts_list, list) {
+ if (cur_bts == bts)
+ continue;
+ bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1);
+ }
+ } else {
+ /* Only add explicit neighbor cells */
+ struct gsm_bts_ref *neigh;
+ llist_for_each_entry(neigh, &bts->local_neighbors, entry) {
+ bitvec_set_bit_pos(bv, neigh->bts->c0->arfcn, 1);
+ }
+ }
+
+ /* Also add neighboring BSS cells' ARFCNs */
+ {
+ struct generate_bcch_chan_list__ni_iter_data data = {
+ .bv = bv,
+ .bts = bts,
+ };
+ neighbor_ident_iter(bts->network->neighbor_bss_cells,
+ generate_bcch_chan_list__ni_iter_cb, &data);
+ }
+ }
+
+ /* then we generate a GSM 04.08 frequency list from the bitvec */
+ 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(21);
+ 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,
+ "SI2bis 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(18);
+ 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,
+ "SI2ter 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_as_configured(&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,
+ "SI5bis 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,
+ "SI5ter 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)
+{
+ int rc;
+ 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) {
+ LOGP(DRR, LOGL_ERROR, "bts %u: no gen_si_fn() for SI%s\n",
+ bts->nr, get_value_string(osmo_sitype_strs, si_type));
+ return -EINVAL;
+ }
+
+ rc = gen_si(si_type, bts);
+ if (rc < 0)
+ LOGP(DRR, LOGL_ERROR, "bts %u: Error while generating SI%s: %s (%d)\n",
+ bts->nr, get_value_string(osmo_sitype_strs, si_type), strerror(-rc), rc);
+ return rc;
+}
diff --git a/src/osmo-bsc/timeslot_fsm.c b/src/osmo-bsc/timeslot_fsm.c
new file mode 100644
index 000000000..fc2546003
--- /dev/null
+++ b/src/osmo-bsc/timeslot_fsm.c
@@ -0,0 +1,932 @@
+/* osmo-bsc API to manage timeslot status: init and switch of dynamic PDCH.
+ *
+ * (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/logging.h>
+
+#include <osmocom/bsc/debug.h>
+
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/pcu_if.h>
+
+static struct osmo_fsm ts_fsm;
+
+#define CHAN_ACT_DEACT_TIMEOUT 4 /* TODO: proper T number? */
+
+enum ts_fsm_T {
+ T_CHAN_ACT_DEACT=23001,
+};
+
+struct gsm_bts_trx_ts *ts_fi_ts(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &ts_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+static void ts_fsm_update_id(struct gsm_bts_trx_ts *ts)
+{
+ osmo_fsm_inst_update_id_f(ts->fi, "%u-%u-%u-%s", ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_id(ts->pchan_on_init));
+}
+
+void ts_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&ts_fsm) == 0);
+}
+
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts)
+{
+ OSMO_ASSERT(!ts->fi);
+ OSMO_ASSERT(ts->trx);
+ ts->fi = osmo_fsm_inst_alloc(&ts_fsm, ts->trx, ts, LOGL_DEBUG, NULL);
+ OSMO_ASSERT(ts->fi);
+}
+
+enum lchan_sanity {
+ LCHAN_IS_INSANE = -1,
+ LCHAN_IS_READY_TO_GO,
+ LCHAN_NEEDS_PCHAN_CHANGE,
+};
+
+static enum lchan_sanity is_lchan_sane(struct gsm_bts_trx_ts *ts, struct gsm_lchan *lchan)
+{
+ OSMO_ASSERT(ts);
+ OSMO_ASSERT(lchan);
+ if (lchan->ts != ts)
+ return LCHAN_IS_INSANE;
+ if (!lchan->fi)
+ return LCHAN_IS_INSANE;
+
+ if (lchan->type == gsm_lchan_type_by_pchan(ts->pchan_is))
+ return LCHAN_IS_READY_TO_GO;
+
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (lchan->type == GSM_LCHAN_TCH_H)
+ return LCHAN_NEEDS_PCHAN_CHANGE;
+ /* fall thru */
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ return LCHAN_NEEDS_PCHAN_CHANGE;
+ /* fall thru */
+ default:
+ return LCHAN_IS_INSANE;
+ }
+
+}
+
+static void lchan_dispatch(struct gsm_lchan *lchan, uint32_t lchan_ev)
+{
+ if (!lchan->fi)
+ return;
+ osmo_fsm_inst_dispatch(lchan->fi, lchan_ev, NULL);
+ OSMO_ASSERT(lchan->fi->state != LCHAN_ST_WAIT_TS_READY);
+}
+
+static int ts_count_active_lchans(struct gsm_bts_trx_ts *ts)
+{
+ struct gsm_lchan *lchan;
+ int count = 0;
+
+ ts_for_each_lchan(lchan, ts) {
+ if (lchan->fi->state == LCHAN_ST_UNUSED)
+ continue;
+ count++;
+ }
+
+ return count;
+}
+
+static void ts_lchans_dispatch(struct gsm_bts_trx_ts *ts, int lchan_state, uint32_t lchan_ev)
+{
+ struct gsm_lchan *lchan;
+
+ ts_for_each_lchan(lchan, ts) {
+ if (lchan_state >= 0
+ && !lchan_state_is(lchan, lchan_state))
+ continue;
+ lchan_dispatch(lchan, lchan_ev);
+ }
+}
+
+static void ts_terminate_lchan_fsms(struct gsm_bts_trx_ts *ts)
+{
+ struct gsm_lchan *lchan;
+
+ ts_for_each_lchan(lchan, ts) {
+ osmo_fsm_inst_term(lchan->fi, OSMO_FSM_TERM_REQUEST, NULL);
+ }
+}
+
+static int ts_lchans_waiting(struct gsm_bts_trx_ts *ts)
+{
+ struct gsm_lchan *lchan;
+ int count = 0;
+ ts_for_each_lchan(lchan, ts)
+ if (lchan->fi->state == LCHAN_ST_WAIT_TS_READY)
+ count++;
+ return count;
+}
+
+static void ts_fsm_error(struct osmo_fsm_inst *fi, uint32_t state_chg, const char *fmt, ...)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+
+ char *errmsg = NULL;
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ errmsg = talloc_vasprintf(ts->trx, fmt, ap);
+ va_end(ap);
+ }
+
+ if (ts->last_errmsg)
+ talloc_free(ts->last_errmsg);
+ ts->last_errmsg = errmsg;
+
+ if (errmsg)
+ LOG_TS(ts, LOGL_ERROR, "%s\n", errmsg);
+
+ ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_ERROR);
+ if (fi->state != state_chg)
+ osmo_fsm_inst_state_chg(fi, state_chg, 0, 0);
+}
+
+static void ts_fsm_err_ready_to_go_in_pdch(struct osmo_fsm_inst *fi, struct gsm_lchan *lchan)
+{
+ /* This shouldn't ever happen, so aggressively mark it. */
+ ts_fsm_error(fi, TS_ST_BORKEN,
+ "Internal error: lchan marked as 'ready to go', but activating"
+ " any lchan should need PCHAN switchover in state %s (lchan: %s)",
+ osmo_fsm_inst_state_name(fi), gsm_lchan_name(lchan));
+}
+
+static void ts_setup_lchans(struct gsm_bts_trx_ts *ts)
+{
+ int i, max_lchans;
+
+ ts->pchan_on_init = ts->pchan_from_config;
+ ts_fsm_update_id(ts);
+
+ max_lchans = pchan_subslots(ts->pchan_on_init);
+ LOG_TS(ts, LOGL_DEBUG, "max lchans: %d\n", max_lchans);
+
+ for (i = 0; i < max_lchans; i++) {
+ /* If we receive more than one Channel OPSTART ACK, don't fail on the second init. */
+ if (ts->lchan[i].fi)
+ continue;
+ lchan_fsm_alloc(&ts->lchan[i]);
+ }
+
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ ts->pchan_is = GSM_PCHAN_NONE;
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ ts->pchan_is = GSM_PCHAN_TCH_F;
+ break;
+ default:
+ ts->pchan_is = ts->pchan_on_init;
+ break;
+ }
+
+ LOG_TS(ts, LOGL_DEBUG, "lchans initialized: %d\n", max_lchans);
+}
+
+static void ts_fsm_not_initialized(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ switch (event) {
+
+ case TS_EV_OML_READY:
+ ts->pdch_act_allowed = true;
+ ts->is_oml_ready = true;
+ ts_setup_lchans(ts);
+ if (!ts->is_rsl_ready) {
+ LOG_TS(ts, LOGL_DEBUG, "No RSL link yet\n");
+ return;
+ }
+ /* -> UNUSED below */
+ break;
+
+ case TS_EV_RSL_READY:
+ ts->pdch_act_allowed = true;
+ ts->is_rsl_ready = true;
+ if (!ts->is_oml_ready) {
+ LOG_TS(ts, LOGL_DEBUG, "OML not ready yet\n");
+ return;
+ }
+ /* -> UNUSED below */
+ break;
+
+ case TS_EV_LCHAN_REQUESTED:
+ {
+ /* TS is not initialized, no lchan can be requested. */
+ struct gsm_lchan *lchan = data;
+ if (lchan && lchan->fi)
+ osmo_fsm_inst_dispatch(fi, LCHAN_EV_TS_ERROR, NULL);
+ }
+ return;
+
+ case TS_EV_LCHAN_UNUSED:
+ /* ignored. */
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+
+ osmo_fsm_inst_state_chg(fi, TS_ST_UNUSED, 0, 0);
+}
+
+static void ts_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ struct gsm_bts *bts = ts->trx->bts;
+
+ /* We are entering the unused state. There must by definition not be any lchans waiting to be
+ * activated. */
+ if (ts_lchans_waiting(ts)) {
+ ts_fsm_error(fi, TS_ST_BORKEN,
+ "Internal error: entering UNUSED state, but there are lchans waiting to be"
+ " activated. Not activating them to prevent infinite loops.");
+ return;
+ }
+
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ LOG_TS(ts, LOGL_DEBUG, "GPRS mode is 'none': not activating PDCH.\n");
+ return;
+ }
+ if (!ts->pdch_act_allowed) {
+ LOG_TS(ts, LOGL_DEBUG, "PDCH is disabled for this timeslot,"
+ " either due to a PDCH ACT NACK, or from manual VTY command:"
+ " not activating PDCH. (last error: %s)\n",
+ ts->last_errmsg ? : "-");
+ return;
+ }
+ osmo_fsm_inst_state_chg(fi, TS_ST_WAIT_PDCH_ACT, CHAN_ACT_DEACT_TIMEOUT,
+ T_CHAN_ACT_DEACT);
+ break;
+
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ /* For any pchans containing a CBCH, lchan[2] is reserved for CBCH and cannot be
+ * allocated for SDCCH. */
+ OSMO_ASSERT(ts->lchan[2].fi);
+ ts->lchan[2].type = GSM_LCHAN_CBCH;
+ osmo_fsm_inst_state_chg(ts->lchan[2].fi, LCHAN_ST_CBCH, 0, 0);
+ break;
+
+ default:
+ /* nothing to do */
+ break;
+ }
+}
+
+static void ts_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+
+ switch (event) {
+
+ case TS_EV_LCHAN_REQUESTED:
+ {
+ struct gsm_lchan *lchan = data;
+ switch (is_lchan_sane(ts, lchan)) {
+ case LCHAN_NEEDS_PCHAN_CHANGE:
+ /* Osmocom style dyn TS: in UNUSED state, PDCH is already switched off,
+ * we merely need to RSL Chan Activ the new lchan. For ip.access style
+ * dyn TS this is already TCH/F, and we should never hit this. */
+ case LCHAN_IS_READY_TO_GO:
+ osmo_fsm_inst_state_chg(fi, TS_ST_IN_USE, 0, 0);
+ return;
+ default:
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_TS_ERROR, NULL);
+ return;
+ }
+ }
+
+ case TS_EV_LCHAN_UNUSED:
+ /* ignored. */
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static inline void ts_fsm_pdch_deact(struct osmo_fsm_inst *fi)
+{
+ osmo_fsm_inst_state_chg(fi, TS_ST_WAIT_PDCH_DEACT, CHAN_ACT_DEACT_TIMEOUT, T_CHAN_ACT_DEACT);
+}
+
+static void ts_fsm_wait_pdch_act_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+
+ rc = rsl_tx_dyn_ts_pdch_act_deact(ts, true);
+
+ /* On error, we couldn't send the activation message. If we can't send messages, we're broken.
+ * (Also avoiding a recursion loop: enter UNUSED, try to PDCH act, fail, enter UNUSED, try to
+ * PDCH act,...). */
+ if (rc)
+ ts_fsm_error(fi, TS_ST_BORKEN, "Unable to send PDCH activation");
+}
+
+static void ts_fsm_wait_pdch_act(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ switch (event) {
+
+ case TS_EV_PDCH_ACT_ACK:
+ osmo_fsm_inst_state_chg(fi, TS_ST_PDCH, 0, 0);
+ return;
+
+ case TS_EV_PDCH_ACT_NACK:
+ if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH)
+ rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+ else
+ rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]);
+ ts->pdch_act_allowed = false;
+ ts_fsm_error(fi, TS_ST_UNUSED, "Received PDCH activation NACK");
+ return;
+
+ case TS_EV_LCHAN_REQUESTED:
+ {
+ struct gsm_lchan *lchan = data;
+ switch (is_lchan_sane(ts, lchan)) {
+ case LCHAN_IS_READY_TO_GO:
+ /* PDCH activation has not been acked, the previous pchan kind may still
+ * linger in ts->pchan and make it look like the ts is usable right away.
+ * But we've started the switchover and must finish that first. */
+ case LCHAN_NEEDS_PCHAN_CHANGE:
+ /* PDCH onenter will see that the lchan is waiting and continue to switch
+ * off PDCH right away. */
+ return;
+
+ default:
+ lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
+ return;
+ }
+ }
+
+ case TS_EV_LCHAN_UNUSED:
+ /* ignored. */
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ts_fsm_pdch_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int count;
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+
+ /* Set pchan = PDCH status, but double check. */
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_TCH_F_PDCH:
+ case GSM_PCHAN_PDCH:
+ ts->pchan_is = GSM_PCHAN_PDCH;
+ break;
+ default:
+ ts_fsm_error(fi, TS_ST_BORKEN, "pchan %s is incapable of activating PDCH",
+ gsm_pchan_name(ts->pchan_on_init));
+ return;
+ }
+
+ /* PDCH use has changed, tell the PCU about it. */
+ pcu_info_update(ts->trx->bts);
+
+ /* If we received TS_EV_LCHAN_REQUESTED in the meantime, go right out of PDCH again. */
+ if ((count = ts_lchans_waiting(ts))) {
+ LOG_TS(ts, LOGL_DEBUG, "%d lchan(s) waiting for usable timeslot\n", count);
+ ts_fsm_pdch_deact(fi);
+ }
+}
+
+static void ts_fsm_pdch(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ switch (event) {
+
+ case TS_EV_LCHAN_REQUESTED:
+ {
+ struct gsm_lchan *lchan = data;
+ switch (is_lchan_sane(ts, lchan)) {
+ case LCHAN_NEEDS_PCHAN_CHANGE:
+ ts_fsm_pdch_deact(fi);
+ return;
+
+ case LCHAN_IS_READY_TO_GO:
+ ts_fsm_err_ready_to_go_in_pdch(fi, lchan);
+ return;
+
+ default:
+ /* Reject just this lchan. */
+ lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
+ return;
+ }
+ }
+
+ case TS_EV_LCHAN_UNUSED:
+ /* ignored */
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ts_fsm_wait_pdch_deact_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+
+ rc = rsl_tx_dyn_ts_pdch_act_deact(ts, false);
+
+ /* On error, we couldn't send the deactivation message. If we can't send messages, we're broken.
+ */
+ if (rc)
+ ts_fsm_error(fi, TS_ST_BORKEN, "Unable to send PDCH deactivation");
+}
+
+static void ts_fsm_wait_pdch_deact(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ switch (event) {
+
+ case TS_EV_PDCH_DEACT_ACK:
+ /* Remove pchan = PDCH status, but double check. */
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ ts->pchan_is = GSM_PCHAN_NONE;
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ ts->pchan_is = GSM_PCHAN_TCH_F;
+ break;
+ default:
+ ts_fsm_error(fi, TS_ST_BORKEN, "pchan %s is incapable of deactivating PDCH",
+ gsm_pchan_name(ts->pchan_on_init));
+ return;
+ }
+ osmo_fsm_inst_state_chg(fi, TS_ST_IN_USE, 0, 0);
+ /* IN_USE onenter will signal all waiting lchans. */
+
+ /* PDCH use has changed, tell the PCU about it. */
+ pcu_info_update(ts->trx->bts);
+ return;
+
+ case TS_EV_PDCH_DEACT_NACK:
+ if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH)
+ rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+ /* For Osmocom style dyn TS, there actually is no NACK, since there is no RF Channel
+ * Release NACK message in RSL. */
+ ts_fsm_error(fi, TS_ST_BORKEN, "Received PDCH deactivation NACK");
+ return;
+
+ case TS_EV_LCHAN_REQUESTED:
+ {
+ struct gsm_lchan *lchan = data;
+ switch (is_lchan_sane(ts, lchan)) {
+ case LCHAN_NEEDS_PCHAN_CHANGE:
+ /* IN_USE onenter will see that the lchan is waiting and signal it. */
+ return;
+
+ case LCHAN_IS_READY_TO_GO:
+ ts_fsm_err_ready_to_go_in_pdch(fi, lchan);
+ return;
+
+ default:
+ /* Reject just this lchan. */
+ lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
+ return;
+ }
+ }
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ bool ok;
+ struct gsm_lchan *lchan;
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ enum gsm_chan_t activating_type = GSM_LCHAN_NONE;
+
+ /* After being in use, allow PDCH act again, if appropriate. */
+ ts->pdch_act_allowed = true;
+
+ /* For static TS, check validity. For dyn TS, figure out which PCHAN this should become. */
+ ts_as_pchan_for_each_lchan(lchan, ts, ts->pchan_on_init) {
+ if (lchan_state_is(lchan, LCHAN_ST_UNUSED))
+ continue;
+
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_H:
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_SDCCH:
+ ok = ts_is_capable_of_lchant(ts, lchan->type);
+ break;
+ default:
+ ok = false;
+ break;
+ }
+
+ if (!ok && lchan_state_is(lchan, LCHAN_ST_WAIT_TS_READY)) {
+ LOG_TS(ts, LOGL_ERROR, "lchan activation of %s is not permitted for %s (%s)\n",
+ gsm_lchant_name(lchan->type), gsm_pchan_name(ts->pchan_on_init),
+ gsm_lchan_name(lchan));
+ lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
+ }
+
+ if (!ok)
+ continue;
+
+ if (activating_type == GSM_LCHAN_NONE)
+ activating_type = lchan->type;
+ else if (activating_type != lchan->type) {
+ LOG_TS(ts, LOGL_ERROR, "lchan type %s mismatches %s (%s)\n",
+ gsm_lchant_name(lchan->type), gsm_lchant_name(activating_type),
+ gsm_lchan_name(lchan));
+ lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
+ }
+ }
+
+ ok = false;
+ switch (activating_type) {
+ case GSM_LCHAN_SDCCH:
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ ok = ts_is_capable_of_lchant(ts, activating_type);
+ break;
+
+ case GSM_LCHAN_NONE:
+ LOG_TS(ts, LOGL_DEBUG, "Entered IN_USE state but no lchans are actually in use now.\n");
+ break;
+
+ default:
+ LOG_TS(ts, LOGL_ERROR, "cannot use timeslot as %s\n", gsm_lchant_name(activating_type));
+ ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_ERROR);
+ break;
+ }
+
+ if (!ok) {
+ osmo_fsm_inst_state_chg(fi, TS_ST_UNUSED, 0, 0);
+ return;
+ }
+
+ /* Make sure dyn TS pchan_is is updated. For TCH/F_PDCH, there are only PDCH or TCH/F modes, but
+ * for Osmocom style TCH/F_TCH/H_PDCH the pchan_is == NONE until an lchan is activated. */
+ if (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ ts->pchan_is = gsm_pchan_by_lchan_type(activating_type);
+ ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_READY);
+}
+
+static void ts_fsm_in_use(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ switch (event) {
+ case TS_EV_LCHAN_UNUSED:
+ if (!ts_count_active_lchans(ts))
+ osmo_fsm_inst_state_chg(fi, TS_ST_UNUSED, 0, 0);
+ return;
+
+ case TS_EV_LCHAN_REQUESTED:
+ {
+ struct gsm_lchan *lchan = data;
+ switch (is_lchan_sane(ts, lchan)) {
+ case LCHAN_IS_READY_TO_GO:
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_TS_READY, NULL);
+ return;
+
+ case LCHAN_NEEDS_PCHAN_CHANGE:
+ LOG_TS(ts, LOGL_ERROR,
+ "cannot activate lchan of mismatching pchan type"
+ " when the TS is already in use: %s\n",
+ gsm_lchan_name(lchan));
+ /* fall thru */
+ default:
+ /* Reject just this lchan. */
+ lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
+ return;
+ }
+ }
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case TS_EV_LCHAN_UNUSED:
+ /* ignored */
+ return;
+
+ case TS_EV_LCHAN_REQUESTED:
+ {
+ struct gsm_lchan *lchan = data;
+ lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
+ return;
+ }
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static int ts_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ switch (fi->state) {
+ case TS_ST_WAIT_PDCH_ACT:
+ ts_fsm_error(fi, TS_ST_BORKEN, "PDCH activation timeout");
+ return 0;
+
+ case TS_ST_WAIT_PDCH_DEACT:
+ ts_fsm_error(fi, TS_ST_BORKEN, "PDCH deactivation timeout");
+ return 0;
+
+ default:
+ ts_fsm_error(fi, TS_ST_BORKEN, "Unknown timeout in state %s",
+ osmo_fsm_inst_state_name(fi));
+ return 0;
+ }
+}
+
+static void ts_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ switch (event) {
+ case TS_EV_OML_DOWN:
+ ts->is_oml_ready = false;
+ if (fi->state != TS_ST_NOT_INITIALIZED)
+ osmo_fsm_inst_state_chg(fi, TS_ST_NOT_INITIALIZED, 0, 0);
+ OSMO_ASSERT(fi->state == TS_ST_NOT_INITIALIZED);
+ ts_terminate_lchan_fsms(ts);
+ ts->pchan_is = ts->pchan_on_init = GSM_PCHAN_NONE;
+ ts_fsm_update_id(ts);
+ break;
+
+ case TS_EV_RSL_DOWN:
+ ts->is_rsl_ready = false;
+ if (fi->state != TS_ST_NOT_INITIALIZED)
+ osmo_fsm_inst_state_chg(fi, TS_ST_NOT_INITIALIZED, 0, 0);
+ OSMO_ASSERT(fi->state == TS_ST_NOT_INITIALIZED);
+ ts->pchan_is = GSM_PCHAN_NONE;
+ ts_lchans_dispatch(ts, -1, LCHAN_EV_TS_ERROR);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state ts_fsm_states[] = {
+ [TS_ST_NOT_INITIALIZED] = {
+ .name = "NOT_INITIALIZED",
+ .action = ts_fsm_not_initialized,
+ .in_event_mask = 0
+ | S(TS_EV_OML_READY)
+ | S(TS_EV_RSL_READY)
+ | S(TS_EV_LCHAN_REQUESTED)
+ | S(TS_EV_LCHAN_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(TS_ST_UNUSED)
+ | S(TS_ST_BORKEN)
+ ,
+ },
+ [TS_ST_UNUSED] = {
+ .name = "UNUSED",
+ .onenter = ts_fsm_unused_onenter,
+ .action = ts_fsm_unused,
+ .in_event_mask = 0
+ | S(TS_EV_LCHAN_REQUESTED)
+ | S(TS_EV_LCHAN_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(TS_ST_WAIT_PDCH_ACT)
+ | S(TS_ST_IN_USE)
+ | S(TS_ST_NOT_INITIALIZED)
+ | S(TS_ST_BORKEN)
+ ,
+ },
+ [TS_ST_WAIT_PDCH_ACT] = {
+ .name = "WAIT_PDCH_ACT",
+ .onenter = ts_fsm_wait_pdch_act_onenter,
+ .action = ts_fsm_wait_pdch_act,
+ .in_event_mask = 0
+ | S(TS_EV_PDCH_ACT_ACK)
+ | S(TS_EV_PDCH_ACT_NACK)
+ | S(TS_EV_LCHAN_REQUESTED)
+ | S(TS_EV_LCHAN_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(TS_ST_PDCH)
+ | S(TS_ST_UNUSED)
+ | S(TS_ST_BORKEN)
+ | S(TS_ST_NOT_INITIALIZED)
+ ,
+ },
+ [TS_ST_PDCH] = {
+ .name = "PDCH",
+ .onenter = ts_fsm_pdch_onenter,
+ .action = ts_fsm_pdch,
+ .in_event_mask = 0
+ | S(TS_EV_LCHAN_REQUESTED)
+ | S(TS_EV_LCHAN_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(TS_ST_WAIT_PDCH_DEACT)
+ | S(TS_ST_NOT_INITIALIZED)
+ | S(TS_ST_BORKEN)
+ ,
+ },
+ [TS_ST_WAIT_PDCH_DEACT] = {
+ .name = "WAIT_PDCH_DEACT",
+ .onenter = ts_fsm_wait_pdch_deact_onenter,
+ .action = ts_fsm_wait_pdch_deact,
+ .in_event_mask = 0
+ | S(TS_EV_PDCH_DEACT_ACK)
+ | S(TS_EV_PDCH_DEACT_NACK)
+ | S(TS_EV_LCHAN_REQUESTED)
+ | S(TS_EV_LCHAN_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(TS_ST_IN_USE)
+ | S(TS_ST_UNUSED)
+ | S(TS_ST_BORKEN)
+ | S(TS_ST_NOT_INITIALIZED)
+ | S(TS_ST_BORKEN)
+ ,
+ },
+ [TS_ST_IN_USE] = {
+ .name = "IN_USE",
+ .onenter = ts_fsm_in_use_onenter,
+ .action = ts_fsm_in_use,
+ .in_event_mask = 0
+ | S(TS_EV_LCHAN_REQUESTED)
+ | S(TS_EV_LCHAN_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(TS_ST_UNUSED)
+ | S(TS_ST_NOT_INITIALIZED)
+ | S(TS_ST_BORKEN)
+ ,
+ },
+ [TS_ST_BORKEN] = {
+ .name = "BORKEN",
+ .action = ts_fsm_borken,
+ .in_event_mask = 0
+ | S(TS_EV_LCHAN_REQUESTED)
+ | S(TS_EV_LCHAN_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(TS_ST_NOT_INITIALIZED)
+ ,
+ },
+
+};
+
+static const struct value_string ts_fsm_event_names[] = {
+ OSMO_VALUE_STRING(TS_EV_OML_READY),
+ OSMO_VALUE_STRING(TS_EV_OML_DOWN),
+ OSMO_VALUE_STRING(TS_EV_RSL_READY),
+ OSMO_VALUE_STRING(TS_EV_RSL_DOWN),
+ OSMO_VALUE_STRING(TS_EV_LCHAN_REQUESTED),
+ OSMO_VALUE_STRING(TS_EV_LCHAN_UNUSED),
+ OSMO_VALUE_STRING(TS_EV_PDCH_ACT_ACK),
+ OSMO_VALUE_STRING(TS_EV_PDCH_ACT_NACK),
+ OSMO_VALUE_STRING(TS_EV_PDCH_DEACT_ACK),
+ OSMO_VALUE_STRING(TS_EV_PDCH_DEACT_NACK),
+ {}
+};
+
+static struct osmo_fsm ts_fsm = {
+ .name = "timeslot",
+ .states = ts_fsm_states,
+ .num_states = ARRAY_SIZE(ts_fsm_states),
+ .timer_cb = ts_fsm_timer_cb,
+ .log_subsys = DTS,
+ .event_names = ts_fsm_event_names,
+ .allstate_event_mask = 0
+ | S(TS_EV_OML_DOWN)
+ | S(TS_EV_RSL_DOWN)
+ ,
+ .allstate_action = ts_fsm_allstate,
+};
+
+/* Return true if any lchans are waiting for this timeslot to become a specific PCHAN. If target_pchan is
+ * not NULL, also return the PCHAN being waited for. */
+bool ts_is_lchan_waiting_for_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config *target_pchan)
+{
+ struct gsm_lchan *lchan;
+ ts_for_each_lchan(lchan, ts) {
+ if (lchan->fi->state == LCHAN_ST_WAIT_TS_READY) {
+ if (target_pchan)
+ *target_pchan = gsm_pchan_by_lchan_type(lchan->type);
+ return true;
+ }
+ }
+
+ if (target_pchan)
+ *target_pchan = ts->pchan_is;
+ return false;
+}
+
+/* Return true if we are busy changing the PCHAN kind. If target_pchan is not NULL, also return the PCHAN
+ * (ultimately) being switched to. */
+bool ts_is_pchan_switching(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config *target_pchan)
+{
+ switch (ts->fi->state) {
+ case TS_ST_NOT_INITIALIZED:
+ case TS_ST_BORKEN:
+ return false;
+ default:
+ break;
+ }
+
+ /* If an lchan is waiting, return the final pchan after all switching is done. */
+ if (ts_is_lchan_waiting_for_pchan(ts, target_pchan))
+ return true;
+
+ /* No lchans waiting. Return any ongoing switching. */
+
+ switch (ts->fi->state) {
+ case TS_ST_WAIT_PDCH_ACT:
+ if (target_pchan)
+ *target_pchan = GSM_PCHAN_PDCH;
+ return true;
+
+ case TS_ST_WAIT_PDCH_DEACT:
+ /* If we were switching to a specific pchan kind, an lchan would be waiting. So this must
+ * be NONE then. */
+ if (target_pchan)
+ *target_pchan = GSM_PCHAN_NONE;
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/* Does the timeslot's *current* state allow use as this PCHAN kind? If the ts is in switchover, return
+ * true if the switchover's target PCHAN matches, i.e. an lchan for this pchan kind could be requested
+ * and will be served after the switch. (Do not check whether any lchans are actually available.) */
+bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan)
+{
+ if (!ts_is_usable(ts))
+ return false;
+
+ switch (ts->fi->state) {
+ case TS_ST_IN_USE:
+ return ts->pchan_is == as_pchan;
+
+ default:
+ break;
+ }
+
+ {
+ enum gsm_phys_chan_config target_pchan;
+ if (ts_is_lchan_waiting_for_pchan(ts, &target_pchan))
+ return target_pchan == as_pchan;
+ }
+
+ return ts_is_capable_of_pchan(ts, as_pchan);
+}
diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am
new file mode 100644
index 000000000..914a6042d
--- /dev/null
+++ b/src/utils/Makefile.am
@@ -0,0 +1,135 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(SQLITE3_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+noinst_HEADERS = \
+ meas_db.h \
+ $(NULL)
+
+bin_PROGRAMS = \
+ bs11_config \
+ isdnsync \
+ meas_json \
+ $(NULL)
+if HAVE_SQLITE3
+bin_PROGRAMS += \
+ osmo-meas-udp2db \
+ $(NULL)
+if HAVE_PCAP
+bin_PROGRAMS += \
+ osmo-meas-pcap2db \
+ $(NULL)
+endif
+endif
+if HAVE_LIBCDK
+bin_PROGRAMS += \
+ meas_vis \
+ $(NULL)
+endif
+
+bs11_config_SOURCES = \
+ bs11_config.c \
+ $(NULL)
+
+bs11_config_LDADD = \
+ $(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/gsm_timers.o \
+ $(top_builddir)/src/osmo-bsc/net_init.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(NULL)
+
+isdnsync_SOURCES = \
+ isdnsync.c \
+ $(NULL)
+
+meas_vis_SOURCES = \
+ meas_vis.c \
+ $(NULL)
+
+meas_vis_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ -lcdk \
+ -lncurses \
+ $(NULL)
+
+meas_vis_CFLAGS = \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(NULL)
+
+osmo_meas_pcap2db_SOURCES = \
+ meas_pcap2db.c \
+ meas_db.c \
+ $(NULL)
+
+osmo_meas_pcap2db_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(SQLITE3_LIBS) \
+ -lpcap \
+ $(NULL)
+
+osmo_meas_pcap2db_CFLAGS = \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+
+osmo_meas_udp2db_SOURCES = \
+ meas_udp2db.c \
+ meas_db.c \
+ $(NULL)
+
+osmo_meas_udp2db_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(SQLITE3_LIBS) \
+ $(NULL)
+
+osmo_meas_udp2db_CFLAGS = \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+
+meas_json_SOURCES = \
+ meas_json.c \
+ $(NULL)
+
+meas_json_LDADD = \
+ $(top_builddir)/src/osmo-bsc/gsm_data.o \
+ $(top_builddir)/src/osmo-bsc/gsm_timers.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(NULL)
+
+meas_json_CFLAGS = \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+
diff --git a/src/utils/bs11_config.c b/src/utils/bs11_config.c
new file mode 100644
index 000000000..7fac3e39a
--- /dev/null
+++ b/src/utils/bs11_config.c
@@ -0,0 +1,985 @@
+/* Siemens BS-11 microBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This software is based on ideas (but not code) of BS11Config
+ * (C) 2009 by Dieter Spaar <spaar@mirider.augusta.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include <sys/stat.h>
+
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/core/select.h>
+#include <osmocom/bsc/rs232.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+
+static void *tall_bs11cfg_ctx;
+static struct e1inp_sign_link *oml_link;
+
+/* state of our bs11_config application */
+enum bs11cfg_state {
+ STATE_NONE,
+ STATE_LOGON_WAIT,
+ STATE_LOGON_ACK,
+ STATE_SWLOAD,
+ STATE_QUERY,
+};
+static enum bs11cfg_state bs11cfg_state = STATE_NONE;
+static char *command, *value;
+struct osmo_timer_list status_timer;
+
+static const uint8_t obj_li_attr[] = {
+ NM_ATT_BS11_BIT_ERR_THESH, 0x09, 0x00,
+ NM_ATT_BS11_L1_PROT_TYPE, 0x00,
+ NM_ATT_BS11_LINE_CFG, 0x00,
+};
+static const uint8_t obj_bbsig0_attr[] = {
+ NM_ATT_BS11_RSSI_OFFS, 0x02, 0x00, 0x00,
+ NM_ATT_BS11_DIVERSITY, 0x01, 0x00,
+};
+static const uint8_t obj_pa0_attr[] = {
+ NM_ATT_BS11_TXPWR, 0x01, BS11_TRX_POWER_GSM_30mW,
+};
+static const char *trx1_password = "1111111111";
+#define TEI_OML 25
+
+/* dummy function to keep gsm_data.c happy */
+struct osmo_counter *osmo_counter_alloc(const char *name)
+{
+ return NULL;
+}
+
+int handle_serial_msg(struct msgb *rx_msg);
+
+/* create all objects for an initial configuration */
+static int create_objects(struct gsm_bts *bts)
+{
+ fprintf(stdout, "Crating Objects for minimal config\n");
+ abis_nm_bs11_create_object(bts, BS11_OBJ_LI, 0, sizeof(obj_li_attr),
+ obj_li_attr);
+ abis_nm_bs11_create_object(bts, BS11_OBJ_GPSU, 0, 0, NULL);
+ abis_nm_bs11_create_object(bts, BS11_OBJ_ALCO, 0, 0, NULL);
+ abis_nm_bs11_create_object(bts, BS11_OBJ_CCLK, 0, 0, NULL);
+ abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 0,
+ sizeof(obj_bbsig0_attr), obj_bbsig0_attr);
+ abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 0,
+ sizeof(obj_pa0_attr), obj_pa0_attr);
+ abis_nm_bs11_create_envaBTSE(bts, 0);
+ abis_nm_bs11_create_envaBTSE(bts, 1);
+ abis_nm_bs11_create_envaBTSE(bts, 2);
+ abis_nm_bs11_create_envaBTSE(bts, 3);
+
+ abis_nm_bs11_conn_oml_tei(bts, 0, 1, 0xff, TEI_OML);
+
+ abis_nm_bs11_set_trx_power(bts->c0, BS11_TRX_POWER_GSM_30mW);
+
+ sleep(1);
+
+ abis_nm_bs11_set_trx1_pw(bts, trx1_password);
+
+ sleep(1);
+
+ return 0;
+}
+
+static int create_trx1(struct gsm_bts *bts)
+{
+ uint8_t bbsig1_attr[sizeof(obj_bbsig0_attr)+12];
+ uint8_t *cur = bbsig1_attr;
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 1);
+
+ if (!trx)
+ trx = gsm_bts_trx_alloc(bts);
+
+ fprintf(stdout, "Crating Objects for TRX1\n");
+
+ abis_nm_bs11_set_trx1_pw(bts, trx1_password);
+
+ sleep(1);
+
+ cur = tlv_put(cur, NM_ATT_BS11_PASSWORD, 10,
+ (uint8_t *)trx1_password);
+ memcpy(cur, obj_bbsig0_attr, sizeof(obj_bbsig0_attr));
+ abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 1,
+ sizeof(bbsig1_attr), bbsig1_attr);
+ abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 1,
+ sizeof(obj_pa0_attr), obj_pa0_attr);
+ abis_nm_bs11_set_trx_power(trx, BS11_TRX_POWER_GSM_30mW);
+
+ return 0;
+}
+
+static char *serial_port = "/dev/ttyUSB0";
+static char *fname_safety = "BTSBMC76.SWI";
+static char *fname_software = "HS011106.SWL";
+static int delay_ms = 0;
+static int win_size = 8;
+static int param_disconnect = 0;
+static int param_restart = 0;
+static int param_forced = 0;
+static struct gsm_bts *g_bts;
+
+static int file_is_readable(const char *fname)
+{
+ int rc;
+ struct stat st;
+
+ rc = stat(fname, &st);
+ if (rc < 0)
+ return 0;
+
+ if (S_ISREG(st.st_mode) && (st.st_mode & S_IRUSR))
+ return 1;
+
+ return 0;
+}
+
+static int percent;
+static int percent_old;
+
+/* callback function passed to the ABIS OML code */
+static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *msg,
+ void *data, void *param)
+{
+ if (hook != GSM_HOOK_NM_SWLOAD)
+ return 0;
+
+ switch (event) {
+ case NM_MT_LOAD_INIT_ACK:
+ fprintf(stdout, "Software Load Initiate ACK\n");
+ break;
+ case NM_MT_LOAD_INIT_NACK:
+ fprintf(stderr, "ERROR: Software Load Initiate NACK\n");
+ exit(5);
+ break;
+ case NM_MT_LOAD_END_ACK:
+ if (data) {
+ /* we did a safety load and must activate it */
+ abis_nm_software_activate(g_bts, fname_safety,
+ swload_cbfn, g_bts);
+ sleep(5);
+ }
+ break;
+ case NM_MT_LOAD_END_NACK:
+ fprintf(stderr, "ERROR: Software Load End NACK\n");
+ exit(3);
+ break;
+ case NM_MT_ACTIVATE_SW_NACK:
+ fprintf(stderr, "ERROR: Activate Software NACK\n");
+ exit(4);
+ break;
+ case NM_MT_ACTIVATE_SW_ACK:
+ bs11cfg_state = STATE_NONE;
+
+ break;
+ case NM_MT_LOAD_SEG_ACK:
+ percent = abis_nm_software_load_status(g_bts);
+ if (percent > percent_old)
+ printf("Software Download Progress: %d%%\n", percent);
+ percent_old = percent;
+ break;
+ }
+ return 0;
+}
+
+static const struct value_string bs11_linkst_names[] = {
+ { 0, "Down" },
+ { 1, "Up" },
+ { 2, "Restoring" },
+ { 0, NULL }
+};
+
+static const char *linkstate_name(uint8_t linkstate)
+{
+ return get_value_string(bs11_linkst_names, linkstate);
+}
+
+static const struct value_string mbccu_load_names[] = {
+ { 0, "No Load" },
+ { 1, "Load BTSCAC" },
+ { 2, "Load BTSDRX" },
+ { 3, "Load BTSBBX" },
+ { 4, "Load BTSARC" },
+ { 5, "Load" },
+ { 0, NULL }
+};
+
+static const char *mbccu_load_name(uint8_t linkstate)
+{
+ return get_value_string(mbccu_load_names, linkstate);
+}
+
+static const char *bts_phase_name(uint8_t phase)
+{
+ switch (phase) {
+ case BS11_STATE_WARM_UP:
+ case BS11_STATE_WARM_UP_2:
+ return "Warm Up";
+ break;
+ case BS11_STATE_LOAD_SMU_SAFETY:
+ return "Load SMU Safety";
+ break;
+ case BS11_STATE_LOAD_SMU_INTENDED:
+ return "Load SMU Intended";
+ break;
+ case BS11_STATE_LOAD_MBCCU:
+ return "Load MBCCU";
+ break;
+ case BS11_STATE_SOFTWARE_RQD:
+ return "Software required";
+ break;
+ case BS11_STATE_WAIT_MIN_CFG:
+ case BS11_STATE_WAIT_MIN_CFG_2:
+ return "Wait minimal config";
+ break;
+ case BS11_STATE_MAINTENANCE:
+ return "Maintenance";
+ break;
+ case BS11_STATE_NORMAL:
+ return "Normal";
+ break;
+ case BS11_STATE_ABIS_LOAD:
+ return "Abis load";
+ break;
+ default:
+ return "Unknown";
+ break;
+ }
+}
+
+static const char *trx_power_name(uint8_t pwr)
+{
+ switch (pwr) {
+ case BS11_TRX_POWER_GSM_2W:
+ return "2W (GSM)";
+ case BS11_TRX_POWER_GSM_250mW:
+ return "250mW (GSM)";
+ case BS11_TRX_POWER_GSM_80mW:
+ return "80mW (GSM)";
+ case BS11_TRX_POWER_GSM_30mW:
+ return "30mW (GSM)";
+ case BS11_TRX_POWER_DCS_3W:
+ return "3W (DCS)";
+ case BS11_TRX_POWER_DCS_1W6:
+ return "1.6W (DCS)";
+ case BS11_TRX_POWER_DCS_500mW:
+ return "500mW (DCS)";
+ case BS11_TRX_POWER_DCS_160mW:
+ return "160mW (DCS)";
+ default:
+ return "unknown value";
+ }
+}
+
+static const char *pll_mode_name(uint8_t mode)
+{
+ switch (mode) {
+ case BS11_LI_PLL_LOCKED:
+ return "E1 Locked";
+ case BS11_LI_PLL_STANDALONE:
+ return "Standalone";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *cclk_acc_name(uint8_t acc)
+{
+ switch (acc) {
+ case 0:
+ /* Out of the demanded +/- 0.05ppm */
+ return "Medium";
+ case 1:
+ /* Synchronized with Abis, within demanded tolerance +/- 0.05ppm */
+ return "High";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *bport_lcfg_name(uint8_t lcfg)
+{
+ switch (lcfg) {
+ case BS11_LINE_CFG_STAR:
+ return "Star";
+ case BS11_LINE_CFG_MULTIDROP:
+ return "Multi-Drop";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *obj_name(struct abis_om_fom_hdr *foh)
+{
+ static char retbuf[256];
+
+ retbuf[0] = 0;
+
+ switch (foh->obj_class) {
+ case NM_OC_BS11:
+ strcat(retbuf, "BS11 ");
+ switch (foh->obj_inst.bts_nr) {
+ case BS11_OBJ_PA:
+ sprintf(retbuf+strlen(retbuf), "Power Amplifier %d ",
+ foh->obj_inst.ts_nr);
+ break;
+ case BS11_OBJ_LI:
+ sprintf(retbuf+strlen(retbuf), "Line Interface ");
+ break;
+ case BS11_OBJ_CCLK:
+ sprintf(retbuf+strlen(retbuf), "CCLK ");
+ break;
+ }
+ break;
+ case NM_OC_SITE_MANAGER:
+ strcat(retbuf, "SITE MANAGER ");
+ break;
+ case NM_OC_BS11_BPORT:
+ sprintf(retbuf+strlen(retbuf), "BPORT%u ",
+ foh->obj_inst.bts_nr);
+ break;
+ }
+ return retbuf;
+}
+
+static void print_state(struct tlv_parsed *tp)
+{
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_BTS_STATE)) {
+ uint8_t phase, mbccu;
+ if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 1) {
+ phase = *TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE);
+ printf("PHASE: %u %-20s ", phase & 0xf,
+ bts_phase_name(phase));
+ }
+ if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 2) {
+ mbccu = *(TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE)+1);
+ printf("MBCCU0: %-11s MBCCU1: %-11s ",
+ mbccu_load_name(mbccu & 0xf), mbccu_load_name(mbccu >> 4));
+ }
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_E1_STATE) &&
+ TLVP_LEN(tp, NM_ATT_BS11_E1_STATE) >= 1) {
+ uint8_t e1_state = *TLVP_VAL(tp, NM_ATT_BS11_E1_STATE);
+ printf("Abis-link: %-9s ", linkstate_name(e1_state & 0xf));
+ }
+ printf("\n");
+}
+
+static int print_attr(struct tlv_parsed *tp)
+{
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_PCB_SERIAL)) {
+ printf("\tBS-11 ESN PCB Serial Number: %s\n",
+ TLVP_VAL(tp, NM_ATT_BS11_ESN_PCB_SERIAL));
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_HW_CODE_NO)) {
+ printf("\tBS-11 ESN Hardware Code Number: %s\n",
+ TLVP_VAL(tp, NM_ATT_BS11_ESN_HW_CODE_NO)+6);
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_FW_CODE_NO)) {
+ printf("\tBS-11 ESN Firmware Code Number: %s\n",
+ TLVP_VAL(tp, NM_ATT_BS11_ESN_FW_CODE_NO)+6);
+ }
+#if 0
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_BOOT_SW_VERS)) {
+ printf("BS-11 Boot Software Version: %s\n",
+ TLVP_VAL(tp, NM_ATT_BS11_BOOT_SW_VERS)+6);
+ }
+#endif
+ if (TLVP_PRESENT(tp, NM_ATT_ABIS_CHANNEL) &&
+ TLVP_LEN(tp, NM_ATT_ABIS_CHANNEL) >= 3) {
+ const uint8_t *chan = TLVP_VAL(tp, NM_ATT_ABIS_CHANNEL);
+ printf("\tE1 Channel: Port=%u Timeslot=%u ",
+ chan[0], chan[1]);
+ if (chan[2] == 0xff)
+ printf("(Full Slot)\n");
+ else
+ printf("Subslot=%u\n", chan[2]);
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_TEI))
+ printf("\tTEI: %d\n", *TLVP_VAL(tp, NM_ATT_TEI));
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_TXPWR) &&
+ TLVP_LEN(tp, NM_ATT_BS11_TXPWR) >= 1) {
+ printf("\tTRX Power: %s\n",
+ trx_power_name(*TLVP_VAL(tp, NM_ATT_BS11_TXPWR)));
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL_MODE) &&
+ TLVP_LEN(tp, NM_ATT_BS11_PLL_MODE) >= 1) {
+ printf("\tPLL Mode: %s\n",
+ pll_mode_name(*TLVP_VAL(tp, NM_ATT_BS11_PLL_MODE)));
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL) &&
+ TLVP_LEN(tp, NM_ATT_BS11_PLL) >= 4) {
+ const uint8_t *vp = TLVP_VAL(tp, NM_ATT_BS11_PLL);
+ printf("\tPLL Set Value=%d, Work Value=%d\n",
+ vp[0] << 8 | vp[1], vp[2] << 8 | vp[3]);
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_ACCURACY) &&
+ TLVP_LEN(tp, NM_ATT_BS11_CCLK_ACCURACY) >= 1) {
+ const uint8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_ACCURACY);
+ printf("\tCCLK Accuracy: %s (%d)\n", cclk_acc_name(*acc), *acc);
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_TYPE) &&
+ TLVP_LEN(tp, NM_ATT_BS11_CCLK_TYPE) >= 1) {
+ const uint8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_TYPE);
+ printf("\tCCLK Type=%d\n", *acc);
+ }
+ if (TLVP_PRESENT(tp, NM_ATT_BS11_LINE_CFG) &&
+ TLVP_LEN(tp, NM_ATT_BS11_LINE_CFG) >= 1) {
+ const uint8_t *lcfg = TLVP_VAL(tp, NM_ATT_BS11_LINE_CFG);
+ printf("\tLine Configuration: %s (%d)\n",
+ bport_lcfg_name(*lcfg), *lcfg);
+ }
+
+
+
+ return 0;
+}
+
+static void cmd_query(void)
+{
+ struct gsm_bts_trx *trx = g_bts->c0;
+
+ bs11cfg_state = STATE_QUERY;
+ abis_nm_bs11_get_serno(g_bts);
+ abis_nm_bs11_get_oml_tei_ts(g_bts);
+ abis_nm_bs11_get_pll_mode(g_bts);
+ abis_nm_bs11_get_cclk(g_bts);
+ abis_nm_bs11_get_trx_power(trx);
+ trx = gsm_bts_trx_num(g_bts, 1);
+ if (trx)
+ abis_nm_bs11_get_trx_power(trx);
+ abis_nm_bs11_get_bport_line_cfg(g_bts, 0);
+ abis_nm_bs11_get_bport_line_cfg(g_bts, 1);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+}
+
+/* handle a response from the BTS to a GET STATE command */
+static int handle_state_resp(enum abis_bs11_phase state)
+{
+ int rc = 0;
+
+ switch (state) {
+ case BS11_STATE_WARM_UP:
+ case BS11_STATE_LOAD_SMU_SAFETY:
+ case BS11_STATE_LOAD_SMU_INTENDED:
+ case BS11_STATE_LOAD_MBCCU:
+ break;
+ case BS11_STATE_SOFTWARE_RQD:
+ bs11cfg_state = STATE_SWLOAD;
+ /* send safety load. Use g_bts as private 'param'
+ * argument, so our swload_cbfn can distinguish
+ * a safety load from a regular software */
+ if (file_is_readable(fname_safety))
+ rc = abis_nm_software_load(g_bts, 0xff, fname_safety,
+ win_size, param_forced,
+ swload_cbfn, g_bts);
+ else
+ fprintf(stderr, "No valid Safety Load file \"%s\"\n",
+ fname_safety);
+ break;
+ case BS11_STATE_WAIT_MIN_CFG:
+ case BS11_STATE_WAIT_MIN_CFG_2:
+ bs11cfg_state = STATE_SWLOAD;
+ rc = create_objects(g_bts);
+ break;
+ case BS11_STATE_MAINTENANCE:
+ if (command) {
+ if (!strcmp(command, "disconnect"))
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ else if (!strcmp(command, "reconnect"))
+ rc = abis_nm_bs11_bsc_disconnect(g_bts, 1);
+ else if (!strcmp(command, "software")
+ && bs11cfg_state != STATE_SWLOAD) {
+ bs11cfg_state = STATE_SWLOAD;
+ /* send software (FIXME: over A-bis?) */
+ if (file_is_readable(fname_software))
+ rc = abis_nm_bs11_load_swl(g_bts, fname_software,
+ win_size, param_forced,
+ swload_cbfn);
+ else
+ fprintf(stderr, "No valid Software file \"%s\"\n",
+ fname_software);
+ } else if (!strcmp(command, "delete-trx1")) {
+ printf("Locing BBSIG and PA objects of TRX1\n");
+ abis_nm_chg_adm_state(g_bts, NM_OC_BS11,
+ BS11_OBJ_BBSIG, 0, 1,
+ NM_STATE_LOCKED);
+ abis_nm_chg_adm_state(g_bts, NM_OC_BS11,
+ BS11_OBJ_PA, 0, 1,
+ NM_STATE_LOCKED);
+ sleep(1);
+ printf("Deleting BBSIG and PA objects of TRX1\n");
+ abis_nm_bs11_delete_object(g_bts, BS11_OBJ_BBSIG, 1);
+ abis_nm_bs11_delete_object(g_bts, BS11_OBJ_PA, 1);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "create-trx1")) {
+ create_trx1(g_bts);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "pll-e1-locked")) {
+ abis_nm_bs11_set_pll_locked(g_bts, 1);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "pll-standalone")) {
+ abis_nm_bs11_set_pll_locked(g_bts, 0);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "pll-setvalue")) {
+ abis_nm_bs11_set_pll(g_bts, atoi(value));
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "pll-workvalue")) {
+ /* To set the work value we need to login as FIELD */
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ sleep(1);
+ abis_nm_bs11_infield_logon(g_bts, 1);
+ sleep(1);
+ abis_nm_bs11_set_pll(g_bts, atoi(value));
+ sleep(1);
+ abis_nm_bs11_infield_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "oml-tei")) {
+ abis_nm_bs11_conn_oml_tei(g_bts, 0, 1, 0xff, TEI_OML);
+ command = NULL;
+ } else if (!strcmp(command, "restart")) {
+ abis_nm_bs11_restart(g_bts);
+ command = NULL;
+ } else if (!strcmp(command, "query")) {
+ cmd_query();
+ } else if (!strcmp(command, "create-bport1")) {
+ abis_nm_bs11_create_bport(g_bts, 1);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "delete-bport1")) {
+ abis_nm_chg_adm_state(g_bts, NM_OC_BS11_BPORT, 1, 0xff, 0xff, NM_STATE_LOCKED);
+ sleep(1);
+ abis_nm_bs11_delete_bport(g_bts, 1);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "bport0-star")) {
+ abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_STAR);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "bport0-multidrop")) {
+ abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_MULTIDROP);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ } else if (!strcmp(command, "bport1-multidrop")) {
+ abis_nm_bs11_set_bport_line_cfg(g_bts, 1, BS11_LINE_CFG_MULTIDROP);
+ sleep(1);
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ command = NULL;
+ }
+
+ }
+ break;
+ case BS11_STATE_NORMAL:
+ if (command) {
+ if (!strcmp(command, "reconnect"))
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ else if (!strcmp(command, "disconnect"))
+ abis_nm_bs11_bsc_disconnect(g_bts, 0);
+ else if (!strcmp(command, "query")) {
+ cmd_query();
+ }
+ } else if (param_disconnect) {
+ param_disconnect = 0;
+ abis_nm_bs11_bsc_disconnect(g_bts, 0);
+ if (param_restart) {
+ param_restart = 0;
+ abis_nm_bs11_restart(g_bts);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return rc;
+}
+
+/* handle a fully-received message/packet from the RS232 port */
+static int abis_nm_bs11cfg_rcvmsg(struct msgb *rx_msg)
+{
+ struct e1inp_sign_link *link = rx_msg->dst;
+ struct abis_om_hdr *oh;
+ struct abis_om_fom_hdr *foh;
+ struct tlv_parsed tp;
+ int rc = -1;
+
+#if 0
+ const uint8_t too_fast[] = { 0x12, 0x80, 0x00, 0x00, 0x02, 0x02 };
+
+ if (rx_msg->len < LAPD_HDR_LEN
+ + sizeof(struct abis_om_fom_hdr)
+ + sizeof(struct abis_om_hdr)) {
+ if (!memcmp(rx_msg->data + 2, too_fast,
+ sizeof(too_fast))) {
+ fprintf(stderr, "BS11 tells us we're too "
+ "fast, try --delay bigger than %u\n",
+ delay_ms);
+ return -E2BIG;
+ } else
+ fprintf(stderr, "unknown BS11 message\n");
+ }
+#endif
+
+ oh = (struct abis_om_hdr *) msgb_l2(rx_msg);
+ foh = (struct abis_om_fom_hdr *) oh->data;
+ switch (foh->msg_type) {
+ case NM_MT_BS11_LMT_LOGON_ACK:
+ printf("LMT LOGON: ACK\n\n");
+ if (bs11cfg_state == STATE_NONE)
+ bs11cfg_state = STATE_LOGON_ACK;
+ rc = abis_nm_bs11_get_state(g_bts);
+ break;
+ case NM_MT_BS11_LMT_LOGOFF_ACK:
+ printf("LMT LOGOFF: ACK\n");
+ exit(0);
+ break;
+ case NM_MT_BS11_GET_STATE_ACK:
+ rc = abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh));
+ print_state(&tp);
+ if (TLVP_PRESENT(&tp, NM_ATT_BS11_BTS_STATE) &&
+ TLVP_LEN(&tp, NM_ATT_BS11_BTS_STATE) >= 1)
+ rc = handle_state_resp(*TLVP_VAL(&tp, NM_ATT_BS11_BTS_STATE));
+ break;
+ case NM_MT_GET_ATTR_RESP:
+ printf("\n%sATTRIBUTES:\n", obj_name(foh));
+ abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh));
+ rc = print_attr(&tp);
+ //osmo_hexdump(foh->data, oh->length-sizeof(*foh));
+ break;
+ case NM_MT_BS11_SET_ATTR_ACK:
+ printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) ACK\n",
+ foh->obj_class, foh->obj_inst.bts_nr,
+ foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
+ rc = 0;
+ break;
+ case NM_MT_BS11_SET_ATTR_NACK:
+ printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) NACK\n",
+ foh->obj_class, foh->obj_inst.bts_nr,
+ foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
+ break;
+ case NM_MT_GET_ATTR_NACK:
+ printf("\n%sGET ATTR NACK\n", obj_name(foh));
+ break;
+ case NM_MT_BS11_CREATE_OBJ_ACK:
+ printf("\n%sCREATE OBJECT ACK\n", obj_name(foh));
+ break;
+ case NM_MT_BS11_CREATE_OBJ_NACK:
+ printf("\n%sCREATE OBJECT NACK\n", obj_name(foh));
+ break;
+ case NM_MT_BS11_DELETE_OBJ_ACK:
+ printf("\n%sDELETE OBJECT ACK\n", obj_name(foh));
+ break;
+ case NM_MT_BS11_DELETE_OBJ_NACK:
+ printf("\n%sDELETE OBJECT NACK\n", obj_name(foh));
+ break;
+ default:
+ rc = abis_nm_rcvmsg(rx_msg);
+ }
+ if (rc < 0) {
+ perror("ERROR in main loop");
+ //break;
+ }
+ /* flush the queue of pending messages to be sent. */
+ abis_nm_queue_send_next(link->trx->bts);
+ if (rc == 1)
+ return rc;
+
+ switch (bs11cfg_state) {
+ case STATE_NONE:
+ abis_nm_bs11_factory_logon(g_bts, 1);
+ break;
+ case STATE_LOGON_ACK:
+ osmo_timer_schedule(&status_timer, 5, 0);
+ break;
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+void status_timer_cb(void *data)
+{
+ abis_nm_bs11_get_state(g_bts);
+}
+
+static void print_banner(void)
+{
+ printf("bs11_config (C) 2009-2010 by Harald Welte and Dieter Spaar\n");
+ printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+}
+
+static void print_help(void)
+{
+ printf("bs11_config [options] [command]\n");
+ printf("\nSupported options:\n");
+ printf("\t-h --help\t\t\tPrint this help text\n");
+ printf("\t-p --port </dev/ttyXXX>\t\tSpecify serial port\n");
+ printf("\t-s --software <file>\t\tSpecify Software file\n");
+ printf("\t-S --safety <file>\t\tSpecify Safety Load file\n");
+ printf("\t-d --delay <ms>\t\t\tSpecify delay in milliseconds\n");
+ printf("\t-D --disconnect\t\t\tDisconnect BTS from BSC\n");
+ printf("\t-w --win-size <num>\t\tSpecify Window Size\n");
+ printf("\t-f --forced\t\t\tForce Software Load\n");
+ printf("\nSupported commands:\n");
+ printf("\tquery\t\t\tQuery the BS-11 about serial number and configuration\n");
+ printf("\tdisconnect\t\tDisconnect A-bis link (go into administrative state)\n");
+ printf("\tresconnect\t\tReconnect A-bis link (go into normal state)\n");
+ printf("\trestart\t\t\tRestart the BTS\n");
+ printf("\tsoftware\t\tDownload Software (only in administrative state)\n");
+ printf("\tcreate-trx1\t\tCreate objects for TRX1 (Danger: Your BS-11 might overheat)\n");
+ printf("\tdelete-trx1\t\tDelete objects for TRX1\n");
+ printf("\tpll-e1-locked\t\tSet the PLL to be locked to E1 clock\n");
+ printf("\tpll-standalone\t\tSet the PLL to be in standalone mode\n");
+ printf("\tpll-setvalue <value>\tSet the PLL set value\n");
+ printf("\tpll-workvalue <value>\tSet the PLL work value\n");
+ printf("\toml-tei\t\t\tSet OML E1 TS and TEI\n");
+ printf("\tbport0-star\t\tSet BPORT0 line config to star\n");
+ printf("\tbport0-multidrop\tSet BPORT0 line config to multidrop\n");
+ printf("\tbport1-multidrop\tSet BPORT1 line config to multidrop\n");
+ printf("\tcreate-bport1\t\tCreate BPORT1 object\n");
+ printf("\tdelete-bport1\t\tDelete BPORT1 object\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+ int option_index = 0;
+ print_banner();
+
+ while (1) {
+ int c;
+ static struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "port", 1, 0, 'p' },
+ { "software", 1, 0, 's' },
+ { "safety", 1, 0, 'S' },
+ { "delay", 1, 0, 'd' },
+ { "disconnect", 0, 0, 'D' },
+ { "win-size", 1, 0, 'w' },
+ { "forced", 0, 0, 'f' },
+ { "restart", 0, 0, 'r' },
+ { "debug", 1, 0, 'b'},
+ };
+
+ c = getopt_long(argc, argv, "hp:s:S:td:Dw:fra:",
+ long_options, &option_index);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ case 'p':
+ serial_port = optarg;
+ break;
+ case 'b':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 's':
+ fname_software = optarg;
+ break;
+ case 'S':
+ fname_safety = optarg;
+ break;
+ case 'd':
+ delay_ms = atoi(optarg);
+ break;
+ case 'w':
+ win_size = atoi(optarg);
+ break;
+ case 'D':
+ param_disconnect = 1;
+ break;
+ case 'f':
+ param_forced = 1;
+ break;
+ case 'r':
+ param_disconnect = 1;
+ param_restart = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ if (optind < argc) {
+ command = argv[optind];
+ if (optind+1 < argc)
+ value = argv[optind+1];
+ }
+
+}
+
+static int num_sigint;
+
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "\nsignal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ num_sigint++;
+ abis_nm_bs11_factory_logon(g_bts, 0);
+ if (num_sigint >= 3)
+ exit(0);
+ break;
+ }
+}
+
+static int bs11cfg_sign_link(struct msgb *msg)
+{
+ msg->dst = oml_link;
+ return abis_nm_bs11cfg_rcvmsg(msg);
+}
+
+struct e1inp_line_ops bs11cfg_e1inp_line_ops = {
+ .sign_link = bs11cfg_sign_link,
+};
+
+static const struct log_info_cat bs11_categories[] = {
+ [DNM] = {
+ .name = "DNM",
+ .description = "A-bis Network Management / O&M (NM/OML)",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+};
+
+const struct log_info log_info = {
+ .cat = bs11_categories,
+ .num_cat = ARRAY_SIZE(bs11_categories),
+};
+
+extern int bts_model_bs11_init(void);
+
+extern void *tall_fle_ctx;
+
+int main(int argc, char **argv)
+{
+ struct gsm_network *gsmnet;
+ struct e1inp_line *line;
+
+ tall_bs11cfg_ctx = talloc_named_const(NULL, 0, "bs11-config");
+ tall_fle_ctx = talloc_named_const(tall_bs11cfg_ctx, 0, "bs11_file_list_entry");
+ msgb_talloc_ctx_init(tall_bs11cfg_ctx, 0);
+
+ osmo_init_logging2(tall_bs11cfg_ctx, &log_info);
+ handle_options(argc, argv);
+ bts_model_bs11_init();
+
+ gsmnet = gsm_network_init(tall_bs11cfg_ctx);
+ if (!gsmnet) {
+ fprintf(stderr, "Unable to allocate gsm network\n");
+ exit(1);
+ }
+ g_bts = gsm_bts_alloc_register(gsmnet, GSM_BTS_TYPE_BS11,
+ HARDCODED_BSIC);
+
+ /* Override existing OML callback handler to set our own. */
+ g_bts->model->oml_rcvmsg = abis_nm_bs11cfg_rcvmsg;
+
+ libosmo_abis_init(tall_bs11cfg_ctx);
+
+ /* Initialize virtual E1 line over rs232. */
+ line = talloc_zero(tall_bs11cfg_ctx, struct e1inp_line);
+ if (!line) {
+ fprintf(stderr, "Unable to allocate memory for virtual E1 line\n");
+ exit(1);
+ }
+ /* set the serial port. */
+ bs11cfg_e1inp_line_ops.cfg.rs232.port = serial_port;
+ bs11cfg_e1inp_line_ops.cfg.rs232.delay = delay_ms;
+
+ line->driver = e1inp_driver_find("rs232");
+ if (!line->driver) {
+ fprintf(stderr, "cannot find `rs232' driver, giving up.\n");
+ exit(1);
+ }
+ e1inp_line_bind_ops(line, &bs11cfg_e1inp_line_ops);
+
+ /* configure and create signalling link for OML. */
+ e1inp_ts_config_sign(&line->ts[0], line);
+ g_bts->oml_link = oml_link =
+ e1inp_sign_link_create(&line->ts[0], E1INP_SIGN_OML,
+ g_bts->c0, TEI_OML, 0);
+
+ e1inp_line_update(line);
+
+ signal(SIGINT, &signal_handler);
+
+ abis_nm_bs11_factory_logon(g_bts, 1);
+ //abis_nm_bs11_get_serno(g_bts);
+
+ osmo_timer_setup(&status_timer, status_timer_cb, NULL);
+
+ while (1) {
+ if (osmo_select_main(0) < 0)
+ break;
+ }
+
+ abis_nm_bs11_factory_logon(g_bts, 0);
+
+ exit(0);
+}
+
+/* Stub */
+int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ return 0;
+}
+
+/* Stub */
+int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ return 0;
+}
+
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) {}
+int abis_rsl_rcvmsg(struct msgb *msg) { return 0; }
diff --git a/src/utils/isdnsync.c b/src/utils/isdnsync.c
new file mode 100644
index 000000000..cc8ff6723
--- /dev/null
+++ b/src/utils/isdnsync.c
@@ -0,0 +1,189 @@
+/* isdnsync.c
+ *
+ * Author Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All rights reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include "mISDNif.h"
+#define MISDN_OLD_AF_COMPATIBILITY
+#define AF_COMPATIBILITY_FUNC
+#include "compat_af_isdn.h"
+
+int card = 0;
+int sock = -1;
+
+int mISDN_open(void)
+{
+ int fd, ret;
+ struct mISDN_devinfo devinfo;
+ struct sockaddr_mISDN l2addr;
+
+ fd = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
+ if (fd < 0) {
+ fprintf(stderr, "could not open socket (%s)\n", strerror(errno));
+ return fd;
+ }
+ devinfo.id = card;
+ ret = ioctl(fd, IMGETDEVINFO, &devinfo);
+ if (ret < 0) {
+ fprintf(stderr,"could not send IOCTL IMGETCOUNT (%s)\n", strerror(errno));
+ close(fd);
+ return ret;
+ }
+ close(fd);
+ if (!(devinfo.Dprotocols & (1 << ISDN_P_TE_S0))
+ && !(devinfo.Dprotocols & (1 << ISDN_P_TE_E1))) {
+ fprintf(stderr,"Interface does not support TE mode (%s)\n", strerror(errno));
+ return ret;
+ }
+ fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_TE);
+ if (fd < 0) {
+ fprintf(stderr,"could not open ISDN_P_LAPD_TE socket (%s)\n", strerror(errno));
+ return fd;
+ }
+ l2addr.family = AF_ISDN;
+ l2addr.dev = card;
+ l2addr.channel = 0;
+ l2addr.sapi = 0;
+ l2addr.tei = 0;
+ ret = bind(fd, (struct sockaddr *)&l2addr, sizeof(l2addr));
+ if (ret < 0) {
+ fprintf(stderr,"could not bind socket for card %d (%s)\n", card, strerror(errno));
+ close(fd);
+ return ret;
+ }
+ sock = fd;
+
+ return sock;
+}
+
+
+void mISDN_handle(void)
+{
+ int ret;
+ fd_set rfd;
+ struct timeval tv;
+ struct sockaddr_mISDN addr;
+ socklen_t alen;
+ unsigned char buffer[2048];
+ struct mISDNhead *hh = (struct mISDNhead *)buffer;
+ int l1 = 0, l2 = 0, tei = 0;
+
+ while(1) {
+again:
+ FD_ZERO(&rfd);
+ FD_SET(sock, &rfd);
+ tv.tv_sec = 2;
+ tv.tv_usec = 0;
+ ret = select(sock+1, &rfd, NULL, NULL, &tv);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "%s aborted: %s\n", __FUNCTION__, strerror(errno));
+ break;
+ }
+ if (FD_ISSET(sock, &rfd)) {
+ alen = sizeof(addr);
+ ret = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &alen);
+ if (ret < 0) {
+ fprintf(stderr, "%s read socket error %s\n", __FUNCTION__, strerror(errno));
+ } else if (ret < MISDN_HEADER_LEN) {
+ fprintf(stderr, "%s read socket shor frame\n", __FUNCTION__);
+ } else {
+ switch(hh->prim) {
+ case MPH_ACTIVATE_IND:
+ case PH_ACTIVATE_IND:
+ if (!l1) {
+ printf("PH_ACTIVATE\n");
+ printf("*** Sync available from interface :-)\n");
+ l1 = 1;
+ }
+ goto again;
+ break;
+ case MPH_DEACTIVATE_IND:
+ case PH_DEACTIVATE_IND:
+ if (l1) {
+ printf("PH_DEACTIVATE\n");
+ printf("*** Lost sync on interface :-(\n");
+ l1 = 0;
+ }
+ goto again;
+ break;
+ case DL_ESTABLISH_IND:
+ case DL_ESTABLISH_CNF:
+ printf("DL_ESTABLISH\n");
+ l2 = 1;
+ goto again;
+ break;
+ case DL_RELEASE_IND:
+ case DL_RELEASE_CNF:
+ printf("DL_RELEASE\n");
+ l2 = 0;
+ goto again;
+ break;
+ case DL_INFORMATION_IND:
+ printf("DL_INFORMATION (tei %d sapi %d)\n", addr.tei, addr.sapi);
+ tei = 1;
+ break;
+ default:
+// printf("prim %x\n", hh->prim);
+ goto again;
+ }
+ }
+ }
+ if (tei && !l2) {
+ hh->prim = DL_ESTABLISH_REQ;
+ printf("-> activating layer 2\n");
+ sendto(sock, buffer, MISDN_HEADER_LEN, 0, (struct sockaddr *) &addr, alen);
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+
+ if (argc <= 1)
+ {
+ printf("Usage: %s <card>\n\n", argv[0]);
+ printf("Opens given card number in TE-mode PTP and tries to keep layer 2 established.\n");
+ printf("This keeps layer 1 activated to retrieve a steady sync signal from network.\n");
+ return(0);
+ }
+
+ card = atoi(argv[1]);
+
+ init_af_isdn();
+
+ if ((ret = mISDN_open() < 0))
+ return(ret);
+
+ mISDN_handle();
+
+ close(sock);
+
+ return 0;
+}
diff --git a/src/utils/meas_db.c b/src/utils/meas_db.c
new file mode 100644
index 000000000..7233dcda6
--- /dev/null
+++ b/src/utils/meas_db.c
@@ -0,0 +1,330 @@
+/* Routines for storing measurement reports in SQLite3 database */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+
+#include <sqlite3.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/bsc/meas_rep.h>
+
+#include "meas_db.h"
+
+#define INS_MR "INSERT INTO meas_rep (time, imsi, name, scenario, nr, bs_power, ms_timing_offset, fpc, ms_l1_pwr, ms_l1_ta) VALUES (?,?,?,?,?,?,?,?,?,?)"
+#define INS_UD "INSERT INTO meas_rep_unidir (meas_id, rx_lev_full, rx_lev_sub, rx_qual_full, rx_qual_sub, dtx, uplink) VALUES (?,?,?,?,?,?,?)"
+#define UPD_MR "UPDATE meas_rep SET ul_unidir=?, dl_unidir=? WHERE id=?"
+
+struct meas_db_state {
+ sqlite3 *db;
+ sqlite3_stmt *stmt_ins_ud;
+ sqlite3_stmt *stmt_ins_mr;
+ sqlite3_stmt *stmt_upd_mr;
+};
+
+/* macros to check for SQLite3 result codes */
+#define _SCK_OK(db, call, exp) \
+ do { \
+ int rc = call; \
+ if (rc != exp) { \
+ fprintf(stderr,"SQL Error in line %u: %s\n", \
+ __LINE__, sqlite3_errmsg(db)); \
+ goto err_io; \
+ } \
+ } while (0)
+#define SCK_OK(db, call) _SCK_OK(db, call, SQLITE_OK)
+#define SCK_DONE(db, call) _SCK_OK(db, call, SQLITE_DONE)
+
+static int _insert_ud(struct meas_db_state *st, unsigned long meas_id, int dtx,
+ int uplink, const struct gsm_meas_rep_unidir *ud)
+{
+ unsigned long rowid;
+
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 1, meas_id));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 2,
+ rxlev2dbm(ud->full.rx_lev)));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 3,
+ rxlev2dbm(ud->sub.rx_lev)));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 4, ud->full.rx_qual));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 5, ud->sub.rx_qual));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 6, dtx));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 7, uplink));
+
+ SCK_DONE(st->db, sqlite3_step(st->stmt_ins_ud));
+
+ SCK_OK(st->db, sqlite3_reset(st->stmt_ins_ud));
+
+ return sqlite3_last_insert_rowid(st->db);
+err_io:
+ exit(1);
+}
+
+/* insert a measurement report into the database */
+int meas_db_insert(struct meas_db_state *st, const char *imsi,
+ const char *name, unsigned long timestamp,
+ const char *scenario,
+ const struct gsm_meas_rep *mr)
+{
+ int rc;
+ sqlite3_int64 rowid, ul_rowid, dl_rowid;
+
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 1, timestamp));
+
+ if (imsi)
+ SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 2,
+ imsi, -1, SQLITE_STATIC));
+ else
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 2));
+
+ if (name)
+ SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 3,
+ name, -1, SQLITE_STATIC));
+ else
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 3));
+
+ if (scenario)
+ SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 4,
+ scenario, -1, SQLITE_STATIC));
+ else
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 4));
+
+
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 5, mr->nr));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 6, mr->bs_power));
+
+ if (mr->flags & MEAS_REP_F_MS_TO)
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 7, mr->ms_timing_offset));
+ else
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 7));
+
+ if (mr->flags & MEAS_REP_F_FPC)
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 8, 1));
+ else
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 8, 0));
+
+ if (mr->flags & MEAS_REP_F_MS_L1) {
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 9,
+ mr->ms_l1.pwr));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 10,
+ mr->ms_l1.ta));
+ }
+
+ SCK_DONE(st->db, sqlite3_step(st->stmt_ins_mr));
+ SCK_OK(st->db, sqlite3_reset(st->stmt_ins_mr));
+
+ rowid = sqlite3_last_insert_rowid(st->db);
+
+ /* insert uplink measurement */
+ ul_rowid = _insert_ud(st, rowid, mr->flags & MEAS_REP_F_UL_DTX,
+ 1, &mr->ul);
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 1, ul_rowid));
+
+ /* insert downlink measurement, if present */
+ if (mr->flags & MEAS_REP_F_DL_VALID) {
+ dl_rowid = _insert_ud(st, rowid, mr->flags & MEAS_REP_F_DL_DTX,
+ 0, &mr->dl);
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 2, dl_rowid));
+ } else
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_upd_mr, 2));
+
+ /* update meas_rep with the id's of the unidirectional
+ * measurements */
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 3, rowid));
+ SCK_DONE(st->db, sqlite3_step(st->stmt_upd_mr));
+ SCK_OK(st->db, sqlite3_reset(st->stmt_upd_mr));
+
+ return 0;
+
+err_io:
+ return -EIO;
+}
+
+int meas_db_begin(struct meas_db_state *st)
+{
+ SCK_OK(st->db, sqlite3_exec(st->db, "BEGIN", NULL, NULL, NULL));
+
+ return 0;
+
+err_io:
+ return -EIO;
+}
+
+int meas_db_commit(struct meas_db_state *st)
+{
+ SCK_OK(st->db, sqlite3_exec(st->db, "COMMIT", NULL, NULL, NULL));
+
+ return 0;
+
+err_io:
+ return -EIO;
+}
+
+static const char *create_stmts[] = {
+ "CREATE TABLE IF NOT EXISTS meas_rep ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "time TIMESTAMP,"
+ "imsi TEXT,"
+ "name TEXT,"
+ "scenario TEXT,"
+ "nr INTEGER,"
+ "bs_power INTEGER NOT NULL,"
+ "ms_timing_offset INTEGER,"
+ "fpc INTEGER NOT NULL DEFAULT 0,"
+ "ul_unidir INTEGER REFERENCES meas_rep_unidir(id),"
+ "dl_unidir INTEGER REFERENCES meas_rep_unidir(id),"
+ "ms_l1_pwr INTEGER,"
+ "ms_l1_ta INTEGER"
+ ")",
+ "CREATE TABLE IF NOT EXISTS meas_rep_unidir ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "meas_id INTEGER NOT NULL REFERENCES meas_rep(id),"
+ "rx_lev_full INTEGER NOT NULL,"
+ "rx_lev_sub INTEGER NOT NULL,"
+ "rx_qual_full INTEGER NOT NULL,"
+ "rx_qual_sub INTEGER NOT NULL,"
+ "dtx BOOLEAN NOT NULL DEFAULT 0,"
+ "uplink BOOLEAN NOT NULL"
+ ")",
+ "CREATE VIEW IF NOT EXISTS path_loss AS "
+ "SELECT "
+ "meas_rep.id, "
+ "datetime(time,'unixepoch') AS timestamp, "
+ "imsi, "
+ "name, "
+ "scenario, "
+ "ms_timing_offset, "
+ "ms_l1_ta, "
+ "fpc, "
+ "ms_l1_pwr, "
+ "ud_ul.rx_lev_full AS ul_rx_lev_full, "
+ "ms_l1_pwr-ud_ul.rx_lev_full AS ul_path_loss_full, "
+ "ud_ul.rx_lev_sub ul_rx_lev_sub, "
+ "ms_l1_pwr-ud_ul.rx_lev_sub AS ul_path_loss_sub, "
+ "ud_ul.rx_qual_full AS ul_rx_qual_full, "
+ "ud_ul.rx_qual_sub AS ul_rx_qual_sub, "
+ "bs_power, "
+ "ud_dl.rx_lev_full AS dl_rx_lev_full, "
+ "bs_power-ud_dl.rx_lev_full AS dl_path_loss_full, "
+ "ud_dl.rx_lev_sub AS dl_rx_lev_sub, "
+ "bs_power-ud_dl.rx_lev_sub AS dl_path_loss_sub, "
+ "ud_dl.rx_qual_full AS dl_rx_qual_full, "
+ "ud_dl.rx_qual_sub AS dl_rx_qual_sub "
+ "FROM "
+ "meas_rep, "
+ "meas_rep_unidir AS ud_dl, "
+ "meas_rep_unidir AS ud_ul "
+ "WHERE "
+ "ud_ul.id = meas_rep.ul_unidir AND "
+ "ud_dl.id = meas_rep.dl_unidir",
+ "CREATE VIEW IF NOT EXISTS overview AS "
+ "SELECT "
+ "id,"
+ "timestamp,"
+ "imsi,"
+ "name,"
+ "scenario,"
+ "ms_l1_pwr,"
+ "ul_rx_lev_full,"
+ "ul_path_loss_full,"
+ "ul_rx_qual_full,"
+ "bs_power,"
+ "dl_rx_lev_full,"
+ "dl_path_loss_full,"
+ "dl_rx_qual_full "
+ "FROM path_loss",
+};
+
+static int check_create_tbl(struct meas_db_state *st)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
+ SCK_OK(st->db, sqlite3_exec(st->db, create_stmts[i],
+ NULL, NULL, NULL));
+ }
+
+ return 0;
+err_io:
+ return -EIO;
+}
+
+
+#define PREP_CHK(db, stmt, ptr) \
+ do { \
+ int rc; \
+ rc = sqlite3_prepare_v2(db, stmt, strlen(stmt)+1, \
+ ptr, NULL); \
+ if (rc != SQLITE_OK) { \
+ fprintf(stderr, "Error during prepare of '%s': %s\n", \
+ stmt, sqlite3_errmsg(db)); \
+ goto err_io; \
+ } \
+ } while (0)
+
+struct meas_db_state *meas_db_open(void *ctx, const char *fname)
+{
+ int rc;
+ struct meas_db_state *st = talloc_zero(ctx, struct meas_db_state);
+
+ if (!st)
+ return NULL;
+
+ rc = sqlite3_open_v2(fname, &st->db,
+ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,
+ NULL);
+ if (rc != SQLITE_OK) {
+ fprintf(stderr, "Unable to open DB: %s\n",
+ sqlite3_errmsg(st->db));
+ goto err_io;
+ }
+
+ rc = check_create_tbl(st);
+
+ PREP_CHK(st->db, INS_MR, &st->stmt_ins_mr);
+ PREP_CHK(st->db, INS_UD, &st->stmt_ins_ud);
+ PREP_CHK(st->db, UPD_MR, &st->stmt_upd_mr);
+
+ return st;
+err_io:
+ talloc_free(st);
+ return NULL;
+}
+
+void meas_db_close(struct meas_db_state *st)
+{
+ if (sqlite3_finalize(st->stmt_ins_mr) != SQLITE_OK)
+ fprintf(stderr, "DB insert measurement report finalize error: %s\n",
+ sqlite3_errmsg(st->db));
+ if (sqlite3_finalize(st->stmt_ins_ud) != SQLITE_OK)
+ fprintf(stderr, "DB insert unidir finalize error: %s\n",
+ sqlite3_errmsg(st->db));
+ if (sqlite3_finalize(st->stmt_upd_mr) != SQLITE_OK)
+ fprintf(stderr, "DB update measurement report finalize error: %s\n",
+ sqlite3_errmsg(st->db));
+ if (sqlite3_close(st->db) != SQLITE_OK)
+ fprintf(stderr, "Unable to close DB, abandoning.\n");
+
+ talloc_free(st);
+
+}
diff --git a/src/utils/meas_db.h b/src/utils/meas_db.h
new file mode 100644
index 000000000..889e9022f
--- /dev/null
+++ b/src/utils/meas_db.h
@@ -0,0 +1,17 @@
+#ifndef OPENBSC_MEAS_DB_H
+#define OPENBSC_MEAS_DB_H
+
+struct meas_db_state;
+
+struct meas_db_state *meas_db_open(void *ctx, const char *fname);
+void meas_db_close(struct meas_db_state *st);
+
+int meas_db_begin(struct meas_db_state *st);
+int meas_db_commit(struct meas_db_state *st);
+
+int meas_db_insert(struct meas_db_state *st, const char *imsi,
+ const char *name, unsigned long timestamp,
+ const char *scenario,
+ const struct gsm_meas_rep *mr);
+
+#endif
diff --git a/src/utils/meas_json.c b/src/utils/meas_json.c
new file mode 100644
index 000000000..365b450f4
--- /dev/null
+++ b/src/utils/meas_json.c
@@ -0,0 +1,192 @@
+/* Convert measurement report feed into JSON feed printed to stdout.
+ * Each measurement report is printed as a separae JSON root entry.
+ * All measurement reports are separated by a new line.
+ */
+
+/* (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * With parts of code adopted from different places in OpenBSC.
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/meas_feed.h>
+
+static void print_meas_rep_uni_json(struct gsm_meas_rep_unidir *mru)
+{
+ printf("\"RXL-FULL\":%d, \"RXL-SUB\":%d, ",
+ rxlev2dbm(mru->full.rx_lev),
+ rxlev2dbm(mru->sub.rx_lev));
+ printf("\"RXQ-FULL\":%d, \"RXQ-SUB\":%d",
+ mru->full.rx_qual, mru->sub.rx_qual);
+}
+
+static void print_meas_rep_json(struct gsm_meas_rep *mr)
+{
+ int i;
+
+ printf("\"NR\":%d", mr->nr);
+
+ if (mr->flags & MEAS_REP_F_DL_DTX)
+ printf(", \"DTXd\":true");
+
+ printf(", \"UL_MEAS\":{");
+ print_meas_rep_uni_json(&mr->ul);
+ printf("}");
+ printf(", \"BS_POWER\":%d", mr->bs_power);
+ if (mr->flags & MEAS_REP_F_MS_TO)
+ printf(", \"MS_TO\":%d", mr->ms_timing_offset);
+
+ if (mr->flags & MEAS_REP_F_MS_L1) {
+ printf(", \"L1_MS_PWR\":%d", mr->ms_l1.pwr);
+ printf(", \"L1_FPC\":%s",
+ mr->flags & MEAS_REP_F_FPC ? "true" : "false");
+ printf(", \"L1_TA\":%u", mr->ms_l1.ta);
+ }
+
+ if (mr->flags & MEAS_REP_F_UL_DTX)
+ printf(", \"DTXu\":true");
+ if (mr->flags & MEAS_REP_F_BA1)
+ printf(", \"BA1\":true");
+ if (mr->flags & MEAS_REP_F_DL_VALID) {
+ printf(", \"DL_MEAS\":{");
+ print_meas_rep_uni_json(&mr->dl);
+ printf("}");
+ }
+
+ if (mr->num_cell == 7)
+ return;
+ printf(", \"NUM_NEIGH\":%u, \"NEIGH\":[", mr->num_cell);
+ for (i = 0; i < mr->num_cell; i++) {
+ struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+ if (i!=0) printf(", ");
+ printf("{\"IDX\":%u, \"ARFCN\":%u, \"BSIC\":%u, \"POWER\":%d}",
+ mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev));
+ }
+ printf("]");
+}
+
+static void print_chan_info_json(struct meas_feed_meas *mfm)
+{
+ printf("\"lchan_type\":\"%s\", \"pchan_type\":\"%s\", "
+ "\"bts_nr\":%d, \"trx_nr\":%d, \"ts_nr\":%d, \"ss_nr\":%d",
+ gsm_lchant_name(mfm->lchan_type), gsm_pchan_name(mfm->pchan_type),
+ mfm->bts_nr, mfm->trx_nr, mfm->ts_nr, mfm->ss_nr);
+}
+
+static void print_meas_feed_json(struct meas_feed_meas *mfm)
+{
+ time_t now = time(NULL);
+
+ printf("{");
+ printf("\"time\":%ld, \"imsi\":\"%s\", \"name\":\"%s\", \"scenario\":\"%s\", ",
+ now, mfm->imsi, mfm->name, mfm->scenario);
+
+ switch (mfm->hdr.version) {
+ case 1:
+ printf("\"chan_info\":{");
+ print_chan_info_json(mfm);
+ printf("}, ");
+ /* no break, fall to version 0 */
+ case 0:
+ printf("\"meas_rep\":{");
+ print_meas_rep_json(&mfm->mr);
+ printf("}");
+ break;
+ }
+
+ printf("}\n");
+
+}
+
+static int handle_meas(struct msgb *msg)
+{
+ struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
+
+ print_meas_feed_json(mfm);
+
+ return 0;
+}
+
+static int handle_msg(struct msgb *msg)
+{
+ struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
+
+ if (mfh->version != MEAS_FEED_VERSION)
+ return -EINVAL;
+
+ switch (mfh->msg_type) {
+ case MEAS_FEED_MEAS:
+ handle_meas(msg);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int rc;
+
+ if (what & BSC_FD_READ) {
+ struct msgb *msg = msgb_alloc(1024, "UDP Rx");
+
+ rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
+ if (rc < 0)
+ return rc;
+ msgb_put(msg, rc);
+ handle_msg(msg);
+ msgb_free(msg);
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+ struct osmo_fd udp_ofd;
+
+ udp_ofd.cb = udp_fd_cb;
+ rc = osmo_sock_init_ofd(&udp_ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
+ if (rc < 0)
+ exit(1);
+
+ while (1) {
+ osmo_select_main(0);
+ };
+
+ exit(0);
+}
+
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) {}
+int abis_rsl_rcvmsg(struct msgb *msg) { return 0; }
diff --git a/src/utils/meas_pcap2db.c b/src/utils/meas_pcap2db.c
new file mode 100644
index 000000000..db00fae49
--- /dev/null
+++ b/src/utils/meas_pcap2db.c
@@ -0,0 +1,138 @@
+/* read PCAP file with meas_feed data and write it to sqlite3 database */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/bsc/meas_feed.h>
+
+#include <pcap/pcap.h>
+
+#include "meas_db.h"
+
+static struct meas_db_state *db;
+
+static void handle_mfm(const struct pcap_pkthdr *h,
+ const struct meas_feed_meas *mfm)
+{
+ const char *scenario;
+
+ if (strlen(mfm->scenario))
+ scenario = mfm->scenario;
+ else
+ scenario = NULL;
+
+ meas_db_insert(db, mfm->imsi, mfm->name, h->ts.tv_sec,
+ scenario, &mfm->mr);
+}
+
+static void pcap_cb(u_char *user, const struct pcap_pkthdr *h,
+ const u_char *bytes)
+{
+ const char *cur = bytes;
+ const struct iphdr *ip;
+ const struct udphdr *udp;
+ const struct meas_feed_meas *mfm;
+ uint16_t udplen;
+
+ if (h->caplen < 14+20+8)
+ return;
+
+ /* Check if there is IPv4 in the Ethernet */
+ if (cur[12] != 0x08 || cur[13] != 0x00)
+ return;
+
+ cur += 14; /* ethernet header */
+ ip = (struct iphdr *) cur;
+
+ if (ip->version != 4)
+ return;
+ cur += ip->ihl * 4;
+
+ if (ip->protocol != IPPROTO_UDP)
+ return;
+
+ udp = (struct udphdr *) cur;
+
+ if (udp->dest != htons(8888))
+ return;
+
+ udplen = ntohs(udp->len);
+ if (udplen != sizeof(*udp) + sizeof(*mfm))
+ return;
+ cur += sizeof(*udp);
+
+ mfm = (const struct meas_feed_meas *) cur;
+
+ handle_mfm(h, mfm);
+}
+
+int main(int argc, char **argv)
+{
+ char errbuf[PCAP_ERRBUF_SIZE+1];
+ char *pcap_fname, *db_fname;
+ pcap_t *pc;
+ int rc;
+
+ if (argc < 3) {
+ fprintf(stderr, "You need to specify PCAP and database file\n");
+ exit(2);
+ }
+
+ pcap_fname = argv[1];
+ db_fname = argv[2];
+
+ pc = pcap_open_offline(pcap_fname, errbuf);
+ if (!pc) {
+ fprintf(stderr, "Cannot open %s: %s\n", pcap_fname, errbuf);
+ exit(1);
+ }
+
+ db = meas_db_open(NULL, db_fname);
+ if (!db)
+ exit(0);
+
+ rc = meas_db_begin(db);
+ if (rc < 0) {
+ fprintf(stderr, "Error during BEGIN\n");
+ exit(1);
+ }
+
+ pcap_loop(pc, 0 , pcap_cb, NULL);
+
+ meas_db_commit(db);
+
+ exit(0);
+}
diff --git a/src/utils/meas_udp2db.c b/src/utils/meas_udp2db.c
new file mode 100644
index 000000000..34f8385e8
--- /dev/null
+++ b/src/utils/meas_udp2db.c
@@ -0,0 +1,126 @@
+/* liesten to meas_feed on UDP and write it to sqlite3 database */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/bsc/meas_feed.h>
+
+#include "meas_db.h"
+
+static struct osmo_fd udp_ofd;
+static struct meas_db_state *db;
+
+static int handle_msg(struct msgb *msg)
+{
+ struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
+ struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
+ const char *scenario;
+ time_t now = time(NULL);
+
+ if (mfh->version != MEAS_FEED_VERSION)
+ return -EINVAL;
+
+ if (mfh->msg_type != MEAS_FEED_MEAS)
+ return -EINVAL;
+
+ if (strlen(mfm->scenario))
+ scenario = mfm->scenario;
+ else
+ scenario = NULL;
+
+ meas_db_insert(db, mfm->imsi, mfm->name, now,
+ scenario, &mfm->mr);
+
+ return 0;
+}
+
+static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int rc;
+
+ if (what & BSC_FD_READ) {
+ struct msgb *msg = msgb_alloc(1024, "UDP Rx");
+
+ rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
+ if (rc < 0)
+ return rc;
+ msgb_put(msg, rc);
+ handle_msg(msg);
+ msgb_free(msg);
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ char *db_fname;
+ int rc;
+
+ msgb_talloc_ctx_init(NULL, 0);
+
+ if (argc < 2) {
+ fprintf(stderr, "You have to specify the database file name\n");
+ exit(2);
+ }
+
+ db_fname = argv[1];
+
+ udp_ofd.cb = udp_fd_cb;
+ rc = osmo_sock_init_ofd(&udp_ofd, AF_INET, SOCK_DGRAM,
+ IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ fprintf(stderr, "Unable to create UDP listen socket\n");
+ exit(1);
+ }
+
+ db = meas_db_open(NULL, db_fname);
+ if (!db) {
+ fprintf(stderr, "Unable to open database\n");
+ exit(1);
+ }
+
+ /* FIXME: timer-based BEGIN/COMMIT */
+
+ while (1) {
+ osmo_select_main(0);
+ };
+
+ meas_db_close(db);
+
+ exit(0);
+}
+
diff --git a/src/utils/meas_vis.c b/src/utils/meas_vis.c
new file mode 100644
index 000000000..851aa03d9
--- /dev/null
+++ b/src/utils/meas_vis.c
@@ -0,0 +1,310 @@
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <netinet/in.h>
+
+#include <cdk/cdk.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/bsc/meas_feed.h>
+
+struct ms_state_uni {
+ CDKSLIDER *cdk;
+ CDKLABEL *cdk_label;
+
+ time_t last_update;
+ char label[32];
+ char *_lbl[1];
+};
+
+
+struct ms_state {
+ struct llist_head list;
+
+ char name[31+1];
+ char imsi[15+1];
+ struct gsm_meas_rep mr;
+
+ struct ms_state_uni ul;
+ struct ms_state_uni dl;
+};
+
+struct state {
+ struct osmo_fd udp_ofd;
+ struct llist_head ms_list;
+
+ CDKSCREEN *cdkscreen;
+ WINDOW *curses_win;
+
+ CDKLABEL *cdk_title;
+ char *title;
+
+ CDKLABEL *cdk_header;
+ char header[256];
+};
+
+static struct state g_st;
+
+struct ms_state *find_ms(const char *imsi)
+{
+ struct ms_state *ms;
+
+ llist_for_each_entry(ms, &g_st.ms_list, list) {
+ if (!strcmp(ms->imsi, imsi))
+ return ms;
+ }
+ return NULL;
+}
+
+static struct ms_state *find_alloc_ms(const char *imsi)
+{
+ struct ms_state *ms;
+
+ ms = find_ms(imsi);
+ if (!ms) {
+ ms = talloc_zero(NULL, struct ms_state);
+ osmo_strlcpy(ms->imsi, imsi, sizeof(ms->imsi));
+ ms->ul._lbl[0] = ms->ul.label;
+ ms->dl._lbl[0] = ms->dl.label;
+ llist_add_tail(&ms->list, &g_st.ms_list);
+ }
+
+ return ms;
+}
+
+static int handle_meas(struct msgb *msg)
+{
+ struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
+ struct ms_state *ms = find_alloc_ms(mfm->imsi);
+ time_t now = time(NULL);
+
+ osmo_strlcpy(ms->name, mfm->name, sizeof(ms->name));
+ memcpy(&ms->mr, &mfm->mr, sizeof(ms->mr));
+ ms->ul.last_update = now;
+ if (ms->mr.flags & MEAS_REP_F_DL_VALID)
+ ms->dl.last_update = now;
+
+ /* move to head of list */
+ llist_del(&ms->list);
+ llist_add(&ms->list, &g_st.ms_list);
+
+ return 0;
+}
+
+static int handle_msg(struct msgb *msg)
+{
+ struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
+
+ if (mfh->version != MEAS_FEED_VERSION)
+ return -EINVAL;
+
+ switch (mfh->msg_type) {
+ case MEAS_FEED_MEAS:
+ handle_meas(msg);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int rc;
+
+ if (what & BSC_FD_READ) {
+ struct msgb *msg = msgb_alloc(1024, "UDP Rx");
+
+ rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
+ if (rc < 0)
+ return rc;
+ msgb_put(msg, rc);
+ handle_msg(msg);
+ msgb_free(msg);
+ }
+
+ return 0;
+}
+
+
+static void destroy_dir(struct ms_state_uni *uni)
+{
+ if (uni->cdk) {
+ destroyCDKSlider(uni->cdk);
+ uni->cdk = NULL;
+ }
+ if (uni->cdk_label) {
+ destroyCDKLabel(uni->cdk_label);
+ uni->cdk_label = NULL;
+ }
+}
+
+#define DIR_UL 0
+#define DIR_DL 1
+static const char *dir_str[2] = {
+ [DIR_UL] = "UL",
+ [DIR_DL] = "DL",
+};
+
+static int colpair_by_qual(uint8_t rx_qual)
+{
+ if (rx_qual == 0)
+ return 24;
+ else if (rx_qual <= 4)
+ return 32;
+ else
+ return 16;
+}
+
+static int colpair_by_lev(int rx_lev)
+{
+ if (rx_lev < -95)
+ return 16;
+ else if (rx_lev < -80)
+ return 32;
+ else
+ return 24;
+}
+
+
+void write_uni(struct ms_state *ms, struct ms_state_uni *msu,
+ struct gsm_rx_lev_qual *lq, int dir, int row)
+{
+
+ char label[128];
+ time_t now = time(NULL);
+ int qual_col = colpair_by_qual(lq->rx_qual);
+ int lev_col = colpair_by_lev(rxlev2dbm(lq->rx_lev));
+ int color, pwr;
+
+ if (dir == DIR_UL) {
+ pwr = ms->mr.ms_l1.pwr;
+ } else {
+ pwr = ms->mr.bs_power;
+ }
+
+ color = A_REVERSE | COLOR_PAIR(lev_col) | ' ';
+ snprintf(label, sizeof(label), "%s %s ", ms->imsi, dir_str[dir]);
+ msu->cdk = newCDKSlider(g_st.cdkscreen, 0, row, NULL, label, color,
+ COLS-40, rxlev2dbm(lq->rx_lev), -110, -47,
+ 1, 2, FALSE, FALSE);
+ //IsVisibleObj(ms->ul.cdk) = FALSE;
+ snprintf(msu->label, sizeof(msu->label), "</%d>%1d<!%d> %3d %2u %2d %4u",
+ qual_col, lq->rx_qual, qual_col, pwr,
+ ms->mr.ms_l1.ta, ms->mr.ms_timing_offset,
+ now - msu->last_update);
+ msu->cdk_label = newCDKLabel(g_st.cdkscreen, RIGHT, row,
+ msu->_lbl, 1, FALSE, FALSE);
+}
+
+static void update_sliders(void)
+{
+ int num_vis_sliders = 0;
+ struct ms_state *ms;
+#define HEADER_LINES 2
+
+ /* remove all sliders */
+ llist_for_each_entry(ms, &g_st.ms_list, list) {
+ destroy_dir(&ms->ul);
+ destroy_dir(&ms->dl);
+
+ }
+
+ llist_for_each_entry(ms, &g_st.ms_list, list) {
+ struct gsm_rx_lev_qual *lq;
+ unsigned int row = HEADER_LINES + num_vis_sliders*3;
+
+ if (ms->mr.flags & MEAS_REP_F_UL_DTX)
+ lq = &ms->mr.ul.sub;
+ else
+ lq = &ms->mr.ul.full;
+ write_uni(ms, &ms->ul, lq, DIR_UL, row);
+
+ if (ms->mr.flags & MEAS_REP_F_DL_DTX)
+ lq = &ms->mr.dl.sub;
+ else
+ lq = &ms->mr.dl.full;
+ write_uni(ms, &ms->dl, lq, DIR_DL, row+1);
+
+ num_vis_sliders++;
+ if (num_vis_sliders >= LINES/3)
+ break;
+ }
+
+ refreshCDKScreen(g_st.cdkscreen);
+
+}
+
+const struct value_string col_strs[] = {
+ { COLOR_WHITE, "white" },
+ { COLOR_RED, "red" },
+ { COLOR_GREEN, "green" },
+ { COLOR_YELLOW, "yellow" },
+ { COLOR_BLUE, "blue" },
+ { COLOR_MAGENTA,"magenta" },
+ { COLOR_CYAN, "cyan" },
+ { COLOR_BLACK, "black" },
+ { 0, NULL }
+};
+
+int main(int argc, char **argv)
+{
+ int rc;
+ char *header[1];
+ char *title[1];
+
+ msgb_talloc_ctx_init(NULL, 0);
+
+ printf("sizeof(gsm_meas_rep)=%u\n", sizeof(struct gsm_meas_rep));
+ printf("sizeof(meas_feed_meas)=%u\n", sizeof(struct meas_feed_meas));
+
+ INIT_LLIST_HEAD(&g_st.ms_list);
+ g_st.curses_win = initscr();
+ g_st.cdkscreen = initCDKScreen(g_st.curses_win);
+ initCDKColor();
+
+ g_st.title = "OpenBSC link quality monitor";
+ title[0] = g_st.title;
+ g_st.cdk_title = newCDKLabel(g_st.cdkscreen, CENTER, 0, title, 1, FALSE, FALSE);
+
+ snprintf(g_st.header, sizeof(g_st.header), "Q Pwr TA TO Time");
+ header[0] = g_st.header;
+ g_st.cdk_header = newCDKLabel(g_st.cdkscreen, RIGHT, 1, header, 1, FALSE, FALSE);
+
+#if 0
+ int i;
+ for (i = 0; i < 64; i++) {
+ short f, b;
+ pair_content(i, &f, &b);
+ attron(COLOR_PAIR(i));
+ printw("%u: %u (%s) ", i, f, get_value_string(col_strs, f));
+ printw("%u (%s)\n\r", b, get_value_string(col_strs, b));
+ }
+ refresh();
+ getch();
+ exit(0);
+#endif
+
+ g_st.udp_ofd.cb = udp_fd_cb;
+ rc = osmo_sock_init_ofd(&g_st.udp_ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
+ if (rc < 0)
+ exit(1);
+
+ while (1) {
+ osmo_select_main(0);
+ update_sliders();
+ };
+
+ exit(0);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 000000000..446276b3d
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,82 @@
+SUBDIRS = \
+ bsc \
+ codec_pref \
+ gsm0408 \
+ abis \
+ subscr \
+ nanobts_omlattr \
+ handover \
+ $(NULL)
+
+# The `:;' works around a Bash 3.2 bug when the output is not writeable.
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+ :;{ \
+ echo '# Signature of the current package.' && \
+ echo 'm4_define([AT_PACKAGE_NAME],' && \
+ echo ' [$(PACKAGE_NAME)])' && \
+ echo 'm4_define([AT_PACKAGE_TARNAME],' && \
+ echo ' [$(PACKAGE_TARNAME)])' && \
+ echo 'm4_define([AT_PACKAGE_VERSION],' && \
+ echo ' [$(PACKAGE_VERSION)])' && \
+ echo 'm4_define([AT_PACKAGE_STRING],' && \
+ echo ' [$(PACKAGE_STRING)])' && \
+ echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \
+ echo ' [$(PACKAGE_BUGREPORT)])'; \
+ echo 'm4_define([AT_PACKAGE_URL],' && \
+ echo ' [$(PACKAGE_URL)])'; \
+ } >'$(srcdir)/package.m4'
+
+EXTRA_DIST = \
+ testsuite.at \
+ $(srcdir)/package.m4 \
+ $(TESTSUITE) \
+ vty_test_runner.py \
+ ctrl_test_runner.py \
+ handover_cfg.vty \
+ $(NULL)
+
+TESTSUITE = $(srcdir)/testsuite
+
+DISTCLEANFILES = \
+ atconfig \
+ $(NULL)
+
+if ENABLE_EXT_TESTS
+python-tests: $(BUILT_SOURCES)
+ $(MAKE) vty-test
+ osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
+ osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
+ $(srcdir)/vty_test_runner.py -w $(abs_top_builddir) -v
+ $(srcdir)/ctrl_test_runner.py -w $(abs_top_builddir) -v
+ rm -f $(top_builddir)/sms.db $(top_builddir)/gsn_restart $(top_builddir)/gtphub_restart_count
+else
+python-tests: $(BUILT_SOURCES)
+ echo "Not running python-based tests (determined at configure-time)"
+endif
+
+# To update the VTY script from current application behavior,
+# pass -u to vty_script_runner.py by doing:
+# make vty-test U=-u
+vty-test:
+ osmo_verify_transcript_vty.py -v \
+ -n OsmoBSC -p 4242 \
+ -r "$(top_builddir)/src/osmo-bsc/osmo-bsc -c $(top_srcdir)/doc/examples/osmo-bsc/osmo-bsc-minimal.cfg" \
+ $(U) $(srcdir)/*.vty
+
+check-local: atconfig $(TESTSUITE)
+ $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
+ $(MAKE) $(AM_MAKEFLAGS) python-tests
+
+installcheck-local: atconfig $(TESTSUITE)
+ $(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \
+ $(TESTSUITEFLAGS)
+
+clean-local:
+ test ! -f '$(TESTSUITE)' || \
+ $(SHELL) '$(TESTSUITE)' --clean
+
+AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4
+ $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at
+ mv $@.tmp $@
diff --git a/tests/abis/Makefile.am b/tests/abis/Makefile.am
new file mode 100644
index 000000000..4fc3605c0
--- /dev/null
+++ b/tests/abis/Makefile.am
@@ -0,0 +1,35 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ -ggdb3 \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+EXTRA_DIST = \
+ abis_test.ok \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ abis_test \
+ $(NULL)
+
+abis_test_SOURCES = \
+ abis_test.c \
+ $(NULL)
+
+abis_test_LDADD = \
+ $(top_builddir)/src/osmo-bsc/abis_nm.o \
+ $(top_builddir)/src/osmo-bsc/gsm_data.o \
+ $(top_builddir)/src/osmo-bsc/gsm_timers.o \
+ $(top_builddir)/src/osmo-bsc/net_init.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(NULL)
diff --git a/tests/abis/abis_test.c b/tests/abis/abis_test.c
new file mode 100644
index 000000000..5102aea6b
--- /dev/null
+++ b/tests/abis/abis_test.c
@@ -0,0 +1,191 @@
+/*
+ * (C) 2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/gsm23003.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/debug.h>
+
+static const uint8_t load_config[] = {
+ 0x42, 0x12, 0x00, 0x08, 0x31, 0x36, 0x38, 0x64,
+ 0x34, 0x37, 0x32, 0x00, 0x13, 0x00, 0x0b, 0x76,
+ 0x32, 0x30, 0x30, 0x62, 0x31, 0x34, 0x33, 0x64,
+ 0x30, 0x00, 0x42, 0x12, 0x00, 0x08, 0x31, 0x36,
+ 0x38, 0x64, 0x34, 0x37, 0x32, 0x00, 0x13, 0x00,
+ 0x0b, 0x76, 0x32, 0x30, 0x30, 0x62, 0x31, 0x34,
+ 0x33, 0x64, 0x31, 0x00
+};
+
+static void test_sw_selection(void)
+{
+ struct abis_nm_sw_desc descr[8], tmp;
+ uint16_t len0, len1;
+ int rc, pos;
+
+ rc = abis_nm_get_sw_conf(load_config, ARRAY_SIZE(load_config),
+ &descr[0], ARRAY_SIZE(descr));
+ if (rc != 2) {
+ printf("%s(): FAILED to parse the File Id/File version: %d\n",
+ __func__, rc);
+ abort();
+ }
+
+ len0 = abis_nm_sw_desc_len(&descr[0], true);
+ printf("len: %u\n", len0);
+ printf("file_id: %s\n", osmo_hexdump(descr[0].file_id, descr[0].file_id_len));
+ printf("file_ver: %s\n", osmo_hexdump(descr[0].file_version, descr[0].file_version_len));
+
+ len1 = abis_nm_sw_desc_len(&descr[1], true);
+ printf("len: %u\n", len1);
+ printf("file_id: %s\n", osmo_hexdump(descr[1].file_id, descr[1].file_id_len));
+ printf("file_ver: %s\n", osmo_hexdump(descr[1].file_version, descr[1].file_version_len));
+
+ /* start */
+ pos = abis_nm_select_newest_sw(descr, rc);
+ if (pos != 1) {
+ printf("Selected the wrong version: %d\n", pos);
+ abort();
+ }
+ printf("SELECTED: %d\n", pos);
+
+ /* shuffle */
+ tmp = descr[0];
+ descr[0] = descr[1];
+ descr[1] = tmp;
+ pos = abis_nm_select_newest_sw(descr, rc);
+ if (pos != 0) {
+ printf("Selected the wrong version: %d\n", pos);
+ abort();
+ }
+ printf("SELECTED: %d\n", pos);
+ printf("%s(): OK\n", __func__);
+}
+
+struct test_abis_nm_ipaccess_cgi {
+ struct osmo_plmn_id plmn;
+ uint16_t lac;
+ uint16_t cell_identity;
+ const char *expect;
+};
+static const struct test_abis_nm_ipaccess_cgi test_abis_nm_ipaccess_cgi_data[] = {
+ {
+ .plmn = { .mcc = 1, .mnc = 2, .mnc_3_digits = false },
+ .lac = 3,
+ .cell_identity = 4,
+ .expect = "00f120" "0003" "0004",
+ },
+ {
+ .plmn = { .mcc = 1, .mnc = 2, .mnc_3_digits = true },
+ .lac = 3,
+ .cell_identity = 4,
+ .expect = "002100" "0003" "0004",
+ },
+ {
+ .plmn = { .mcc = 0, .mnc = 0, .mnc_3_digits = false },
+ .lac = 0,
+ .cell_identity = 0,
+ .expect = "00f000" "0000" "0000",
+ },
+ {
+ .plmn = { .mcc = 0, .mnc = 0, .mnc_3_digits = true },
+ .lac = 0,
+ .cell_identity = 0,
+ .expect = "000000" "0000" "0000",
+ },
+ {
+ .plmn = { .mcc = 999, .mnc = 999, .mnc_3_digits = false },
+ .lac = 65535,
+ .cell_identity = 65535,
+ .expect = "999999" "ffff" "ffff",
+ },
+ {
+ .plmn = { .mcc = 909, .mnc = 90, .mnc_3_digits = false },
+ .lac = 0xabcd,
+ .cell_identity = 0x2345,
+ .expect = "09f909" "abcd" "2345",
+ },
+ {
+ .plmn = { .mcc = 909, .mnc = 90, .mnc_3_digits = true },
+ .lac = 0xabcd,
+ .cell_identity = 0x2345,
+ .expect = "090990" "abcd" "2345",
+ },
+};
+
+static void test_abis_nm_ipaccess_cgi()
+{
+ int i;
+ bool pass = true;
+
+ for (i = 0; i < ARRAY_SIZE(test_abis_nm_ipaccess_cgi_data); i++) {
+ struct gsm_network net;
+ struct gsm_bts bts;
+ const struct test_abis_nm_ipaccess_cgi *t = &test_abis_nm_ipaccess_cgi_data[i];
+ uint8_t result_buf[7] = {};
+ char *result;
+ bool ok;
+
+ net.plmn = t->plmn;
+ bts.network = &net;
+ bts.location_area_code = t->lac;
+ bts.cell_identity = t->cell_identity;
+
+ abis_nm_ipaccess_cgi(result_buf, &bts);
+ result = osmo_hexdump_nospc(result_buf, sizeof(result_buf));
+
+ ok = (strcmp(result, t->expect) == 0);
+ printf("%s[%d]: result=%s %s\n", __func__, i, result, ok ? "pass" : "FAIL");
+ pass = pass && ok;
+ }
+
+ OSMO_ASSERT(pass);
+}
+
+
+static const struct log_info_cat log_categories[] = {
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ osmo_init_logging2(NULL, &log_info);
+
+ test_sw_selection();
+ test_abis_nm_ipaccess_cgi();
+
+ return EXIT_SUCCESS;
+}
+
+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; }
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) {}
diff --git a/tests/abis/abis_test.ok b/tests/abis/abis_test.ok
new file mode 100644
index 000000000..e7e309c5f
--- /dev/null
+++ b/tests/abis/abis_test.ok
@@ -0,0 +1,16 @@
+len: 26
+file_id: 31 36 38 64 34 37 32 00
+file_ver: 76 32 30 30 62 31 34 33 64 30 00
+len: 26
+file_id: 31 36 38 64 34 37 32 00
+file_ver: 76 32 30 30 62 31 34 33 64 31 00
+SELECTED: 1
+SELECTED: 0
+test_sw_selection(): OK
+test_abis_nm_ipaccess_cgi[0]: result=00f12000030004 pass
+test_abis_nm_ipaccess_cgi[1]: result=00210000030004 pass
+test_abis_nm_ipaccess_cgi[2]: result=00f00000000000 pass
+test_abis_nm_ipaccess_cgi[3]: result=00000000000000 pass
+test_abis_nm_ipaccess_cgi[4]: result=999999ffffffff pass
+test_abis_nm_ipaccess_cgi[5]: result=09f909abcd2345 pass
+test_abis_nm_ipaccess_cgi[6]: result=090990abcd2345 pass
diff --git a/tests/atlocal.in b/tests/atlocal.in
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/atlocal.in
diff --git a/tests/bsc/Makefile.am b/tests/bsc/Makefile.am
new file mode 100644
index 000000000..c8ad0e40a
--- /dev/null
+++ b/tests/bsc/Makefile.am
@@ -0,0 +1,53 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ -ggdb3 \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOLEGACYMGCP_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+EXTRA_DIST = \
+ bsc_test.ok \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ bsc_test \
+ $(NULL)
+
+bsc_test_SOURCES = \
+ bsc_test.c \
+ $(NULL)
+
+bsc_test_LDADD = \
+ $(top_builddir)/src/osmo-bsc/abis_nm.o \
+ $(top_builddir)/src/osmo-bsc/arfcn_range_encode.o \
+ $(top_builddir)/src/osmo-bsc/osmo_bsc_filter.o \
+ $(top_builddir)/src/osmo-bsc/bsc_subscriber.o \
+ $(top_builddir)/src/osmo-bsc/gsm_data.o \
+ $(top_builddir)/src/osmo-bsc/gsm_timers.o \
+ $(top_builddir)/src/osmo-bsc/handover_cfg.o \
+ $(top_builddir)/src/osmo-bsc/handover_logic.o \
+ $(top_builddir)/src/osmo-bsc/neighbor_ident.o \
+ $(top_builddir)/src/osmo-bsc/net_init.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOLEGACYMGCP_LIBS) \
+ $(LIBRARY_GSM) \
+ -lrt \
+ $(NULL)
diff --git a/tests/bsc/bsc_test.c b/tests/bsc/bsc_test.c
new file mode 100644
index 000000000..8e88ba82f
--- /dev/null
+++ b/tests/bsc/bsc_test.c
@@ -0,0 +1,254 @@
+/*
+ * BSC Message filtering
+ *
+ * (C) 2013 by sysmocom s.f.m.c. GmbH
+ * Written by Jacob Erlbeck <jerlbeck@sysmocom.de>
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/gsm_04_80.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/backtrace.h>
+#include <osmocom/core/talloc.h>
+
+#include <stdio.h>
+#include <search.h>
+
+void *ctx = NULL;
+
+enum test {
+ TEST_SCAN_TO_BTS,
+};
+
+/* GSM 04.08 MM INFORMATION test message */
+static uint8_t gsm48_mm_info_nn_tzt[] = {
+ 0x05, 0x32, 0x45, 0x08, 0x80, 0x4f, 0x77, 0xeb,
+ 0x1a, 0xb6, 0x97, 0xe7, 0x47, 0x31, 0x90, 0x61,
+ 0x11, 0x02, 0x73, 0x00,
+};
+
+static uint8_t gsm48_mm_info_nn_tzt_out[] = {
+ 0x05, 0x32, 0x45, 0x08, 0x80, 0x4f, 0x77, 0xeb,
+ 0x1a, 0xb6, 0x97, 0xe7, 0x47, 0x31, 0x90, 0x61,
+ 0x11, 0x02, 0x73, 0x1a,
+};
+
+static uint8_t gsm48_mm_info_nn_tzt_dst[] = {
+ 0x05, 0x32, 0x45, 0x08, 0x80, 0x4f, 0x77, 0xeb,
+ 0x1a, 0xb6, 0x97, 0xe7, 0x47, 0x31, 0x90, 0x61,
+ 0x11, 0x02, 0x73, 0x00, 0x49, 0x01, 0x00,
+};
+
+static uint8_t gsm48_mm_info_nn_tzt_dst_out[] = {
+ 0x05, 0x32, 0x45, 0x08, 0x80, 0x4f, 0x77, 0xeb,
+ 0x1a, 0xb6, 0x97, 0xe7, 0x47, 0x31, 0x90, 0x61,
+ 0x11, 0x02, 0x73, 0x1a, 0x49, 0x01, 0x02,
+};
+
+struct test_definition {
+ const uint8_t *data;
+ const uint16_t length;
+ const int dir;
+ const int result;
+ const uint8_t *out_data;
+ const uint16_t out_length;
+ const char* params;
+ const int n_params;
+};
+
+static int get_int(const char *params, size_t nmemb, const char *key, int def, int *is_set)
+{
+ const char *kv = NULL;
+
+ kv = strstr(params, key);
+ if (kv) {
+ kv += strlen(key) + 1;
+ fprintf(stderr, "get_int(%s) -> %d\n", key, atoi(kv));
+ if (is_set)
+ *is_set = 1;
+ }
+
+ return kv ? atoi(kv) : def;
+}
+
+static const struct test_definition test_scan_defs[] = {
+ {
+ .data = gsm48_mm_info_nn_tzt_dst,
+ .length = ARRAY_SIZE(gsm48_mm_info_nn_tzt),
+ .dir = TEST_SCAN_TO_BTS,
+ .result = 0,
+ .out_data = gsm48_mm_info_nn_tzt_dst_out,
+ .out_length = ARRAY_SIZE(gsm48_mm_info_nn_tzt_out),
+ .params = "tz_hr=-5 tz_mn=15 tz_dst=2",
+ .n_params = 3,
+ },
+ {
+ .data = gsm48_mm_info_nn_tzt_dst,
+ .length = ARRAY_SIZE(gsm48_mm_info_nn_tzt_dst),
+ .dir = TEST_SCAN_TO_BTS,
+ .result = 0,
+ .out_data = gsm48_mm_info_nn_tzt_dst_out,
+ .out_length = ARRAY_SIZE(gsm48_mm_info_nn_tzt_dst_out),
+ .params = "tz_hr=-5 tz_mn=15 tz_dst=2",
+ .n_params = 3,
+ },
+};
+
+static void test_scan(void)
+{
+ int i;
+
+ 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;
+
+ msc = talloc_zero(net, struct bsc_msc_data);
+ conn = talloc_zero(net, struct gsm_subscriber_connection);
+
+ bts->network = net;
+ conn->sccp.msc = msc;
+ conn->lchan = &bts->c0->ts[1].lchan[0];
+
+ /* start testing with proper messages */
+ printf("Testing BTS<->MSC message scan.\n");
+ for (i = 0; i < ARRAY_SIZE(test_scan_defs); ++i) {
+ const struct test_definition *test_def = &test_scan_defs[i];
+ int result;
+ struct msgb *msg = msgb_alloc(4096, "test-message");
+ int is_set = 0;
+
+ net->tz.hr = get_int(test_def->params, test_def->n_params, "tz_hr", 0, &is_set);
+ net->tz.mn = get_int(test_def->params, test_def->n_params, "tz_mn", 0, &is_set);
+ net->tz.dst = get_int(test_def->params, test_def->n_params, "tz_dst", 0, &is_set);
+ net->tz.override = 1;
+
+ printf("Going to test item: %d\n", i);
+ msg->l3h = msgb_put(msg, test_def->length);
+ memcpy(msg->l3h, test_def->data, test_def->length);
+
+ switch (test_def->dir) {
+ case TEST_SCAN_TO_BTS:
+ /* override timezone of msg coming from the MSC */
+ result = bsc_scan_msc_msg(conn, msg);
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ if (result != test_def->result) {
+ printf("FAIL: Not the expected result, got: %d wanted: %d\n",
+ result, test_def->result);
+ goto out;
+ }
+
+ if (msgb_l3len(msg) != test_def->out_length) {
+ printf("FAIL: Not the expected message size, got: %d wanted: %d\n",
+ msgb_l3len(msg), test_def->out_length);
+ goto out;
+ }
+
+ if (memcmp(msgb_l3(msg), test_def->out_data, test_def->out_length) != 0) {
+ printf("FAIL: Not the expected message\n");
+ goto out;
+ }
+
+out:
+ msgb_free(msg);
+ }
+
+ talloc_free(net);
+}
+
+static const struct log_info_cat log_categories[] = {
+ [DNM] = {
+ .name = "DNM",
+ .description = "A-bis Network Management / O&M (NM/OML)",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DNAT] = {
+ .name = "DNAT",
+ .description = "GSM 08.08 NAT/Multiplexer",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMSC] = {
+ .name = "DMSC",
+ .description = "Mobile Switching Center",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DCTRL] = {
+ .name = "DCTRL",
+ .description = "Control interface",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DFILTER] = {
+ .name = "DFILTER",
+ .description = "BSC/NAT IMSI based filtering",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ ctx = talloc_named_const(NULL, 0, "bsc-test");
+ msgb_talloc_ctx_init(ctx, 0);
+ osmo_init_logging2(ctx, &log_info);
+
+ test_scan();
+
+ printf("Testing execution completed.\n");
+ talloc_free(ctx);
+ return 0;
+}
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) {
+ OSMO_ASSERT(0);
+}
+
+void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) {}
+void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_encr) {}
+int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, uint16_t chosen_channel)
+{ return 0; }
+void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) {}
+void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause) {}
+void bsc_assign_fail(struct gsm_subscriber_connection *conn, uint8_t cause, uint8_t *rr_cause) {}
+int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{ return 0; }
+void bsc_cm_update(struct gsm_subscriber_connection *conn,
+ const uint8_t *cm2, uint8_t cm2_len,
+ const uint8_t *cm3, uint8_t cm3_len) {}
+void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int link_id, int allow_sacch) {}
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) {}
+void lchan_activate(struct gsm_lchan *lchan, void *info) {}
diff --git a/tests/bsc/bsc_test.ok b/tests/bsc/bsc_test.ok
new file mode 100644
index 000000000..0564bf0cd
--- /dev/null
+++ b/tests/bsc/bsc_test.ok
@@ -0,0 +1,4 @@
+Testing BTS<->MSC message scan.
+Going to test item: 0
+Going to test item: 1
+Testing execution completed.
diff --git a/tests/codec_pref/Makefile.am b/tests/codec_pref/Makefile.am
new file mode 100644
index 000000000..e000252da
--- /dev/null
+++ b/tests/codec_pref/Makefile.am
@@ -0,0 +1,34 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(NULL)
+
+EXTRA_DIST = \
+ codec_pref_test.ok \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ codec_pref_test \
+ $(NULL)
+
+codec_pref_test_SOURCES = \
+ codec_pref_test.c \
+ $(NULL)
+
+codec_pref_test_LDADD = \
+ $(top_builddir)/src/osmo-bsc/codec_pref.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ -lrt \
+ $(NULL)
diff --git a/tests/codec_pref/codec_pref_test.c b/tests/codec_pref/codec_pref_test.c
new file mode 100644
index 000000000..534b99ef7
--- /dev/null
+++ b/tests/codec_pref/codec_pref_test.c
@@ -0,0 +1,725 @@
+/*
+ * (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/core/application.h>
+#include <osmocom/bsc/codec_pref.h>
+
+#include <stdio.h>
+
+void *ctx = NULL;
+
+#define MSC_AUDIO_SUPPORT_MAX 5
+#define N_CONFIG_VARIANTS 9
+
+/* Make sure that there is some memory to put our test configuration. */
+static void init_msc_config(struct bsc_msc_data *msc)
+{
+ unsigned int i;
+
+ msc->audio_support = talloc_zero_array(ctx, struct gsm_audio_support *, MSC_AUDIO_SUPPORT_MAX);
+ msc->audio_length = MSC_AUDIO_SUPPORT_MAX;
+ for (i = 0; i < MSC_AUDIO_SUPPORT_MAX; i++) {
+ msc->audio_support[i] = talloc_zero(msc->audio_support, struct gsm_audio_support);
+ }
+}
+
+/* Free memory that we have used for the test configuration. */
+static void free_msc_config(struct bsc_msc_data *msc)
+{
+ talloc_free(msc->audio_support);
+}
+
+/* The speech codec list is sent by the MS and lists the voice codec settings
+ * that the MS is able to support. The BSC must select one of this codecs
+ * depending on what the MSC is able to support. The following function
+ * generates some realistically made up speech codec lists. */
+static void make_scl_config(struct gsm0808_speech_codec_list *scl, uint8_t config_no)
+{
+ OSMO_ASSERT(config_no < N_CONFIG_VARIANTS);
+
+ switch (config_no) {
+ case 0:
+ /* FR1 only */
+ scl->codec[0].type = GSM0808_SCT_FR1;
+ scl->len = 1;
+ break;
+ case 1:
+ /* HR1 only */
+ scl->codec[0].type = GSM0808_SCT_HR1;
+ scl->len = 1;
+ break;
+ case 2:
+ /* FR2 only */
+ scl->codec[0].type = GSM0808_SCT_FR2;
+ scl->len = 1;
+ break;
+ case 3:
+ /* FR3 only */
+ scl->codec[0].type = GSM0808_SCT_FR3;
+ scl->codec[0].cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR;
+ scl->len = 1;
+ break;
+ case 4:
+ /* HR3 only */
+ scl->codec[0].type = GSM0808_SCT_HR3;
+ scl->codec[0].cfg = GSM0808_SC_CFG_DEFAULT_HR_AMR;
+ scl->len = 1;
+ break;
+ case 5:
+ /* FR1 and HR1 */
+ scl->codec[0].type = GSM0808_SCT_FR1;
+ scl->codec[1].type = GSM0808_SCT_HR1;
+ scl->len = 2;
+ break;
+ case 6:
+ /* FR1, FR2 and HR1 */
+ scl->codec[0].type = GSM0808_SCT_FR1;
+ scl->codec[1].type = GSM0808_SCT_FR2;
+ scl->codec[2].type = GSM0808_SCT_HR1;
+ scl->len = 3;
+ break;
+ case 7:
+ /* FR1, FR3 and HR3 */
+ scl->codec[0].type = GSM0808_SCT_FR1;
+ scl->codec[1].type = GSM0808_SCT_FR3;
+ scl->codec[1].cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR;
+ scl->codec[2].type = GSM0808_SCT_HR3;
+ scl->codec[2].cfg = GSM0808_SC_CFG_DEFAULT_HR_AMR;
+ scl->len = 3;
+ break;
+ case 8:
+ /* FR1, FR2, FR3, HR1 and HR3 */
+ scl->codec[0].type = GSM0808_SCT_FR1;
+ scl->codec[1].type = GSM0808_SCT_FR2;
+ scl->codec[2].type = GSM0808_SCT_FR3;
+ scl->codec[2].cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR;
+ scl->codec[3].type = GSM0808_SCT_HR1;
+ scl->codec[4].type = GSM0808_SCT_HR3;
+ scl->codec[4].cfg = GSM0808_SC_CFG_DEFAULT_HR_AMR;
+ scl->len = 5;
+ break;
+ }
+}
+
+/* The channel type element which is sent to the BSC by the MSC lists all the
+ * codecs that the MSC is able to support. The following function generates
+ * a realistic permitted speech settings */
+static void make_ct_config(struct gsm0808_channel_type *ct, uint8_t config_no)
+{
+ OSMO_ASSERT(config_no < N_CONFIG_VARIANTS);
+
+ switch (config_no) {
+ case 0:
+ /* FR1 only */
+ ct->perm_spch[0] = GSM0808_PERM_FR1;
+ ct->perm_spch_len = 1;
+ break;
+ case 1:
+ /* HR1 only */
+ ct->perm_spch[0] = GSM0808_PERM_HR1;
+ ct->perm_spch_len = 1;
+ break;
+ case 2:
+ /* FR2 only */
+ ct->perm_spch[0] = GSM0808_PERM_FR2;
+ ct->perm_spch_len = 1;
+ break;
+ case 3:
+ /* FR3 only */
+ ct->perm_spch[0] = GSM0808_PERM_FR3;
+ ct->perm_spch_len = 1;
+ break;
+ case 4:
+ /* HR3 only */
+ ct->perm_spch[0] = GSM0808_PERM_HR3;
+ ct->perm_spch_len = 1;
+ break;
+ case 5:
+ /* FR1 and HR1 */
+ ct->perm_spch[0] = GSM0808_PERM_FR1;
+ ct->perm_spch[1] = GSM0808_PERM_HR1;
+ ct->perm_spch_len = 2;
+ break;
+ case 6:
+ /* FR1, FR2 and HR1 */
+ ct->perm_spch[0] = GSM0808_PERM_FR1;
+ ct->perm_spch[1] = GSM0808_PERM_FR2;
+ ct->perm_spch[2] = GSM0808_PERM_HR1;
+ ct->perm_spch_len = 3;
+ break;
+ case 7:
+ /* FR1, FR3 and HR3 */
+ ct->perm_spch[0] = GSM0808_PERM_FR1;
+ ct->perm_spch[1] = GSM0808_PERM_FR3;
+ ct->perm_spch[2] = GSM0808_PERM_HR3;
+ ct->perm_spch_len = 3;
+ break;
+ case 8:
+ /* FR1, FR2, FR3, HR1 and HR3 */
+ ct->perm_spch[0] = GSM0808_PERM_FR1;
+ ct->perm_spch[1] = GSM0808_PERM_FR2;
+ ct->perm_spch[2] = GSM0808_PERM_FR3;
+ ct->perm_spch[3] = GSM0808_PERM_HR1;
+ ct->perm_spch[4] = GSM0808_PERM_HR3;
+ ct->perm_spch_len = 5;
+ break;
+ }
+}
+
+/* Generate some realistic MSC configuration which one also could find in the
+ * real world. This configuration acts as a filter. While the MSC could in
+ * theory advertise codecs more codecs as we are able to support we have to
+ * make sure that only the codecs we have support for are considered. */
+static void make_msc_config(struct bsc_msc_data *msc, uint8_t config_no)
+{
+ /* 1 = FR1/HR1
+ * 2 = FR2/HR2
+ * 3 = FR2/HR3
+ * Note: HR2 is deprecated */
+
+ OSMO_ASSERT(config_no < N_CONFIG_VARIANTS);
+
+ /* Setup an AMR configuration, this configuration is separate and does
+ * not influence other codecs than AMR */
+ msc->amr_conf.m4_75 = 1;
+ msc->amr_conf.m5_15 = 1;
+ msc->amr_conf.m5_90 = 1;
+ msc->amr_conf.m6_70 = 1;
+ msc->amr_conf.m7_40 = 1;
+ msc->amr_conf.m7_95 = 1;
+ msc->amr_conf.m10_2 = 1;
+ msc->amr_conf.m12_2 = 1;
+
+ switch (config_no) {
+ case 0:
+ /* FR1 only */
+ msc->audio_support[0]->ver = 1;
+ msc->audio_support[0]->hr = 0;
+ msc->audio_length = 1;
+ break;
+ case 1:
+ /* HR1 only */
+ msc->audio_support[0]->ver = 1;
+ msc->audio_support[0]->hr = 1;
+ msc->audio_length = 1;
+ break;
+ case 2:
+ /* FR2 only */
+ msc->audio_support[0]->ver = 2;
+ msc->audio_support[0]->hr = 0;
+ msc->audio_length = 1;
+ break;
+ case 3:
+ /* FR3 only */
+ msc->audio_support[0]->ver = 3;
+ msc->audio_support[0]->hr = 0;
+ msc->audio_length = 1;
+ break;
+ case 4:
+ /* HR3 only */
+ msc->audio_support[0]->ver = 3;
+ msc->audio_support[0]->hr = 1;
+ msc->audio_length = 1;
+ break;
+ case 5:
+ /* FR1 and HR1 */
+ msc->audio_support[0]->ver = 1;
+ msc->audio_support[0]->hr = 0;
+ msc->audio_support[1]->ver = 1;
+ msc->audio_support[1]->hr = 1;
+ msc->audio_length = 2;
+ break;
+ case 6:
+ /* FR1, FR2 and HR1 */
+ msc->audio_support[0]->ver = 1;
+ msc->audio_support[0]->hr = 0;
+ msc->audio_support[1]->ver = 2;
+ msc->audio_support[1]->hr = 0;
+ msc->audio_support[2]->ver = 1;
+ msc->audio_support[2]->hr = 1;
+ msc->audio_length = 3;
+ break;
+ case 7:
+ /* FR1, FR3 and HR3 */
+ msc->audio_support[0]->ver = 1;
+ msc->audio_support[0]->hr = 0;
+ msc->audio_support[1]->ver = 3;
+ msc->audio_support[1]->hr = 0;
+ msc->audio_support[2]->ver = 3;
+ msc->audio_support[2]->hr = 1;
+ msc->audio_length = 3;
+ break;
+ case 8:
+ /* FR1, FR2, FR3, HR1 and HR3 */
+ msc->audio_support[0]->ver = 1;
+ msc->audio_support[0]->hr = 0;
+ msc->audio_support[1]->ver = 2;
+ msc->audio_support[1]->hr = 0;
+ msc->audio_support[2]->ver = 3;
+ msc->audio_support[2]->hr = 0;
+ msc->audio_support[3]->ver = 1;
+ msc->audio_support[3]->hr = 1;
+ msc->audio_support[4]->ver = 3;
+ msc->audio_support[4]->hr = 1;
+ msc->audio_length = 5;
+ break;
+ }
+}
+
+/* Generate a realitically looking bts codec configuration */
+static void make_bts_config(struct gsm_bts *bts, uint8_t config_no)
+{
+ /* Note: FR is supported by all BTSs, so there is no flag for it */
+
+ struct gsm48_multi_rate_conf *cfg;
+ static struct gsm_bts_trx trx;
+
+ OSMO_ASSERT(config_no < N_CONFIG_VARIANTS);
+
+ bts->codec.hr = 0;
+ bts->codec.efr = 0;
+ bts->codec.amr = 0;
+ memset(&bts->mr_full.gsm48_ie, 0, sizeof(bts->mr_full.gsm48_ie));
+ memset(&bts->mr_full.gsm48_ie, 0, sizeof(bts->mr_half.gsm48_ie));
+
+ /* Setup an AMR configuration, this configuration is separate and does
+ * not influence other codecs than AMR */
+ cfg = (struct gsm48_multi_rate_conf*) &bts->mr_full.gsm48_ie;
+ cfg->m4_75 = 1;
+ cfg->m5_15 = 1;
+ cfg->m5_90 = 1;
+ cfg->m6_70 = 1;
+ cfg->m7_40 = 1;
+ cfg->m7_95 = 1;
+ cfg->m10_2 = 1;
+ cfg->m12_2 = 1;
+ cfg = (struct gsm48_multi_rate_conf*) &bts->mr_half.gsm48_ie;
+ cfg->m4_75 = 1;
+ cfg->m5_15 = 1;
+ cfg->m5_90 = 1;
+ cfg->m6_70 = 1;
+ cfg->m7_40 = 1;
+ cfg->m7_95 = 1;
+ cfg->m10_2 = 0;
+ cfg->m12_2 = 0;
+
+ /* Initalize TRX with a TCH/F and a TCH/H channel */
+ memset(&trx, 0, sizeof(trx));
+ INIT_LLIST_HEAD(&bts->trx_list);
+ llist_add(&trx.list, &bts->trx_list);
+ trx.ts[0].pchan_from_config = GSM_PCHAN_TCH_F;
+ trx.ts[1].pchan_from_config = GSM_PCHAN_TCH_H;
+
+ switch (config_no) {
+ case 0:
+ /* FR1 (implicit) only */
+ break;
+ case 1:
+ /* HR1 only (+FR implicit) */
+ bts->codec.hr = 1;
+ break;
+ case 2:
+ /* FR2 only (+FR implicit) */
+ bts->codec.efr = 1;
+ break;
+ case 3:
+ /* FR3 only (+FR implicit) */
+ bts->codec.amr = 1;
+ break;
+ case 4:
+ /* HR3 only (+FR implicit) */
+ bts->codec.amr = 1;
+ break;
+ case 5:
+ /* FR1 (implicit) and HR1 */
+ bts->codec.hr = 1;
+ break;
+ case 6:
+ /* FR1 (implicit), FR2 and HR1 */
+ bts->codec.efr = 1;
+ bts->codec.hr = 1;
+ break;
+ case 7:
+ /* FR1 (implicit), FR3 and HR3 */
+ bts->codec.amr = 1;
+ break;
+ case 8:
+ /* FR1 (implicit), FR2, FR3, HR1 and HR3 */
+ bts->codec.hr = 1;
+ bts->codec.efr = 1;
+ bts->codec.amr = 1;
+ break;
+ }
+}
+
+/* Try execute match_codec_pref(), display input and output parameters */
+static int test_match_codec_pref(const struct gsm0808_channel_type *ct, const struct gsm0808_speech_codec_list *scl,
+ const struct bsc_msc_data *msc, struct gsm_bts *bts)
+{
+ int rc;
+ unsigned int i;
+ bool full_rate;
+ enum gsm48_chan_mode chan_mode;
+ uint16_t s15_s0;
+
+ printf("Determining channel mode and rate:\n");
+
+ printf(" * MS: speech codec list (%u items):\n", scl->len);
+ for (i = 0; i < scl->len; i++)
+ printf(" codec[%u]->type=%s\n", i, gsm0808_speech_codec_type_name(scl->codec[i].type));
+
+ printf(" * MSC: channel type permitted speech (%u items):\n", ct->perm_spch_len);
+ for (i = 0; i < ct->perm_spch_len; i++)
+ printf(" perm_spch[%u]=%s\n", i, gsm0808_permitted_speech_name(ct->perm_spch[i]));
+
+ printf(" * BSS: audio support settings (%u items):\n", msc->audio_length);
+ for (i = 0; i < msc->audio_length; i++)
+ if (msc->audio_support[i]->hr)
+ printf(" audio_support[%u]=HR%u\n", i, msc->audio_support[i]->ver);
+ else
+ printf(" audio_support[%u]=FR%u\n", i, msc->audio_support[i]->ver);
+
+ printf(" * BTS: audio support settings:\n");
+ printf(" (GSM-FR implicitly supported)\n");
+ printf(" codec->hr=%u\n", bts->codec.hr);
+ printf(" codec->efr=%u\n", bts->codec.efr);
+ printf(" codec->amr=%u\n", bts->codec.amr);
+
+ rc = match_codec_pref(&chan_mode, &full_rate, &s15_s0, ct, scl, msc, bts);
+ printf(" * result: rc=%i, full_rate=%i, s15_s0=%04x, chan_mode=%s\n",
+ rc, full_rate, s15_s0, gsm48_chan_mode_name(chan_mode));
+
+ printf("\n");
+
+ return rc;
+}
+
+/* MS, MSC and local MSC settings are the same */
+static void test_one_to_one(void)
+{
+ unsigned int i;
+ struct gsm0808_channel_type ct_msc;
+ struct gsm0808_speech_codec_list scl_ms;
+ struct bsc_msc_data msc_local;
+ struct gsm_bts bts_local;
+ int rc;
+
+ printf("============== test_one_to_one ==============\n\n");
+
+ init_msc_config(&msc_local);
+
+ for (i = 0; i < N_CONFIG_VARIANTS; i++) {
+ make_msc_config(&msc_local, i);
+ make_scl_config(&scl_ms, i);
+ make_ct_config(&ct_msc, i);
+ make_bts_config(&bts_local, i);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+ }
+
+ free_msc_config(&msc_local);
+}
+
+/* Network supports all combinations, MS varies */
+static void test_ms(void)
+{
+ unsigned int i;
+ struct gsm0808_channel_type ct_msc;
+ struct gsm0808_speech_codec_list scl_ms;
+ struct bsc_msc_data msc_local;
+ struct gsm_bts bts_local;
+ int rc;
+
+ printf("============== test_ms ==============\n\n");
+
+ init_msc_config(&msc_local);
+
+ make_msc_config(&msc_local, 8);
+ make_ct_config(&ct_msc, 8);
+ make_bts_config(&bts_local, 8);
+ for (i = 0; i < N_CONFIG_VARIANTS; i++) {
+ make_scl_config(&scl_ms, i);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+ }
+
+ free_msc_config(&msc_local);
+}
+
+/* BSS and MS support all combinations, MSC varies */
+static void test_ct(void)
+{
+ unsigned int i;
+ struct gsm0808_channel_type ct_msc;
+ struct gsm0808_speech_codec_list scl_ms;
+ struct bsc_msc_data msc_local;
+ struct gsm_bts bts_local;
+ int rc;
+
+ printf("============== test_ct ==============\n\n");
+
+ init_msc_config(&msc_local);
+
+ make_msc_config(&msc_local, 8);
+ make_scl_config(&scl_ms, 8);
+ make_bts_config(&bts_local, 8);
+ for (i = 0; i < N_CONFIG_VARIANTS; i++) {
+ make_ct_config(&ct_msc, i);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+ }
+
+ free_msc_config(&msc_local);
+}
+
+/* MSC and MS support all combinations, BSS varies */
+static void test_msc(void)
+{
+ unsigned int i;
+ struct gsm0808_channel_type ct_msc;
+ struct gsm0808_speech_codec_list scl_ms;
+ struct bsc_msc_data msc_local;
+ struct gsm_bts bts_local;
+ int rc;
+
+ printf("============== test_msc ==============\n\n");
+
+ init_msc_config(&msc_local);
+
+ make_ct_config(&ct_msc, 8);
+ make_scl_config(&scl_ms, 8);
+ make_bts_config(&bts_local, 8);
+ for (i = 0; i < N_CONFIG_VARIANTS; i++) {
+ make_msc_config(&msc_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+ }
+
+ free_msc_config(&msc_local);
+}
+
+/* Some mixed configurations that are supposed to work */
+static void test_selected_working(void)
+{
+ struct gsm0808_channel_type ct_msc;
+ struct gsm0808_speech_codec_list scl_ms;
+ struct bsc_msc_data msc_local;
+ struct gsm_bts bts_local;
+ int rc;
+
+ printf("============== test_selected_working ==============\n\n");
+
+ init_msc_config(&msc_local);
+
+ make_scl_config(&scl_ms, 6);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 7);
+ make_bts_config(&bts_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+
+ make_scl_config(&scl_ms, 0);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 7);
+ make_bts_config(&bts_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+
+ make_scl_config(&scl_ms, 1);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 6);
+ make_bts_config(&bts_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+
+ make_scl_config(&scl_ms, 6);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 7);
+ make_bts_config(&bts_local, 4);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+
+ make_scl_config(&scl_ms, 0);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 7);
+ make_bts_config(&bts_local, 2);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+
+ make_scl_config(&scl_ms, 1);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 6);
+ make_bts_config(&bts_local, 1);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == 0);
+
+ free_msc_config(&msc_local);
+}
+
+/* Some mixed configurations that can not work */
+static void test_selected_non_working(void)
+{
+ struct gsm0808_channel_type ct_msc;
+ struct gsm0808_speech_codec_list scl_ms;
+ struct bsc_msc_data msc_local;
+ struct gsm_bts bts_local;
+ int rc;
+
+ printf("============== test_selected_non_working ==============\n\n");
+
+ init_msc_config(&msc_local);
+
+ make_scl_config(&scl_ms, 1);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 7);
+ make_bts_config(&bts_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == -1);
+
+ make_scl_config(&scl_ms, 1);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 7);
+ make_bts_config(&bts_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == -1);
+
+ make_scl_config(&scl_ms, 1);
+ make_ct_config(&ct_msc, 4);
+ make_msc_config(&msc_local, 6);
+ make_bts_config(&bts_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == -1);
+
+ make_scl_config(&scl_ms, 1);
+ make_ct_config(&ct_msc, 2);
+ make_msc_config(&msc_local, 7);
+ make_bts_config(&bts_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == -1);
+
+ make_scl_config(&scl_ms, 1);
+ make_ct_config(&ct_msc, 5);
+ make_msc_config(&msc_local, 4);
+ make_bts_config(&bts_local, 8);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == -1);
+
+ make_scl_config(&scl_ms, 8);
+ make_ct_config(&ct_msc, 4);
+ make_msc_config(&msc_local, 6);
+ make_bts_config(&bts_local, 7);
+ rc = test_match_codec_pref(&ct_msc, &scl_ms, &msc_local, &bts_local);
+ OSMO_ASSERT(rc == -1);
+
+ free_msc_config(&msc_local);
+}
+
+/* Try execute bss_supp_codec_list(), display input and output parameters */
+static void test_gen_bss_supported_codec_list(const struct bsc_msc_data *msc, struct gsm_bts *bts)
+{
+ unsigned int i;
+ struct gsm0808_speech_codec_list scl;
+
+ printf("Determining Codec List (BSS Supported):\n");
+
+ printf(" * BSS: audio support settings (%u items):\n", msc->audio_length);
+ for (i = 0; i < msc->audio_length; i++)
+ if (msc->audio_support[i]->hr)
+ printf(" audio_support[%u]=HR%u\n", i, msc->audio_support[i]->ver);
+ else
+ printf(" audio_support[%u]=FR%u\n", i, msc->audio_support[i]->ver);
+
+ printf(" * BTS: audio support settings:\n");
+ printf(" (GSM-FR implicitly supported)\n");
+ printf(" codec->hr=%u\n", bts->codec.hr);
+ printf(" codec->efr=%u\n", bts->codec.efr);
+ printf(" codec->amr=%u\n", bts->codec.amr);
+
+ gen_bss_supported_codec_list(&scl, msc, bts);
+
+ printf(" * result: speech codec list (%u items):\n", scl.len);
+ for (i = 0; i < scl.len; i++) {
+ printf(" codec[%u]->type=%s", i, gsm0808_speech_codec_type_name(scl.codec[i].type));
+ if (msc->audio_support[i]->ver == 3)
+ printf(" S15-S0=%04x", scl.codec[i].cfg);
+ printf("\n");
+ }
+ printf("\n");
+}
+
+/* Test gen_bss_supported_codec_list() with some mixed configurations */
+static void test_gen_bss_supported_codec_list_cfgs(void)
+{
+ struct bsc_msc_data msc_local;
+ struct gsm_bts bts_local;
+ uint8_t i;
+ uint8_t k;
+
+ printf("============== test_gen_bss_supp_codec_list_cfgs ==============\n\n");
+ init_msc_config(&msc_local);
+
+ for (i = 0; i < N_CONFIG_VARIANTS; i++) {
+ for (k = 0; k < N_CONFIG_VARIANTS; k++) {
+ make_msc_config(&msc_local, i);
+ make_bts_config(&bts_local, k);
+ printf("MSC config: %u, BTS config: %u\n", i, k);
+ test_gen_bss_supported_codec_list(&msc_local, &bts_local);
+ }
+ }
+
+ free_msc_config(&msc_local);
+}
+
+static const struct log_info_cat log_categories[] = {
+ [DMSC] = {
+ .name = "DMSC",
+ .description = "Mobile Switching Center",
+ .enabled = 1,.loglevel = LOGL_NOTICE,
+ },
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ ctx = talloc_named_const(NULL, 0, "codec_pref_test");
+ msgb_talloc_ctx_init(ctx, 0);
+ osmo_init_logging2(ctx, &log_info);
+
+ test_one_to_one();
+ test_ms();
+ test_ct();
+ test_msc();
+ test_selected_working();
+ test_selected_non_working();
+ test_gen_bss_supported_codec_list_cfgs();
+
+ printf("Testing execution completed.\n");
+ talloc_free(ctx);
+ return 0;
+}
diff --git a/tests/codec_pref/codec_pref_test.ok b/tests/codec_pref/codec_pref_test.ok
new file mode 100644
index 000000000..16d86ba67
--- /dev/null
+++ b/tests/codec_pref/codec_pref_test.ok
@@ -0,0 +1,2089 @@
+============== test_one_to_one ==============
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=FR1
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=FR1
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=HR1
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: rc=0, full_rate=0, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=FR2
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=FR2
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_EFR
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=FR3
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=FR3
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=57ff, chan_mode=SPEECH_AMR
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR3
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=HR3
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: rc=0, full_rate=0, s15_s0=073f, chan_mode=SPEECH_AMR
+
+Determining channel mode and rate:
+ * MS: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=HR1
+ * MSC: channel type permitted speech (3 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3
+ codec[2]->type=HR3
+ * MSC: channel type permitted speech (3 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR3
+ perm_spch[2]=HR3
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+============== test_ms ==============
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=FR1
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=0, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=FR2
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_EFR
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=FR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=57ff, chan_mode=SPEECH_AMR
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=0, s15_s0=073f, chan_mode=SPEECH_AMR
+
+Determining channel mode and rate:
+ * MS: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=HR1
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3
+ codec[2]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+============== test_ct ==============
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=FR1
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=HR1
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=0, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=FR2
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_EFR
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=FR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=57ff, chan_mode=SPEECH_AMR
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=0, s15_s0=073f, chan_mode=SPEECH_AMR
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (3 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=HR1
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (3 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR3
+ perm_spch[2]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+============== test_msc ==============
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (5 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=FR2
+ perm_spch[2]=FR3
+ perm_spch[3]=HR1
+ perm_spch[4]=HR3
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+============== test_selected_working ==============
+
+Determining channel mode and rate:
+ * MS: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=HR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=FR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=0, full_rate=0, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=HR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=FR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: rc=0, full_rate=1, s15_s0=0000, chan_mode=SPEECH_V1
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: rc=0, full_rate=0, s15_s0=0000, chan_mode=SPEECH_V1
+
+============== test_selected_non_working ==============
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=-1, full_rate=0, s15_s0=0000, chan_mode=SIGNALLING
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=-1, full_rate=0, s15_s0=0000, chan_mode=SIGNALLING
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=HR3
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=-1, full_rate=0, s15_s0=0000, chan_mode=SIGNALLING
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=FR2
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=-1, full_rate=0, s15_s0=0000, chan_mode=SIGNALLING
+
+Determining channel mode and rate:
+ * MS: speech codec list (1 items):
+ codec[0]->type=HR1
+ * MSC: channel type permitted speech (2 items):
+ perm_spch[0]=FR1
+ perm_spch[1]=HR1
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: rc=-1, full_rate=0, s15_s0=0000, chan_mode=SIGNALLING
+
+Determining channel mode and rate:
+ * MS: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3
+ codec[3]->type=HR1
+ codec[4]->type=HR3
+ * MSC: channel type permitted speech (1 items):
+ perm_spch[0]=HR3
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: rc=-1, full_rate=0, s15_s0=0000, chan_mode=SIGNALLING
+
+============== test_gen_bss_supp_codec_list_cfgs ==============
+
+MSC config: 0, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 0, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 0, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 0, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 0, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 0, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 0, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 0, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 0, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 1, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 1, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=HR1
+
+MSC config: 1, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 1, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (0 items):
+
+MSC config: 1, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (0 items):
+
+MSC config: 1, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=HR1
+
+MSC config: 1, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=HR1
+
+MSC config: 1, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (0 items):
+
+MSC config: 1, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=HR1
+
+MSC config: 2, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 2, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 2, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR2
+
+MSC config: 2, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (0 items):
+
+MSC config: 2, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (0 items):
+
+MSC config: 2, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 2, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR2
+
+MSC config: 2, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (0 items):
+
+MSC config: 2, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR2
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR2
+
+MSC config: 3, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 3, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 3, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 3, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR3 S15-S0=57ff
+
+MSC config: 3, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR3 S15-S0=57ff
+
+MSC config: 3, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 3, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 3, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR3 S15-S0=57ff
+
+MSC config: 3, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=FR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR3 S15-S0=57ff
+
+MSC config: 4, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 4, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 4, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 4, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=HR3 S15-S0=073f
+
+MSC config: 4, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=HR3 S15-S0=073f
+
+MSC config: 4, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 4, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (0 items):
+
+MSC config: 4, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=HR3 S15-S0=073f
+
+MSC config: 4, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (1 items):
+ audio_support[0]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=HR3 S15-S0=073f
+
+MSC config: 5, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 5, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+
+MSC config: 5, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 5, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 5, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 5, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+
+MSC config: 5, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+
+MSC config: 5, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 5, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (2 items):
+ audio_support[0]=FR1
+ audio_support[1]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+
+MSC config: 6, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 6, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+
+MSC config: 6, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+
+MSC config: 6, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 6, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 6, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+
+MSC config: 6, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=HR1
+
+MSC config: 6, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 6, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=HR1
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=HR1
+
+MSC config: 7, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 7, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 7, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 7, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3 S15-S0=57ff
+ codec[2]->type=HR3 S15-S0=073f
+
+MSC config: 7, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3 S15-S0=57ff
+ codec[2]->type=HR3 S15-S0=073f
+
+MSC config: 7, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 7, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 7, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3 S15-S0=57ff
+ codec[2]->type=HR3 S15-S0=073f
+
+MSC config: 7, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (3 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR3
+ audio_support[2]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3 S15-S0=57ff
+ codec[2]->type=HR3 S15-S0=073f
+
+MSC config: 8, BTS config: 0
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (1 items):
+ codec[0]->type=FR1
+
+MSC config: 8, BTS config: 1
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+
+MSC config: 8, BTS config: 2
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+
+MSC config: 8, BTS config: 3
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3
+ codec[2]->type=HR3 S15-S0=073f
+
+MSC config: 8, BTS config: 4
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3
+ codec[2]->type=HR3 S15-S0=073f
+
+MSC config: 8, BTS config: 5
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=0
+ codec->amr=0
+ * result: speech codec list (2 items):
+ codec[0]->type=FR1
+ codec[1]->type=HR1
+
+MSC config: 8, BTS config: 6
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=0
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=HR1 S15-S0=0000
+
+MSC config: 8, BTS config: 7
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=0
+ codec->efr=0
+ codec->amr=1
+ * result: speech codec list (3 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR3
+ codec[2]->type=HR3 S15-S0=073f
+
+MSC config: 8, BTS config: 8
+Determining Codec List (BSS Supported):
+ * BSS: audio support settings (5 items):
+ audio_support[0]=FR1
+ audio_support[1]=FR2
+ audio_support[2]=FR3
+ audio_support[3]=HR1
+ audio_support[4]=HR3
+ * BTS: audio support settings:
+ (GSM-FR implicitly supported)
+ codec->hr=1
+ codec->efr=1
+ codec->amr=1
+ * result: speech codec list (5 items):
+ codec[0]->type=FR1
+ codec[1]->type=FR2
+ codec[2]->type=FR3 S15-S0=57ff
+ codec[3]->type=HR1
+ codec[4]->type=HR3 S15-S0=073f
+
+Testing execution completed.
diff --git a/tests/ctrl_test_runner.py b/tests/ctrl_test_runner.py
new file mode 100755
index 000000000..cc37c9702
--- /dev/null
+++ b/tests/ctrl_test_runner.py
@@ -0,0 +1,531 @@
+#!/usr/bin/env python2
+
+# (C) 2013 by Jacob Erlbeck <jerlbeck@sysmocom.de>
+# (C) 2014 by Holger Hans Peter Freyther
+# based on vty_test_runner.py:
+# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
+# (C) 2013 by Holger Hans Peter Freyther
+# based on bsc_control.py.
+
+# 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 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import time
+import unittest
+import socket
+import sys
+import struct
+
+import osmopy.obscvty as obscvty
+import osmopy.osmoutil as osmoutil
+from osmopy.osmo_ipa import Ctrl, IPA
+
+# to be able to find $top_srcdir/doc/...
+confpath = os.path.join(sys.path[0], '..')
+verbose = False
+
+class TestCtrlBase(unittest.TestCase):
+
+ def ctrl_command(self):
+ raise Exception("Needs to be implemented by a subclass")
+
+ def ctrl_app(self):
+ raise Exception("Needs to be implemented by a subclass")
+
+ def setUp(self):
+ osmo_ctrl_cmd = self.ctrl_command()[:]
+ config_index = osmo_ctrl_cmd.index('-c')
+ if config_index:
+ cfi = config_index + 1
+ osmo_ctrl_cmd[cfi] = os.path.join(confpath, osmo_ctrl_cmd[cfi])
+
+ try:
+ self.proc = osmoutil.popen_devnull(osmo_ctrl_cmd)
+ except OSError:
+ print >> sys.stderr, "Current directory: %s" % os.getcwd()
+ print >> sys.stderr, "Consider setting -b"
+ time.sleep(2)
+
+ appstring = self.ctrl_app()[2]
+ appport = self.ctrl_app()[0]
+ self.connect("127.0.0.1", appport)
+ self.next_id = 1000
+
+ def tearDown(self):
+ self.disconnect()
+ osmoutil.end_proc(self.proc)
+
+ def disconnect(self):
+ if not (self.sock is None):
+ self.sock.close()
+
+ def connect(self, host, port):
+ if verbose:
+ print "Connecting to host %s:%i" % (host, port)
+
+ retries = 30
+ while True:
+ try:
+ sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sck.setblocking(1)
+ sck.connect((host, port))
+ except IOError:
+ retries -= 1
+ if retries <= 0:
+ raise
+ time.sleep(.1)
+ continue
+ break
+ self.sock = sck
+ return sck
+
+ def send(self, data):
+ if verbose:
+ print "Sending \"%s\"" %(data)
+ data = Ctrl().add_header(data)
+ return self.sock.send(data) == len(data)
+
+ def send_set(self, var, value, id):
+ setmsg = "SET %s %s %s" %(id, var, value)
+ return self.send(setmsg)
+
+ def send_get(self, var, id):
+ getmsg = "GET %s %s" %(id, var)
+ return self.send(getmsg)
+
+ def do_set(self, var, value):
+ id = self.next_id
+ self.next_id += 1
+ self.send_set(var, value, id)
+ return self.recv_msgs()[id]
+
+ def do_get(self, var):
+ id = self.next_id
+ self.next_id += 1
+ self.send_get(var, id)
+ return self.recv_msgs()[id]
+
+ def recv_msgs(self):
+ responses = {}
+ data = self.sock.recv(4096)
+ while (len(data)>0):
+ (head, data) = IPA().split_combined(data)
+ answer = Ctrl().rem_header(head)
+ if verbose:
+ print "Got message:", answer
+ (mtype, id, msg) = answer.split(None, 2)
+ id = int(id)
+ rsp = {'mtype': mtype, 'id': id}
+ if mtype == "ERROR":
+ rsp['error'] = msg
+ else:
+ split = msg.split(None, 1)
+ rsp['var'] = split[0]
+ if len(split) > 1:
+ rsp['value'] = split[1]
+ else:
+ rsp['value'] = None
+ responses[id] = rsp
+
+ if verbose:
+ print "Decoded replies: ", responses
+
+ return responses
+
+
+class TestCtrlBSC(TestCtrlBase):
+
+ def tearDown(self):
+ TestCtrlBase.tearDown(self)
+ os.unlink("tmp_dummy_sock")
+
+ def ctrl_command(self):
+ return ["./src/osmo-bsc/osmo-bsc", "-r", "tmp_dummy_sock", "-c",
+ "doc/examples/osmo-bsc/osmo-bsc.cfg"]
+
+ def ctrl_app(self):
+ return (4249, "./src/osmo-bsc/osmo-bsc", "OsmoBSC", "bsc")
+
+ def testCtrlErrs(self):
+ r = self.do_get('invalid')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Command not found')
+
+ r = self.do_set('rf_locked', '999')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Value failed verification.')
+
+ r = self.do_get('bts')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Error while parsing the index.')
+
+ r = self.do_get('bts.999')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Error while resolving object')
+
+ def testBtsLac(self):
+ r = self.do_get('bts.0.location-area-code')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.location-area-code')
+ self.assertEquals(r['value'], '1')
+
+ r = self.do_set('bts.0.location-area-code', '23')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.location-area-code')
+ self.assertEquals(r['value'], '23')
+
+ r = self.do_get('bts.0.location-area-code')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.location-area-code')
+ self.assertEquals(r['value'], '23')
+
+ r = self.do_set('bts.0.location-area-code', '-1')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Input not within the range')
+
+ def testBtsCi(self):
+ r = self.do_get('bts.0.cell-identity')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.cell-identity')
+ self.assertEquals(r['value'], '0')
+
+ r = self.do_set('bts.0.cell-identity', '23')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.cell-identity')
+ self.assertEquals(r['value'], '23')
+
+ r = self.do_get('bts.0.cell-identity')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.cell-identity')
+ self.assertEquals(r['value'], '23')
+
+ r = self.do_set('bts.0.cell-identity', '-1')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Input not within the range')
+
+ def testBtsGenerateSystemInformation(self):
+ r = self.do_get('bts.0.send-new-system-informations')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Write Only attribute')
+
+ # No RSL links so it will fail
+ r = self.do_set('bts.0.send-new-system-informations', '1')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Failed to generate SI')
+
+ def testBtsChannelLoad(self):
+ r = self.do_set('bts.0.channel-load', '1')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Read Only attribute')
+
+ # No RSL link so everything is 0
+ r = self.do_get('bts.0.channel-load')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['value'],
+ 'CCCH+SDCCH4,0,0 TCH/F,0,0 TCH/H,0,0 SDCCH8,0,0'
+ + ' TCH/F_PDCH,0,0 CCCH+SDCCH4+CBCH,0,0'
+ + ' SDCCH8+CBCH,0,0 TCH/F_TCH/H_PDCH,0,0')
+
+ def testBtsOmlConnectionState(self):
+ """Check OML state. It will not be connected"""
+ r = self.do_set('bts.0.oml-connection-state', '1')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Read Only attribute')
+
+ # No RSL link so everything is 0
+ r = self.do_get('bts.0.oml-connection-state')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['value'], 'disconnected')
+
+ def testTrxPowerRed(self):
+ r = self.do_get('bts.0.trx.0.max-power-reduction')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.trx.0.max-power-reduction')
+ self.assertEquals(r['value'], '20')
+
+ r = self.do_set('bts.0.trx.0.max-power-reduction', '22')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.trx.0.max-power-reduction')
+ self.assertEquals(r['value'], '22')
+
+ r = self.do_get('bts.0.trx.0.max-power-reduction')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.trx.0.max-power-reduction')
+ self.assertEquals(r['value'], '22')
+
+ r = self.do_set('bts.0.trx.0.max-power-reduction', '1')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Value must be even')
+
+ def testTrxArfcn(self):
+ r = self.do_get('bts.0.trx.0.arfcn')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.trx.0.arfcn')
+ self.assertEquals(r['value'], '871')
+
+ r = self.do_set('bts.0.trx.0.arfcn', '873')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.trx.0.arfcn')
+ self.assertEquals(r['value'], '873')
+
+ r = self.do_get('bts.0.trx.0.arfcn')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.trx.0.arfcn')
+ self.assertEquals(r['value'], '873')
+
+ r = self.do_set('bts.0.trx.0.arfcn', '2000')
+ self.assertEquals(r['mtype'], 'ERROR')
+ self.assertEquals(r['error'], 'Input not within the range')
+
+ def testRfLock(self):
+ r = self.do_get('bts.0.rf_state')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.rf_state')
+ self.assertEquals(r['value'], 'inoperational,unlocked,on')
+
+ r = self.do_set('rf_locked', '1')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'rf_locked')
+ self.assertEquals(r['value'], '1')
+
+ time.sleep(1.5)
+
+ r = self.do_get('bts.0.rf_state')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.rf_state')
+ self.assertEquals(r['value'], 'inoperational,locked,off')
+
+ r = self.do_get('rf_locked')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'rf_locked')
+ self.assertEquals(r['value'], 'state=off,policy=off')
+
+ r = self.do_set('rf_locked', '0')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'rf_locked')
+ self.assertEquals(r['value'], '0')
+
+ time.sleep(1.5)
+
+ r = self.do_get('bts.0.rf_state')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'bts.0.rf_state')
+ self.assertEquals(r['value'], 'inoperational,unlocked,on')
+
+ r = self.do_get('rf_locked')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'rf_locked')
+ self.assertEquals(r['value'], 'state=off,policy=on')
+
+ def testTimezone(self):
+ r = self.do_get('timezone')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'timezone')
+ self.assertEquals(r['value'], 'off')
+
+ r = self.do_set('timezone', '-2,15,2')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'timezone')
+ self.assertEquals(r['value'], '-2,15,2')
+
+ r = self.do_get('timezone')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'timezone')
+ self.assertEquals(r['value'], '-2,15,2')
+
+ # Test invalid input
+ r = self.do_set('timezone', '-2,15,2,5,6,7')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'timezone')
+ self.assertEquals(r['value'], '-2,15,2')
+
+ r = self.do_set('timezone', '-2,15')
+ self.assertEquals(r['mtype'], 'ERROR')
+ r = self.do_set('timezone', '-2')
+ self.assertEquals(r['mtype'], 'ERROR')
+ r = self.do_set('timezone', '1')
+
+ r = self.do_set('timezone', 'off')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'timezone')
+ self.assertEquals(r['value'], 'off')
+
+ r = self.do_get('timezone')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'timezone')
+ self.assertEquals(r['value'], 'off')
+
+ def testMcc(self):
+ r = self.do_set('mcc', '23')
+ r = self.do_get('mcc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mcc')
+ self.assertEquals(r['value'], '023')
+
+ r = self.do_set('mcc', '023')
+ r = self.do_get('mcc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mcc')
+ self.assertEquals(r['value'], '023')
+
+ def testMnc(self):
+ r = self.do_set('mnc', '9')
+ r = self.do_get('mnc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mnc')
+ self.assertEquals(r['value'], '09')
+
+ r = self.do_set('mnc', '09')
+ r = self.do_get('mnc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mnc')
+ self.assertEquals(r['value'], '09')
+
+ r = self.do_set('mnc', '009')
+ r = self.do_get('mnc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mnc')
+ self.assertEquals(r['value'], '009')
+
+
+ def testMccMncApply(self):
+ # Test some invalid input
+ r = self.do_set('mcc-mnc-apply', 'WRONG')
+ self.assertEquals(r['mtype'], 'ERROR')
+
+ r = self.do_set('mcc-mnc-apply', '1,')
+ self.assertEquals(r['mtype'], 'ERROR')
+
+ r = self.do_set('mcc-mnc-apply', '200,3')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'mcc-mnc-apply')
+ self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+ # Set it again
+ r = self.do_set('mcc-mnc-apply', '200,3')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'mcc-mnc-apply')
+ self.assertEquals(r['value'], 'Nothing changed')
+
+ # Change it
+ r = self.do_set('mcc-mnc-apply', '200,4')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'mcc-mnc-apply')
+ self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+ # Change it
+ r = self.do_set('mcc-mnc-apply', '201,4')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'mcc-mnc-apply')
+ self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+ # Verify
+ r = self.do_get('mnc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mnc')
+ self.assertEquals(r['value'], '04')
+
+ r = self.do_get('mcc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mcc')
+ self.assertEquals(r['value'], '201')
+
+ # Change it
+ r = self.do_set('mcc-mnc-apply', '202,03')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'mcc-mnc-apply')
+ self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+ r = self.do_get('mnc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mnc')
+ self.assertEquals(r['value'], '03')
+
+ r = self.do_get('mcc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mcc')
+ self.assertEquals(r['value'], '202')
+
+ # Test MNC with 3 digits
+ r = self.do_set('mcc-mnc-apply', '2,003')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'mcc-mnc-apply')
+ self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+ r = self.do_get('mnc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mnc')
+ self.assertEquals(r['value'], '003')
+
+ r = self.do_get('mcc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mcc')
+ self.assertEquals(r['value'], '002')
+
+ # Set same MNC with 3 digits
+ r = self.do_set('mcc-mnc-apply', '2,003')
+ self.assertEquals(r['mtype'], 'SET_REPLY')
+ self.assertEquals(r['var'], 'mcc-mnc-apply')
+ self.assertEquals(r['value'], 'Nothing changed')
+
+ r = self.do_get('mnc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mnc')
+ self.assertEquals(r['value'], '003')
+
+ r = self.do_get('mcc')
+ self.assertEquals(r['mtype'], 'GET_REPLY')
+ self.assertEquals(r['var'], 'mcc')
+ self.assertEquals(r['value'], '002')
+
+def add_bsc_test(suite, workdir):
+ if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc/osmo-bsc")):
+ print("Skipping the BSC test")
+ return
+ test = unittest.TestLoader().loadTestsFromTestCase(TestCtrlBSC)
+ suite.addTest(test)
+
+if __name__ == '__main__':
+ import argparse
+ import sys
+
+ workdir = '.'
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-v", "--verbose", dest="verbose",
+ action="store_true", help="verbose mode")
+ parser.add_argument("-p", "--pythonconfpath", dest="p",
+ help="searchpath for config")
+ parser.add_argument("-w", "--workdir", dest="w",
+ help="Working directory")
+ args = parser.parse_args()
+
+ verbose_level = 1
+ if args.verbose:
+ verbose_level = 2
+ verbose = True
+
+ if args.w:
+ workdir = args.w
+
+ if args.p:
+ confpath = args.p
+
+ print "confpath %s, workdir %s" % (confpath, workdir)
+ os.chdir(workdir)
+ print "Running tests for specific control commands"
+ suite = unittest.TestSuite()
+ add_bsc_test(suite, workdir)
+ res = unittest.TextTestRunner(verbosity=verbose_level).run(suite)
+ sys.exit(len(res.errors) + len(res.failures))
diff --git a/tests/gsm0408/Makefile.am b/tests/gsm0408/Makefile.am
new file mode 100644
index 000000000..b207f8b09
--- /dev/null
+++ b/tests/gsm0408/Makefile.am
@@ -0,0 +1,37 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gsm0408_test \
+ $(NULL)
+
+EXTRA_DIST = \
+ gsm0408_test.ok \
+ $(NULL)
+
+gsm0408_test_SOURCES = \
+ gsm0408_test.c \
+ $(NULL)
+
+gsm0408_test_LDADD = \
+ $(top_builddir)/src/osmo-bsc/gsm_04_08_rr.o \
+ $(top_builddir)/src/osmo-bsc/arfcn_range_encode.o \
+ $(top_builddir)/src/osmo-bsc/gsm_data.o \
+ $(top_builddir)/src/osmo-bsc/gsm_timers.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 \
+ $(top_builddir)/src/osmo-bsc/neighbor_ident.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(NULL)
diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c
new file mode 100644
index 000000000..a30aaf841
--- /dev/null
+++ b/tests/gsm0408/gsm0408_test.c
@@ -0,0 +1,998 @@
+/* simple test for the gsm0408 formatting functions */
+/*
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <arpa/inet.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/arfcn_range_encode.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/abis_rsl.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/bsc/gsm_04_08_rr.h>
+
+#define COMPARE(result, op, value) \
+ if (!((result) op (value))) {\
+ fprintf(stderr, "Compare failed. Was %x should be %x in %s:%d\n",result, value, __FILE__, __LINE__); \
+ exit(-1); \
+ }
+
+#define COMPARE_STR(result, value) \
+ if (strcmp(result, value) != 0) { \
+ fprintf(stderr, "Compare failed. Was %s should be %s in %s:%d\n",result, value, __FILE__, __LINE__); \
+ exit(-1); \
+ }
+
+#define DBG(...)
+
+#define VERIFY(res, cmp, wanted) \
+ if (!(res cmp wanted)) { \
+ printf("ASSERT failed: %s:%d Wanted: %d %s %d\n", \
+ __FILE__, __LINE__, (int) res, # cmp, (int) wanted); \
+ }
+
+
+
+static inline void gen(struct gsm_bts *bts, const char *s)
+{
+ int r;
+
+ bts->si_valid = 0;
+ bts->si_valid |= (1 << SYSINFO_TYPE_2quater);
+
+ printf("generating SI2quater for %zu EARFCNs and %zu UARFCNs...\n",
+ si2q_earfcn_count(&bts->si_common.si2quater_neigh_list), bts->si_common.uarfcn_length);
+
+ r = gsm_generate_si(bts, SYSINFO_TYPE_2quater);
+ if (r > 0)
+ for (bts->si2q_index = 0; bts->si2q_index < bts->si2q_count + 1; bts->si2q_index++)
+ printf("generated %s SI2quater [%02u/%02u]: [%d] %s\n",
+ GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) ? "valid" : "invalid",
+ bts->si2q_index, bts->si2q_count, r,
+ osmo_hexdump((void *)GSM_BTS_SI2Q(bts, bts->si2q_index), GSM_MACBLOCK_LEN));
+ else
+ printf("%s() failed to generate SI2quater: %s\n", s, strerror(-r));
+}
+
+static inline void del_earfcn_b(struct gsm_bts *bts, uint16_t earfcn)
+{
+ struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+ int r = osmo_earfcn_del(e, earfcn);
+ if (r)
+ printf("failed to remove EARFCN %u: %s\n", earfcn, strerror(-r));
+ else
+ printf("removed EARFCN %u - ", earfcn);
+
+ gen(bts, __func__);
+}
+
+static inline void add_earfcn_b(struct gsm_bts *bts, uint16_t earfcn, uint8_t bw)
+{
+ struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+ int r = osmo_earfcn_add(e, earfcn, bw);
+ if (r)
+ printf("failed to add EARFCN %u: %s\n", earfcn, strerror(-r));
+ else
+ printf("added EARFCN %u - ", earfcn);
+
+ gen(bts, __func__);
+}
+
+static inline void _bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool diversity)
+{
+ int r;
+
+ bts->u_offset = 0;
+
+ r = bts_uarfcn_add(bts, arfcn, scramble, diversity);
+ if (r < 0)
+ printf("failed to add UARFCN to SI2quater: %s\n", strerror(-r));
+ else {
+ bts->si2q_count = si2q_num(bts) - 1;
+ gen(bts, __func__);
+ }
+}
+
+#define bts_init(net) _bts_init(net, __func__)
+static inline struct gsm_bts *_bts_init(struct gsm_network *net, const char *msg)
+{
+ struct gsm_bts *bts = gsm_bts_alloc(net, 0);
+ if (!bts) {
+ printf("BTS allocation failure in %s()\n", msg);
+ exit(1);
+ }
+ printf("BTS allocation OK in %s()\n", msg);
+
+ bts->network = net;
+
+ return bts;
+}
+
+#define bts_del(bts) _bts_del(bts, __func__)
+static inline void _bts_del(struct gsm_bts *bts, const char *msg)
+{
+ osmo_stat_item_group_free(bts->bts_statg);
+ rate_ctr_group_free(bts->bts_ctrs);
+ /* no need to llist_del(&bts->list), we never registered the bts there. */
+ talloc_free(bts);
+ printf("BTS deallocated OK in %s()\n", msg);
+}
+
+static inline void test_si2q_segfault(struct gsm_network *net)
+{
+ struct gsm_bts *bts = bts_init(net);
+ printf("Test SI2quater UARFCN (same scrambling code and diversity):\n");
+
+ _bts_uarfcn_add(bts, 10564, 319, 0);
+ _bts_uarfcn_add(bts, 10612, 319, 0);
+ gen(bts, __func__);
+
+ bts_del(bts);
+}
+
+static inline void test_si2q_mu(struct gsm_network *net)
+{
+ struct gsm_bts *bts = bts_init(net);
+ printf("Test SI2quater multiple UARFCNs:\n");
+
+ _bts_uarfcn_add(bts, 10564, 318, 0);
+ _bts_uarfcn_add(bts, 10612, 319, 0);
+ _bts_uarfcn_add(bts, 10612, 31, 0);
+ _bts_uarfcn_add(bts, 10612, 19, 0);
+ _bts_uarfcn_add(bts, 10613, 64, 0);
+ _bts_uarfcn_add(bts, 10613, 164, 0);
+ _bts_uarfcn_add(bts, 10613, 14, 0);
+
+ bts_del(bts);
+}
+
+static inline void test_si2q_u(struct gsm_network *net)
+{
+ struct gsm_bts *bts = bts_init(net);
+ printf("Testing SYSINFO_TYPE_2quater UARFCN generation:\n");
+
+ /* first generate invalid SI as no UARFCN added */
+ gen(bts, __func__);
+
+ /* subsequent calls should produce valid SI if there's enough memory */
+ _bts_uarfcn_add(bts, 1982, 13, 1);
+ _bts_uarfcn_add(bts, 1982, 44, 0);
+ _bts_uarfcn_add(bts, 1982, 61, 1);
+ _bts_uarfcn_add(bts, 1982, 89, 1);
+ _bts_uarfcn_add(bts, 1982, 113, 0);
+ _bts_uarfcn_add(bts, 1982, 123, 0);
+ _bts_uarfcn_add(bts, 1982, 56, 1);
+ _bts_uarfcn_add(bts, 1982, 72, 1);
+ _bts_uarfcn_add(bts, 1982, 223, 1);
+ _bts_uarfcn_add(bts, 1982, 14, 0);
+ _bts_uarfcn_add(bts, 1982, 88, 0);
+
+ bts_del(bts);
+}
+
+static inline void test_si2q_e(struct gsm_network *net)
+{
+ struct gsm_bts *bts = bts_init(net);
+ printf("Testing SYSINFO_TYPE_2quater EARFCN generation:\n");
+
+ 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 = 5;
+
+ osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
+
+ /* first generate invalid SI as no EARFCN added */
+ gen(bts, __func__);
+
+ /* subsequent calls should produce valid SI if there's enough memory and EARFCNs */
+ add_earfcn_b(bts, 1917, 5);
+ del_earfcn_b(bts, 1917);
+ add_earfcn_b(bts, 1917, 1);
+ add_earfcn_b(bts, 1932, OSMO_EARFCN_MEAS_INVALID);
+ add_earfcn_b(bts, 1937, 2);
+ add_earfcn_b(bts, 1945, OSMO_EARFCN_MEAS_INVALID);
+ add_earfcn_b(bts, 1965, OSMO_EARFCN_MEAS_INVALID);
+ add_earfcn_b(bts, 1967, 4);
+ add_earfcn_b(bts, 1982, 3);
+
+ bts_del(bts);
+}
+
+static inline void test_si2q_long(struct gsm_network *net)
+{
+ struct gsm_bts *bts = bts_init(net);
+ printf("Testing SYSINFO_TYPE_2quater combined EARFCN & UARFCN generation:\n");
+
+ 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 = 5;
+
+ osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
+
+ bts_earfcn_add(bts, 1922, 11, 22, 8,32, 8);
+ bts_earfcn_add(bts, 1922, 11, 22, 8, 32, 8);
+ bts_earfcn_add(bts, 1924, 11, 12, 6, 11, 5);
+ bts_earfcn_add(bts, 1923, 11, 12, 6, 11, 5);
+ bts_earfcn_add(bts, 1925, 11, 12, 6, 11, 5);
+ bts_earfcn_add(bts, 2111, 11, 12, 6, 11, 5);
+ bts_earfcn_add(bts, 2112, 11, 12, 6, 11, 4);
+ bts_earfcn_add(bts, 2113, 11, 12, 6, 11, 3);
+ bts_earfcn_add(bts, 2114, 11, 12, 6, 11, 2);
+ bts_earfcn_add(bts, 2131, 11, 12, 6, 11, 5);
+ bts_earfcn_add(bts, 2132, 11, 12, 6, 11, 4);
+ bts_earfcn_add(bts, 2133, 11, 12, 6, 11, 3);
+ bts_earfcn_add(bts, 2134, 11, 12, 6, 11, 2);
+ bts_earfcn_add(bts, 2121, 11, 12, 6, 11, 5);
+ bts_earfcn_add(bts, 2122, 11, 12, 6, 11, 4);
+ bts_earfcn_add(bts, 2123, 11, 12, 6, 11, 3);
+ bts_earfcn_add(bts, 2124, 11, 12, 6, 11, 2);
+ _bts_uarfcn_add(bts, 1976, 13, 1);
+ _bts_uarfcn_add(bts, 1976, 38, 1);
+ _bts_uarfcn_add(bts, 1976, 44, 1);
+ _bts_uarfcn_add(bts, 1976, 120, 1);
+ _bts_uarfcn_add(bts, 1976, 140, 1);
+ _bts_uarfcn_add(bts, 1976, 163, 1);
+ _bts_uarfcn_add(bts, 1976, 166, 1);
+ _bts_uarfcn_add(bts, 1976, 217, 1);
+ _bts_uarfcn_add(bts, 1976, 224, 1);
+ _bts_uarfcn_add(bts, 1976, 225, 1);
+ _bts_uarfcn_add(bts, 1976, 226, 1);
+
+ bts_del(bts);
+}
+
+static void test_mi_functionality(void)
+{
+ const char *imsi_odd = "987654321098763";
+ const char *imsi_even = "9876543210987654";
+ const uint32_t tmsi = 0xfabeacd0;
+ uint8_t mi[128];
+ unsigned int mi_len;
+ char mi_parsed[GSM48_MI_SIZE];
+
+ printf("Testing parsing and generating TMSI/IMSI\n");
+
+ /* tmsi code */
+ mi_len = gsm48_generate_mid_from_tmsi(mi, tmsi);
+ gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len - 2);
+ COMPARE((uint32_t)strtoul(mi_parsed, NULL, 10), ==, tmsi);
+
+ /* imsi code */
+ mi_len = gsm48_generate_mid_from_imsi(mi, imsi_odd);
+ gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2);
+ printf("hex: %s\n", osmo_hexdump(mi, mi_len));
+ COMPARE_STR(mi_parsed, imsi_odd);
+
+ mi_len = gsm48_generate_mid_from_imsi(mi, imsi_even);
+ gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2);
+ printf("hex: %s\n", osmo_hexdump(mi, mi_len));
+ COMPARE_STR(mi_parsed, imsi_even);
+}
+
+struct {
+ int range;
+ int arfcns_num;
+ int arfcns[RANGE_ENC_MAX_ARFCNS];
+} arfcn_test_ranges[] = {
+ {ARFCN_RANGE_512, 12,
+ { 1, 12, 31, 51, 57, 91, 97, 98, 113, 117, 120, 125 }},
+ {ARFCN_RANGE_512, 17,
+ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }},
+ {ARFCN_RANGE_512, 18,
+ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }},
+ {ARFCN_RANGE_512, 18,
+ { 1, 17, 31, 45, 58, 79, 81, 97,
+ 113, 127, 213, 277, 287, 311, 331, 391,
+ 417, 511 }},
+ {ARFCN_RANGE_512, 6,
+ { 1, 17, 31, 45, 58, 79 }},
+ {ARFCN_RANGE_512, 6,
+ { 10, 17, 31, 45, 58, 79 }},
+ {ARFCN_RANGE_1024, 17,
+ { 0, 17, 31, 45, 58, 79, 81, 97,
+ 113, 127, 213, 277, 287, 311, 331, 391,
+ 1023 }},
+ {ARFCN_RANGE_1024, 16,
+ { 17, 31, 45, 58, 79, 81, 97, 113,
+ 127, 213, 277, 287, 311, 331, 391, 1023 }},
+ {-1}
+};
+
+static int test_single_range_encoding(int range, const int *orig_arfcns,
+ int arfcns_num, int silent)
+{
+ int arfcns[RANGE_ENC_MAX_ARFCNS];
+ int w[RANGE_ENC_MAX_ARFCNS];
+ int f0_included = 0;
+ int rc, f0;
+ uint8_t chan_list[16] = {0};
+ struct gsm_sysinfo_freq dec_freq[1024] = {{0}};
+ int dec_arfcns[RANGE_ENC_MAX_ARFCNS] = {0};
+ int dec_arfcns_count = 0;
+ int arfcns_used = 0;
+ int i;
+
+ arfcns_used = arfcns_num;
+ memmove(arfcns, orig_arfcns, sizeof(arfcns));
+
+ f0 = range == ARFCN_RANGE_1024 ? 0 : arfcns[0];
+ /*
+ * Manipulate the ARFCN list according to the rules in J4 depending
+ * on the selected range.
+ */
+ arfcns_used = range_enc_filter_arfcns(arfcns, arfcns_used,
+ f0, &f0_included);
+
+ memset(w, 0, sizeof(w));
+ range_enc_arfcns(range, arfcns, arfcns_used, w, 0);
+
+ if (!silent)
+ fprintf(stderr, "range=%d, arfcns_used=%d, f0=%d, f0_included=%d\n",
+ range, arfcns_used, f0, f0_included);
+
+ /* Select the range and the amount of bits needed */
+ switch (range) {
+ case ARFCN_RANGE_128:
+ range_enc_range128(chan_list, f0, w);
+ break;
+ case ARFCN_RANGE_256:
+ range_enc_range256(chan_list, f0, w);
+ break;
+ case ARFCN_RANGE_512:
+ range_enc_range512(chan_list, f0, w);
+ break;
+ case ARFCN_RANGE_1024:
+ range_enc_range1024(chan_list, f0, f0_included, w);
+ break;
+ default:
+ return 1;
+ };
+
+ if (!silent)
+ printf("chan_list = %s\n",
+ osmo_hexdump(chan_list, sizeof(chan_list)));
+
+ rc = gsm48_decode_freq_list(dec_freq, chan_list, sizeof(chan_list),
+ 0xfe, 1);
+ if (rc != 0) {
+ printf("Cannot decode freq list, rc = %d\n", rc);
+ return 1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dec_freq); i++) {
+ if (dec_freq[i].mask &&
+ dec_arfcns_count < ARRAY_SIZE(dec_arfcns))
+ dec_arfcns[dec_arfcns_count++] = i;
+ }
+
+ if (!silent) {
+ printf("Decoded freqs %d (expected %d)\n",
+ dec_arfcns_count, arfcns_num);
+ printf("Decoded: ");
+ for (i = 0; i < dec_arfcns_count; i++) {
+ printf("%d ", dec_arfcns[i]);
+ if (dec_arfcns[i] != orig_arfcns[i])
+ printf("(!= %d) ", orig_arfcns[i]);
+ }
+ printf("\n");
+ }
+
+ if (dec_arfcns_count != arfcns_num) {
+ printf("Wrong number of arfcns\n");
+ return 1;
+ }
+
+ if (memcmp(dec_arfcns, orig_arfcns, sizeof(dec_arfcns)) != 0) {
+ printf("Decoding error, got wrong freqs\n");
+ fprintf(stderr, " w = ");
+ for (i = 0; i < ARRAY_SIZE(w); i++)
+ fprintf(stderr, "%d ", w[i]);
+ fprintf(stderr, "\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void test_random_range_encoding(int range, int max_arfcn_num)
+{
+ int arfcns_num = 0;
+ int test_idx;
+ int rc, max_count;
+ int num_tests = 1024;
+
+ printf("Random range test: range %d, max num ARFCNs %d\n",
+ range, max_arfcn_num);
+
+ srandom(1);
+
+ for (max_count = 1; max_count < max_arfcn_num; max_count++) {
+ for (test_idx = 0; test_idx < num_tests; test_idx++) {
+ int count;
+ int i;
+ int min_freq = 0;
+
+ int rnd_arfcns[RANGE_ENC_MAX_ARFCNS] = {0};
+ char rnd_arfcns_set[1024] = {0};
+
+ if (range < ARFCN_RANGE_1024)
+ min_freq = random() % (1023 - range);
+
+ for (count = max_count; count; ) {
+ int arfcn = min_freq + random() % (range + 1);
+ OSMO_ASSERT(arfcn < ARRAY_SIZE(rnd_arfcns_set));
+
+ if (!rnd_arfcns_set[arfcn]) {
+ rnd_arfcns_set[arfcn] = 1;
+ count -= 1;
+ }
+ }
+
+ arfcns_num = 0;
+ for (i = 0; i < ARRAY_SIZE(rnd_arfcns_set); i++)
+ if (rnd_arfcns_set[i])
+ rnd_arfcns[arfcns_num++] = i;
+
+ rc = test_single_range_encoding(range, rnd_arfcns,
+ arfcns_num, 1);
+ if (rc != 0) {
+ printf("Failed on test %d, range %d, num ARFCNs %d\n",
+ test_idx, range, max_count);
+ test_single_range_encoding(range, rnd_arfcns,
+ arfcns_num, 0);
+ return;
+ }
+ }
+ }
+}
+
+static void test_range_encoding()
+{
+ int *arfcns;
+ int arfcns_num = 0;
+ int test_idx;
+ int range;
+
+ for (test_idx = 0; arfcn_test_ranges[test_idx].arfcns_num > 0; test_idx++)
+ {
+ arfcns_num = arfcn_test_ranges[test_idx].arfcns_num;
+ arfcns = &arfcn_test_ranges[test_idx].arfcns[0];
+ range = arfcn_test_ranges[test_idx].range;
+
+ printf("Range test %d: range %d, num ARFCNs %d\n",
+ test_idx, range, arfcns_num);
+
+ test_single_range_encoding(range, arfcns, arfcns_num, 0);
+ }
+
+ test_random_range_encoding(ARFCN_RANGE_128, 29);
+ test_random_range_encoding(ARFCN_RANGE_256, 22);
+ test_random_range_encoding(ARFCN_RANGE_512, 18);
+ test_random_range_encoding(ARFCN_RANGE_1024, 16);
+}
+
+static int freqs1[] = {
+ 12, 70, 121, 190, 250, 320, 401, 475, 520, 574, 634, 700, 764, 830, 905, 980
+};
+
+static int freqs2[] = {
+ 402, 460, 1, 67, 131, 197, 272, 347,
+};
+
+static int freqs3[] = {
+ 68, 128, 198, 279, 353, 398, 452,
+
+};
+
+static int w_out[] = {
+ 122, 2, 69, 204, 75, 66, 60, 70, 83, 3, 24, 67, 54, 64, 70, 9,
+};
+
+static int range128[] = {
+ 1, 1 + 127,
+};
+
+static int range256[] = {
+ 1, 1 + 128,
+};
+
+static int range512[] = {
+ 1, 1+ 511,
+};
+
+
+static void test_arfcn_filter()
+{
+ int arfcns[50], i, res, f0_included;
+ for (i = 0; i < ARRAY_SIZE(arfcns); ++i)
+ arfcns[i] = (i + 1) * 2;
+
+ /* check that the arfcn is taken out. f0_included is only set for Range1024 */
+ f0_included = 24;
+ res = range_enc_filter_arfcns(arfcns, ARRAY_SIZE(arfcns),
+ arfcns[0], &f0_included);
+ VERIFY(res, ==, ARRAY_SIZE(arfcns) - 1);
+ VERIFY(f0_included, ==, 1);
+ for (i = 0; i < res; ++i)
+ VERIFY(arfcns[i], ==, ((i+2) * 2) - (2+1));
+
+ /* check with range1024, ARFCN 0 is included */
+ for (i = 0; i < ARRAY_SIZE(arfcns); ++i)
+ arfcns[i] = i * 2;
+ res = range_enc_filter_arfcns(arfcns, ARRAY_SIZE(arfcns),
+ 0, &f0_included);
+ VERIFY(res, ==, ARRAY_SIZE(arfcns) - 1);
+ VERIFY(f0_included, ==, 1);
+ for (i = 0; i < res; ++i)
+ VERIFY(arfcns[i], ==, (i + 1) * 2 - 1);
+
+ /* check with range1024, ARFCN 0 not included */
+ for (i = 0; i < ARRAY_SIZE(arfcns); ++i)
+ arfcns[i] = (i + 1) * 2;
+ res = range_enc_filter_arfcns(arfcns, ARRAY_SIZE(arfcns),
+ 0, &f0_included);
+ VERIFY(res, ==, ARRAY_SIZE(arfcns));
+ VERIFY(f0_included, ==, 0);
+ for (i = 0; i < res; ++i)
+ VERIFY(arfcns[i], ==, ((i + 1) * 2) - 1);
+}
+
+static void test_print_encoding()
+{
+ int rc;
+ int w[17];
+ uint8_t chan_list[16];
+ memset(chan_list, 0x23, sizeof(chan_list));
+
+ for (rc = 0; rc < ARRAY_SIZE(w); ++rc)
+ switch (rc % 3) {
+ case 0:
+ w[rc] = 0xAAAA;
+ break;
+ case 1:
+ w[rc] = 0x5555;
+ break;
+ case 2:
+ w[rc] = 0x9696;
+ break;
+ }
+
+ range_enc_range512(chan_list, (1 << 9) | 0x96, w);
+
+ printf("Range512: %s\n", osmo_hexdump(chan_list, ARRAY_SIZE(chan_list)));
+}
+
+static void test_si_range_helpers()
+{
+ int ws[(sizeof(freqs1)/sizeof(freqs1[0]))];
+ int i, f0 = 0xFFFFFF;
+
+ memset(&ws[0], 0x23, sizeof(ws));
+
+ i = range_enc_find_index(1023, freqs1, ARRAY_SIZE(freqs1));
+ printf("Element is: %d => freqs[i] = %d\n", i, i >= 0 ? freqs1[i] : -1);
+ VERIFY(i, ==, 2);
+
+ i = range_enc_find_index(511, freqs2, ARRAY_SIZE(freqs2));
+ printf("Element is: %d => freqs[i] = %d\n", i, i >= 0 ? freqs2[i] : -1);
+ VERIFY(i, ==, 2);
+
+ i = range_enc_find_index(511, freqs3, ARRAY_SIZE(freqs3));
+ printf("Element is: %d => freqs[i] = %d\n", i, i >= 0 ? freqs3[i] : -1);
+ VERIFY(i, ==, 0);
+
+ range_enc_arfcns(1023, freqs1, ARRAY_SIZE(freqs1), ws, 0);
+
+ for (i = 0; i < sizeof(freqs1)/sizeof(freqs1[0]); ++i) {
+ printf("w[%d]=%d\n", i, ws[i]);
+ VERIFY(ws[i], ==, w_out[i]);
+ }
+
+ i = range_enc_determine_range(range128, ARRAY_SIZE(range128), &f0);
+ VERIFY(i, ==, ARFCN_RANGE_128);
+ VERIFY(f0, ==, 1);
+
+ i = range_enc_determine_range(range256, ARRAY_SIZE(range256), &f0);
+ VERIFY(i, ==, ARFCN_RANGE_256);
+ VERIFY(f0, ==, 1);
+
+ i = range_enc_determine_range(range512, ARRAY_SIZE(range512), &f0);
+ VERIFY(i, ==, ARFCN_RANGE_512);
+ VERIFY(f0, ==, 1);
+}
+
+static void test_si_ba_ind(struct gsm_network *net)
+{
+ struct gsm_bts *bts = bts_init(net);
+
+ const struct gsm48_system_information_type_2 *si2 =
+ (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ const struct gsm48_system_information_type_2bis *si2bis =
+ (struct gsm48_system_information_type_2bis *) GSM_BTS_SI(bts, SYSINFO_TYPE_2bis);
+ const struct gsm48_system_information_type_2ter *si2ter =
+ (struct gsm48_system_information_type_2ter *) GSM_BTS_SI(bts, SYSINFO_TYPE_2ter);
+ const struct gsm48_system_information_type_5 *si5 =
+ (struct gsm48_system_information_type_5 *) GSM_BTS_SI(bts, SYSINFO_TYPE_5);
+ const struct gsm48_system_information_type_5bis *si5bis =
+ (struct gsm48_system_information_type_5bis *) GSM_BTS_SI(bts, SYSINFO_TYPE_5bis);
+ const struct gsm48_system_information_type_5ter *si5ter =
+ (struct gsm48_system_information_type_5ter *) GSM_BTS_SI(bts, SYSINFO_TYPE_5ter);
+
+ int rc;
+
+ bts->c0->arfcn = 23;
+
+ printf("Testing if BA-IND is set as expected in SI2xxx and SI5xxx\n");
+
+ rc = gsm_generate_si(bts, SYSINFO_TYPE_2);
+ OSMO_ASSERT(rc > 0);
+ printf("SI2: %s\n", osmo_hexdump((uint8_t *)si2, rc));
+ /* Validate BA-IND == 0 */
+ OSMO_ASSERT(!(si2->bcch_frequency_list[0] & 0x10));
+
+ rc = gsm_generate_si(bts, SYSINFO_TYPE_2bis);
+ OSMO_ASSERT(rc > 0);
+ printf("SI2bis: %s\n", osmo_hexdump((uint8_t *)si2bis, rc));
+ /* Validate BA-IND == 0 */
+ OSMO_ASSERT(!(si2bis->bcch_frequency_list[0] & 0x10));
+
+ rc = gsm_generate_si(bts, SYSINFO_TYPE_2ter);
+ OSMO_ASSERT(rc > 0);
+ printf("SI2ter: %s\n", osmo_hexdump((uint8_t *)si2ter, rc));
+ /* Validate BA-IND == 0 */
+ OSMO_ASSERT(!(si2ter->ext_bcch_frequency_list[0] & 0x10));
+
+ rc = gsm_generate_si(bts, SYSINFO_TYPE_5);
+ OSMO_ASSERT(rc > 0);
+ printf("SI5: %s\n", osmo_hexdump((uint8_t *)si5, rc));
+ /* Validate BA-IND == 1 */
+ OSMO_ASSERT(si5->bcch_frequency_list[0] & 0x10);
+
+ rc = gsm_generate_si(bts, SYSINFO_TYPE_5bis);
+ OSMO_ASSERT(rc > 0);
+ printf("SI5bis: %s\n", osmo_hexdump((uint8_t *)si5bis, rc));
+ /* Validate BA-IND == 1 */
+ OSMO_ASSERT(si5bis->bcch_frequency_list[0] & 0x10);
+
+ rc = gsm_generate_si(bts, SYSINFO_TYPE_5ter);
+ OSMO_ASSERT(rc > 0);
+ printf("SI5ter: %s\n", osmo_hexdump((uint8_t *)si5ter, rc));
+ /* Validate BA-IND == 1 */
+ OSMO_ASSERT(si5ter->bcch_frequency_list[0] & 0x10);
+
+ bts_del(bts);
+}
+
+struct test_gsm48_ra_id_by_bts {
+ struct osmo_plmn_id plmn;
+ uint16_t lac;
+ uint8_t rac;
+ struct gsm48_ra_id expect;
+};
+static const struct test_gsm48_ra_id_by_bts test_gsm48_ra_id_by_bts_data[] = {
+ {
+ .plmn = { .mcc = 1, .mnc = 2, .mnc_3_digits = false },
+ .lac = 3,
+ .rac = 4,
+ .expect = {
+ .digits = { 0x00, 0xf1, 0x20 },
+ .lac = 0x0300, /* network byte order of 3 */
+ .rac = 4,
+ },
+ },
+ {
+ .plmn = { .mcc = 1, .mnc = 2, .mnc_3_digits = true },
+ .lac = 3,
+ .rac = 4,
+ .expect = {
+ .digits = { 0x00, 0x21, 0x00 },
+ .lac = 0x0300, /* network byte order of 3 */
+ .rac = 4,
+ },
+ },
+ {
+ .plmn = { .mcc = 0, .mnc = 0, .mnc_3_digits = false },
+ .lac = 0,
+ .rac = 0,
+ .expect = {
+ .digits = { 0x00, 0xf0, 0x00 },
+ },
+ },
+ {
+ .plmn = { .mcc = 0, .mnc = 0, .mnc_3_digits = true },
+ .lac = 0,
+ .rac = 0,
+ .expect = {
+ .digits = {},
+ },
+ },
+ {
+ .plmn = { .mcc = 999, .mnc = 999, .mnc_3_digits = false },
+ .lac = 65535,
+ .rac = 255,
+ .expect = {
+ .digits = { 0x99, 0x99, 0x99 },
+ .lac = 0xffff,
+ .rac = 0xff,
+ },
+ },
+ {
+ .plmn = { .mcc = 909, .mnc = 90, .mnc_3_digits = false },
+ .lac = 0xabcd,
+ .rac = 0xab,
+ .expect = {
+ .digits = { 0x09, 0xf9, 0x09 },
+ .lac = 0xcdab,
+ .rac = 0xab,
+ },
+ },
+ {
+ .plmn = { .mcc = 909, .mnc = 90, .mnc_3_digits = true },
+ .lac = 0xabcd,
+ .rac = 0xab,
+ .expect = {
+ .digits = { 0x09, 0x09, 0x90 },
+ .lac = 0xcdab,
+ .rac = 0xab,
+ },
+ },
+};
+
+static void test_gsm48_ra_id_by_bts()
+{
+ int i;
+ bool pass = true;
+
+ for (i = 0; i < ARRAY_SIZE(test_gsm48_ra_id_by_bts_data); i++) {
+ struct gsm_network net;
+ struct gsm_bts bts;
+ const struct test_gsm48_ra_id_by_bts *t = &test_gsm48_ra_id_by_bts_data[i];
+ struct gsm48_ra_id result = {};
+ bool ok;
+
+ net.plmn = t->plmn;
+ bts.network = &net;
+ bts.location_area_code = t->lac;
+ bts.gprs.rac = t->rac;
+
+ gsm48_ra_id_by_bts(&result, &bts);
+
+ ok = (t->expect.digits[0] == result.digits[0])
+ && (t->expect.digits[1] == result.digits[1])
+ && (t->expect.digits[2] == result.digits[2])
+ && (t->expect.lac == result.lac)
+ && (t->expect.rac == result.rac);
+ printf("%s[%d]: digits='%02x%02x%02x' lac=0x%04x=htons(%u) rac=0x%02x=%u %s\n",
+ __func__, i,
+ result.digits[0], result.digits[1], result.digits[2],
+ result.lac, osmo_ntohs(result.lac), result.rac, result.rac,
+ ok ? "pass" : "FAIL");
+ pass = pass && ok;
+ }
+
+ OSMO_ASSERT(pass);
+}
+
+static void test_gsm48_multirate_config()
+{
+ uint8_t lv[7];
+ struct gsm48_multi_rate_conf *gsm48_ie;
+ struct amr_multirate_conf mr;
+ int rc;
+
+ memset(&mr, 0, sizeof(mr));
+
+ /* Use some made up threshold and hysteresis values */
+ mr.ms_mode[0].threshold = 11;
+ mr.ms_mode[1].threshold = 12;
+ mr.ms_mode[2].threshold = 13;
+ mr.ms_mode[0].hysteresis = 15;
+ mr.ms_mode[1].hysteresis = 12;
+ mr.ms_mode[2].hysteresis = 8;
+
+ gsm48_ie = (struct gsm48_multi_rate_conf *)&mr.gsm48_ie;
+ gsm48_ie->ver = 1;
+ gsm48_ie->m5_90 = 1;
+ gsm48_ie->m7_40 = 1;
+ gsm48_ie->m7_95 = 1;
+ gsm48_ie->m12_2 = 1;
+
+ /* Test #1: Normal configuration with 4 active set members */
+ mr.ms_mode[0].mode = 2;
+ mr.ms_mode[1].mode = 4;
+ mr.ms_mode[2].mode = 5;
+ mr.ms_mode[3].mode = 7;
+ rc = gsm48_multirate_config(lv, gsm48_ie, mr.ms_mode, 4);
+ OSMO_ASSERT(rc == 0);
+ printf("gsm48_multirate_config(): rc=%i, lv=%s\n", rc,
+ osmo_hexdump_nospc(lv, lv[0]));
+
+ /* Test #2: 4 active set members, but wrong mode order: */
+ mr.ms_mode[3].mode = 2;
+ mr.ms_mode[2].mode = 4;
+ mr.ms_mode[1].mode = 5;
+ mr.ms_mode[0].mode = 7;
+ rc = gsm48_multirate_config(lv, gsm48_ie, mr.ms_mode, 4);
+ OSMO_ASSERT(rc == -EINVAL);
+
+ /* Test #3: Normal configuration with 3 active set members */
+ mr.ms_mode[0].mode = 2;
+ mr.ms_mode[1].mode = 4;
+ mr.ms_mode[2].mode = 5;
+ mr.ms_mode[3].mode = 0;
+ gsm48_ie->m12_2 = 0;
+ mr.ms_mode[2].threshold = 0;
+ mr.ms_mode[2].hysteresis = 0;
+
+ rc = gsm48_multirate_config(lv, gsm48_ie, mr.ms_mode, 3);
+ OSMO_ASSERT(rc == 0);
+ printf("gsm48_multirate_config(): rc=%i, lv=%s\n", rc,
+ osmo_hexdump_nospc(lv, lv[0]));
+
+ /* Test #4: 3 active set members, but wrong mode order: */
+ mr.ms_mode[0].mode = 2;
+ mr.ms_mode[2].mode = 4;
+ mr.ms_mode[1].mode = 5;
+ rc = gsm48_multirate_config(lv, gsm48_ie, mr.ms_mode, 3);
+ OSMO_ASSERT(rc == -EINVAL);
+
+ /* Test #5: Normal configuration with 2 active set members */
+ mr.ms_mode[0].mode = 2;
+ mr.ms_mode[1].mode = 4;
+ mr.ms_mode[2].mode = 0;
+ gsm48_ie->m7_95 = 0;
+ mr.ms_mode[1].threshold = 0;
+ mr.ms_mode[1].hysteresis = 0;
+
+ rc = gsm48_multirate_config(lv, gsm48_ie, mr.ms_mode, 2);
+ OSMO_ASSERT(rc == 0);
+ printf("gsm48_multirate_config(): rc=%i, lv=%s\n", rc,
+ osmo_hexdump_nospc(lv, lv[0]));
+
+ /* Test #6: 2 active set members, but wrong mode order: */
+ mr.ms_mode[1].mode = 2;
+ mr.ms_mode[0].mode = 4;
+ rc = gsm48_multirate_config(lv, gsm48_ie, mr.ms_mode, 2);
+ OSMO_ASSERT(rc == -EINVAL);
+
+ /* Test #7: Normal configuration with 1 active set member */
+ mr.ms_mode[0].mode = 2;
+ mr.ms_mode[1].mode = 0;
+ gsm48_ie->m7_40 = 0;
+ mr.ms_mode[0].threshold = 0;
+ mr.ms_mode[0].hysteresis = 0;
+
+ rc = gsm48_multirate_config(lv, gsm48_ie, mr.ms_mode, 1);
+ OSMO_ASSERT(rc == 0);
+ printf("gsm48_multirate_config(): rc=%i, lv=%s\n", rc,
+ osmo_hexdump_nospc(lv, lv[0]));
+
+ /* Test #8: 0 active set members: */
+ mr.ms_mode[0].mode = 0;
+ rc = gsm48_multirate_config(lv, gsm48_ie, mr.ms_mode, 1);
+ OSMO_ASSERT(rc == -EINVAL);
+}
+
+static const struct log_info_cat log_categories[] = {
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ struct gsm_network *net;
+
+ tall_bsc_ctx = talloc_named_const(NULL, 0, "gsm0408_test");
+
+ osmo_init_logging2(tall_bsc_ctx, &log_info);
+ log_set_log_level(osmo_stderr_target, LOGL_INFO);
+
+ net = gsm_network_init(tall_bsc_ctx);
+ if (!net) {
+ printf("Network init failure.\n");
+ return EXIT_FAILURE;
+ }
+
+ test_mi_functionality();
+
+ test_si_range_helpers();
+ test_arfcn_filter();
+ test_print_encoding();
+ test_range_encoding();
+
+ test_si2q_segfault(net);
+ test_si2q_e(net);
+ test_si2q_u(net);
+ test_si2q_mu(net);
+ test_si2q_long(net);
+
+ test_si_ba_ind(net);
+
+ test_gsm48_ra_id_by_bts();
+
+ test_gsm48_multirate_config();
+
+ printf("Done.\n");
+
+ return EXIT_SUCCESS;
+}
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) {
+ OSMO_ASSERT(0);
+}
+
+void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_release) {
+ OSMO_ASSERT(0);
+}
+
+bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts)
+{
+ return true;
+}
+
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) {}
+
+void bsc_cm_update(struct gsm_subscriber_connection *conn,
+ const uint8_t *cm2, uint8_t cm2_len,
+ const uint8_t *cm3, uint8_t cm3_len) {}
+
+int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci)
+{ return 0; }
+
+int rsl_chan_mode_modify_req(struct gsm_lchan *ts) { return 0; }
+
+int rsl_tx_ipacc_crcx(const struct gsm_lchan *lchan) { return 0; }
+
+void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int link_id, int allow_sacch) {}
+
+bool lchan_may_receive_data(struct gsm_lchan *lchan) { return true; }
+
+int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
+ uint16_t chosen_channel) { return 0; }
+
+void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id,
+ struct msgb *msg) {}
+
+void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, uint8_t chosen_encr) {}
+
+const char *bsc_subscr_name(struct bsc_subscr *bsub) { return NULL; }
+
+void lchan_release(struct gsm_lchan *lchan, bool do_rr_release,
+ bool err, enum gsm48_rr_cause cause_rr) {}
+
+int rsl_data_request(struct msgb *msg, uint8_t link_id) { return 0; }
+
+int rsl_encryption_cmd(struct msgb *msg) { return 0; }
diff --git a/tests/gsm0408/gsm0408_test.ok b/tests/gsm0408/gsm0408_test.ok
new file mode 100644
index 000000000..b083f086c
--- /dev/null
+++ b/tests/gsm0408/gsm0408_test.ok
@@ -0,0 +1,233 @@
+Testing parsing and generating TMSI/IMSI
+hex: 17 08 99 78 56 34 12 90 78 36
+hex: 17 09 91 78 56 34 12 90 78 56 f4
+Element is: 2 => freqs[i] = 121
+Element is: 2 => freqs[i] = 1
+Element is: 0 => freqs[i] = 68
+w[0]=122
+w[1]=2
+w[2]=69
+w[3]=204
+w[4]=75
+w[5]=66
+w[6]=60
+w[7]=70
+w[8]=83
+w[9]=3
+w[10]=24
+w[11]=67
+w[12]=54
+w[13]=64
+w[14]=70
+w[15]=9
+Range512: 89 4b 2a 95 65 95 55 2c a9 55 aa 55 6a 95 59 55
+Range test 0: range 511, num ARFCNs 12
+chan_list = 88 00 98 34 85 36 7c 50 22 dc 5e ec 00 00 00 00
+Decoded freqs 12 (expected 12)
+Decoded: 1 12 31 51 57 91 97 98 113 117 120 125
+Range test 1: range 511, num ARFCNs 17
+chan_list = 88 00 82 7f 01 3f 7e 04 0b ff ff fc 10 41 07 e0
+Decoded freqs 17 (expected 17)
+Decoded: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
+Range test 2: range 511, num ARFCNs 18
+chan_list = 88 00 82 7f 01 7f 7e 04 0b ff ff fc 10 41 07 ff
+Decoded freqs 18 (expected 18)
+Decoded: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
+Range test 3: range 511, num ARFCNs 18
+chan_list = 88 00 94 3a 44 32 d7 2a 43 2a 13 94 e5 38 39 f6
+Decoded freqs 18 (expected 18)
+Decoded: 1 17 31 45 58 79 81 97 113 127 213 277 287 311 331 391 417 511
+Range test 4: range 511, num ARFCNs 6
+chan_list = 88 00 8b 3c 88 b9 6b 00 00 00 00 00 00 00 00 00
+Decoded freqs 6 (expected 6)
+Decoded: 1 17 31 45 58 79
+Range test 5: range 511, num ARFCNs 6
+chan_list = 88 05 08 fc 88 b9 6b 00 00 00 00 00 00 00 00 00
+Decoded freqs 6 (expected 6)
+Decoded: 10 17 31 45 58 79
+Range test 6: range 1023, num ARFCNs 17
+chan_list = 84 71 e4 ab b9 58 05 cb 39 17 fd b0 75 62 0f 2f
+Decoded freqs 17 (expected 17)
+Decoded: 0 17 31 45 58 79 81 97 113 127 213 277 287 311 331 391 1023
+Range test 7: range 1023, num ARFCNs 16
+chan_list = 80 71 e4 ab b9 58 05 cb 39 17 fd b0 75 62 0f 2f
+Decoded freqs 16 (expected 16)
+Decoded: 17 31 45 58 79 81 97 113 127 213 277 287 311 331 391 1023
+Random range test: range 127, max num ARFCNs 29
+Random range test: range 255, max num ARFCNs 22
+Random range test: range 511, max num ARFCNs 18
+Random range test: range 1023, max num ARFCNs 16
+BTS allocation OK in test_si2q_segfault()
+Test SI2quater UARFCN (same scrambling code and diversity):
+generating SI2quater for 0 EARFCNs and 1 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7e 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7f 52 e8 0a 7e 0b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7f 52 e8 0a 7e 0b 2b 2b 2b 2b 2b 2b 2b 2b
+BTS deallocated OK in test_si2q_segfault()
+BTS allocation OK in test_si2q_e()
+Testing SYSINFO_TYPE_2quater EARFCN generation:
+generating SI2quater for 0 EARFCNs and 0 UARFCNs...
+generated invalid SI2quater [00/00]: [23] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+added EARFCN 1917 - generating SI2quater for 1 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be e8 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+removed EARFCN 1917 - generating SI2quater for 0 EARFCNs and 0 UARFCNs...
+generated invalid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be e8 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+added EARFCN 1917 - generating SI2quater for 1 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be c8 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+added EARFCN 1932 - generating SI2quater for 2 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be cc 1e 30 14 03 2b 2b 2b 2b 2b 2b 2b 2b
+added EARFCN 1937 - generating SI2quater for 3 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be cc 1e 31 07 91 a0 a0 2b 2b 2b 2b 2b 2b
+added EARFCN 1945 - generating SI2quater for 4 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be cc 1e 31 07 91 a8 3c c8 28 0b 2b 2b 2b
+added EARFCN 1965 - generating SI2quater for 5 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 04 86 59 83 be cc 1e 31 07 91 a8 3c ca 0f 5a 0a 03 2b
+added EARFCN 1967 - generating SI2quater for 6 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/01]: [23] 59 06 07 40 20 04 86 59 83 be cc 1e 31 07 91 a8 3c ca 0f 5a 0a 03 2b
+generated valid SI2quater [01/01]: [23] 59 06 07 42 20 04 86 59 83 d7 e0 50 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+added EARFCN 1982 - generating SI2quater for 7 EARFCNs and 0 UARFCNs...
+generated valid SI2quater [00/01]: [23] 59 06 07 40 20 04 86 59 83 be cc 1e 31 07 91 a8 3c ca 0f 5a 0a 03 2b
+generated valid SI2quater [01/01]: [23] 59 06 07 42 20 04 86 59 83 d7 e4 1e fa c2 80 2b 2b 2b 2b 2b 2b 2b 2b
+BTS deallocated OK in test_si2q_e()
+BTS allocation OK in test_si2q_u()
+Testing SYSINFO_TYPE_2quater UARFCN generation:
+generating SI2quater for 0 EARFCNs and 0 UARFCNs...
+generated invalid SI2quater [00/00]: [23] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+generating SI2quater for 0 EARFCNs and 1 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 0c 1a 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 14 1a 1f 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 3 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 1c 7b d0 f7 03 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 4 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 24 b3 e4 e9 68 03 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 5 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 2c 7a 34 0e 4e e9 83 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 6 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 34 7a 34 0e 4e e9 85 03 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 7 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 3c 70 39 02 ce f7 85 0e 03 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 8 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 44 7a 34 05 e4 72 05 08 d5 0b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 9 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 4c 7a 34 0e 64 77 85 43 55 c8 0b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 10 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 50 1c 3b 31 fa dd 88 85 7b c4 1c 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 11 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 0f 7c 58 1c 3b 25 7a ea 08 91 fb c4 1f b0 2b 2b 2b
+BTS deallocated OK in test_si2q_u()
+BTS allocation OK in test_si2q_mu()
+Test SI2quater multiple UARFCNs:
+generating SI2quater for 0 EARFCNs and 1 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7c 0b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7d 52 e8 0a 7e 0b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 3 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7d 52 e8 12 7e e0 0b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 4 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7d 52 e8 18 3f f4 90 03 2b 2b 2b 2b 2b 2b
+generating SI2quater for 0 EARFCNs and 5 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7d 52 e8 18 3f f4 90 54 ba 82 20 03 2b 2b
+generating SI2quater for 0 EARFCNs and 6 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7d 52 e8 18 3f f4 90 54 ba 84 52 67 03 2b
+generating SI2quater for 0 EARFCNs and 7 UARFCNs...
+generated valid SI2quater [00/00]: [23] 59 06 07 40 00 25 52 88 0a 7d 52 e8 18 3f f4 90 54 ba 86 20 73 8c 81
+BTS deallocated OK in test_si2q_mu()
+BTS allocation OK in test_si2q_long()
+Testing SYSINFO_TYPE_2quater combined EARFCN & UARFCN generation:
+generating SI2quater for 17 EARFCNs and 1 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 0c 1a 10 99 66 0f 04 83 c1 1c bb 2b 03 2b 2b
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c2 6c 1e 0f 60 f0 bb 08 3f d7 2e ca c1 2b
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 20 64 21 06 e1 08 55 08 53 d7 2e ca c1 2b
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 2a 64 21 56 e1 0a d5 08 49 d7 2e ca c1 2b
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 25 64 21 2e e1 09 94 e5 d9 58 2b 2b 2b 2b
+generating SI2quater for 17 EARFCNs and 2 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 14 4d e7 00 44 b3 07 82 41 e0 8e 5d 95 83 2b
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c2 6c 1e 0f 60 f0 bb 08 3f d7 2e ca c1 2b
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 20 64 21 06 e1 08 55 08 53 d7 2e ca c1 2b
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 2a 64 21 56 e1 0a d5 08 49 d7 2e ca c1 2b
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 25 64 21 2e e1 09 94 e5 d9 58 2b 2b 2b 2b
+generating SI2quater for 17 EARFCNs and 3 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 1c 4d e7 03 04 86 59 83 c1 20 f0 47 2e ca c1
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c2 6c 1e 0f 60 f0 bb 08 3f d7 2e ca c1 2b
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 20 64 21 06 e1 08 55 08 53 d7 2e ca c1 2b
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 2a 64 21 56 e1 0a d5 08 49 d7 2e ca c1 2b
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 25 64 21 2e e1 09 94 e5 d9 58 2b 2b 2b 2b
+generating SI2quater for 17 EARFCNs and 4 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 24 59 fa 26 73 84 86 59 83 c1 1c bb 2b 03 2b
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c1 20 f0 9b 07 83 d8 3c 2e b9 76 56 0b 2b
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 1f ec 21 03 21 08 37 08 42 a7 2e ca c1 2b
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 29 ec 21 53 21 0a b7 08 56 a7 2e ca c1 2b
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 24 ec 21 2b 21 09 77 08 4c a7 2e ca c1 2b
+generating SI2quater for 17 EARFCNs and 5 UARFCNs...
+generated valid SI2quater [00/04]: [23] 59 06 07 40 80 25 0f 70 2c 59 fa 30 73 f6 04 86 59 83 c1 1c bb 2b 03
+generated valid SI2quater [01/04]: [23] 59 06 07 42 80 04 86 59 83 c1 20 f0 9b 07 83 d8 3c 2e b9 76 56 0b 2b
+generated valid SI2quater [02/04]: [23] 59 06 07 44 80 04 86 59 84 1f ec 21 03 21 08 37 08 42 a7 2e ca c1 2b
+generated valid SI2quater [03/04]: [23] 59 06 07 46 80 04 86 59 84 29 ec 21 53 21 0a b7 08 56 a7 2e ca c1 2b
+generated valid SI2quater [04/04]: [23] 59 06 07 48 80 04 86 59 84 24 ec 21 2b 21 09 77 08 4c a7 2e ca c1 2b
+generating SI2quater for 17 EARFCNs and 6 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 34 f1 ae 15 f3 f4 83 04 86 59 72 ec ac 0b 2b
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 17 EARFCNs and 7 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 3c f1 ae 15 f3 f4 83 01 84 86 59 72 ec ac 0b
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 17 EARFCNs and 8 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 45 19 a0 0d 7d 7e a6 19 e7 0b 2b 2b 2b 2b 2b
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 17 EARFCNs and 9 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 4d 19 a0 26 fd 66 a6 03 e7 fa 0b 2b 2b 2b 2b
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 17 EARFCNs and 10 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 55 47 89 1e fd 7c b0 00 e7 9b b0 2b 2b 2b 2b
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b
+generating SI2quater for 17 EARFCNs and 11 UARFCNs...
+generated valid SI2quater [00/05]: [23] 59 06 07 40 a0 25 0f 70 5d 47 89 1e fd 7c b0 01 67 9b b3 f8 2b 2b 2b
+generated valid SI2quater [01/05]: [23] 59 06 07 42 a0 04 86 59 83 c1 20 f0 48 3c 26 c1 e0 f5 cb b2 b0 2b 2b
+generated valid SI2quater [02/05]: [23] 59 06 07 44 a0 04 86 59 83 c2 ec 20 ff 61 08 19 08 41 b7 2e ca c1 2b
+generated valid SI2quater [03/05]: [23] 59 06 07 46 a0 04 86 59 84 21 54 21 4f 61 0a 99 08 55 b7 2e ca c1 2b
+generated valid SI2quater [04/05]: [23] 59 06 07 48 a0 04 86 59 84 2b 54 21 27 61 09 59 08 4b b7 2e ca c1 2b
+generated valid SI2quater [05/05]: [23] 59 06 07 4a a0 04 86 59 84 26 53 97 65 60 2b 2b 2b 2b 2b 2b 2b 2b 2b
+BTS deallocated OK in test_si2q_long()
+BTS allocation OK in test_si_ba_ind()
+Testing if BA-IND is set as expected in SI2xxx and SI5xxx
+SI2: 59 06 1a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e5 04 00
+SI2bis: 55 06 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e5 04 00 2b
+SI2ter: 49 06 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2b 2b 2b 2b
+SI5: 06 1d 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+SI5bis: 06 05 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+SI5ter: 06 06 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+BTS deallocated OK in test_si_ba_ind()
+test_gsm48_ra_id_by_bts[0]: digits='00f120' lac=0x0300=htons(3) rac=0x04=4 pass
+test_gsm48_ra_id_by_bts[1]: digits='002100' lac=0x0300=htons(3) rac=0x04=4 pass
+test_gsm48_ra_id_by_bts[2]: digits='00f000' lac=0x0000=htons(0) rac=0x00=0 pass
+test_gsm48_ra_id_by_bts[3]: digits='000000' lac=0x0000=htons(0) rac=0x00=0 pass
+test_gsm48_ra_id_by_bts[4]: digits='999999' lac=0xffff=htons(65535) rac=0xff=255 pass
+test_gsm48_ra_id_by_bts[5]: digits='09f909' lac=0xcdab=htons(43981) rac=0xab=171 pass
+test_gsm48_ra_id_by_bts[6]: digits='090990' lac=0xcdab=htons(43981) rac=0xab=171 pass
+gsm48_multirate_config(): rc=0, lv=0620b40bf330
+gsm48_multirate_config(): rc=0, lv=0520340bf3
+gsm48_multirate_config(): rc=0, lv=0420140b
+gsm48_multirate_config(): rc=0, lv=0220
+Done.
diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am
new file mode 100644
index 000000000..5e9af0452
--- /dev/null
+++ b/tests/handover/Makefile.am
@@ -0,0 +1,97 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ -ggdb3 \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+EXTRA_DIST = \
+ handover_test.ok \
+ neighbor_ident_test.ok \
+ neighbor_ident_test.err \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ handover_test \
+ neighbor_ident_test \
+ $(NULL)
+
+handover_test_SOURCES = \
+ handover_test.c \
+ $(NULL)
+
+handover_test_LDFLAGS = \
+ -Wl,--wrap=abis_rsl_sendmsg \
+ -Wl,--wrap=mgw_endpoint_ci_request \
+ $(NULL)
+
+handover_test_LDADD = \
+ $(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/assignment_fsm.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/codec_pref.o \
+ $(top_builddir)/src/osmo-bsc/gsm_04_08_rr.o \
+ $(top_builddir)/src/osmo-bsc/gsm_04_80_utils.o \
+ $(top_builddir)/src/osmo-bsc/gsm_data.o \
+ $(top_builddir)/src/osmo-bsc/gsm_timers.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_fsm.o \
+ $(top_builddir)/src/osmo-bsc/handover_logic.o \
+ $(top_builddir)/src/osmo-bsc/lchan_fsm.o \
+ $(top_builddir)/src/osmo-bsc/lchan_rtp_fsm.o \
+ $(top_builddir)/src/osmo-bsc/lchan_select.o \
+ $(top_builddir)/src/osmo-bsc/meas_rep.o \
+ $(top_builddir)/src/osmo-bsc/mgw_endpoint_fsm.o \
+ $(top_builddir)/src/osmo-bsc/neighbor_ident.o \
+ $(top_builddir)/src/osmo-bsc/net_init.o \
+ $(top_builddir)/src/osmo-bsc/osmo_bsc_lcls.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 \
+ $(top_builddir)/src/osmo-bsc/timeslot_fsm.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOSIGTRAN_LIBS) \
+ $(LIBOSMOMGCPCLIENT_LIBS) \
+ $(NULL)
+
+neighbor_ident_test_SOURCES = \
+ neighbor_ident_test.c \
+ $(NULL)
+
+neighbor_ident_test_LDADD = \
+ $(top_builddir)/src/osmo-bsc/neighbor_ident.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(NULL)
+
+.PHONY: update_exp
+update_exp:
+ $(builddir)/neighbor_ident_test >$(srcdir)/neighbor_ident_test.ok 2>$(srcdir)/neighbor_ident_test.err
diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c
new file mode 100644
index 000000000..f728c5bd8
--- /dev/null
+++ b/tests/handover/handover_test.c
@@ -0,0 +1,1793 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <assert.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/handover_decision.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/handover_decision_2.h>
+#include <osmocom/bsc/bss.h>
+#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+
+void *ctx;
+
+struct gsm_network *bsc_gsmnet;
+
+/* override, requires '-Wl,--wrap=mgw_endpoint_ci_request'.
+ * Catch modification of an MGCP connection. */
+void __real_mgw_endpoint_ci_request(struct mgwep_ci *ci,
+ enum mgcp_verb verb, const struct mgcp_conn_peer *verb_info,
+ struct osmo_fsm_inst *notify,
+ uint32_t event_success, uint32_t event_failure,
+ void *notify_data);
+void __wrap_mgw_endpoint_ci_request(struct mgwep_ci *ci,
+ enum mgcp_verb verb, const struct mgcp_conn_peer *verb_info,
+ struct osmo_fsm_inst *notify,
+ uint32_t event_success, uint32_t event_failure,
+ void *notify_data)
+{
+ struct mgcp_conn_peer fake_data = {};
+ /* All MGCP shall be successful */
+ if (!notify)
+ return;
+ osmo_fsm_inst_dispatch(notify, event_success, &fake_data);
+}
+
+/* measurement report */
+
+uint8_t meas_rep_ba = 0, meas_rep_valid = 1, meas_valid = 1, meas_multi_rep = 0;
+uint8_t meas_dl_rxlev = 0, meas_dl_rxqual = 0;
+uint8_t meas_ul_rxlev = 0, meas_ul_rxqual = 0;
+uint8_t meas_tx_power_ms = 0, meas_tx_power_bs = 0, meas_ta_ms = 0;
+uint8_t meas_dtx_ms = 0, meas_dtx_bs = 0, meas_nr = 0;
+uint8_t meas_num_nc = 0, meas_rxlev_nc[6], meas_bsic_nc[6], meas_bcch_f_nc[6];
+
+static void gen_meas_rep(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_dchan_hdr *dh;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t ulm[3], l1i[2], *buf;
+ struct gsm48_hdr *gh;
+ struct gsm48_meas_res *mr;
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ dh->c.msg_type = RSL_MT_MEAS_RES;
+ dh->ie_chan = RSL_IE_CHAN_NR;
+ dh->chan_nr = chan_nr;
+
+ msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, meas_nr++);
+
+ ulm[0] = meas_ul_rxlev | (meas_dtx_bs << 7);
+ ulm[1] = meas_ul_rxlev;
+ ulm[2] = (meas_ul_rxqual << 3) | meas_ul_rxqual;
+ msgb_tlv_put(msg, RSL_IE_UPLINK_MEAS, sizeof(ulm), ulm);
+
+ msgb_tv_put(msg, RSL_IE_BS_POWER, meas_tx_power_bs);
+
+ l1i[0] = 0;
+ l1i[1] = meas_ta_ms;
+ msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, sizeof(l1i), l1i);
+
+ buf = msgb_put(msg, 3);
+ buf[0] = RSL_IE_L3_INFO;
+ buf[1] = (sizeof(*gh) + sizeof(*mr)) >> 8;
+ buf[2] = (sizeof(*gh) + sizeof(*mr)) & 0xff;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ mr = (struct gsm48_meas_res *) msgb_put(msg, sizeof(*mr));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_MEAS_REP;
+
+ /* measurement results */
+ mr->rxlev_full = meas_dl_rxlev;
+ mr->rxlev_sub = meas_dl_rxlev;
+ mr->rxqual_full = meas_dl_rxqual;
+ mr->rxqual_sub = meas_dl_rxqual;
+ mr->dtx_used = meas_dtx_ms;
+ mr->ba_used = meas_rep_ba;
+ mr->meas_valid = !meas_valid; /* 0 = valid */
+ if (meas_rep_valid) {
+ mr->no_nc_n_hi = meas_num_nc >> 2;
+ mr->no_nc_n_lo = meas_num_nc & 3;
+ } else {
+ /* no results for serving cells */
+ mr->no_nc_n_hi = 1;
+ mr->no_nc_n_lo = 3;
+ }
+ mr->rxlev_nc1 = meas_rxlev_nc[0];
+ mr->rxlev_nc2_hi = meas_rxlev_nc[1] >> 1;
+ mr->rxlev_nc2_lo = meas_rxlev_nc[1] & 1;
+ mr->rxlev_nc3_hi = meas_rxlev_nc[2] >> 2;
+ mr->rxlev_nc3_lo = meas_rxlev_nc[2] & 3;
+ mr->rxlev_nc4_hi = meas_rxlev_nc[3] >> 3;
+ mr->rxlev_nc4_lo = meas_rxlev_nc[3] & 7;
+ mr->rxlev_nc5_hi = meas_rxlev_nc[4] >> 4;
+ mr->rxlev_nc5_lo = meas_rxlev_nc[4] & 15;
+ mr->rxlev_nc6_hi = meas_rxlev_nc[5] >> 5;
+ mr->rxlev_nc6_lo = meas_rxlev_nc[5] & 31;
+ mr->bsic_nc1_hi = meas_bsic_nc[0] >> 3;
+ mr->bsic_nc1_lo = meas_bsic_nc[0] & 7;
+ mr->bsic_nc2_hi = meas_bsic_nc[1] >> 4;
+ mr->bsic_nc2_lo = meas_bsic_nc[1] & 15;
+ mr->bsic_nc3_hi = meas_bsic_nc[2] >> 5;
+ mr->bsic_nc3_lo = meas_bsic_nc[2] & 31;
+ mr->bsic_nc4 = meas_bsic_nc[3];
+ mr->bsic_nc5 = meas_bsic_nc[4];
+ mr->bsic_nc6 = meas_bsic_nc[5];
+ mr->bcch_f_nc1 = meas_bcch_f_nc[0];
+ mr->bcch_f_nc2 = meas_bcch_f_nc[1];
+ mr->bcch_f_nc3 = meas_bcch_f_nc[2];
+ mr->bcch_f_nc4 = meas_bcch_f_nc[3];
+ mr->bcch_f_nc5_hi = meas_bcch_f_nc[4] >> 1;
+ mr->bcch_f_nc5_lo = meas_bcch_f_nc[4] & 1;
+ mr->bcch_f_nc6_hi = meas_bcch_f_nc[5] >> 2;
+ mr->bcch_f_nc6_lo = meas_bcch_f_nc[5] & 3;
+
+ msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->l2h = (unsigned char *)dh;
+ msg->l3h = (unsigned char *)gh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
+static struct gsm_bts *create_bts(int arfcn)
+{
+ struct gsm_bts *bts;
+ struct e1inp_sign_link *rsl_link;
+ int i;
+
+ bts = bsc_bts_alloc_register(bsc_gsmnet, GSM_BTS_TYPE_OSMOBTS, 0x3f);
+ if (!bts) {
+ printf("No resource for bts1\n");
+ return NULL;
+ }
+
+ bts->location_area_code = 23;
+ bts->c0->arfcn = arfcn;
+
+ bts->codec.efr = 1;
+ bts->codec.hr = 1;
+ bts->codec.amr = 1;
+
+ rsl_link = talloc_zero(ctx, struct e1inp_sign_link);
+ rsl_link->trx = bts->c0;
+ bts->c0->rsl_link = rsl_link;
+
+ bts->c0->mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ bts->c0->mo.nm_state.availability = NM_AVSTATE_OK;
+ bts->c0->bb_transc.mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ bts->c0->bb_transc.mo.nm_state.availability = NM_AVSTATE_OK;
+
+ /* 4 full rate and 4 half rate channels */
+ for (i = 1; i <= 6; i++) {
+ bts->c0->ts[i].pchan_from_config = (i < 5) ? GSM_PCHAN_TCH_F : GSM_PCHAN_TCH_H;
+ bts->c0->ts[i].mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ bts->c0->ts[i].mo.nm_state.availability = NM_AVSTATE_OK;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bts->c0->ts); i++) {
+ /* make sure ts->lchans[] get initialized */
+ osmo_fsm_inst_dispatch(bts->c0->ts[i].fi, TS_EV_RSL_READY, 0);
+ osmo_fsm_inst_dispatch(bts->c0->ts[i].fi, TS_EV_OML_READY, 0);
+ }
+ return bts;
+}
+
+void create_conn(struct gsm_lchan *lchan)
+{
+ static struct bsc_msc_data fake_msc_data = {};
+ static unsigned int next_imsi = 0;
+ char imsi[sizeof(lchan->conn->bsub->imsi)];
+ struct gsm_network *net = lchan->ts->trx->bts->network;
+ struct gsm_subscriber_connection *conn;
+ struct mgcp_client *fake_mgcp_client = (void*)talloc_zero(net, int);
+ uint8_t *amr_conf;
+
+ /* HACK: lchan_fsm.c requires some AMR codec rates to be enabled,
+ * lets pretend that all AMR codec rates are allowed */
+ amr_conf = (uint8_t*) &fake_msc_data.amr_conf;
+ amr_conf[1] = 0xff;
+
+ conn = bsc_subscr_con_allocate(net);
+
+ conn->user_plane.mgw_endpoint = mgw_endpoint_alloc(conn->fi,
+ GSCON_EV_FORGET_MGW_ENDPOINT,
+ fake_mgcp_client, "test",
+ "fake endpoint");
+ conn->sccp.msc = &fake_msc_data;
+
+ lchan->conn = conn;
+ conn->lchan = lchan;
+
+ /* Make up a new IMSI for this test, for logging the subscriber */
+ next_imsi ++;
+ snprintf(imsi, sizeof(imsi), "%06u", next_imsi);
+ lchan->conn->bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers, imsi);
+
+ /* kick the FSM from INIT through to the ACTIVE state */
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_REQ, NULL);
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_CFM, NULL);
+}
+
+/* create lchan */
+struct gsm_lchan *create_lchan(struct gsm_bts *bts, int full_rate, char *codec)
+{
+ struct gsm_lchan *lchan;
+
+ lchan = lchan_select_by_type(bts, (full_rate) ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H);
+ if (!lchan) {
+ printf("No resource for lchan\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* serious hack into osmo_fsm */
+ lchan->fi->state = LCHAN_ST_ESTABLISHED;
+ lchan->ts->fi->state = TS_ST_IN_USE;
+ LOG_LCHAN(lchan, LOGL_DEBUG, "activated by handover_test.c\n");
+
+ create_conn(lchan);
+ if (!strcasecmp(codec, "FR") && full_rate)
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ else if (!strcasecmp(codec, "HR") && !full_rate)
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ else if (!strcasecmp(codec, "EFR") && full_rate)
+ lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
+ else if (!strcasecmp(codec, "AMR"))
+ lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
+ else {
+ printf("Given codec unknown\n");
+ exit(EXIT_FAILURE);
+ }
+
+ lchan->conn->codec_list = (struct gsm0808_speech_codec_list){
+ .codec = {
+ { .fi=true, .type=GSM0808_SCT_FR1, },
+ { .fi=true, .type=GSM0808_SCT_FR2, },
+ { .fi=true, .type=GSM0808_SCT_FR3, },
+ { .fi=true, .type=GSM0808_SCT_HR1, },
+ { .fi=true, .type=GSM0808_SCT_HR3, },
+ },
+ .len = 5,
+ };
+
+ return lchan;
+}
+
+/* parse channel request */
+
+static int got_chan_req = 0;
+static struct gsm_lchan *chan_req_lchan = NULL;
+
+static int parse_chan_act(struct gsm_lchan *lchan, uint8_t *data)
+{
+ chan_req_lchan = lchan;
+ return 0;
+}
+
+static int parse_chan_rel(struct gsm_lchan *lchan, uint8_t *data)
+{
+ chan_req_lchan = lchan;
+ return 0;
+}
+
+/* parse handover request */
+
+static int got_ho_req = 0;
+static struct gsm_lchan *ho_req_lchan = NULL;
+
+static int parse_ho_command(struct gsm_lchan *lchan, uint8_t *data, int len)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) data;
+ struct gsm48_ho_cmd *ho = (struct gsm48_ho_cmd *) gh->data;
+ int arfcn;
+ struct gsm_bts *neigh;
+
+ switch (gh->msg_type) {
+ case GSM48_MT_RR_HANDO_CMD:
+ arfcn = (ho->cell_desc.arfcn_hi << 8) | ho->cell_desc.arfcn_lo;
+
+ /* look up trx. since every dummy bts uses different arfcn and
+ * only one trx, it is simple */
+ llist_for_each_entry(neigh, &bsc_gsmnet->bts_list, list) {
+ if (neigh->c0->arfcn != arfcn)
+ continue;
+ ho_req_lchan = lchan;
+ return 0;
+ }
+ break;
+ case GSM48_MT_RR_ASS_CMD:
+ ho_req_lchan = lchan;
+ return 0;
+ break;
+ default:
+ fprintf(stderr, "Error, expecting HO or AS command\n");
+ return -EINVAL;
+ }
+
+ return -1;
+}
+
+/* send channel activation ack */
+static void send_chan_act_ack(struct gsm_lchan *lchan, int act)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_dchan_hdr *dh;
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ dh->c.msg_type = (act) ? RSL_MT_CHAN_ACTIV_ACK : RSL_MT_RF_CHAN_REL_ACK;
+ dh->ie_chan = RSL_IE_CHAN_NR;
+ dh->chan_nr = gsm_lchan2chan_nr(lchan);
+
+ msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->l2h = (unsigned char *)dh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
+/* Send RLL Est Ind for SAPI[0] */
+static void send_est_ind(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_rll_hdr *rh;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
+ rh->c.msg_discr = ABIS_RSL_MDISC_RLL;
+ rh->c.msg_type = RSL_MT_EST_IND;
+ rh->ie_chan = RSL_IE_CHAN_NR;
+ rh->chan_nr = chan_nr;
+ rh->ie_link_id = RSL_IE_LINK_IDENT;
+ rh->link_id = 0x00;
+
+ msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->l2h = (unsigned char *)rh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
+/* send handover complete */
+static void send_ho_complete(struct gsm_lchan *lchan, bool success)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_rll_hdr *rh;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t *buf;
+ struct gsm48_hdr *gh;
+ struct gsm48_ho_cpl *hc;
+
+ send_est_ind(lchan);
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RTP_READY, 0);
+
+ rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
+ rh->c.msg_discr = ABIS_RSL_MDISC_RLL;
+ rh->c.msg_type = RSL_MT_DATA_IND;
+ rh->ie_chan = RSL_IE_CHAN_NR;
+ rh->chan_nr = chan_nr;
+ rh->ie_link_id = RSL_IE_LINK_IDENT;
+ rh->link_id = 0x00;
+
+ buf = msgb_put(msg, 3);
+ buf[0] = RSL_IE_L3_INFO;
+ buf[1] = (sizeof(*gh) + sizeof(*hc)) >> 8;
+ buf[2] = (sizeof(*gh) + sizeof(*hc)) & 0xff;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ hc = (struct gsm48_ho_cpl *) msgb_put(msg, sizeof(*hc));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type =
+ success ? GSM48_MT_RR_HANDO_COMPL : GSM48_MT_RR_HANDO_FAIL;
+
+ msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->l2h = (unsigned char *)rh;
+ msg->l3h = (unsigned char *)gh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
+/* override, requires '-Wl,--wrap=abis_rsl_sendmsg'.
+ * Catch RSL messages sent towards the BTS. */
+int __real_abis_rsl_sendmsg(struct msgb *msg);
+int __wrap_abis_rsl_sendmsg(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = (struct abis_rsl_dchan_hdr *) msg->data;
+ struct e1inp_sign_link *sign_link = msg->dst;
+ int rc;
+ struct gsm_lchan *lchan = rsl_lchan_lookup(sign_link->trx, dh->chan_nr, &rc);
+
+ if (rc) {
+ printf("rsl_lchan_lookup() failed\n");
+ exit(1);
+ }
+
+ switch (dh->c.msg_type) {
+ case RSL_MT_CHAN_ACTIV:
+ rc = parse_chan_act(lchan, dh->data);
+ if (rc == 0)
+ got_chan_req = 1;
+ break;
+ case RSL_MT_RF_CHAN_REL:
+ rc = parse_chan_rel(lchan, dh->data);
+ if (rc == 0)
+ send_chan_act_ack(chan_req_lchan, 0);
+ break;
+ case RSL_MT_DATA_REQ:
+ rc = parse_ho_command(lchan, msg->l3h, msgb_l3len(msg));
+ if (rc == 0)
+ got_ho_req = 1;
+ break;
+ case RSL_MT_IPAC_CRCX:
+ break;
+ case RSL_MT_DEACTIVATE_SACCH:
+ break;
+ default:
+ printf("unknown rsl message=0x%x\n", dh->c.msg_type);
+ }
+ return 0;
+}
+
+/* test cases */
+
+static char *test_case_0[] = {
+ "2",
+
+ "Stay in better cell\n\n"
+ "There are many neighbor cells, but only the current cell is the best\n"
+ "cell, so no handover is performed\n",
+
+ "create-bts", "7",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "30","0",
+ "6","0","20","1","21","2","18","3","20","4","23","5","19",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_1[] = {
+ "2",
+
+ "Handover to best better cell\n\n"
+ "The best neighbor cell is selected\n",
+
+ "create-bts", "7",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "10","0",
+ "6","0","20","1","21","2","18","3","20","4","23","5","19",
+ "expect-chan", "5", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_2[] = {
+ "2",
+
+ "Handover and Assignment must be enabled\n\n"
+ "This test will start with disabled assignment and handover. A\n"
+ "better neighbor cell (assignment enabled) will not be selected and \n"
+ "also no assignment from TCH/H to TCH/F to improve quality. There\n"
+ "will be no handover nor assignment. After enabling assignment on the\n"
+ "current cell, the MS will assign to TCH/F. After enabling handover\n"
+ "in the current cell, but disabling in the neighbor cell, handover\n"
+ "will not be performed, until it is enabled in the neighbor cell too.\n",
+
+ "create-bts", "2",
+ "afs-rxlev-improve", "0", "5",
+ "create-ms", "0", "TCH/H", "AMR",
+ "as-enable", "0", "0",
+ "ho-enable", "0", "0",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-no-chan",
+ "as-enable", "0", "1",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "0", "5",
+ "ho-complete",
+ "ho-enable", "0", "1",
+ "ho-enable", "1", "0",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-no-chan",
+ "ho-enable", "1", "1",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_3[] = {
+ "2",
+
+ "Penalty timer must not run\n\n"
+ "The MS will try to handover to a better cell, but this will fail.\n"
+ "Even though the cell is still better, handover will not be performed\n"
+ "due to penalty timer after handover failure\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-failed",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_4[] = {
+ "2",
+
+ "TCH/H keeping with HR codec\n\n"
+ "The MS is using half rate V1 codec, but the better cell is congested\n"
+ "at TCH/H slots. As the congestion is removed, the handover takes\n"
+ "place.\n",
+
+ "create-bts", "2",
+ "set-min-free", "1", "TCH/H", "4",
+ "create-ms", "0", "TCH/H", "HR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "set-min-free", "1", "TCH/H", "3",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "5",
+ "ack-chan",
+ "expect-ho", "0", "5",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_5[] = {
+ "2",
+
+ "TCH/F keeping with FR codec\n\n"
+ "The MS is using full rate V1 codec, but the better cell is congested\n"
+ "at TCH/F slots. As the congestion is removed, the handover takes\n"
+ "place.\n",
+
+ "create-bts", "2",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "FR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "set-min-free", "1", "TCH/F", "3",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_6[] = {
+ "2",
+
+ "TCH/F keeping with EFR codec\n\n"
+ "The MS is using full rate V2 codec, but the better cell is congested\n"
+ "at TCH/F slots. As the congestion is removed, the handover takes\n"
+ "place.\n",
+
+ "create-bts", "2",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "EFR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "set-min-free", "1", "TCH/F", "3",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_7[] = {
+ "2",
+
+ "TCH/F to TCH/H changing with AMR codec\n\n"
+ "The MS is using AMR V3 codec, the better cell is congested at TCH/F\n"
+ "slots. The handover is performed to non-congested TCH/H slots.\n",
+
+ "create-bts", "2",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "5",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_8[] = {
+ "2",
+
+ "No handover to a cell with no slots available\n\n"
+ "If no slot is available, no handover is performed\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_9[] = {
+ "2",
+
+ "No more parallel handovers, if max_unsync_ho is defined\n\n"
+ "There are tree mobiles that want to handover, but only two can do\n"
+ "it at a time, because the maximum number is limited to two.\n",
+
+ "create-bts", "2",
+ "set-max-ho", "1", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "meas-rep", "1", "0","0", "1","0","30",
+ "expect-chan", "1", "2",
+ "meas-rep", "2", "0","0", "1","0","30",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_10[] = {
+ "2",
+
+ "Hysteresis\n\n"
+ "If neighbor cell is better, handover is only performed if the\n"
+ "ammount of improvement is greater or equal hyteresis\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "27","0", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "26","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_11[] = {
+ "2",
+
+ "No Hysteresis and minimum RX level\n\n"
+ "If current cell's RX level is below mimium level, handover must be\n"
+ "performed, no matter of the hysteresis. First do not perform\n"
+ "handover to better neighbor cell, because the hysteresis is not\n"
+ "met. Second do not perform handover because better neighbor cell is\n"
+ "below minimum RX level. Third perform handover because current cell\n"
+ "is below minimum RX level, even if the better neighbor cell (minimum\n"
+ "RX level reached) does not meet the hysteresis.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "10","0", "1","0","11",
+ "expect-no-chan",
+ "meas-rep", "0", "8","0", "1","0","9",
+ "expect-no-chan",
+ "meas-rep", "0", "9","0", "1","0","10",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_12[] = {
+ "2",
+
+ "No handover to congested cell\n\n"
+ "The better neighbor cell is congested, so no handover is performed.\n"
+ "After the congestion is over, handover will be performed.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "set-min-free", "1", "TCH/F", "3",
+ "set-min-free", "1", "TCH/H", "3",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_13[] = {
+ "2",
+
+ "Handover to balance congestion\n\n"
+ "The current and the better cell are congested, so no handover is\n"
+ "performed. This is because handover would congest the neighbor cell\n"
+ "more. After congestion raises in the current cell, the handover is\n"
+ "performed to balance congestion\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_14[] = {
+ "2",
+
+ "Handover to congested cell, if RX level is below minimum\n\n"
+ "The better neighbor cell is congested, so no handover is performed.\n"
+ "If the RX level of the current cell drops below minimum acceptable\n"
+ "level, the handover is performed.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "meas-rep", "0", "10","0", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "9","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_15[] = {
+ "2",
+
+ "Handover to cell with worse RXLEV, if RXQUAL is below minimum\n\n"
+ "The neighbor cell has worse RXLEV, so no handover is performed.\n"
+ "If the RXQUAL of the current cell drops below minimum acceptable\n"
+ "level, the handover is performed. It is also required that 10\n"
+ "reports are received, before RXQUAL is checked.\n",
+ /* (See also test 28, which tests for RXQUAL triggering HO to congested cell.) */
+ /* TODO: bad RXQUAL may want to prefer assignment within the same cell to avoid interference.
+ * See Performence Enhancements in a Frequency Hopping GSM Network (Nielsen Wigard 2002), Chapter
+ * 2.1.1, "Interference" in the list of triggers on p.157. */
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_16[] = {
+ "2",
+
+ "Handover due to maximum TA exceeded\n\n"
+ "The MS in the current (best) cell has reached maximum allowed timing\n"
+ "advance. No handover is performed until the timing advance exceeds\n"
+ "it. The originating cell is still the best, but no handover is\n"
+ "performed back to that cell, because the penalty timer (due to\n"
+ "maximum allowed timing advance) is running.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-max-ta", "0", "5", /* of cell */
+ "set-ta", "0", "5", /* of ms */
+ "meas-rep", "0", "30","0", "1","0","20",
+ "expect-no-chan",
+ "set-ta", "0", "6", /* of ms */
+ "meas-rep", "0", "30","0", "1","0","20",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_17[] = {
+ "2",
+
+ "Congestion check: No congestion\n\n"
+ "Three cells have different number of used slots, but there is no\n"
+ "congestion in any of these cells. No handover is performed.\n",
+
+ "create-bts", "3",
+ "set-min-free", "0", "TCH/F", "2",
+ "set-min-free", "0", "TCH/H", "2",
+ "set-min-free", "1", "TCH/F", "2",
+ "set-min-free", "1", "TCH/H", "2",
+ "set-min-free", "2", "TCH/F", "2",
+ "set-min-free", "2", "TCH/H", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "1", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "2", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "3", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "4", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "5", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_18[] = {
+ "2",
+
+ "Congestion check: One out of three cells is congested\n\n"
+ "Three cells have different number of used slots, but there is\n"
+ "congestion at TCH/F in the first cell. Handover is performed with\n"
+ "the best candidate.\n",
+
+ "create-bts", "3",
+ "set-min-free", "0", "TCH/F", "2",
+ "set-min-free", "0", "TCH/H", "2",
+ "set-min-free", "1", "TCH/F", "2",
+ "set-min-free", "1", "TCH/H", "2",
+ "set-min-free", "2", "TCH/F", "2",
+ "set-min-free", "2", "TCH/H", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "1", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "2", "30","0", "2","0","21","1","20",
+ "expect-no-chan",
+ "meas-rep", "3", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "4", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "5", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "6", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "2",
+ "ack-chan",
+ "expect-ho", "0", "3", /* best candidate is MS 2 at BTS 1, TS 3 */
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_19[] = {
+ "2",
+
+ "Congestion check: Balancing over congested cells\n\n"
+ "Two cells are congested, but the second cell is more congested.\n"
+ "Handover is performed to solve the congestion.\n",
+
+ "create-bts", "2",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "FR",
+ "create-ms", "0", "TCH/F", "FR",
+ "create-ms", "0", "TCH/F", "FR",
+ "create-ms", "1", "TCH/F", "FR",
+ "meas-rep", "0", "30","0", "1","0","20",
+ "expect-no-chan",
+ "meas-rep", "1", "30","0", "1","0","21",
+ "expect-no-chan",
+ "meas-rep", "2", "30","0", "1","0","20",
+ "expect-no-chan",
+ "meas-rep", "3", "30","0", "1","0","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "2",
+ "ack-chan",
+ "expect-ho", "0", "2", /* best candidate is MS 1 at BTS 0, TS 2 */
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_20[] = {
+ "2",
+
+ "Congestion check: Solving congestion by handover TCH/F -> TCH/H\n\n"
+ "Two BTS, one MS in the first congested BTS must handover to\n"
+ "non-congested TCH/H of second BTS, in order to solve congestion\n",
+ "create-bts", "2",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "30","0", "1","0","30",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "5",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_21[] = {
+ "2",
+
+ "Congestion check: Balancing congestion by handover TCH/F -> TCH/H\n\n"
+ "Two BTS, one MS in the first congested BTS must handover to\n"
+ "less-congested TCH/H of second BTS, in order to balance congestion\n",
+ "create-bts", "2",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "1","0","30",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_22[] = {
+ "2",
+
+ "Congestion check: Upgrading worst candidate from TCH/H -> TCH/F\n\n"
+ "There is only one BTS. The TCH/H slots are congested. Since\n"
+ "assignment is performed to less-congested TCH/F, the candidate with\n"
+ "the worst RX level is chosen.\n",
+
+ "create-bts", "1",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "0",
+ "meas-rep", "1", "34","0", "0",
+ "meas-rep", "2", "20","0", "0",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "0", "6",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_23[] = {
+ "2",
+
+ "Story: 'A neighbor is your friend'\n",
+
+ "create-bts", "3",
+
+ "print",
+ "Andreas is driving along the coast, on a sunny june afternoon.\n"
+ "Suddenly he is getting a call from his friend and neighbor Axel.\n"
+ "\n"
+ "What happens: Two MS are created, #0 for Axel, #1 for Andreas.",
+ /* Axel */
+ "create-ms", "2", "TCH/F", "AMR",
+ /* andreas */
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "1", "40","0", "1","0","30",
+ "expect-no-chan",
+
+ "print",
+ "Axel asks Andreas if he would like to join them for a barbecue.\n"
+ "Axel's house is right in the neighborhood and the weather is fine.\n"
+ "Andreas agrees, so he drives to a close store to buy some barbecue\n"
+ "skewers.\n"
+ "\n"
+ "What happens: While driving, a different cell (mounted atop the\n"
+ "store) becomes better.",
+ /* drive to bts 1 */
+ "meas-rep", "1", "20","0", "1","0","35",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+
+ "print",
+ "While Andreas is walking into the store, Axel asks, if he could also\n"
+ "bring some beer. Andreas has problems understanding him: \"I have a\n"
+ "bad reception here. The cell tower is right atop the store, but poor\n"
+ "coverage inside. Can you repeat please?\"\n"
+ "\n"
+ "What happens: Inside the store the close cell is so bad, that\n"
+ "handover back to the previous cell is required.",
+ /* bts 1 becomes bad, so bts 0 helps out */
+ "meas-rep", "1", "5","0", "1","0","20",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "1", "1",
+ "ho-complete",
+
+ "print",
+ "After Andreas bought skewers and beer, he leaves the store.\n"
+ "\n"
+ "What happens: Outside the store the close cell is better again, so\n"
+ "handover back to the that cell is performed.",
+ /* bts 1 becomes better again */
+ "meas-rep", "1", "20","0", "1","0","35",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+
+ "print",
+ /* bts 2 becomes better */
+ "Andreas drives down to the lake where Axel's house is.\n"
+ "\n"
+ "What happens: There is a small cell at Axel's house, which becomes\n"
+ "better, because the current cell has no good comverage at the lake.",
+ "meas-rep", "1", "14","0", "2","0","2","1","63",
+ "expect-chan", "2", "2",
+ "ack-chan",
+ "expect-ho", "1", "1",
+ "ho-complete",
+
+ "print",
+ "Andreas wonders why he still has good radio coverage: \"Last time it\n"
+ "was so bad\". Axel says: \"I installed a pico cell in my house,\n"
+ "now we can use our mobile phones down here at the lake.\"",
+
+ NULL
+};
+
+static char *test_case_24[] = {
+ "2",
+ "No (or not enough) measurements for handover\n\n"
+ "Do not solve congestion in cell, because there is no measurement.\n"
+ "As soon as enough measurments available (1 in our case), perform\n"
+ "handover. Afterwards the old cell becomes congested and the new\n"
+ "cell is not. Do not perform handover until new measurements are\n"
+ "received.\n",
+
+ /* two cells, first in congested, but no handover */
+ "create-bts", "2",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "create-ms", "0", "TCH/F", "AMR",
+ "congestion-check",
+ "expect-no-chan",
+
+ /* send measurement and trigger congestion check */
+ "meas-rep", "0", "20","0", "1","0","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+
+ /* congest the first cell and remove congestion from second cell */
+ "set-min-free", "0", "TCH/F", "0",
+ "set-min-free", "0", "TCH/H", "0",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+
+ /* no handover until measurements applied */
+ "congestion-check",
+ "expect-no-chan",
+ "meas-rep", "0", "20","0", "1","0","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "1", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_25[] = {
+ "1",
+
+ "Stay in better cell\n\n"
+ "There are many neighbor cells, but only the current cell is the best\n"
+ "cell, so no handover is performed\n",
+
+ "create-bts", "7",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "30","0",
+ "6","0","20","1","21","2","18","3","20","4","23","5","19",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_26[] = {
+ "1",
+
+ "Handover to best better cell\n\n"
+ "The best neighbor cell is selected\n",
+
+ "create-bts", "7",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "10","0",
+ "6","0","20","1","21","2","18","3","20","4","23","5","19",
+ "expect-chan", "5", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_27[] = {
+ "2",
+
+ "Congestion check: Upgrading worst candidate from TCH/H -> TCH/F\n\n"
+ "There is only one BTS. The TCH/H slots are congested. Since\n"
+ "assignment is performed to less-congested TCH/F, the candidate with\n"
+ "the worst RX level is chosen. (So far like test 22.)\n"
+ "After that, trigger more congestion checks to ensure stability.\n",
+
+ "create-bts", "1",
+ "set-min-free", "0", "TCH/F", "2",
+ "set-min-free", "0", "TCH/H", "4",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "0",
+ "meas-rep", "1", "34","0", "0",
+ "meas-rep", "2", "20","0", "0",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "0", "6",
+ "ho-complete",
+ "congestion-check",
+ "expect-chan", "0", "2",
+ "ack-chan",
+ "expect-ho", "0", "5",
+ "ho-complete",
+ "congestion-check",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_28[] = {
+ "2",
+
+ "Handover to congested cell, if RX quality is below minimum\n\n"
+ "The better neighbor cell is congested, so no handover is performed.\n"
+ "If the RX quality of the current cell drops below minimum acceptable\n"
+ "level, the handover is performed. It is also required that 10\n"
+ "resports are received, before RX quality is checked.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-no-chan",
+ "meas-rep", "0", "30","6", "1","0","40",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char **test_cases[] = {
+ test_case_0,
+ test_case_1,
+ test_case_2,
+ test_case_3,
+ test_case_4,
+ test_case_5,
+ test_case_6,
+ test_case_7,
+ test_case_8,
+ test_case_9,
+ test_case_10,
+ test_case_11,
+ test_case_12,
+ test_case_13,
+ test_case_14,
+ test_case_15,
+ test_case_16,
+ test_case_17,
+ test_case_18,
+ test_case_19,
+ test_case_20,
+ test_case_21,
+ test_case_22,
+ test_case_23,
+ test_case_24,
+ test_case_25,
+ test_case_26,
+ test_case_27,
+ test_case_28,
+};
+
+static const struct log_info_cat log_categories[] = {
+ [DHO] = {
+ .name = "DHO",
+ .description = "Hand-Over Process",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DHODEC] = {
+ .name = "DHODEC",
+ .description = "Hand-Over Decision",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DMEAS] = {
+ .name = "DMEAS",
+ .description = "Radio Measurement Processing",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DREF] = {
+ .name = "DREF",
+ .description = "Reference Counting",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DRSL] = {
+ .name = "DRSL",
+ .description = "A-bis Radio Signalling Link (RSL)",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DRR] = {
+ .name = "DRR",
+ .description = "RR",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DRLL] = {
+ .name = "DRLL",
+ .description = "RLL",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DMSC] = {
+ .name = "DMSC",
+ .description = "Mobile Switching Center",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DCHAN] = {
+ .name = "DCHAN",
+ .description = "lchan FSM",
+ .color = "\033[1;32m",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DTS] = {
+ .name = "DTS",
+ .description = "timeslot FSM",
+ .color = "\033[1;31m",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DAS] = {
+ .name = "DAS",
+ .description = "assignment FSM",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+};
+
+const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ char **test_case;
+ struct gsm_bts *bts[256];
+ int bts_num = 0;
+ struct gsm_lchan *lchan[256];
+ int lchan_num = 0;
+ int i;
+ int algorithm;
+ int test_case_i;
+ int last_test_i;
+
+ ctx = talloc_named_const(NULL, 0, "handover_test");
+ msgb_talloc_ctx_init(ctx, 0);
+
+ test_case_i = argc > 1? atoi(argv[1]) : -1;
+ last_test_i = ARRAY_SIZE(test_cases) - 1;
+
+ if (test_case_i < 0 || test_case_i > last_test_i) {
+ for (i = 0; i <= last_test_i; i++) {
+ printf("Test #%d (algorithm %s):\n%s\n", i,
+ test_cases[i][0], test_cases[i][1]);
+ }
+ printf("\nPlease specify test case number 0..%d\n", last_test_i);
+ return EXIT_FAILURE;
+ }
+
+ osmo_init_logging2(ctx, &log_info);
+
+ log_set_print_category(osmo_stderr_target, 1);
+ log_set_print_category_hex(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
+ osmo_fsm_log_addr(false);
+
+ bsc_network_alloc();
+ if (!bsc_gsmnet)
+ exit(1);
+
+ ts_fsm_init();
+ lchan_fsm_init();
+ mgw_endpoint_fsm_init(bsc_gsmnet->T_defs);
+ bsc_subscr_conn_fsm_init();
+ handover_fsm_init();
+
+ ho_set_algorithm(bsc_gsmnet->ho, 2);
+ ho_set_ho_active(bsc_gsmnet->ho, true);
+ ho_set_hodec2_as_active(bsc_gsmnet->ho, true);
+ ho_set_hodec2_min_rxlev(bsc_gsmnet->ho, -100);
+ ho_set_hodec2_rxlev_avg_win(bsc_gsmnet->ho, 1);
+ ho_set_hodec2_rxlev_neigh_avg_win(bsc_gsmnet->ho, 1);
+ ho_set_hodec2_rxqual_avg_win(bsc_gsmnet->ho, 10);
+ ho_set_hodec2_pwr_hysteresis(bsc_gsmnet->ho, 3);
+ ho_set_hodec2_pwr_interval(bsc_gsmnet->ho, 1);
+ ho_set_hodec2_afs_bias_rxlev(bsc_gsmnet->ho, 0);
+ ho_set_hodec2_min_rxqual(bsc_gsmnet->ho, 5);
+ ho_set_hodec2_afs_bias_rxqual(bsc_gsmnet->ho, 0);
+ ho_set_hodec2_max_distance(bsc_gsmnet->ho, 9999);
+ ho_set_hodec2_ho_max(bsc_gsmnet->ho, 9999);
+ ho_set_hodec2_penalty_max_dist(bsc_gsmnet->ho, 300);
+ ho_set_hodec2_penalty_failed_ho(bsc_gsmnet->ho, 60);
+ ho_set_hodec2_penalty_failed_as(bsc_gsmnet->ho, 60);
+
+ bts_model_sysmobts_init();
+
+ test_case = test_cases[test_case_i];
+
+ fprintf(stderr, "--------------------\n");
+ fprintf(stderr, "Performing the following test %d (algorithm %s):\n%s",
+ test_case_i, test_case[0], test_case[1]);
+ algorithm = atoi(test_case[0]);
+ test_case += 2;
+ fprintf(stderr, "--------------------\n");
+
+ /* Disable the congestion check timer, we will trigger manually. */
+ bsc_gsmnet->hodec2.congestion_check_interval_s = 0;
+
+ handover_decision_1_init();
+ hodec2_init(bsc_gsmnet);
+
+ while (*test_case) {
+ if (!strcmp(*test_case, "create-bts")) {
+ static int arfcn = 870;
+ int n = atoi(test_case[1]);
+ fprintf(stderr, "- Creating %d BTS (one TRX each, "
+ "TS(1-4) are TCH/F, TS(5-6) are TCH/H)\n", n);
+ for (i = 0; i < n; i++)
+ bts[bts_num + i] = create_bts(arfcn++);
+ for (i = 0; i < n; i++) {
+ if (gsm_generate_si(bts[bts_num + i], SYSINFO_TYPE_2) <= 0)
+ fprintf(stderr, "Error generating SI2\n");
+ }
+ bts_num += n;
+ test_case += 2;
+ } else
+ if (!strcmp(*test_case, "as-enable")) {
+ fprintf(stderr, "- Set assignment enable state at "
+ "BTS %s to %s\n", test_case[1], test_case[2]);
+ ho_set_hodec2_as_active(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "ho-enable")) {
+ fprintf(stderr, "- Set handover enable state at "
+ "BTS %s to %s\n", test_case[1], test_case[2]);
+ ho_set_ho_active(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "afs-rxlev-improve")) {
+ fprintf(stderr, "- Set afs RX level improvement at "
+ "BTS %s to %s\n", test_case[1], test_case[2]);
+ ho_set_hodec2_afs_bias_rxlev(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "afs-rxqual-improve")) {
+ fprintf(stderr, "- Set afs RX quality improvement at "
+ "BTS %s to %s\n", test_case[1], test_case[2]);
+ ho_set_hodec2_afs_bias_rxqual(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "set-min-free")) {
+ fprintf(stderr, "- Setting minimum required free %s "
+ "slots at BTS %s to %s\n", test_case[2],
+ test_case[1], test_case[3]);
+ if (!strcmp(test_case[2], "TCH/F"))
+ ho_set_hodec2_tchf_min_slots(bts[atoi(test_case[1])]->ho, atoi(test_case[3]));
+ else
+ ho_set_hodec2_tchh_min_slots(bts[atoi(test_case[1])]->ho, atoi(test_case[3]));
+ test_case += 4;
+ } else
+ if (!strcmp(*test_case, "set-max-ho")) {
+ fprintf(stderr, "- Setting maximum parallel handovers "
+ "at BTS %s to %s\n", test_case[1],
+ test_case[2]);
+ ho_set_hodec2_ho_max( bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "set-max-ta")) {
+ fprintf(stderr, "- Setting maximum timing advance "
+ "at BTS %s to %s\n", test_case[1],
+ test_case[2]);
+ ho_set_hodec2_max_distance(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "create-ms")) {
+ fprintf(stderr, "- Creating mobile #%d at BTS %s on "
+ "%s with %s codec\n", lchan_num, test_case[1],
+ test_case[2], test_case[3]);
+ lchan[lchan_num] = create_lchan(bts[atoi(test_case[1])],
+ !strcmp(test_case[2], "TCH/F"), test_case[3]);
+ if (!lchan[lchan_num]) {
+ printf("Failed to create lchan!\n");
+ return EXIT_FAILURE;
+ }
+ fprintf(stderr, " * New MS is at BTS %d TS %d\n",
+ lchan[lchan_num]->ts->trx->bts->nr,
+ lchan[lchan_num]->ts->nr);
+ lchan_num++;
+ test_case += 4;
+ } else
+ if (!strcmp(*test_case, "set-ta")) {
+ fprintf(stderr, "- Setting maximum timing advance "
+ "at MS %s to %s\n", test_case[1],
+ test_case[2]);
+ meas_ta_ms = atoi(test_case[2]);
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "meas-rep")) {
+ /* meas-rep <lchan-nr> <rxlev> <rxqual> <nr-of-neighbors> [<cell-idx> <rxlev> [...]] */
+ int n = atoi(test_case[4]);
+ struct gsm_lchan *lc = lchan[atoi(test_case[1])];
+ fprintf(stderr, "- Sending measurement report from "
+ "mobile #%s (rxlev=%s, rxqual=%s)\n",
+ test_case[1], test_case[2], test_case[3]);
+ meas_dl_rxlev = atoi(test_case[2]);
+ meas_dl_rxqual = atoi(test_case[3]);
+ meas_num_nc = n;
+ test_case += 5;
+ for (i = 0; i < n; i++) {
+ int nr = atoi(test_case[0]);
+ /* since our bts is not in the list of neighbor
+ * cells, we need to shift */
+ if (nr >= lc->ts->trx->bts->nr)
+ nr++;
+ fprintf(stderr, " * Neighbor cell #%s, actual "
+ "BTS %d (rxlev=%s)\n", test_case[0], nr,
+ test_case[1]);
+ meas_bcch_f_nc[i] = atoi(test_case[0]);
+ /* bts number, not counting our own */
+ meas_rxlev_nc[i] = atoi(test_case[1]);
+ meas_bsic_nc[i] = 0x3f;
+ test_case += 2;
+ }
+ got_chan_req = 0;
+ gen_meas_rep(lc);
+ } else
+ if (!strcmp(*test_case, "congestion-check")) {
+ fprintf(stderr, "- Triggering congestion check\n");
+ got_chan_req = 0;
+ if (algorithm == 2)
+ hodec2_congestion_check(bsc_gsmnet);
+ test_case += 1;
+ } else
+ if (!strcmp(*test_case, "expect-chan")) {
+ fprintf(stderr, "- Expecting channel request at BTS %s "
+ "TS %s\n", test_case[1], test_case[2]);
+ if (!got_chan_req) {
+ printf("Test failed, because no channel was "
+ "requested\n");
+ return EXIT_FAILURE;
+ }
+ fprintf(stderr, " * Got channel request at BTS %d "
+ "TS %d\n", chan_req_lchan->ts->trx->bts->nr,
+ chan_req_lchan->ts->nr);
+ if (chan_req_lchan->ts->trx->bts->nr
+ != atoi(test_case[1])) {
+ printf("Test failed, because channel was not "
+ "requested on expected BTS\n");
+ return EXIT_FAILURE;
+ }
+ if (chan_req_lchan->ts->nr != atoi(test_case[2])) {
+ printf("Test failed, because channel was not "
+ "requested on expected TS\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "expect-no-chan")) {
+ fprintf(stderr, "- Expecting no channel request\n");
+ if (got_chan_req) {
+ fprintf(stderr, " * Got channel request at "
+ "BTS %d TS %d\n",
+ chan_req_lchan->ts->trx->bts->nr,
+ chan_req_lchan->ts->nr);
+ printf("Test failed, because channel was "
+ "requested\n");
+ return EXIT_FAILURE;
+ }
+ fprintf(stderr, " * Got no channel request\n");
+ test_case += 1;
+ } else
+ if (!strcmp(*test_case, "expect-ho")) {
+ fprintf(stderr, "- Expecting handover/assignment "
+ "request at BTS %s TS %s\n", test_case[1],
+ test_case[2]);
+ if (!got_ho_req) {
+ printf("Test failed, because no handover was "
+ "requested\n");
+ return EXIT_FAILURE;
+ }
+ fprintf(stderr, " * Got handover/assignment request at "
+ "BTS %d TS %d\n",
+ ho_req_lchan->ts->trx->bts->nr,
+ ho_req_lchan->ts->nr);
+ if (ho_req_lchan->ts->trx->bts->nr
+ != atoi(test_case[1])) {
+ printf("Test failed, because "
+ "handover/assignment was not commanded "
+ "at the expected BTS\n");
+ return EXIT_FAILURE;
+ }
+ if (ho_req_lchan->ts->nr != atoi(test_case[2])) {
+ printf("Test failed, because "
+ "handover/assignment was not commanded "
+ "at the expected TS\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "ack-chan")) {
+ fprintf(stderr, "- Acknowledging channel request\n");
+ if (!got_chan_req) {
+ printf("Cannot ack channel, because no "
+ "request\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 1;
+ got_ho_req = 0;
+ send_chan_act_ack(chan_req_lchan, 1);
+ } else
+ if (!strcmp(*test_case, "ho-complete")) {
+ fprintf(stderr, "- Acknowledging handover/assignment "
+ "request\n");
+ if (!got_chan_req) {
+ printf("Cannot ack handover/assignment, "
+ "because no chan request\n");
+ return EXIT_FAILURE;
+ }
+ if (!got_ho_req) {
+ printf("Cannot ack handover/assignment, "
+ "because no ho request\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 1;
+ got_chan_req = 0;
+ got_ho_req = 0;
+ /* switch lchan */
+ for (i = 0; i < lchan_num; i++) {
+ if (lchan[i] == ho_req_lchan) {
+ fprintf(stderr, " * MS %d changes from "
+ "BTS=%d TS=%d to BTS=%d "
+ "TS=%d\n", i,
+ lchan[i]->ts->trx->bts->nr,
+ lchan[i]->ts->nr,
+ chan_req_lchan->ts->trx->bts->nr,
+ chan_req_lchan->ts->nr);
+ lchan[i] = chan_req_lchan;
+ }
+ }
+ send_ho_complete(chan_req_lchan, true);
+ } else
+ if (!strcmp(*test_case, "ho-failed")) {
+ fprintf(stderr, "- Making handover fail\n");
+ if (!got_chan_req) {
+ printf("Cannot fail handover, because no chan "
+ "request\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 1;
+ got_chan_req = 0;
+ got_ho_req = 0;
+ send_ho_complete(ho_req_lchan, false);
+ } else
+ if (!strcmp(*test_case, "print")) {
+ fprintf(stderr, "\n%s\n\n", test_case[1]);
+ test_case += 2;
+ } else {
+ printf("Unknown test command '%s', please fix!\n",
+ *test_case);
+ return EXIT_FAILURE;
+ }
+
+ {
+ /* Help the lchan out of releasing states */
+ struct gsm_bts *bts;
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ int ts_nr;
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ struct gsm_lchan *lchan;
+ ts_for_each_lchan(lchan, &trx->ts[ts_nr]) {
+
+ if (lchan->fi && lchan->fi->state == LCHAN_ST_WAIT_BEFORE_RF_RELEASE) {
+ osmo_fsm_inst_state_chg(lchan->fi, LCHAN_ST_WAIT_RF_RELEASE_ACK, 0, 0);
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RSL_RF_CHAN_REL_ACK, 0);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < lchan_num; i++) {
+ struct gsm_subscriber_connection *conn = lchan[i]->conn;
+ lchan[i]->conn = NULL;
+ conn->lchan = NULL;
+ osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REGULAR, NULL);
+ }
+
+ fprintf(stderr, "--------------------\n");
+
+ printf("Test OK\n");
+
+ fprintf(stderr, "--------------------\n");
+
+ talloc_free(ctx);
+ return EXIT_SUCCESS;
+}
+
+void rtp_socket_free() {}
+void rtp_send_frame() {}
+void rtp_socket_upstream() {}
+void rtp_socket_create() {}
+void rtp_socket_connect() {}
+void rtp_socket_proxy() {}
+void trau_mux_unmap() {}
+void trau_mux_map_lchan() {}
+void trau_recv_lchan() {}
+void trau_send_frame() {}
+int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg) { return 0; }
+int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg) { return 0; }
+void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) {}
+void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_encr) {}
+int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, uint16_t chosen_channel)
+{ return 0; }
+void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) {}
+void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause) {}
+int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{ return 0; }
+void bsc_cm_update(struct gsm_subscriber_connection *conn,
+ const uint8_t *cm2, uint8_t cm2_len,
+ const uint8_t *cm3, uint8_t cm3_len) {}
+struct gsm0808_handover_required;
+int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell_id_list2 *target_cells)
+{ return 0; }
+int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn, struct msgb *rr_ho_command)
+{ return 0; }
+int bsc_tx_bssmap_ho_detect(struct gsm_subscriber_connection *conn) { return 0; }
+enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection *conn,
+ struct gsm_lchan *lchan) { return HO_RESULT_OK; }
+void bsc_tx_bssmap_ho_failure(struct gsm_subscriber_connection *conn) {}
diff --git a/tests/handover/handover_test.ok b/tests/handover/handover_test.ok
new file mode 100644
index 000000000..678f9a34e
--- /dev/null
+++ b/tests/handover/handover_test.ok
@@ -0,0 +1 @@
+Test OK
diff --git a/tests/handover/neighbor_ident_test.c b/tests/handover/neighbor_ident_test.c
new file mode 100644
index 000000000..9acbea035
--- /dev/null
+++ b/tests/handover/neighbor_ident_test.c
@@ -0,0 +1,270 @@
+/* Test the neighbor_ident.h API */
+/*
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <talloc.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/bsc/neighbor_ident.h>
+
+static struct neighbor_ident_list *nil;
+
+static const struct neighbor_ident_key *k(int from_bts, uint16_t arfcn, uint8_t bsic)
+{
+ static struct neighbor_ident_key key;
+ key = (struct neighbor_ident_key){
+ .from_bts = from_bts,
+ .arfcn = arfcn,
+ .bsic = bsic,
+ };
+ return &key;
+}
+
+static const struct gsm0808_cell_id_list2 cgi1 = {
+ .id_discr = CELL_IDENT_WHOLE_GLOBAL,
+ .id_list_len = 1,
+ .id_list = {
+ {
+ .global = {
+ .lai = {
+ .plmn = { .mcc = 1, .mnc = 2, .mnc_3_digits = false },
+ .lac = 3,
+ },
+ .cell_identity = 4,
+ }
+ },
+ },
+};
+
+static const struct gsm0808_cell_id_list2 cgi2 = {
+ .id_discr = CELL_IDENT_WHOLE_GLOBAL,
+ .id_list_len = 2,
+ .id_list = {
+ {
+ .global = {
+ .lai = {
+ .plmn = { .mcc = 1, .mnc = 2, .mnc_3_digits = false },
+ .lac = 3,
+ },
+ .cell_identity = 4,
+ }
+ },
+ {
+ .global = {
+ .lai = {
+ .plmn = { .mcc = 5, .mnc = 6, .mnc_3_digits = true },
+ .lac = 7,
+ },
+ .cell_identity = 8,
+ }
+ },
+ },
+};
+
+static const struct gsm0808_cell_id_list2 lac1 = {
+ .id_discr = CELL_IDENT_LAC,
+ .id_list_len = 1,
+ .id_list = {
+ {
+ .lac = 123
+ },
+ },
+};
+
+static const struct gsm0808_cell_id_list2 lac2 = {
+ .id_discr = CELL_IDENT_LAC,
+ .id_list_len = 2,
+ .id_list = {
+ {
+ .lac = 456
+ },
+ {
+ .lac = 789
+ },
+ },
+};
+
+static void print_cil(const struct gsm0808_cell_id_list2 *cil)
+{
+ unsigned int i;
+ if (!cil) {
+ printf(" cell_id_list == NULL\n");
+ return;
+ }
+ switch (cil->id_discr) {
+ case CELL_IDENT_WHOLE_GLOBAL:
+ printf(" cell_id_list cgi[%u] = {\n", cil->id_list_len);
+ for (i = 0; i < cil->id_list_len; i++)
+ printf(" %2d: %s\n", i, osmo_cgi_name(&cil->id_list[i].global));
+ printf(" }\n");
+ break;
+ case CELL_IDENT_LAC:
+ printf(" cell_id_list lac[%u] = {\n", cil->id_list_len);
+ for (i = 0; i < cil->id_list_len; i++)
+ printf(" %2d: %u\n", i, cil->id_list[i].lac);
+ printf(" }\n");
+ break;
+ default:
+ printf(" Unimplemented id_disc\n");
+ }
+}
+
+static int print_nil_i;
+
+static bool nil_cb(const struct neighbor_ident_key *key, const struct gsm0808_cell_id_list2 *val,
+ void *cb_data)
+{
+ printf(" %2d: %s\n", print_nil_i++, neighbor_ident_key_name(key));
+ print_cil(val);
+ return true;
+}
+
+static void print_nil()
+{
+ print_nil_i = 0;
+ neighbor_ident_iter(nil, nil_cb, NULL);
+ if (!print_nil_i)
+ printf(" (empty)\n");
+}
+
+#define check_add(key, val, expect_rc) \
+ do { \
+ int rc; \
+ rc = neighbor_ident_add(nil, key, val); \
+ printf("neighbor_ident_add(" #key ", " #val ") --> expect rc=" #expect_rc ", got %d\n", rc); \
+ if (rc != expect_rc) \
+ printf("ERROR\n"); \
+ print_nil(); \
+ } while(0)
+
+#define check_del(key, expect_rc) \
+ do { \
+ bool rc; \
+ rc = neighbor_ident_del(nil, key); \
+ printf("neighbor_ident_del(" #key ") --> %s\n", rc ? "entry deleted" : "nothing deleted"); \
+ if (rc != expect_rc) \
+ printf("ERROR: expected: %s\n", expect_rc ? "entry deleted" : "nothing deleted"); \
+ print_nil(); \
+ } while(0)
+
+#define check_get(key, expect_rc) \
+ do { \
+ const struct gsm0808_cell_id_list2 *rc; \
+ rc = neighbor_ident_get(nil, key); \
+ printf("neighbor_ident_get(" #key ") --> %s\n", \
+ rc ? "entry returned" : "NULL"); \
+ if (((bool)expect_rc) != ((bool) rc)) \
+ printf("ERROR: expected %s\n", expect_rc ? "an entry" : "NULL"); \
+ if (rc) \
+ print_cil(rc); \
+ } while(0)
+
+int main(void)
+{
+ void *ctx = talloc_named_const(NULL, 0, "neighbor_ident_test");
+
+ printf("\n--- testing NULL neighbor_ident_list\n");
+ nil = NULL;
+ check_add(k(0, 1, 2), &cgi1, -ENOMEM);
+ check_get(k(0, 1, 2), false);
+ check_del(k(0, 1, 2), false);
+
+ printf("\n--- adding entries, test that no two identical entries are added\n");
+ nil = neighbor_ident_init(ctx);
+ check_add(k(0, 1, 2), &cgi1, 1);
+ check_get(k(0, 1, 2), true);
+ check_add(k(0, 1, 2), &cgi1, 1);
+ check_add(k(0, 1, 2), &cgi2, 2);
+ check_add(k(0, 1, 2), &cgi2, 2);
+ check_del(k(0, 1, 2), true);
+
+ printf("\n--- Cannot mix cell identifier types for one entry\n");
+ check_add(k(0, 1, 2), &cgi1, 1);
+ check_add(k(0, 1, 2), &lac1, -EINVAL);
+ check_del(k(0, 1, 2), true);
+ neighbor_ident_free(nil);
+
+ printf("\n--- BTS matching: specific BTS is stronger\n");
+ nil = neighbor_ident_init(ctx);
+ check_add(k(NEIGHBOR_IDENT_KEY_ANY_BTS, 1, 2), &lac1, 1);
+ check_add(k(3, 1, 2), &lac2, 2);
+ check_get(k(2, 1, 2), true);
+ check_get(k(3, 1, 2), true);
+ check_get(k(4, 1, 2), true);
+ check_get(k(NEIGHBOR_IDENT_KEY_ANY_BTS, 1, 2), true);
+ neighbor_ident_free(nil);
+
+ printf("\n--- BSIC matching: 6bit and 9bit are different realms, and wildcard match is weaker\n");
+ nil = neighbor_ident_init(ctx);
+ check_add(k(0, 1, BSIC_ANY), &cgi1, 1);
+ check_add(k(0, 1, 2), &lac1, 1);
+ check_add(k(0, 1, 2), &lac2, 2);
+ check_get(k(0, 1, 2), true);
+ check_get(k(0, 1, 2), true);
+ neighbor_ident_free(nil);
+
+ printf("\n--- Value ranges\n");
+ nil = neighbor_ident_init(ctx);
+ check_add(k(0, 6, 1 << 6), &lac1, -ERANGE);
+ check_add(k(0, 6, BSIC_ANY - 1), &lac1, -ERANGE);
+ check_add(k(NEIGHBOR_IDENT_KEY_ANY_BTS - 1, 1, BSIC_ANY), &cgi2, -ERANGE);
+ check_add(k(256, 1, BSIC_ANY), &cgi2, -ERANGE);
+ check_add(k(0, 0, BSIC_ANY), &cgi1, 1);
+ check_add(k(255, 65535, BSIC_ANY), &lac1, 1);
+ check_add(k(0, 0, 0), &cgi2, 2);
+ check_add(k(255, 65535, 0x3f), &lac2, 2);
+
+ neighbor_ident_free(nil);
+
+ printf("\n--- size limits\n");
+ {
+ int i;
+ struct gsm0808_cell_id_list2 a = { .id_discr = CELL_IDENT_LAC };
+ struct gsm0808_cell_id_list2 b = {
+ .id_discr = CELL_IDENT_LAC,
+ .id_list = {
+ { .lac = 423 }
+ },
+ .id_list_len = 1,
+ };
+ for (i = 0; i < ARRAY_SIZE(a.id_list); i++) {
+ a.id_list[a.id_list_len ++].lac = i;
+ }
+
+ nil = neighbor_ident_init(ctx);
+
+ i = neighbor_ident_add(nil, k(0, 1, 2), &a);
+ printf("Added first cell identifier list (added %u) --> rc = %d\n", a.id_list_len, i);
+ i = neighbor_ident_add(nil, k(0, 1, 2), &b);
+ printf("Added second cell identifier list (tried to add %u) --> rc = %d\n", b.id_list_len, i);
+ if (i != -ENOSPC)
+ printf("ERROR: expected rc=%d\n", -ENOSPC);
+ neighbor_ident_free(nil);
+ }
+
+ OSMO_ASSERT(talloc_total_blocks(ctx) == 1);
+ talloc_free(ctx);
+
+ return 0;
+}
diff --git a/tests/handover/neighbor_ident_test.err b/tests/handover/neighbor_ident_test.err
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/handover/neighbor_ident_test.err
diff --git a/tests/handover/neighbor_ident_test.ok b/tests/handover/neighbor_ident_test.ok
new file mode 100644
index 000000000..961a33cdc
--- /dev/null
+++ b/tests/handover/neighbor_ident_test.ok
@@ -0,0 +1,186 @@
+
+--- testing NULL neighbor_ident_list
+neighbor_ident_add(k(0, 1, 2), &cgi1) --> expect rc=-ENOMEM, got -12
+ (empty)
+neighbor_ident_get(k(0, 1, 2)) --> NULL
+neighbor_ident_del(k(0, 1, 2)) --> nothing deleted
+ (empty)
+
+--- adding entries, test that no two identical entries are added
+neighbor_ident_add(k(0, 1, 2), &cgi1) --> expect rc=1, got 1
+ 0: BTS 0 to ARFCN 1 BSIC 2
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+neighbor_ident_get(k(0, 1, 2)) --> entry returned
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+neighbor_ident_add(k(0, 1, 2), &cgi1) --> expect rc=1, got 1
+ 0: BTS 0 to ARFCN 1 BSIC 2
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+neighbor_ident_add(k(0, 1, 2), &cgi2) --> expect rc=2, got 2
+ 0: BTS 0 to ARFCN 1 BSIC 2
+ cell_id_list cgi[2] = {
+ 0: 001-02-3-4
+ 1: 005-006-7-8
+ }
+neighbor_ident_add(k(0, 1, 2), &cgi2) --> expect rc=2, got 2
+ 0: BTS 0 to ARFCN 1 BSIC 2
+ cell_id_list cgi[2] = {
+ 0: 001-02-3-4
+ 1: 005-006-7-8
+ }
+neighbor_ident_del(k(0, 1, 2)) --> entry deleted
+ (empty)
+
+--- Cannot mix cell identifier types for one entry
+neighbor_ident_add(k(0, 1, 2), &cgi1) --> expect rc=1, got 1
+ 0: BTS 0 to ARFCN 1 BSIC 2
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+neighbor_ident_add(k(0, 1, 2), &lac1) --> expect rc=-EINVAL, got -22
+ 0: BTS 0 to ARFCN 1 BSIC 2
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+neighbor_ident_del(k(0, 1, 2)) --> entry deleted
+ (empty)
+
+--- BTS matching: specific BTS is stronger
+neighbor_ident_add(k(NEIGHBOR_IDENT_KEY_ANY_BTS, 1, 2), &lac1) --> expect rc=1, got 1
+ 0: BTS * to ARFCN 1 BSIC 2
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+neighbor_ident_add(k(3, 1, 2), &lac2) --> expect rc=2, got 2
+ 0: BTS * to ARFCN 1 BSIC 2
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+ 1: BTS 3 to ARFCN 1 BSIC 2
+ cell_id_list lac[2] = {
+ 0: 456
+ 1: 789
+ }
+neighbor_ident_get(k(2, 1, 2)) --> entry returned
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+neighbor_ident_get(k(3, 1, 2)) --> entry returned
+ cell_id_list lac[2] = {
+ 0: 456
+ 1: 789
+ }
+neighbor_ident_get(k(4, 1, 2)) --> entry returned
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+neighbor_ident_get(k(NEIGHBOR_IDENT_KEY_ANY_BTS, 1, 2)) --> entry returned
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+
+--- BSIC matching: 6bit and 9bit are different realms, and wildcard match is weaker
+neighbor_ident_add(k(0, 1, BSIC_ANY), &cgi1) --> expect rc=1, got 1
+ 0: BTS 0 to ARFCN 1 (any BSIC)
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+neighbor_ident_add(k(0, 1, 2), &lac1) --> expect rc=1, got 1
+ 0: BTS 0 to ARFCN 1 (any BSIC)
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+ 1: BTS 0 to ARFCN 1 BSIC 2
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+neighbor_ident_add(k(0, 1, 2), &lac2) --> expect rc=2, got 3
+ERROR
+ 0: BTS 0 to ARFCN 1 (any BSIC)
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+ 1: BTS 0 to ARFCN 1 BSIC 2
+ cell_id_list lac[3] = {
+ 0: 123
+ 1: 456
+ 2: 789
+ }
+neighbor_ident_get(k(0, 1, 2)) --> entry returned
+ cell_id_list lac[3] = {
+ 0: 123
+ 1: 456
+ 2: 789
+ }
+neighbor_ident_get(k(0, 1, 2)) --> entry returned
+ cell_id_list lac[3] = {
+ 0: 123
+ 1: 456
+ 2: 789
+ }
+
+--- Value ranges
+neighbor_ident_add(k(0, 6, 1 << 6), &lac1) --> expect rc=-ERANGE, got -34
+ (empty)
+neighbor_ident_add(k(0, 6, BSIC_ANY - 1), &lac1) --> expect rc=-ERANGE, got -34
+ (empty)
+neighbor_ident_add(k(NEIGHBOR_IDENT_KEY_ANY_BTS - 1, 1, BSIC_ANY), &cgi2) --> expect rc=-ERANGE, got -34
+ (empty)
+neighbor_ident_add(k(256, 1, BSIC_ANY), &cgi2) --> expect rc=-ERANGE, got -34
+ (empty)
+neighbor_ident_add(k(0, 0, BSIC_ANY), &cgi1) --> expect rc=1, got 1
+ 0: BTS 0 to ARFCN 0 (any BSIC)
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+neighbor_ident_add(k(255, 65535, BSIC_ANY), &lac1) --> expect rc=1, got 1
+ 0: BTS 0 to ARFCN 0 (any BSIC)
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+ 1: BTS 255 to ARFCN 65535 (any BSIC)
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+neighbor_ident_add(k(0, 0, 0), &cgi2) --> expect rc=2, got 2
+ 0: BTS 0 to ARFCN 0 (any BSIC)
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+ 1: BTS 255 to ARFCN 65535 (any BSIC)
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+ 2: BTS 0 to ARFCN 0 BSIC 0
+ cell_id_list cgi[2] = {
+ 0: 001-02-3-4
+ 1: 005-006-7-8
+ }
+neighbor_ident_add(k(255, 65535, 0x3f), &lac2) --> expect rc=2, got 2
+ 0: BTS 0 to ARFCN 0 (any BSIC)
+ cell_id_list cgi[1] = {
+ 0: 001-02-3-4
+ }
+ 1: BTS 255 to ARFCN 65535 (any BSIC)
+ cell_id_list lac[1] = {
+ 0: 123
+ }
+ 2: BTS 0 to ARFCN 0 BSIC 0
+ cell_id_list cgi[2] = {
+ 0: 001-02-3-4
+ 1: 005-006-7-8
+ }
+ 3: BTS 255 to ARFCN 65535 BSIC 63
+ cell_id_list lac[2] = {
+ 0: 456
+ 1: 789
+ }
+
+--- size limits
+Added first cell identifier list (added 127) --> rc = 127
+Added second cell identifier list (tried to add 1) --> rc = -28
diff --git a/tests/handover_cfg.vty b/tests/handover_cfg.vty
new file mode 100644
index 000000000..94c20d962
--- /dev/null
+++ b/tests/handover_cfg.vty
@@ -0,0 +1,622 @@
+OsmoBSC> show network
+...
+ Handover: Off
+...
+OsmoBSC> enable
+
+OsmoBSC# ### No handover config present
+OsmoBSC# show running-config
+... !handover
+
+OsmoBSC# ### Toggling handover on network level affects 'show network':
+OsmoBSC# configure terminal
+OsmoBSC(config)# network
+OsmoBSC(config-net)# do show network
+...
+ Handover: Off
+...
+OsmoBSC(config-net)# handover 1
+OsmoBSC(config-net)# do show network
+...
+ Handover: On
+...
+
+OsmoBSC(config-net)# ### If network level default is 'on', bts level can still override to 'off':
+OsmoBSC(config-net)# bts 0
+OsmoBSC(config-net-bts)# handover 0
+OsmoBSC(config-net-bts)# do show network
+...
+ Handover: Off
+...
+OsmoBSC(config-net-bts)# exit
+
+OsmoBSC(config-net)# ### Create a *second* BTS that is not explicitly 'off':
+OsmoBSC(config-net)# bts 1
+OsmoBSC(config-net-bts)# do show network
+...
+ Handover: On at 1 BTS, Off at 1 BTS
+...
+
+OsmoBSC(config-net-bts)# ### Add arbitrary handover config item for bts 1:
+OsmoBSC(config-net-bts)# handover1 power budget interval 23
+OsmoBSC(config-net-bts)# exit
+OsmoBSC(config-net)# ### HO is 'on' globally, bts 0 disables it, bts 1 tweaks a param:
+OsmoBSC(config-net)# show running-config
+...
+network
+... !handover
+ handover 1
+... !handover
+ bts 0
+... !handover
+ handover 0
+... !handover
+ bts 1
+... !handover
+ handover1 power budget interval 23
+... !handover
+
+OsmoBSC(config-net)# ### Set global default to 'off', now bts 1 also uses the global default of 'off':
+OsmoBSC(config-net)# handover 0
+OsmoBSC(config-net)# do show network
+...
+ Handover: Off
+...
+OsmoBSC(config-net)# show running-config
+...
+network
+... !handover
+ handover 0
+... !handover
+ bts 0
+... !handover
+ handover 0
+... !handover
+ bts 1
+... !handover
+ handover1 power budget interval 23
+... !handover
+
+OsmoBSC(config-net)# ### Remove the global setting, i.e. use the factory default net level, with same effect:
+OsmoBSC(config-net)# handover default
+% 'handover' setting removed, now is 0
+OsmoBSC(config-net)# handover default
+% 'handover' already was unset, still is 0
+OsmoBSC(config-net)# do show network
+...
+ Handover: Off
+...
+OsmoBSC(config-net)# show running-config
+...
+network
+... !handover
+ bts 0
+... !handover
+ handover 0
+... !handover
+ bts 1
+... !handover
+ handover1 power budget interval 23
+... !handover
+
+OsmoBSC(config-net)# ### Re-enable net-level handover, but bts 0 remains disabled explicitly
+OsmoBSC(config-net)# handover 1
+OsmoBSC(config-net)# do show network
+...
+ Handover: On at 1 BTS, Off at 1 BTS
+...
+OsmoBSC(config-net)# show running-config
+...
+network
+... !handover
+ handover 1
+... !handover
+ bts 0
+... !handover
+ handover 0
+... !handover
+ bts 1
+... !handover
+ handover1 power budget interval 23
+... !handover
+
+OsmoBSC(config-net)# ### Remove explicit setting of bts 0 to also use the global setting:
+OsmoBSC(config-net)# bts 0
+OsmoBSC(config-net-bts)# handover default
+% 'handover' setting removed, now is 1 (set on higher level node)
+OsmoBSC(config-net-bts)# handover default
+% 'handover' already was unset, still is 1 (set on higher level node)
+OsmoBSC(config-net-bts)# do show network
+...
+ Handover: On
+...
+OsmoBSC(config-net-bts)# show running-config
+...
+network
+... !handover
+ handover 1
+... !handover
+ bts 0
+... !handover
+ bts 1
+... !handover
+ handover1 power budget interval 23
+... !handover
+
+OsmoBSC(config-net-bts)# ### Verify that 'min rxlev' value range stops at -50
+OsmoBSC(config-net-bts)# handover2 min rxlev ?
+ <-110--50> minimum RxLev (dBm)
+ default Use default (-100), remove explicit setting on this node
+OsmoBSC(config-net-bts)# handover2 min rxlev -111
+% Unknown command.
+OsmoBSC(config-net-bts)# handover2 min rxlev -110
+OsmoBSC(config-net-bts)# handover2 min rxlev -50
+OsmoBSC(config-net-bts)# handover2 min rxlev -49
+% Unknown command.
+OsmoBSC(config-net-bts)# handover2 min rxlev 50
+% Unknown command.
+OsmoBSC(config-net-bts)# handover2 min rxlev default
+% 'handover2 min rxlev' setting removed, now is -100
+
+
+OsmoBSC(config-net-bts)# ### Checking online help
+OsmoBSC(config-net-bts)# exit
+OsmoBSC(config-net)# list
+...
+ handover (0|1|default)
+ handover algorithm (1|2|default)
+ handover1 window rxlev averaging (<1-10>|default)
+ handover1 window rxqual averaging (<1-10>|default)
+ handover1 window rxlev neighbor averaging (<1-10>|default)
+ handover1 power budget interval (<1-99>|default)
+ handover1 power budget hysteresis (<0-999>|default)
+ handover1 maximum distance (<0-9999>|default)
+ handover2 window rxlev averaging (<1-10>|default)
+ handover2 window rxqual averaging (<1-10>|default)
+ handover2 window rxlev neighbor averaging (<1-10>|default)
+ handover2 power budget interval (<1-99>|default)
+ handover2 power budget hysteresis (<0-999>|default)
+ handover2 maximum distance (<0-9999>|default)
+ handover2 assignment (0|1|default)
+ handover2 tdma-measurement (full|subset|default)
+ handover2 min rxlev (<-110--50>|default)
+ handover2 min rxqual (<0-7>|default)
+ handover2 afs-bias rxlev (<0-20>|default)
+ handover2 afs-bias rxqual (<0-7>|default)
+ handover2 min-free-slots tch/f (<0-9999>|default)
+ handover2 min-free-slots tch/h (<0-9999>|default)
+ handover2 max-handovers (<1-9999>|default)
+ handover2 penalty-time max-distance (<0-99999>|default)
+ handover2 penalty-time failed-ho (<0-99999>|default)
+ handover2 penalty-time failed-assignment (<0-99999>|default)
+ handover2 retries (<0-9>|default)
+ handover2 congestion-check (disabled|<1-999>|now)
+...
+
+OsmoBSC(config-net)# handover?
+ handover Handover general config
+
+OsmoBSC(config-net)# handover1?
+ handover1 Handover options for handover decision algorithm 1
+
+OsmoBSC(config-net)# handover2?
+ handover2 Handover options for handover decision algorithm 2
+
+OsmoBSC(config-net)# handover ?
+ 0 Disable in-call handover
+ 1 Enable in-call handover
+ default Enable/disable handover: Use default (0), remove explicit setting on this node
+ algorithm Choose algorithm for handover decision
+...
+
+OsmoBSC(config-net)# handover1 ?
+ window Measurement averaging settings
+ power Neighbor cell power triggering
+ maximum Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net)# handover2 ?
+ window Measurement averaging settings
+ power Neighbor cell power triggering
+ maximum Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+ assignment Enable or disable in-call channel re-assignment
+ tdma-measurement Define measurement set of TDMA frames
+ min Minimum Level/Quality thresholds before triggering HO
+ afs-bias Configure bias to prefer AFS (AMR on TCH/F) over other codecs
+ min-free-slots Minimum free TCH timeslots before cell is considered congested
+ max-handovers Maximum number of concurrent handovers allowed per cell
+ penalty-time Set penalty times to wait between repeated handovers
+ retries Immediately retry on handover/assignment failure
+ congestion-check Configure congestion check interval
+
+OsmoBSC(config-net)# handover algorithm ?
+ 1 Algorithm 1: trigger handover based on comparing current cell and neighbor RxLev and RxQual, only.
+ 2 Algorithm 2: trigger handover on RxLev/RxQual, and also to balance the load across several cells. Consider available codecs. Prevent repeated handover by penalty timers.
+ default Use default (1), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover1 window ?
+ rxlev Received-Level averaging
+ rxqual Received-Quality averaging
+
+OsmoBSC(config-net)# handover1 window rxlev ?
+ averaging How many RxLev measurements are used for averaging
+ neighbor How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net)# handover1 window rxlev averaging ?
+ <1-10> RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover1 window rxlev neighbor ?
+ averaging How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net)# handover1 window rxlev neighbor averaging ?
+ <1-10> Neighbor RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover1 window rxqual ?
+ averaging How many RxQual measurements are used for averaging
+
+OsmoBSC(config-net)# handover1 window rxqual averaging ?
+ <1-10> RxQual averaging: Number of values to average over
+ default Use default (1), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover1 power ?
+ budget Neighbor cell power triggering
+
+OsmoBSC(config-net)# handover1 power budget ?
+ interval How often to check for a better cell (SACCH frames)
+ hysteresis How many dB stronger must a neighbor be to become a HO candidate
+
+OsmoBSC(config-net)# handover1 power budget interval ?
+ <1-99> Check for stronger neighbor every N number of SACCH frames
+ default Use default (6), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover1 power budget hysteresis ?
+ <0-999> Neighbor's strength difference in dB
+ default Use default (3), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover1 maximum ?
+ distance Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net)# handover1 maximum distance ?
+ <0-9999> Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+ default Use default (9999), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 window ?
+ rxlev Received-Level averaging
+ rxqual Received-Quality averaging
+
+OsmoBSC(config-net)# handover2 window rxlev ?
+ averaging How many RxLev measurements are used for averaging
+ neighbor How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net)# handover2 window rxlev averaging ?
+ <1-10> RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 window rxlev neighbor ?
+ averaging How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net)# handover2 window rxlev neighbor averaging ?
+ <1-10> Neighbor RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 window rxqual ?
+ averaging How many RxQual measurements are used for averaging
+
+OsmoBSC(config-net)# handover2 window rxqual averaging ?
+ <1-10> RxQual averaging: Number of values to average over
+ default Use default (1), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 power ?
+ budget Neighbor cell power triggering
+
+OsmoBSC(config-net)# handover2 power budget ?
+ interval How often to check for a better cell (SACCH frames)
+ hysteresis How many dB stronger must a neighbor be to become a HO candidate
+
+OsmoBSC(config-net)# handover2 power budget interval ?
+ <1-99> Check for stronger neighbor every N number of SACCH frames
+ default Use default (6), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 power budget hysteresis ?
+ <0-999> Neighbor's strength difference in dB
+ default Use default (3), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 maximum ?
+ distance Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net)# handover2 maximum distance ?
+ <0-9999> Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+ default Use default (9999), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 assignment ?
+ 0 Disable in-call assignment
+ 1 Enable in-call assignment
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 tdma-measurement ?
+ full Full set of 102/104 TDMA frames
+ subset Sub set of 4 TDMA frames (SACCH)
+ default Use default (subset), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 min ?
+ rxlev How weak may RxLev of an MS become before triggering HO
+ rxqual How bad may RxQual of an MS become before triggering HO
+
+OsmoBSC(config-net)# handover2 min rxlev ?
+ <-110--50> minimum RxLev (dBm)
+ default Use default (-100), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 min rxqual ?
+ <0-7> minimum RxQual
+ default Use default (5), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 afs-bias ?
+ rxlev RxLev improvement bias for AFS over other codecs
+ rxqual RxQual improvement bias for AFS over other codecs
+
+OsmoBSC(config-net)# handover2 afs-bias rxlev ?
+ <0-20> Virtual RxLev improvement (dB)
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 afs-bias rxqual ?
+ <0-7> Virtual RxQual improvement
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 min-free-slots ?
+ tch/f Minimum free TCH/F timeslots before cell is considered congested
+ tch/h Minimum free TCH/H timeslots before cell is considered congested
+
+OsmoBSC(config-net)# handover2 min-free-slots tch/f ?
+ <0-9999> Number of TCH/F slots
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 min-free-slots TCH/F ?
+% There is no matched command.
+
+OsmoBSC(config-net)# handover2 min-free-slots tch/h ?
+ <0-9999> Number of TCH/H slots
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 max-handovers ?
+ <1-9999> Number
+ default Use default (9999), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 penalty-time ?
+ max-distance Time to suspend handovers after leaving this cell due to exceeding max distance
+ failed-ho Time to suspend handovers after handover failure to this cell
+ failed-assignment Time to suspend handovers after assignment failure in this cell
+
+OsmoBSC(config-net)# handover2 penalty-time max-distance ?
+ <0-99999> Seconds
+ default Use default (300), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 penalty-time failed-ho ?
+ <0-99999> Seconds
+ default Use default (60), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 penalty-time failed-assignment ?
+ <0-99999> Seconds
+ default Use default (60), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 retries ?
+ <0-9> Number of retries
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover2 congestion-check ?
+ disabled Disable congestion checking, do not handover based on cell overload
+ <1-999> Congestion check interval in seconds (default 10)
+ now Manually trigger a congestion check to run right now
+
+
+OsmoBSC(config-net)# ### Same on BTS level, except for the congestion-check
+OsmoBSC(config-net)# bts 0
+
+OsmoBSC(config-net-bts)# handover?
+ handover Handover general config
+
+OsmoBSC(config-net-bts)# handover1?
+ handover1 Handover options for handover decision algorithm 1
+
+OsmoBSC(config-net-bts)# handover2?
+ handover2 Handover options for handover decision algorithm 2
+
+OsmoBSC(config-net-bts)# handover ?
+ 0 Disable in-call handover
+ 1 Enable in-call handover
+ default Enable/disable handover: Use default (0), remove explicit setting on this node
+ algorithm Choose algorithm for handover decision
+...
+
+OsmoBSC(config-net-bts)# handover1 ?
+ window Measurement averaging settings
+ power Neighbor cell power triggering
+ maximum Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net-bts)# handover2 ?
+ window Measurement averaging settings
+ power Neighbor cell power triggering
+ maximum Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+ assignment Enable or disable in-call channel re-assignment
+ tdma-measurement Define measurement set of TDMA frames
+ min Minimum Level/Quality thresholds before triggering HO
+ afs-bias Configure bias to prefer AFS (AMR on TCH/F) over other codecs
+ min-free-slots Minimum free TCH timeslots before cell is considered congested
+ max-handovers Maximum number of concurrent handovers allowed per cell
+ penalty-time Set penalty times to wait between repeated handovers
+ retries Immediately retry on handover/assignment failure
+
+OsmoBSC(config-net-bts)# handover algorithm ?
+ 1 Algorithm 1: trigger handover based on comparing current cell and neighbor RxLev and RxQual, only.
+ 2 Algorithm 2: trigger handover on RxLev/RxQual, and also to balance the load across several cells. Consider available codecs. Prevent repeated handover by penalty timers.
+ default Use default (1), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover1 window ?
+ rxlev Received-Level averaging
+ rxqual Received-Quality averaging
+
+OsmoBSC(config-net-bts)# handover1 window rxlev ?
+ averaging How many RxLev measurements are used for averaging
+ neighbor How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover1 window rxlev averaging ?
+ <1-10> RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover1 window rxlev neighbor ?
+ averaging How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover1 window rxlev neighbor averaging ?
+ <1-10> Neighbor RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover1 window rxqual ?
+ averaging How many RxQual measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover1 window rxqual averaging ?
+ <1-10> RxQual averaging: Number of values to average over
+ default Use default (1), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover1 power ?
+ budget Neighbor cell power triggering
+
+OsmoBSC(config-net-bts)# handover1 power budget ?
+ interval How often to check for a better cell (SACCH frames)
+ hysteresis How many dB stronger must a neighbor be to become a HO candidate
+
+OsmoBSC(config-net-bts)# handover1 power budget interval ?
+ <1-99> Check for stronger neighbor every N number of SACCH frames
+ default Use default (6), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover1 power budget hysteresis ?
+ <0-999> Neighbor's strength difference in dB
+ default Use default (3), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover1 maximum ?
+ distance Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net-bts)# handover1 maximum distance ?
+ <0-9999> Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+ default Use default (9999), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 window ?
+ rxlev Received-Level averaging
+ rxqual Received-Quality averaging
+
+OsmoBSC(config-net-bts)# handover2 window rxlev ?
+ averaging How many RxLev measurements are used for averaging
+ neighbor How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover2 window rxlev averaging ?
+ <1-10> RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 window rxlev neighbor ?
+ averaging How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover2 window rxlev neighbor averaging ?
+ <1-10> Neighbor RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 window rxqual ?
+ averaging How many RxQual measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover2 window rxqual averaging ?
+ <1-10> RxQual averaging: Number of values to average over
+ default Use default (1), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 power ?
+ budget Neighbor cell power triggering
+
+OsmoBSC(config-net-bts)# handover2 power budget ?
+ interval How often to check for a better cell (SACCH frames)
+ hysteresis How many dB stronger must a neighbor be to become a HO candidate
+
+OsmoBSC(config-net-bts)# handover2 power budget interval ?
+ <1-99> Check for stronger neighbor every N number of SACCH frames
+ default Use default (6), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 power budget hysteresis ?
+ <0-999> Neighbor's strength difference in dB
+ default Use default (3), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 maximum ?
+ distance Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net-bts)# handover2 maximum distance ?
+ <0-9999> Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+ default Use default (9999), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 assignment ?
+ 0 Disable in-call assignment
+ 1 Enable in-call assignment
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 tdma-measurement ?
+ full Full set of 102/104 TDMA frames
+ subset Sub set of 4 TDMA frames (SACCH)
+ default Use default (subset), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 min ?
+ rxlev How weak may RxLev of an MS become before triggering HO
+ rxqual How bad may RxQual of an MS become before triggering HO
+
+OsmoBSC(config-net-bts)# handover2 min rxlev ?
+ <-110--50> minimum RxLev (dBm)
+ default Use default (-100), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 min rxqual ?
+ <0-7> minimum RxQual
+ default Use default (5), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 afs-bias ?
+ rxlev RxLev improvement bias for AFS over other codecs
+ rxqual RxQual improvement bias for AFS over other codecs
+
+OsmoBSC(config-net-bts)# handover2 afs-bias rxlev ?
+ <0-20> Virtual RxLev improvement (dB)
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 afs-bias rxqual ?
+ <0-7> Virtual RxQual improvement
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 min-free-slots ?
+ tch/f Minimum free TCH/F timeslots before cell is considered congested
+ tch/h Minimum free TCH/H timeslots before cell is considered congested
+
+OsmoBSC(config-net-bts)# handover2 min-free-slots tch/f ?
+ <0-9999> Number of TCH/F slots
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 min-free-slots TCH/F ?
+% There is no matched command.
+
+OsmoBSC(config-net-bts)# handover2 min-free-slots tch/h ?
+ <0-9999> Number of TCH/H slots
+ default Use default (0), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 max-handovers ?
+ <1-9999> Number
+ default Use default (9999), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 penalty-time ?
+ max-distance Time to suspend handovers after leaving this cell due to exceeding max distance
+ failed-ho Time to suspend handovers after handover failure to this cell
+ failed-assignment Time to suspend handovers after assignment failure in this cell
+
+OsmoBSC(config-net-bts)# handover2 penalty-time max-distance ?
+ <0-99999> Seconds
+ default Use default (300), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 penalty-time failed-ho ?
+ <0-99999> Seconds
+ default Use default (60), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 penalty-time failed-assignment ?
+ <0-99999> Seconds
+ default Use default (60), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover2 retries ?
+ <0-9> Number of retries
+ default Use default (0), remove explicit setting on this node
diff --git a/tests/nanobts_omlattr/Makefile.am b/tests/nanobts_omlattr/Makefile.am
new file mode 100644
index 000000000..312cf7d93
--- /dev/null
+++ b/tests/nanobts_omlattr/Makefile.am
@@ -0,0 +1,33 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ nanobts_omlattr_test \
+ $(NULL)
+
+EXTRA_DIST = \
+ nanobts_omlattr_test.ok \
+ $(NULL)
+
+nanobts_omlattr_test_SOURCES = \
+ nanobts_omlattr_test.c \
+ $(NULL)
+
+nanobts_omlattr_test_LDADD = \
+ $(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 \
+ $(top_builddir)/src/osmo-bsc/gsm_timers.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(NULL)
diff --git a/tests/nanobts_omlattr/nanobts_omlattr_test.c b/tests/nanobts_omlattr/nanobts_omlattr_test.c
new file mode 100644
index 000000000..38729ac35
--- /dev/null
+++ b/tests/nanobts_omlattr/nanobts_omlattr_test.c
@@ -0,0 +1,321 @@
+/* Test OML attribute generator */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/application.h>
+
+#include <stdio.h>
+#include <string.h>
+
+struct gsm_bts_model bts_model_nanobts = {
+ .type = GSM_BTS_TYPE_NANOBTS,
+ .name = "nanobts",
+ .start = NULL,
+ .oml_rcvmsg = NULL,
+ .e1line_bind_ops = NULL,
+ .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},
+ },
+ },
+};
+
+static void test_nanobts_attr_bts_get(struct gsm_bts *bts, uint8_t *expected)
+{
+ struct msgb *msgb;
+
+ printf("Testing nanobts_attr_bts_get()...\n");
+
+ msgb = nanobts_attr_bts_get(bts);
+ printf("result= %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+ printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+ OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+ msgb_free(msgb);
+
+ printf("ok.\n");
+ printf("\n");
+}
+
+static void test_nanobts_attr_nse_get(struct gsm_bts *bts, uint8_t *expected)
+{
+ struct msgb *msgb;
+
+ printf("Testing nanobts_attr_nse_get()...\n");
+
+ msgb = nanobts_attr_nse_get(bts);
+ printf("result= %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+ printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+ OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+ msgb_free(msgb);
+
+ printf("ok.\n");
+ printf("\n");
+}
+
+static void test_nanobts_attr_cell_get(struct gsm_bts *bts, uint8_t *expected)
+{
+ struct msgb *msgb;
+
+ printf("Testing nanobts_attr_cell_get()...\n");
+
+ msgb = nanobts_attr_cell_get(bts);
+ printf("result= %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+ printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+ OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+ msgb_free(msgb);
+
+ printf("ok.\n");
+ printf("\n");
+}
+
+static void test_nanobts_attr_nscv_get(struct gsm_bts *bts, uint8_t *expected)
+{
+ struct msgb *msgb;
+
+ printf("Testing nanobts_attr_nscv_get()...\n");
+
+ msgb = nanobts_attr_nscv_get(bts);
+ printf("result= %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+ printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+ OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+ msgb_free(msgb);
+
+ printf("ok.\n");
+ printf("\n");
+}
+
+static void test_nanobts_attr_radio_get(struct gsm_bts *bts,
+ struct gsm_bts_trx *trx,
+ uint8_t *expected)
+{
+ struct msgb *msgb;
+
+ printf("Testing nanobts_attr_nscv_get()...\n");
+
+ msgb = nanobts_attr_radio_get(bts, trx);
+ printf("result= %s\n", osmo_hexdump_nospc(msgb->data, msgb->len));
+ printf("expected=%s\n", osmo_hexdump_nospc(expected, msgb->len));
+ OSMO_ASSERT(memcmp(msgb->data, expected, msgb->len) == 0);
+ msgb_free(msgb);
+
+ printf("ok.\n");
+ printf("\n");
+}
+
+static const struct log_info_cat log_categories[] = {
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+static struct T_def gsm_network_T_defs[] = {
+ { .T=3105, .default_val=100, .val=13, .unit=T_MS, .desc="Physical Information" },
+ { .T=3212, .default_val=5, .unit=T_CUSTOM,
+ .desc="Periodic Location Update timer, sent to MS (1 = 6 minutes)" },
+ {}
+};
+
+int main(int argc, char **argv)
+{
+ void *ctx;
+
+ struct gsm_bts *bts;
+ struct gsm_network *net;
+ struct gsm_bts_trx *trx;
+
+ ctx = talloc_named_const(NULL, 0, "ctx");
+
+ osmo_init_logging2(ctx, &log_info);
+ log_set_log_level(osmo_stderr_target, LOGL_INFO);
+
+ /* Allocate environmental structs (bts, net, trx) */
+ net = talloc_zero(ctx, struct gsm_network);
+ INIT_LLIST_HEAD(&net->bts_list);
+ net->T_defs = gsm_network_T_defs;
+ gsm_bts_model_register(&bts_model_nanobts);
+ bts = gsm_bts_alloc_register(net, GSM_BTS_TYPE_NANOBTS, 63);
+ OSMO_ASSERT(bts);
+ bts->network = net;
+ trx = talloc_zero(ctx, struct gsm_bts_trx);
+
+ /* Parameters needed by nanobts_attr_bts_get() */
+ bts->rach_b_thresh = -1;
+ bts->rach_ldavg_slots = -1;
+ bts->c0->arfcn = 866;
+ bts->cell_identity = 1337;
+ bts->network->plmn = (struct osmo_plmn_id){ .mcc=1, .mnc=1 };
+ bts->location_area_code = 1;
+ bts->gprs.rac = 0;
+ uint8_t attr_bts_expected[] =
+ { 0x19, 0x55, 0x5b, 0x61, 0x67, 0x6d, 0x73, 0x18, 0x06, 0x0e, 0x00,
+ 0x02, 0x01, 0x20, 0x33, 0x1e, 0x24, 0x24, 0xa8, 0x34, 0x21,
+ 0xa8, 0x1f, 0x3f, 0x25,
+ 0x00, 0x01, 0x0a, 0x0c, 0x0a, 0x0b, 0x01, 0x2a, 0x5a, 0x2b,
+ 0x03, 0xe8, 0x0a, 0x0d,
+ 0x23, 0x0a, 0x08, 0x03, 0x62, 0x09, 0x3f, 0x99, 0x00, 0x07,
+ 0x00, 0xf1, 0x10, 0x00,
+ 0x01, 0x05, 0x39
+ };
+
+ /* Parameters needed to test nanobts_attr_nse_get() */
+ bts->gprs.nse.nsei = 101;
+ uint8_t attr_nse_expected[] =
+ { 0x9d, 0x00, 0x02, 0x00, 0x65, 0xa0, 0x00, 0x07, 0x03, 0x03, 0x03,
+ 0x03, 0x1e, 0x03, 0x0a, 0xa1, 0x00, 0x0b, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x0a, 0x03,
+ 0x0a, 0x03, 0x0a, 0x03
+ };
+
+ /* Parameters needed to test nanobts_attr_cell_get() */
+ bts->gprs.rac = 0x00;
+ bts->gprs.cell.bvci = 2;
+ bts->gprs.mode = BTS_GPRS_GPRS;
+ uint8_t attr_cell_expected[] =
+ { 0x9a, 0x00, 0x01, 0x00, 0x9c, 0x00, 0x02, 0x05, 0x03, 0x9e, 0x00,
+ 0x02, 0x00, 0x02, 0xa3, 0x00, 0x09, 0x14, 0x05, 0x05, 0xa0,
+ 0x05, 0x0a, 0x04, 0x08,
+ 0x0f, 0xa8, 0x00, 0x02, 0x0f, 0x00, 0xa9, 0x00, 0x05, 0x00,
+ 0xfa, 0x00, 0xfa, 0x02
+ };
+
+ /* Parameters needed to test nanobts_attr_nscv_get() */
+ bts->gprs.nsvc[0].nsvci = 0x65;
+ bts->gprs.nsvc[0].remote_port = 0x59d8;
+ bts->gprs.nsvc[0].remote_ip = 0x0a090165;
+ bts->gprs.nsvc[0].local_port = 0x5a3c;
+ uint8_t attr_nscv_expected[] =
+ { 0x9f, 0x00, 0x02, 0x00, 0x65, 0xa2, 0x00, 0x08, 0x59, 0xd8, 0x0a,
+ 0x09, 0x01, 0x65, 0x5a, 0x3c
+ };
+
+ /* Parameters needed to test nanobts_attr_radio_get() */
+ trx->arfcn = 866;
+ trx->max_power_red = 22;
+ bts->c0->max_power_red = 22;
+ uint8_t attr_radio_expected[] =
+ { 0x2d, 0x0b, 0x05, 0x00, 0x02, 0x03, 0x62 };
+
+ /* Run tests */
+ test_nanobts_attr_bts_get(bts, attr_bts_expected);
+ test_nanobts_attr_nse_get(bts, attr_nse_expected);
+ test_nanobts_attr_cell_get(bts, attr_cell_expected);
+ test_nanobts_attr_nscv_get(bts, attr_nscv_expected);
+ test_nanobts_attr_radio_get(bts, trx, attr_radio_expected);
+
+ printf("Done\n");
+ talloc_free(bts);
+ talloc_free(net);
+ talloc_free(trx);
+ talloc_report_full(ctx, stderr);
+ /* Expecting something like:
+ * full talloc report on 'ctx' (total 813 bytes in 6 blocks)
+ * logging contains 813 bytes in 5 blocks (ref 0) 0x60b0000000a0
+ * struct log_target contains 196 bytes in 2 blocks (ref 0) 0x6110000000a0
+ * struct log_category contains 36 bytes in 1 blocks (ref 0) 0x60d0000003e0
+ * struct log_info contains 616 bytes in 2 blocks (ref 0) 0x60d000000310
+ * struct log_info_cat contains 576 bytes in 1 blocks (ref 0) 0x6170000000e0
+ * That's the root ctx + 5x logging: */
+ OSMO_ASSERT(talloc_total_blocks(ctx) == 6);
+ talloc_free(ctx);
+ return 0;
+}
+
+/* stubs */
+struct osmo_prim_hdr;
+int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ abort();
+}
+
+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; }
+
+void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) {}
diff --git a/tests/nanobts_omlattr/nanobts_omlattr_test.ok b/tests/nanobts_omlattr/nanobts_omlattr_test.ok
new file mode 100644
index 000000000..ef46cf951
--- /dev/null
+++ b/tests/nanobts_omlattr/nanobts_omlattr_test.ok
@@ -0,0 +1,26 @@
+Testing nanobts_attr_bts_get()...
+result= 19555b61676d7318060e00020120331e2424a83421a81f3f2500010a0c0a0b012a5a2b03e80a0d230a080362093f99000700f11000010539
+expected=19555b61676d7318060e00020120331e2424a83421a81f3f2500010a0c0a0b012a5a2b03e80a0d230a080362093f99000700f11000010539
+ok.
+
+Testing nanobts_attr_nse_get()...
+result= 9d00020065a00007030303031e030aa1000b03030303030a030a030a03
+expected=9d00020065a00007030303031e030aa1000b03030303030a030a030a03
+ok.
+
+Testing nanobts_attr_cell_get()...
+result= 9a0001009c000205039e00020002a30009140505a0050a04080fa800020f00a9000500fa00fa02
+expected=9a0001009c000205039e00020002a30009140505a0050a04080fa800020f00a9000500fa00fa02
+ok.
+
+Testing nanobts_attr_nscv_get()...
+result= 9f00020065a2000859d80a0901655a3c
+expected=9f00020065a2000859d80a0901655a3c
+ok.
+
+Testing nanobts_attr_nscv_get()...
+result= 2d0b0500020362
+expected=2d0b0500020362
+ok.
+
+Done
diff --git a/tests/neighbor_ident.vty b/tests/neighbor_ident.vty
new file mode 100644
index 000000000..4aeb6cc7c
--- /dev/null
+++ b/tests/neighbor_ident.vty
@@ -0,0 +1,334 @@
+OsmoBSC> ### Neighbor-BSS Cell Identifier List config
+
+OsmoBSC> list
+...
+ show bts <0-255> neighbor arfcn <0-1023> bsic (<0-63>|any)
+...
+
+OsmoBSC> enable
+OsmoBSC# list
+...
+ show bts <0-255> neighbor arfcn <0-1023> bsic (<0-63>|any)
+...
+
+OsmoBSC# configure terminal
+OsmoBSC(config)# network
+
+OsmoBSC(config-net)# bts 0
+OsmoBSC(config-net-bts)# type sysmobts
+OsmoBSC(config-net-bts)# base_station_id_code 10
+OsmoBSC(config-net-bts)# location_area_code 20
+OsmoBSC(config-net-bts)# cell_identity 30
+OsmoBSC(config-net-bts)# trx 0
+OsmoBSC(config-net-bts-trx)# arfcn 40
+OsmoBSC(config-net-bts-trx)# exit
+OsmoBSC(config-net-bts)# exit
+
+OsmoBSC(config-net)# bts 1
+OsmoBSC(config-net-bts)# type sysmobts
+OsmoBSC(config-net-bts)# base_station_id_code 11
+OsmoBSC(config-net-bts)# location_area_code 21
+OsmoBSC(config-net-bts)# cell_identity 31
+OsmoBSC(config-net-bts)# trx 0
+OsmoBSC(config-net-bts-trx)# arfcn 41
+OsmoBSC(config-net-bts-trx)# exit
+OsmoBSC(config-net-bts)# exit
+
+OsmoBSC(config-net)# bts 2
+OsmoBSC(config-net-bts)# type sysmobts
+OsmoBSC(config-net-bts)# base_station_id_code 12
+OsmoBSC(config-net-bts)# location_area_code 22
+OsmoBSC(config-net-bts)# cell_identity 65535
+OsmoBSC(config-net-bts)# trx 0
+OsmoBSC(config-net-bts-trx)# arfcn 42
+OsmoBSC(config-net-bts-trx)# exit
+OsmoBSC(config-net-bts)# exit
+
+OsmoBSC(config-net)# show running-config
+...
+ bts 0
+...
+ cell_identity 30
+ location_area_code 20
+ base_station_id_code 10
+...
+ trx 0
+...
+ arfcn 40
+...
+ bts 1
+...
+ cell_identity 31
+ location_area_code 21
+ base_station_id_code 11
+...
+ trx 0
+...
+ arfcn 41
+...
+ bts 2
+...
+ cell_identity 65535
+ location_area_code 22
+ base_station_id_code 12
+...
+ trx 0
+...
+ arfcn 42
+...
+
+OsmoBSC(config-net)# bts 0
+OsmoBSC(config-net-bts)# list
+...
+ neighbor bts <0-255>
+ neighbor lac <0-65535>
+ neighbor lac-ci <0-65535> <0-65535>
+ neighbor cgi <0-999> <0-999> <0-65535> <0-65535>
+ neighbor lac <0-65535> arfcn <0-1023> bsic (<0-63>|any)
+ neighbor lac-ci <0-65535> <0-65535> arfcn <0-1023> bsic (<0-63>|any)
+ neighbor cgi <0-999> <0-999> <0-65535> <0-65535> arfcn <0-1023> bsic (<0-63>|any)
+ no neighbor bts <0-255>
+ no neighbor arfcn <0-1023> bsic (<0-63>|any)
+...
+
+OsmoBSC(config-net-bts)# neighbor?
+ neighbor Manage local and remote-BSS neighbor cells
+
+OsmoBSC(config-net-bts)# neighbor ?
+ bts Add Neighbor cell by local BTS number
+ lac Add Neighbor cell by LAC
+ lac-ci Add Neighbor cell by LAC and CI
+ cgi Add Neighbor cell by cgi
+
+OsmoBSC(config-net-bts)# neighbor bts ?
+ <0-255> BTS number
+OsmoBSC(config-net-bts)# neighbor bts 0 ?
+ <cr>
+
+OsmoBSC(config-net-bts)# neighbor lac ?
+ <0-65535> LAC
+OsmoBSC(config-net-bts)# neighbor lac 0 ?
+ arfcn ARFCN of neighbor cell
+ <cr>
+OsmoBSC(config-net-bts)# neighbor lac 0 arfcn ?
+ <0-1023> ARFCN value
+OsmoBSC(config-net-bts)# neighbor lac 0 arfcn 0 ?
+ bsic BSIC of neighbor cell
+OsmoBSC(config-net-bts)# neighbor lac 0 arfcn 0 bsic ?
+ <0-63> BSIC value
+ any for all BSICs / use any BSIC in this ARFCN
+OsmoBSC(config-net-bts)# neighbor lac 0 arfcn 0 bsic 0 ?
+ <cr>
+
+OsmoBSC(config-net-bts)# neighbor lac-ci ?
+ <0-65535> LAC
+OsmoBSC(config-net-bts)# neighbor lac-ci 0 ?
+ <0-65535> CI
+OsmoBSC(config-net-bts)# neighbor lac-ci 0 0 ?
+ arfcn ARFCN of neighbor cell
+ <cr>
+OsmoBSC(config-net-bts)# neighbor lac-ci 0 0 arfcn ?
+ <0-1023> ARFCN value
+OsmoBSC(config-net-bts)# neighbor lac-ci 0 0 arfcn 0 ?
+ bsic BSIC of neighbor cell
+OsmoBSC(config-net-bts)# neighbor lac-ci 0 0 arfcn 0 bsic ?
+ <0-63> BSIC value
+ any for all BSICs / use any BSIC in this ARFCN
+OsmoBSC(config-net-bts)# neighbor lac-ci 0 0 arfcn 0 bsic 0 ?
+ <cr>
+
+OsmoBSC(config-net-bts)# neighbor cgi ?
+ <0-999> MCC
+OsmoBSC(config-net-bts)# neighbor cgi 0 ?
+ <0-999> MNC
+OsmoBSC(config-net-bts)# neighbor cgi 0 0 ?
+ <0-65535> LAC
+OsmoBSC(config-net-bts)# neighbor cgi 0 0 0 ?
+ <0-65535> CI
+OsmoBSC(config-net-bts)# neighbor cgi 0 0 0 0 ?
+ arfcn ARFCN of neighbor cell
+ <cr>
+OsmoBSC(config-net-bts)# neighbor cgi 0 0 0 0 arfcn ?
+ <0-1023> ARFCN value
+OsmoBSC(config-net-bts)# neighbor cgi 0 0 0 0 arfcn 0 ?
+ bsic BSIC of neighbor cell
+OsmoBSC(config-net-bts)# neighbor cgi 0 0 0 0 arfcn 0 bsic ?
+ <0-63> BSIC value
+ any for all BSICs / use any BSIC in this ARFCN
+OsmoBSC(config-net-bts)# neighbor cgi 0 0 0 0 arfcn 0 bsic 0 ?
+ <cr>
+
+OsmoBSC(config-net-bts)# no neighbor?
+ neighbor Remove local or remote-BSS neighbor cell
+
+OsmoBSC(config-net-bts)# no neighbor ?
+ bts Neighbor cell by local BTS number
+ arfcn ARFCN of neighbor cell
+
+OsmoBSC(config-net-bts)# no neighbor bts ?
+ <0-255> BTS number
+OsmoBSC(config-net-bts)# no neighbor bts 0 ?
+ <cr>
+
+OsmoBSC(config-net-bts)# no neighbor arfcn ?
+ <0-1023> ARFCN value
+OsmoBSC(config-net-bts)# no neighbor arfcn 0 ?
+ bsic BSIC of neighbor cell
+OsmoBSC(config-net-bts)# no neighbor arfcn 0 bsic ?
+ <0-63> BSIC value
+ any for all BSICs / use any BSIC in this ARFCN
+OsmoBSC(config-net-bts)# no neighbor arfcn 0 bsic 0 ?
+ <cr>
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+
+OsmoBSC(config-net-bts)# ! BSIC out of range
+OsmoBSC(config-net-bts)# neighbor cgi 23 42 423 5 arfcn 23 bsic 64
+% Unknown command.
+
+OsmoBSC(config-net-bts)# neighbor bts 0
+% Error: cannot add local BTS 0 as neighbor to BTS 0: Invalid argument
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+
+OsmoBSC(config-net-bts)# neighbor bts 1
+% BTS 0 now has local neighbor BTS 1 with LAC 21 CI 31 and ARFCN 41 BSIC 11
+
+OsmoBSC(config-net-bts)# neighbor lac 22
+% BTS 0 now has local neighbor BTS 2 with LAC 22 CI 65535 and ARFCN 42 BSIC 12
+OsmoBSC(config-net-bts)# no neighbor bts 2
+OsmoBSC(config-net-bts)# neighbor cgi 901 70 22 65535
+% BTS 0 now has local neighbor BTS 2 with LAC 22 CI 65535 and ARFCN 42 BSIC 12
+
+OsmoBSC(config-net-bts)# neighbor cgi 23 42 423 5 arfcn 23 bsic 42
+% BTS 0 to ARFCN 23 BSIC 42 now has 1 remote BSS Cell Identifier List entry
+
+OsmoBSC(config-net-bts)# ### adding the same entry again results in no change
+OsmoBSC(config-net-bts)# neighbor bts 1
+% BTS 0 already had local neighbor BTS 1 with LAC 21 CI 31 and ARFCN 41 BSIC 11
+OsmoBSC(config-net-bts)# neighbor lac-ci 21 31
+% BTS 0 already had local neighbor BTS 1 with LAC 21 CI 31 and ARFCN 41 BSIC 11
+OsmoBSC(config-net-bts)# neighbor cgi 23 42 423 5 arfcn 23 bsic 42
+% Error: only one Cell Identifier entry is allowed per remote neighbor. Already have: BTS 0 to ARFCN 23 BSIC 42 -> CGI[1]:{023-42-423-5}
+OsmoBSC(config-net-bts)# neighbor cgi 23 42 423 5 arfcn 23 bsic 42
+% Error: only one Cell Identifier entry is allowed per remote neighbor. Already have: BTS 0 to ARFCN 23 BSIC 42 -> CGI[1]:{023-42-423-5}
+OsmoBSC(config-net-bts)# neighbor cgi 23 42 423 5 arfcn 23 bsic 42
+% Error: only one Cell Identifier entry is allowed per remote neighbor. Already have: BTS 0 to ARFCN 23 BSIC 42 -> CGI[1]:{023-42-423-5}
+
+OsmoBSC(config-net-bts)# neighbor cgi 23 042 423 6 arfcn 23 bsic 42
+% Error: only one Cell Identifier entry is allowed per remote neighbor. Already have: BTS 0 to ARFCN 23 BSIC 42 -> CGI[1]:{023-42-423-5}
+
+OsmoBSC(config-net-bts)# neighbor lac 456 arfcn 123 bsic 45
+% BTS 0 to ARFCN 123 BSIC 45 now has 1 remote BSS Cell Identifier List entry
+
+OsmoBSC(config-net-bts)# neighbor cgi 23 042 234 56 arfcn 23 bsic 42
+% Error: only one Cell Identifier entry is allowed per remote neighbor. Already have: BTS 0 to ARFCN 23 BSIC 42 -> CGI[1]:{023-42-423-5}
+
+OsmoBSC(config-net-bts)# neighbor lac-ci 789 10 arfcn 423 bsic any
+% BTS 0 to ARFCN 423 (any BSIC) now has 1 remote BSS Cell Identifier List entry
+
+OsmoBSC(config-net-bts)# neighbor lac-ci 789 10 arfcn 423 bsic 63
+% Error: only one Cell Identifier entry is allowed per remote neighbor. Already have: BTS 0 to ARFCN 423 BSIC 63 -> LAC-CI[1]:{789-10}
+
+OsmoBSC(config-net-bts)# neighbor lac-ci 789 10 arfcn 423 bsic 1
+% Error: only one Cell Identifier entry is allowed per remote neighbor. Already have: BTS 0 to ARFCN 423 BSIC 1 -> LAC-CI[1]:{789-10}
+
+OsmoBSC(config-net-bts)# show running-config
+...
+network
+... !neighbor
+ bts 0
+... !neighbor
+ neighbor bts 1
+ neighbor bts 2
+ neighbor cgi 023 42 423 5 arfcn 23 bsic 42
+ neighbor lac 456 arfcn 123 bsic 45
+ neighbor lac-ci 789 10 arfcn 423 bsic any
+... !neighbor
+
+OsmoBSC(config-net-bts)# do show bts 0 neighbor arfcn 99 bsic any
+% No entry for BTS 0 to ARFCN 99 (any BSIC)
+
+OsmoBSC(config-net-bts)# do show bts 0 neighbor arfcn 41 bsic any
+% BTS 0 to ARFCN 41 (any BSIC) resolves to local BTS 1 lac-ci 21 31
+
+OsmoBSC(config-net-bts)# do show bts 0 neighbor arfcn 423 bsic 1
+% neighbor lac-ci 789 10 arfcn 423 bsic 1
+
+OsmoBSC(config-net-bts)# do show bts 0 neighbor arfcn 423 bsic 23
+% neighbor lac-ci 789 10 arfcn 423 bsic 23
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 99 bsic 7
+% Cannot remove, no such neighbor: BTS 0 to ARFCN 99 BSIC 7
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 23 bsic 42
+% Removed remote BSS neighbor BTS 0 to ARFCN 23 BSIC 42
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+ neighbor bts 1
+ neighbor bts 2
+ neighbor lac 456 arfcn 123 bsic 45
+ neighbor lac-ci 789 10 arfcn 423 bsic any
+... !neighbor
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 123 bsic 45
+% Removed remote BSS neighbor BTS 0 to ARFCN 123 BSIC 45
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+ neighbor bts 1
+ neighbor bts 2
+ neighbor lac-ci 789 10 arfcn 423 bsic any
+... !neighbor
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 423 bsic any
+% Removed remote BSS neighbor BTS 0 to ARFCN 423 (any BSIC)
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+ neighbor bts 1
+ neighbor bts 2
+... !neighbor
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 423 bsic 63
+% Cannot remove, no such neighbor: BTS 0 to ARFCN 423 BSIC 63
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+ neighbor bts 1
+ neighbor bts 2
+... !neighbor
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 423 bsic 1
+% Cannot remove, no such neighbor: BTS 0 to ARFCN 423 BSIC 1
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+ neighbor bts 1
+ neighbor bts 2
+... !neighbor
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 41 bsic any
+% Removed local neighbor bts 0 to bts 1
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+ neighbor bts 2
+... !neighbor
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 41 bsic any
+% Cannot remove, no such neighbor: BTS 0 to ARFCN 41 (any BSIC)
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
+ neighbor bts 2
+... !neighbor
+
+OsmoBSC(config-net-bts)# no neighbor arfcn 42 bsic 12
+% Removed local neighbor bts 0 to bts 2
+
+OsmoBSC(config-net-bts)# show running-config
+... !neighbor
diff --git a/tests/osmo-bsc.vty b/tests/osmo-bsc.vty
new file mode 100644
index 000000000..560fb3683
--- /dev/null
+++ b/tests/osmo-bsc.vty
@@ -0,0 +1,19 @@
+OsmoBSC> enable
+
+OsmoBSC# configure terminal
+OsmoBSC(config)# network
+OsmoBSC(config-net)# list
+...
+ meas-feed destination ADDR <0-65535>
+ meas-feed scenario NAME
+...
+
+OsmoBSC(config-net)# meas-feed destination 127.0.0.23 4223
+OsmoBSC(config-net)# meas-feed scenario foo23
+OsmoBSC(config-net)# show running-config
+...
+network
+...
+ meas-feed destination 127.0.0.23 4223
+ meas-feed scenario foo23
+...
diff --git a/tests/subscr/Makefile.am b/tests/subscr/Makefile.am
new file mode 100644
index 000000000..e56d142bc
--- /dev/null
+++ b/tests/subscr/Makefile.am
@@ -0,0 +1,40 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ -ggdb3 \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBSMPP34_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+EXTRA_DIST = \
+ bsc_subscr_test.ok \
+ bsc_subscr_test.err \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ bsc_subscr_test \
+ $(NULL)
+
+bsc_subscr_test_SOURCES = \
+ bsc_subscr_test.c \
+ $(NULL)
+
+bsc_subscr_test_LDADD = \
+ $(top_builddir)/src/osmo-bsc/bsc_subscriber.o \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBSMPP34_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(NULL)
diff --git a/tests/subscr/bsc_subscr_test.c b/tests/subscr/bsc_subscr_test.c
new file mode 100644
index 000000000..3c94b8662
--- /dev/null
+++ b/tests/subscr/bsc_subscr_test.c
@@ -0,0 +1,143 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2014 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+struct llist_head *bsc_subscribers;
+
+#define VERBOSE_ASSERT(val, expect_op, fmt) \
+ do { \
+ printf(#val " == " fmt "\n", (val)); \
+ OSMO_ASSERT((val) expect_op); \
+ } while (0);
+
+static void assert_bsc_subscr(const struct bsc_subscr *bsub, const char *imsi)
+{
+ struct bsc_subscr *sfound;
+ OSMO_ASSERT(bsub);
+ OSMO_ASSERT(strcmp(bsub->imsi, imsi) == 0);
+
+ sfound = bsc_subscr_find_by_imsi(bsc_subscribers, imsi);
+ OSMO_ASSERT(sfound == bsub);
+
+ bsc_subscr_put(sfound);
+}
+
+static void test_bsc_subscr(void)
+{
+ struct bsc_subscr *s1, *s2, *s3;
+ const char *imsi1 = "1234567890";
+ const char *imsi2 = "9876543210";
+ const char *imsi3 = "5656565656";
+
+ printf("Test BSC subscriber allocation and deletion\n");
+
+ /* Check for emptiness */
+ VERBOSE_ASSERT(llist_count(bsc_subscribers), == 0, "%d");
+ OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi1) == NULL);
+ OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi2) == NULL);
+ OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi3) == NULL);
+
+ /* Allocate entry 1 */
+ s1 = bsc_subscr_find_or_create_by_imsi(bsc_subscribers, imsi1);
+ VERBOSE_ASSERT(llist_count(bsc_subscribers), == 1, "%d");
+ assert_bsc_subscr(s1, imsi1);
+ VERBOSE_ASSERT(llist_count(bsc_subscribers), == 1, "%d");
+ OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi2) == NULL);
+
+ /* Allocate entry 2 */
+ s2 = bsc_subscr_find_or_create_by_imsi(bsc_subscribers, imsi2);
+ VERBOSE_ASSERT(llist_count(bsc_subscribers), == 2, "%d");
+
+ /* Allocate entry 3 */
+ s3 = bsc_subscr_find_or_create_by_imsi(bsc_subscribers, imsi3);
+ VERBOSE_ASSERT(llist_count(bsc_subscribers), == 3, "%d");
+
+ /* Check entries */
+ assert_bsc_subscr(s1, imsi1);
+ assert_bsc_subscr(s2, imsi2);
+ assert_bsc_subscr(s3, imsi3);
+
+ /* Free entry 1 */
+ bsc_subscr_put(s1);
+ s1 = NULL;
+ VERBOSE_ASSERT(llist_count(bsc_subscribers), == 2, "%d");
+ OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi1) == NULL);
+
+ assert_bsc_subscr(s2, imsi2);
+ assert_bsc_subscr(s3, imsi3);
+
+ /* Free entry 2 */
+ bsc_subscr_put(s2);
+ s2 = NULL;
+ VERBOSE_ASSERT(llist_count(bsc_subscribers), == 1, "%d");
+ OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi1) == NULL);
+ OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi2) == NULL);
+ assert_bsc_subscr(s3, imsi3);
+
+ /* Free entry 3 */
+ bsc_subscr_put(s3);
+ s3 = NULL;
+ VERBOSE_ASSERT(llist_count(bsc_subscribers), == 0, "%d");
+ OSMO_ASSERT(bsc_subscr_find_by_imsi(bsc_subscribers, imsi3) == NULL);
+
+ OSMO_ASSERT(llist_empty(bsc_subscribers));
+}
+
+static const struct log_info_cat log_categories[] = {
+ [DREF] = {
+ .name = "DREF",
+ .description = "Reference Counting",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main()
+{
+ void *ctx = talloc_named_const(NULL, 0, "bsc_subscr_test");
+ printf("Testing BSC subscriber core code.\n");
+ osmo_init_logging2(ctx, &log_info);
+ log_set_print_filename(osmo_stderr_target, 0);
+ log_set_print_timestamp(osmo_stderr_target, 0);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_category(osmo_stderr_target, 1);
+
+ bsc_subscribers = talloc_zero(ctx, struct llist_head);
+ INIT_LLIST_HEAD(bsc_subscribers);
+
+ test_bsc_subscr();
+
+ printf("Done\n");
+ return 0;
+}
diff --git a/tests/subscr/bsc_subscr_test.err b/tests/subscr/bsc_subscr_test.err
new file mode 100644
index 000000000..afc8bf778
--- /dev/null
+++ b/tests/subscr/bsc_subscr_test.err
@@ -0,0 +1,20 @@
+DREF BSC subscr IMSI:1234567890 usage increases to: 1
+DREF BSC subscr IMSI:1234567890 usage increases to: 2
+DREF BSC subscr IMSI:1234567890 usage decreases to: 1
+DREF BSC subscr IMSI:9876543210 usage increases to: 1
+DREF BSC subscr IMSI:5656565656 usage increases to: 1
+DREF BSC subscr IMSI:1234567890 usage increases to: 2
+DREF BSC subscr IMSI:1234567890 usage decreases to: 1
+DREF BSC subscr IMSI:9876543210 usage increases to: 2
+DREF BSC subscr IMSI:9876543210 usage decreases to: 1
+DREF BSC subscr IMSI:5656565656 usage increases to: 2
+DREF BSC subscr IMSI:5656565656 usage decreases to: 1
+DREF BSC subscr IMSI:1234567890 usage decreases to: 0
+DREF BSC subscr IMSI:9876543210 usage increases to: 2
+DREF BSC subscr IMSI:9876543210 usage decreases to: 1
+DREF BSC subscr IMSI:5656565656 usage increases to: 2
+DREF BSC subscr IMSI:5656565656 usage decreases to: 1
+DREF BSC subscr IMSI:9876543210 usage decreases to: 0
+DREF BSC subscr IMSI:5656565656 usage increases to: 2
+DREF BSC subscr IMSI:5656565656 usage decreases to: 1
+DREF BSC subscr IMSI:5656565656 usage decreases to: 0
diff --git a/tests/subscr/bsc_subscr_test.ok b/tests/subscr/bsc_subscr_test.ok
new file mode 100644
index 000000000..0f6a8be01
--- /dev/null
+++ b/tests/subscr/bsc_subscr_test.ok
@@ -0,0 +1,11 @@
+Testing BSC subscriber core code.
+Test BSC subscriber allocation and deletion
+llist_count(bsc_subscribers) == 0
+llist_count(bsc_subscribers) == 1
+llist_count(bsc_subscribers) == 1
+llist_count(bsc_subscribers) == 2
+llist_count(bsc_subscribers) == 3
+llist_count(bsc_subscribers) == 2
+llist_count(bsc_subscribers) == 1
+llist_count(bsc_subscribers) == 0
+Done
diff --git a/tests/testsuite.at b/tests/testsuite.at
new file mode 100644
index 000000000..1a190dda3
--- /dev/null
+++ b/tests/testsuite.at
@@ -0,0 +1,220 @@
+AT_INIT
+AT_BANNER([Regression tests.])
+
+AT_SETUP([gsm0408])
+AT_KEYWORDS([gsm0408])
+cat $abs_srcdir/gsm0408/gsm0408_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gsm0408/gsm0408_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([bsc_subscr])
+AT_KEYWORDS([bsc_subscr])
+cat $abs_srcdir/subscr/bsc_subscr_test.ok > expout
+cat $abs_srcdir/subscr/bsc_subscr_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/subscr/bsc_subscr_test], [], [expout], [experr])
+AT_CLEANUP
+
+AT_SETUP([abis])
+AT_KEYWORDS([abis])
+cat $abs_srcdir/abis/abis_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/abis/abis_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([bsc])
+AT_KEYWORDS([bsc])
+cat $abs_srcdir/bsc/bsc_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/bsc/bsc_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([codec_pref])
+AT_KEYWORDS([codec_pref])
+cat $abs_srcdir/codec_pref/codec_pref_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/codec_pref/codec_pref_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([nanobts_omlattr])
+AT_KEYWORDS([nanobts_omlattr])
+cat $abs_srcdir/nanobts_omlattr/nanobts_omlattr_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/nanobts_omlattr/nanobts_omlattr_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([neighbor_ident])
+AT_KEYWORDS([neighbor_ident])
+cat $abs_srcdir/handover/neighbor_ident_test.ok > expout
+cat $abs_srcdir/handover/neighbor_ident_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/handover/neighbor_ident_test], [], [expout], [experr])
+AT_CLEANUP
+
+AT_SETUP([handover test 0])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 0], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 1])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 1], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 2])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 2], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 3])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 3], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 4])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 4], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 5])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 5], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 6])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 6], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 7])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 7], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 8])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 8], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 9])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 9], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 10])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 10], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 11])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 11], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 12])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 12], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 13])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 13], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 14])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 14], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 15])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 15], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 16])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 16], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 17])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 17], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 18])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 18], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 19])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 19], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 20])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 20], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 21])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 21], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 22])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 22], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 23])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 23], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 24])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 24], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 25])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 25], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 26])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 26], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 27])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 27], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 28])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 28], [], [expout], [ignore])
+AT_CLEANUP
diff --git a/tests/vty_test_runner.py b/tests/vty_test_runner.py
new file mode 100755
index 000000000..d2b3da4ff
--- /dev/null
+++ b/tests/vty_test_runner.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python2
+
+# (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
+# (C) 2013 by Holger Hans Peter Freyther
+# 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 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os, sys
+import time
+import unittest
+import socket
+import subprocess
+
+import osmopy.obscvty as obscvty
+import osmopy.osmoutil as osmoutil
+from osmopy.osmo_ipa import IPA
+
+# to be able to find $top_srcdir/doc/...
+confpath = os.path.join(sys.path[0], '..')
+
+class TestVTYBase(unittest.TestCase):
+
+ def checkForEndAndExit(self):
+ res = self.vty.command("list")
+ #print ('looking for "exit"\n')
+ self.assert_(res.find(' exit\r') > 0)
+ #print 'found "exit"\nlooking for "end"\n'
+ self.assert_(res.find(' end\r') > 0)
+ #print 'found "end"\n'
+
+ def vty_command(self):
+ raise Exception("Needs to be implemented by a subclass")
+
+ def vty_app(self):
+ raise Exception("Needs to be implemented by a subclass")
+
+ def setUp(self):
+ osmo_vty_cmd = self.vty_command()[:]
+ config_index = osmo_vty_cmd.index('-c')
+ if config_index:
+ cfi = config_index + 1
+ osmo_vty_cmd[cfi] = os.path.join(confpath, osmo_vty_cmd[cfi])
+
+ try:
+ self.proc = osmoutil.popen_devnull(osmo_vty_cmd)
+ except OSError:
+ print >> sys.stderr, "Current directory: %s" % os.getcwd()
+ print >> sys.stderr, "Consider setting -b"
+
+ appstring = self.vty_app()[2]
+ appport = self.vty_app()[0]
+ self.vty = obscvty.VTYInteract(appstring, "127.0.0.1", appport)
+
+ def tearDown(self):
+ if self.vty:
+ self.vty._close_socket()
+ self.vty = None
+ osmoutil.end_proc(self.proc)
+
+
+class TestVTYGenericBSC(TestVTYBase):
+
+ def _testConfigNetworkTree(self, include_bsc_items=True):
+ self.vty.enable()
+ self.assertTrue(self.vty.verify("configure terminal",['']))
+ self.assertEquals(self.vty.node(), 'config')
+ self.checkForEndAndExit()
+ self.assertTrue(self.vty.verify("network",['']))
+ self.assertEquals(self.vty.node(), 'config-net')
+ self.checkForEndAndExit()
+ self.assertTrue(self.vty.verify("bts 0",['']))
+ self.assertEquals(self.vty.node(), 'config-net-bts')
+ self.checkForEndAndExit()
+ self.assertTrue(self.vty.verify("trx 0",['']))
+ self.assertEquals(self.vty.node(), 'config-net-bts-trx')
+ self.checkForEndAndExit()
+ self.vty.command("write terminal")
+ self.assertTrue(self.vty.verify("exit",['']))
+ self.assertEquals(self.vty.node(), 'config-net-bts')
+ self.assertTrue(self.vty.verify("exit",['']))
+ self.assertTrue(self.vty.verify("bts 1",['']))
+ self.assertEquals(self.vty.node(), 'config-net-bts')
+ self.checkForEndAndExit()
+ self.assertTrue(self.vty.verify("trx 1",['']))
+ self.assertEquals(self.vty.node(), 'config-net-bts-trx')
+ self.checkForEndAndExit()
+ self.vty.command("write terminal")
+ self.assertTrue(self.vty.verify("exit",['']))
+ self.assertEquals(self.vty.node(), 'config-net-bts')
+ self.assertTrue(self.vty.verify("exit",['']))
+ self.assertEquals(self.vty.node(), 'config-net')
+ self.assertTrue(self.vty.verify("exit",['']))
+ self.assertEquals(self.vty.node(), 'config')
+ self.assertTrue(self.vty.verify("exit",['']))
+ self.assertTrue(self.vty.node() is None)
+
+
+class TestVTYBSC(TestVTYGenericBSC):
+
+ def vty_command(self):
+ return ["./src/osmo-bsc/osmo-bsc", "-c",
+ "doc/examples/osmo-bsc/osmo-bsc.cfg"]
+
+ def vty_app(self):
+ return (4242, "./src/osmo-bsc/osmo-bsc", "OsmoBSC", "bsc")
+
+ def testConfigNetworkTree(self):
+ self._testConfigNetworkTree()
+
+ def testVtyTree(self):
+ self.vty.enable()
+ self.assertTrue(self.vty.verify("configure terminal", ['']))
+ self.assertEquals(self.vty.node(), 'config')
+ self.checkForEndAndExit()
+ self.assertTrue(self.vty.verify("msc 0", ['']))
+ self.assertEquals(self.vty.node(), 'config-msc')
+ self.checkForEndAndExit()
+ self.assertTrue(self.vty.verify("exit", ['']))
+ self.assertEquals(self.vty.node(), 'config')
+ self.assertTrue(self.vty.verify("bsc", ['']))
+ self.assertEquals(self.vty.node(), 'config-bsc')
+ self.checkForEndAndExit()
+ self.assertTrue(self.vty.verify("exit", ['']))
+ self.assertEquals(self.vty.node(), 'config')
+ self.assertTrue(self.vty.verify("exit", ['']))
+ self.assertTrue(self.vty.node() is None)
+
+ def testUssdNotificationsMsc(self):
+ self.vty.enable()
+ self.vty.command("configure terminal")
+ self.vty.command("msc")
+
+ # Test invalid input
+ self.vty.verify("bsc-msc-lost-text", ['% Command incomplete.'])
+ self.vty.verify("bsc-welcome-text", ['% Command incomplete.'])
+ self.vty.verify("bsc-grace-text", ['% Command incomplete.'])
+
+ # Enable USSD notifications
+ self.vty.verify("bsc-msc-lost-text MSC disconnected", [''])
+ self.vty.verify("bsc-welcome-text Hello MS", [''])
+ self.vty.verify("bsc-grace-text In grace period", [''])
+
+ # Verify settings
+ res = self.vty.command("write terminal")
+ self.assert_(res.find('bsc-msc-lost-text MSC disconnected') > 0)
+ self.assertEquals(res.find('no bsc-msc-lost-text'), -1)
+ self.assert_(res.find('bsc-welcome-text Hello MS') > 0)
+ self.assertEquals(res.find('no bsc-welcome-text'), -1)
+ self.assert_(res.find('bsc-grace-text In grace period') > 0)
+ self.assertEquals(res.find('no bsc-grace-text'), -1)
+
+ # Now disable it..
+ self.vty.verify("no bsc-msc-lost-text", [''])
+ self.vty.verify("no bsc-welcome-text", [''])
+ self.vty.verify("no bsc-grace-text", [''])
+
+ # Verify settings
+ res = self.vty.command("write terminal")
+ self.assertEquals(res.find('bsc-msc-lost-text MSC disconnected'), -1)
+ self.assert_(res.find('no bsc-msc-lost-text') > 0)
+ self.assertEquals(res.find('bsc-welcome-text Hello MS'), -1)
+ self.assert_(res.find('no bsc-welcome-text') > 0)
+ self.assertEquals(res.find('bsc-grace-text In grace period'), -1)
+ self.assert_(res.find('no bsc-grace-text') > 0)
+
+ def testUssdNotificationsBsc(self):
+ self.vty.enable()
+ self.vty.command("configure terminal")
+ self.vty.command("bsc")
+
+ # Test invalid input
+ self.vty.verify("missing-msc-text", ['% Command incomplete.'])
+
+ # Enable USSD notifications
+ self.vty.verify("missing-msc-text No MSC found", [''])
+
+ # Verify settings
+ res = self.vty.command("write terminal")
+ self.assert_(res.find('missing-msc-text No MSC found') > 0)
+ self.assertEquals(res.find('no missing-msc-text'), -1)
+
+ # Now disable it..
+ self.vty.verify("no missing-msc-text", [''])
+
+ # Verify settings
+ res = self.vty.command("write terminal")
+ self.assertEquals(res.find('missing-msc-text No MSC found'), -1)
+ self.assert_(res.find('no missing-msc-text') > 0)
+
+ def testNetworkTimezone(self):
+ self.vty.enable()
+ self.vty.verify("configure terminal", [''])
+ self.vty.verify("network", [''])
+
+ # Test invalid input
+ self.vty.verify("timezone", ['% Command incomplete.'])
+ self.vty.verify("timezone 20 0", ['% Unknown command.'])
+ self.vty.verify("timezone 0 11", ['% Unknown command.'])
+ self.vty.verify("timezone 0 0 99", ['% Unknown command.'])
+
+ # Set time zone without DST
+ self.vty.verify("timezone 2 30", [''])
+
+ # Verify settings
+ res = self.vty.command("write terminal")
+ self.assert_(res.find('timezone 2 30') > 0)
+ self.assertEquals(res.find('timezone 2 30 '), -1)
+
+ # Set time zone with DST
+ self.vty.verify("timezone 2 30 1", [''])
+
+ # Verify settings
+ res = self.vty.command("write terminal")
+ self.assert_(res.find('timezone 2 30 1') > 0)
+
+ # Now disable it..
+ self.vty.verify("no timezone", [''])
+
+ # Verify settings
+ res = self.vty.command("write terminal")
+ self.assertEquals(res.find(' timezone'), -1)
+
+ def testShowNetwork(self):
+ res = self.vty.command("show network")
+ self.assert_(res.startswith('BSC is on Country Code') >= 0)
+
+ def testMscDataCoreLACCI(self):
+ self.vty.enable()
+ res = self.vty.command("show running-config")
+ self.assertEquals(res.find("core-location-area-code"), -1)
+ self.assertEquals(res.find("core-cell-identity"), -1)
+
+ self.vty.command("configure terminal")
+ self.vty.command("msc 0")
+ self.vty.command("core-location-area-code 666")
+ self.vty.command("core-cell-identity 333")
+
+ res = self.vty.command("show running-config")
+ self.assert_(res.find("core-location-area-code 666") > 0)
+ self.assert_(res.find("core-cell-identity 333") > 0)
+
+
+def add_bsc_test(suite, workdir):
+ if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc/osmo-bsc")):
+ print("Skipping the BSC test")
+ return
+ test = unittest.TestLoader().loadTestsFromTestCase(TestVTYBSC)
+ suite.addTest(test)
+
+if __name__ == '__main__':
+ import argparse
+ import sys
+
+ workdir = '.'
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-v", "--verbose", dest="verbose",
+ action="store_true", help="verbose mode")
+ parser.add_argument("-p", "--pythonconfpath", dest="p",
+ help="searchpath for config")
+ parser.add_argument("-w", "--workdir", dest="w",
+ help="Working directory")
+ parser.add_argument("test_name", nargs="*", help="(parts of) test names to run, case-insensitive")
+ args = parser.parse_args()
+
+ verbose_level = 1
+ if args.verbose:
+ verbose_level = 2
+
+ if args.w:
+ workdir = args.w
+
+ if args.p:
+ confpath = args.p
+
+ print "confpath %s, workdir %s" % (confpath, workdir)
+ os.chdir(workdir)
+ print "Running tests for specific VTY commands"
+ suite = unittest.TestSuite()
+ add_bsc_test(suite, workdir)
+
+ if args.test_name:
+ osmoutil.pick_tests(suite, *args.test_name)
+
+ res = unittest.TextTestRunner(verbosity=verbose_level, stream=sys.stdout).run(suite)
+ sys.exit(len(res.errors) + len(res.failures))
+
+# vim: shiftwidth=4 expandtab nocin ai